pe.c revision 332615
1/*-
2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3 *
4 * Copyright (c) 2014 The FreeBSD Foundation
5 * All rights reserved.
6 *
7 * This software was developed by Edward Tomasz Napierala under sponsorship
8 * from the FreeBSD Foundation.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 *    notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 *    notice, this list of conditions and the following disclaimer in the
17 *    documentation and/or other materials provided with the distribution.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29 * SUCH DAMAGE.
30 *
31 */
32
33/*
34 * PE format reference:
35 * http://www.microsoft.com/whdc/system/platform/firmware/PECOFF.mspx
36 */
37
38#include <sys/cdefs.h>
39__FBSDID("$FreeBSD: stable/11/usr.sbin/uefisign/pe.c 332615 2018-04-16 17:13:54Z trasz $");
40
41#include <assert.h>
42#include <err.h>
43#include <errno.h>
44#include <stddef.h>
45#include <stdio.h>
46#include <stdint.h>
47#include <stdlib.h>
48#include <string.h>
49#include <unistd.h>
50
51#include "uefisign.h"
52
53#ifndef CTASSERT
54#define CTASSERT(x)		_CTASSERT(x, __LINE__)
55#define _CTASSERT(x, y)		__CTASSERT(x, y)
56#define __CTASSERT(x, y)	typedef char __assert_ ## y [(x) ? 1 : -1]
57#endif
58
59struct mz_header {
60	uint8_t			mz_signature[2];
61	uint8_t			mz_dont_care[58];
62	uint16_t		mz_lfanew;
63} __attribute__((packed));
64
65struct coff_header {
66	uint8_t			coff_dont_care[2];
67	uint16_t		coff_number_of_sections;
68	uint8_t			coff_dont_care_either[16];
69} __attribute__((packed));
70
71#define	PE_SIGNATURE		0x00004550
72
73struct pe_header {
74	uint32_t		pe_signature;
75	struct coff_header	pe_coff;
76} __attribute__((packed));
77
78#define	PE_OPTIONAL_MAGIC_32		0x010B
79#define	PE_OPTIONAL_MAGIC_32_PLUS	0x020B
80
81#define	PE_OPTIONAL_SUBSYSTEM_EFI_APPLICATION	10
82#define	PE_OPTIONAL_SUBSYSTEM_EFI_BOOT		11
83#define	PE_OPTIONAL_SUBSYSTEM_EFI_RUNTIME	12
84
85struct pe_optional_header_32 {
86	uint16_t		po_magic;
87	uint8_t			po_dont_care[58];
88	uint32_t		po_size_of_headers;
89	uint32_t		po_checksum;
90	uint16_t		po_subsystem;
91	uint8_t			po_dont_care_either[22];
92	uint32_t		po_number_of_rva_and_sizes;
93} __attribute__((packed));
94
95CTASSERT(offsetof(struct pe_optional_header_32, po_size_of_headers) == 60);
96CTASSERT(offsetof(struct pe_optional_header_32, po_checksum) == 64);
97CTASSERT(offsetof(struct pe_optional_header_32, po_subsystem) == 68);
98CTASSERT(offsetof(struct pe_optional_header_32, po_number_of_rva_and_sizes) == 92);
99
100struct pe_optional_header_32_plus {
101	uint16_t		po_magic;
102	uint8_t			po_dont_care[58];
103	uint32_t		po_size_of_headers;
104	uint32_t		po_checksum;
105	uint16_t		po_subsystem;
106	uint8_t			po_dont_care_either[38];
107	uint32_t		po_number_of_rva_and_sizes;
108} __attribute__((packed));
109
110CTASSERT(offsetof(struct pe_optional_header_32_plus, po_size_of_headers) == 60);
111CTASSERT(offsetof(struct pe_optional_header_32_plus, po_checksum) == 64);
112CTASSERT(offsetof(struct pe_optional_header_32_plus, po_subsystem) == 68);
113CTASSERT(offsetof(struct pe_optional_header_32_plus, po_number_of_rva_and_sizes) == 108);
114
115#define	PE_DIRECTORY_ENTRY_CERTIFICATE	4
116
117struct pe_directory_entry {
118	uint32_t	pde_rva;
119	uint32_t	pde_size;
120} __attribute__((packed));
121
122struct pe_section_header {
123	uint8_t			psh_dont_care[16];
124	uint32_t		psh_size_of_raw_data;
125	uint32_t		psh_pointer_to_raw_data;
126	uint8_t			psh_dont_care_either[16];
127} __attribute__((packed));
128
129CTASSERT(offsetof(struct pe_section_header, psh_size_of_raw_data) == 16);
130CTASSERT(offsetof(struct pe_section_header, psh_pointer_to_raw_data) == 20);
131
132#define	PE_CERTIFICATE_REVISION		0x0200
133#define	PE_CERTIFICATE_TYPE		0x0002
134
135struct pe_certificate {
136	uint32_t	pc_len;
137	uint16_t	pc_revision;
138	uint16_t	pc_type;
139	char		pc_signature[0];
140} __attribute__((packed));
141
142void
143range_check(const struct executable *x, off_t off, size_t len,
144    const char *name)
145{
146
147	if (off < 0) {
148		errx(1, "%s starts at negative offset %jd",
149		    name, (intmax_t)off);
150	}
151	if (off >= (off_t)x->x_len) {
152		errx(1, "%s starts at %jd, past the end of executable at %zd",
153		    name, (intmax_t)off, x->x_len);
154	}
155	if (len >= x->x_len) {
156		errx(1, "%s size %zd is larger than the executable size %zd",
157		    name, len, x->x_len);
158	}
159	if (off + len > x->x_len) {
160		errx(1, "%s extends to %jd, past the end of executable at %zd",
161		    name, (intmax_t)(off + len), x->x_len);
162	}
163}
164
165size_t
166signature_size(const struct executable *x)
167{
168	const struct pe_directory_entry *pde;
169
170	range_check(x, x->x_certificate_entry_off,
171	    x->x_certificate_entry_len, "Certificate Directory");
172
173	pde = (struct pe_directory_entry *)
174	    (x->x_buf + x->x_certificate_entry_off);
175
176	if (pde->pde_rva != 0 && pde->pde_size == 0)
177		warnx("signature size is 0, but its RVA is %d", pde->pde_rva);
178	if (pde->pde_rva == 0 && pde->pde_size != 0)
179		warnx("signature RVA is 0, but its size is %d", pde->pde_size);
180
181	return (pde->pde_size);
182}
183
184void
185show_certificate(const struct executable *x)
186{
187	struct pe_certificate *pc;
188	const struct pe_directory_entry *pde;
189
190	range_check(x, x->x_certificate_entry_off,
191	    x->x_certificate_entry_len, "Certificate Directory");
192
193	pde = (struct pe_directory_entry *)
194	    (x->x_buf + x->x_certificate_entry_off);
195
196	if (signature_size(x) == 0) {
197		printf("file not signed\n");
198		return;
199	}
200
201#if 0
202	printf("certificate chunk at offset %zd, size %zd\n",
203	    pde->pde_rva, pde->pde_size);
204#endif
205
206	range_check(x, pde->pde_rva, pde->pde_size, "Certificate chunk");
207
208	pc = (struct pe_certificate *)(x->x_buf + pde->pde_rva);
209	if (pc->pc_revision != PE_CERTIFICATE_REVISION) {
210		errx(1, "wrong certificate chunk revision, is %d, should be %d",
211		    pc->pc_revision, PE_CERTIFICATE_REVISION);
212	}
213	if (pc->pc_type != PE_CERTIFICATE_TYPE) {
214		errx(1, "wrong certificate chunk type, is %d, should be %d",
215		    pc->pc_type, PE_CERTIFICATE_TYPE);
216	}
217	printf("to dump PKCS7:\n    "
218	    "dd if='%s' bs=1 skip=%zd | openssl pkcs7 -inform DER -print\n",
219	    x->x_path, pde->pde_rva + offsetof(struct pe_certificate, pc_signature));
220	printf("to dump raw ASN.1:\n    "
221	    "openssl asn1parse -i -inform DER -offset %zd -in '%s'\n",
222	    pde->pde_rva + offsetof(struct pe_certificate, pc_signature), x->x_path);
223}
224
225static void
226parse_section_table(struct executable *x, off_t off, int number_of_sections)
227{
228	const struct pe_section_header *psh;
229	int i;
230
231	range_check(x, off, sizeof(*psh) * number_of_sections,
232	    "section table");
233
234	if (x->x_headers_len <= off + sizeof(*psh) * number_of_sections)
235		errx(1, "section table outside of headers");
236
237	psh = (const struct pe_section_header *)(x->x_buf + off);
238
239	if (number_of_sections >= MAX_SECTIONS) {
240		errx(1, "too many sections: got %d, should be %d",
241		    number_of_sections, MAX_SECTIONS);
242	}
243	x->x_nsections = number_of_sections;
244
245	for (i = 0; i < number_of_sections; i++) {
246		if (psh->psh_pointer_to_raw_data < x->x_headers_len)
247			errx(1, "section points inside the headers");
248
249		range_check(x, psh->psh_pointer_to_raw_data,
250		    psh->psh_size_of_raw_data, "section");
251#if 0
252		printf("section %d: start %d, size %d\n",
253		    i, psh->psh_pointer_to_raw_data, psh->psh_size_of_raw_data);
254#endif
255		x->x_section_off[i] = psh->psh_pointer_to_raw_data;
256		x->x_section_len[i] = psh->psh_size_of_raw_data;
257		psh++;
258	}
259}
260
261static void
262parse_directory(struct executable *x, off_t off,
263    int number_of_rva_and_sizes, int number_of_sections)
264{
265	//int i;
266	const struct pe_directory_entry *pde;
267
268	//printf("Data Directory at offset %zd\n", off);
269
270	if (number_of_rva_and_sizes <= PE_DIRECTORY_ENTRY_CERTIFICATE) {
271		errx(1, "wrong NumberOfRvaAndSizes %d; should be at least %d",
272		    number_of_rva_and_sizes, PE_DIRECTORY_ENTRY_CERTIFICATE);
273	}
274
275	range_check(x, off, sizeof(*pde) * number_of_rva_and_sizes,
276	    "PE Data Directory");
277	if (x->x_headers_len <= off + sizeof(*pde) * number_of_rva_and_sizes)
278		errx(1, "PE Data Directory outside of headers");
279
280	x->x_certificate_entry_off =
281	    off + sizeof(*pde) * PE_DIRECTORY_ENTRY_CERTIFICATE;
282	x->x_certificate_entry_len = sizeof(*pde);
283#if 0
284	printf("certificate directory entry at offset %zd, len %zd\n",
285	    x->x_certificate_entry_off, x->x_certificate_entry_len);
286
287	pde = (struct pe_directory_entry *)(x->x_buf + off);
288	for (i = 0; i < number_of_rva_and_sizes; i++) {
289		printf("rva %zd, size %zd\n", pde->pde_rva, pde->pde_size);
290		pde++;
291	}
292#endif
293
294	return (parse_section_table(x,
295	    off + sizeof(*pde) * number_of_rva_and_sizes, number_of_sections));
296}
297
298/*
299 * The PE checksum algorithm is undocumented; this code is mostly based on
300 * http://forum.sysinternals.com/optional-header-checksum-calculation_topic24214.html
301 *
302 * "Sum the entire image file, excluding the CheckSum field in the optional
303 * header, as an array of USHORTs, allowing any carry above 16 bits to be added
304 * back onto the low 16 bits. Then add the file size to get a 32-bit value."
305 *
306 * Note that most software does not care about the checksum at all; perhaps
307 * we could just set it to 0 instead.
308 *
309 * XXX: Endianness?
310 */
311static uint32_t
312compute_checksum(const struct executable *x)
313{
314	uint32_t cksum = 0;
315	uint16_t tmp;
316	int i;
317
318	range_check(x, x->x_checksum_off, x->x_checksum_len, "PE checksum");
319
320	assert(x->x_checksum_off % 2 == 0);
321
322	for (i = 0; i + sizeof(tmp) < x->x_len; i += 2) {
323		/*
324		 * Don't checksum the checksum.  The +2 is because the checksum
325		 * is 4 bytes, and here we're iterating over 2 byte chunks.
326		 */
327		if (i == x->x_checksum_off || i == x->x_checksum_off + 2) {
328			tmp = 0;
329		} else {
330			assert(i + sizeof(tmp) <= x->x_len);
331			memcpy(&tmp, x->x_buf + i, sizeof(tmp));
332		}
333
334		cksum += tmp;
335		cksum += cksum >> 16;
336		cksum &= 0xffff;
337	}
338
339	cksum += cksum >> 16;
340	cksum &= 0xffff;
341
342	cksum += x->x_len;
343
344	return (cksum);
345}
346
347static void
348parse_optional_32_plus(struct executable *x, off_t off,
349    int number_of_sections)
350{
351#if 0
352	uint32_t computed_checksum;
353#endif
354	const struct pe_optional_header_32_plus	*po;
355
356	range_check(x, off, sizeof(*po), "PE Optional Header");
357
358	po = (struct pe_optional_header_32_plus *)(x->x_buf + off);
359	switch (po->po_subsystem) {
360	case PE_OPTIONAL_SUBSYSTEM_EFI_APPLICATION:
361	case PE_OPTIONAL_SUBSYSTEM_EFI_BOOT:
362	case PE_OPTIONAL_SUBSYSTEM_EFI_RUNTIME:
363		break;
364	default:
365		errx(1, "wrong PE Optional Header subsystem 0x%x",
366		    po->po_subsystem);
367	}
368
369#if 0
370	printf("subsystem %d, checksum 0x%x, %d data directories\n",
371	    po->po_subsystem, po->po_checksum, po->po_number_of_rva_and_sizes);
372#endif
373
374	x->x_checksum_off = off +
375	    offsetof(struct pe_optional_header_32_plus, po_checksum);
376	x->x_checksum_len = sizeof(po->po_checksum);
377#if 0
378	printf("checksum 0x%x at offset %zd, len %zd\n",
379	    po->po_checksum, x->x_checksum_off, x->x_checksum_len);
380
381	computed_checksum = compute_checksum(x);
382	if (computed_checksum != po->po_checksum) {
383		warnx("invalid PE+ checksum; is 0x%x, should be 0x%x",
384		    po->po_checksum, computed_checksum);
385	}
386#endif
387
388	if (x->x_len < x->x_headers_len)
389		errx(1, "invalid SizeOfHeaders %d", po->po_size_of_headers);
390	x->x_headers_len = po->po_size_of_headers;
391	//printf("Size of Headers: %d\n", po->po_size_of_headers);
392
393	return (parse_directory(x, off + sizeof(*po),
394	    po->po_number_of_rva_and_sizes, number_of_sections));
395}
396
397static void
398parse_optional_32(struct executable *x, off_t off, int number_of_sections)
399{
400#if 0
401	uint32_t computed_checksum;
402#endif
403	const struct pe_optional_header_32 *po;
404
405	range_check(x, off, sizeof(*po), "PE Optional Header");
406
407	po = (struct pe_optional_header_32 *)(x->x_buf + off);
408	switch (po->po_subsystem) {
409	case PE_OPTIONAL_SUBSYSTEM_EFI_APPLICATION:
410	case PE_OPTIONAL_SUBSYSTEM_EFI_BOOT:
411	case PE_OPTIONAL_SUBSYSTEM_EFI_RUNTIME:
412		break;
413	default:
414		errx(1, "wrong PE Optional Header subsystem 0x%x",
415		    po->po_subsystem);
416	}
417
418#if 0
419	printf("subsystem %d, checksum 0x%x, %d data directories\n",
420	    po->po_subsystem, po->po_checksum, po->po_number_of_rva_and_sizes);
421#endif
422
423	x->x_checksum_off = off +
424	    offsetof(struct pe_optional_header_32, po_checksum);
425	x->x_checksum_len = sizeof(po->po_checksum);
426#if 0
427	printf("checksum at offset %zd, len %zd\n",
428	    x->x_checksum_off, x->x_checksum_len);
429
430	computed_checksum = compute_checksum(x);
431	if (computed_checksum != po->po_checksum) {
432		warnx("invalid PE checksum; is 0x%x, should be 0x%x",
433		    po->po_checksum, computed_checksum);
434	}
435#endif
436
437	if (x->x_len < x->x_headers_len)
438		errx(1, "invalid SizeOfHeaders %d", po->po_size_of_headers);
439	x->x_headers_len = po->po_size_of_headers;
440	//printf("Size of Headers: %d\n", po->po_size_of_headers);
441
442	return (parse_directory(x, off + sizeof(*po),
443	    po->po_number_of_rva_and_sizes, number_of_sections));
444}
445
446static void
447parse_optional(struct executable *x, off_t off, int number_of_sections)
448{
449	const struct pe_optional_header_32 *po;
450
451	//printf("Optional header offset %zd\n", off);
452
453	range_check(x, off, sizeof(*po), "PE Optional Header");
454
455	po = (struct pe_optional_header_32 *)(x->x_buf + off);
456
457	switch (po->po_magic) {
458	case PE_OPTIONAL_MAGIC_32:
459		return (parse_optional_32(x, off, number_of_sections));
460	case PE_OPTIONAL_MAGIC_32_PLUS:
461		return (parse_optional_32_plus(x, off, number_of_sections));
462	default:
463		errx(1, "wrong PE Optional Header magic 0x%x", po->po_magic);
464	}
465}
466
467static void
468parse_pe(struct executable *x, off_t off)
469{
470	const struct pe_header *pe;
471
472	//printf("PE offset %zd, PE size %zd\n", off, sizeof(*pe));
473
474	range_check(x, off, sizeof(*pe), "PE header");
475
476	pe = (struct pe_header *)(x->x_buf + off);
477	if (pe->pe_signature != PE_SIGNATURE)
478		errx(1, "wrong PE signature 0x%x", pe->pe_signature);
479
480	//printf("Number of sections: %d\n", pe->pe_coff.coff_number_of_sections);
481
482	parse_optional(x, off + sizeof(*pe),
483	    pe->pe_coff.coff_number_of_sections);
484}
485
486void
487parse(struct executable *x)
488{
489	const struct mz_header *mz;
490
491	range_check(x, 0, sizeof(*mz), "MZ header");
492
493	mz = (struct mz_header *)x->x_buf;
494	if (mz->mz_signature[0] != 'M' || mz->mz_signature[1] != 'Z')
495		errx(1, "MZ header not found");
496
497	return (parse_pe(x, mz->mz_lfanew));
498}
499
500static off_t
501append(struct executable *x, void *ptr, size_t len)
502{
503	off_t off;
504
505	/*
506	 * XXX: Alignment.
507	 */
508	off = x->x_len;
509	x->x_buf = realloc(x->x_buf, x->x_len + len);
510	if (x->x_buf == NULL)
511		err(1, "realloc");
512	memcpy(x->x_buf + x->x_len, ptr, len);
513	x->x_len += len;
514
515	return (off);
516}
517
518void
519update(struct executable *x)
520{
521	uint32_t checksum;
522	struct pe_certificate *pc;
523	struct pe_directory_entry pde;
524	size_t pc_len;
525	off_t pc_off;
526
527	pc_len = sizeof(*pc) + x->x_signature_len;
528	pc = calloc(1, pc_len);
529	if (pc == NULL)
530		err(1, "calloc");
531
532#if 0
533	/*
534	 * Note that pc_len is the length of pc_certificate,
535	 * not the whole structure.
536	 *
537	 * XXX: That's what the spec says - but it breaks at least
538	 *      sbverify and "pesign -S", so the spec is probably wrong.
539	 */
540	pc->pc_len = x->x_signature_len;
541#else
542	pc->pc_len = pc_len;
543#endif
544	pc->pc_revision = PE_CERTIFICATE_REVISION;
545	pc->pc_type = PE_CERTIFICATE_TYPE;
546	memcpy(&pc->pc_signature, x->x_signature, x->x_signature_len);
547
548	pc_off = append(x, pc, pc_len);
549#if 0
550	printf("added signature chunk at offset %zd, len %zd\n",
551	    pc_off, pc_len);
552#endif
553
554	free(pc);
555
556	pde.pde_rva = pc_off;
557	pde.pde_size = pc_len;
558	memcpy(x->x_buf + x->x_certificate_entry_off, &pde, sizeof(pde));
559
560	checksum = compute_checksum(x);
561	assert(sizeof(checksum) == x->x_checksum_len);
562	memcpy(x->x_buf + x->x_checksum_off, &checksum, sizeof(checksum));
563#if 0
564	printf("new checksum 0x%x\n", checksum);
565#endif
566}
567