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