xref: /freebsd/usr.sbin/uefisign/pe.c (revision 22cf89c938886d14f5796fc49f9f020c23ea8eaf)
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 #include <assert.h>
39 #include <err.h>
40 #include <errno.h>
41 #include <stddef.h>
42 #include <stdio.h>
43 #include <stdint.h>
44 #include <stdlib.h>
45 #include <string.h>
46 #include <unistd.h>
47 
48 #include "uefisign.h"
49 
50 #ifndef CTASSERT
51 #define CTASSERT(x)		_CTASSERT(x, __LINE__)
52 #define _CTASSERT(x, y)		__CTASSERT(x, y)
53 #define __CTASSERT(x, y)	typedef char __assert_ ## y [(x) ? 1 : -1]
54 #endif
55 
56 #define PE_ALIGMENT_SIZE	8
57 
58 struct mz_header {
59 	uint8_t			mz_signature[2];
60 	uint8_t			mz_dont_care[58];
61 	uint16_t		mz_lfanew;
62 } __attribute__((packed));
63 
64 struct coff_header {
65 	uint8_t			coff_dont_care[2];
66 	uint16_t		coff_number_of_sections;
67 	uint8_t			coff_dont_care_either[16];
68 } __attribute__((packed));
69 
70 #define	PE_SIGNATURE		0x00004550
71 
72 struct pe_header {
73 	uint32_t		pe_signature;
74 	struct coff_header	pe_coff;
75 } __attribute__((packed));
76 
77 #define	PE_OPTIONAL_MAGIC_32		0x010B
78 #define	PE_OPTIONAL_MAGIC_32_PLUS	0x020B
79 
80 #define	PE_OPTIONAL_SUBSYSTEM_EFI_APPLICATION	10
81 #define	PE_OPTIONAL_SUBSYSTEM_EFI_BOOT		11
82 #define	PE_OPTIONAL_SUBSYSTEM_EFI_RUNTIME	12
83 
84 struct pe_optional_header_32 {
85 	uint16_t		po_magic;
86 	uint8_t			po_dont_care[58];
87 	uint32_t		po_size_of_headers;
88 	uint32_t		po_checksum;
89 	uint16_t		po_subsystem;
90 	uint8_t			po_dont_care_either[22];
91 	uint32_t		po_number_of_rva_and_sizes;
92 } __attribute__((packed));
93 
94 CTASSERT(offsetof(struct pe_optional_header_32, po_size_of_headers) == 60);
95 CTASSERT(offsetof(struct pe_optional_header_32, po_checksum) == 64);
96 CTASSERT(offsetof(struct pe_optional_header_32, po_subsystem) == 68);
97 CTASSERT(offsetof(struct pe_optional_header_32, po_number_of_rva_and_sizes) == 92);
98 
99 struct pe_optional_header_32_plus {
100 	uint16_t		po_magic;
101 	uint8_t			po_dont_care[58];
102 	uint32_t		po_size_of_headers;
103 	uint32_t		po_checksum;
104 	uint16_t		po_subsystem;
105 	uint8_t			po_dont_care_either[38];
106 	uint32_t		po_number_of_rva_and_sizes;
107 } __attribute__((packed));
108 
109 CTASSERT(offsetof(struct pe_optional_header_32_plus, po_size_of_headers) == 60);
110 CTASSERT(offsetof(struct pe_optional_header_32_plus, po_checksum) == 64);
111 CTASSERT(offsetof(struct pe_optional_header_32_plus, po_subsystem) == 68);
112 CTASSERT(offsetof(struct pe_optional_header_32_plus, po_number_of_rva_and_sizes) == 108);
113 
114 #define	PE_DIRECTORY_ENTRY_CERTIFICATE	4
115 
116 struct pe_directory_entry {
117 	uint32_t	pde_rva;
118 	uint32_t	pde_size;
119 } __attribute__((packed));
120 
121 struct pe_section_header {
122 	uint8_t			psh_dont_care[16];
123 	uint32_t		psh_size_of_raw_data;
124 	uint32_t		psh_pointer_to_raw_data;
125 	uint8_t			psh_dont_care_either[16];
126 } __attribute__((packed));
127 
128 CTASSERT(offsetof(struct pe_section_header, psh_size_of_raw_data) == 16);
129 CTASSERT(offsetof(struct pe_section_header, psh_pointer_to_raw_data) == 20);
130 
131 #define	PE_CERTIFICATE_REVISION		0x0200
132 #define	PE_CERTIFICATE_TYPE		0x0002
133 
134 struct pe_certificate {
135 	uint32_t	pc_len;
136 	uint16_t	pc_revision;
137 	uint16_t	pc_type;
138 	char		pc_signature[0];
139 } __attribute__((packed));
140 
141 void
142 range_check(const struct executable *x, off_t off, size_t len,
143     const char *name)
144 {
145 
146 	if (off < 0) {
147 		errx(1, "%s starts at negative offset %jd",
148 		    name, (intmax_t)off);
149 	}
150 	if (off >= (off_t)x->x_len) {
151 		errx(1, "%s starts at %jd, past the end of executable at %zd",
152 		    name, (intmax_t)off, x->x_len);
153 	}
154 	if (len >= x->x_len) {
155 		errx(1, "%s size %zd is larger than the executable size %zd",
156 		    name, len, x->x_len);
157 	}
158 	if (off + len > x->x_len) {
159 		errx(1, "%s extends to %jd, past the end of executable at %zd",
160 		    name, (intmax_t)(off + len), x->x_len);
161 	}
162 }
163 
164 size_t
165 signature_size(const struct executable *x)
166 {
167 	const struct pe_directory_entry *pde;
168 
169 	range_check(x, x->x_certificate_entry_off,
170 	    x->x_certificate_entry_len, "Certificate Directory");
171 
172 	pde = (struct pe_directory_entry *)
173 	    (x->x_buf + x->x_certificate_entry_off);
174 
175 	if (pde->pde_rva != 0 && pde->pde_size == 0)
176 		warnx("signature size is 0, but its RVA is %d", pde->pde_rva);
177 	if (pde->pde_rva == 0 && pde->pde_size != 0)
178 		warnx("signature RVA is 0, but its size is %d", pde->pde_size);
179 
180 	return (pde->pde_size);
181 }
182 
183 void
184 show_certificate(const struct executable *x)
185 {
186 	struct pe_certificate *pc;
187 	const struct pe_directory_entry *pde;
188 
189 	range_check(x, x->x_certificate_entry_off,
190 	    x->x_certificate_entry_len, "Certificate Directory");
191 
192 	pde = (struct pe_directory_entry *)
193 	    (x->x_buf + x->x_certificate_entry_off);
194 
195 	if (signature_size(x) == 0) {
196 		printf("file not signed\n");
197 		return;
198 	}
199 
200 #if 0
201 	printf("certificate chunk at offset %zd, size %zd\n",
202 	    pde->pde_rva, pde->pde_size);
203 #endif
204 
205 	range_check(x, pde->pde_rva, pde->pde_size, "Certificate chunk");
206 
207 	pc = (struct pe_certificate *)(x->x_buf + pde->pde_rva);
208 	if (pc->pc_revision != PE_CERTIFICATE_REVISION) {
209 		errx(1, "wrong certificate chunk revision, is %d, should be %d",
210 		    pc->pc_revision, PE_CERTIFICATE_REVISION);
211 	}
212 	if (pc->pc_type != PE_CERTIFICATE_TYPE) {
213 		errx(1, "wrong certificate chunk type, is %d, should be %d",
214 		    pc->pc_type, PE_CERTIFICATE_TYPE);
215 	}
216 	printf("to dump PKCS7:\n    "
217 	    "dd if='%s' bs=1 skip=%zd | openssl pkcs7 -inform DER -print\n",
218 	    x->x_path, pde->pde_rva + offsetof(struct pe_certificate, pc_signature));
219 	printf("to dump raw ASN.1:\n    "
220 	    "openssl asn1parse -i -inform DER -offset %zd -in '%s'\n",
221 	    pde->pde_rva + offsetof(struct pe_certificate, pc_signature), x->x_path);
222 }
223 
224 static void
225 parse_section_table(struct executable *x, off_t off, int number_of_sections)
226 {
227 	const struct pe_section_header *psh;
228 	int i;
229 
230 	range_check(x, off, sizeof(*psh) * number_of_sections,
231 	    "section table");
232 
233 	if (x->x_headers_len < off + sizeof(*psh) * number_of_sections)
234 		errx(1, "section table outside of headers");
235 
236 	psh = (const struct pe_section_header *)(x->x_buf + off);
237 
238 	if (number_of_sections >= MAX_SECTIONS) {
239 		errx(1, "too many sections: got %d, should be %d",
240 		    number_of_sections, MAX_SECTIONS);
241 	}
242 	x->x_nsections = number_of_sections;
243 
244 	for (i = 0; i < number_of_sections; i++) {
245 		if (psh->psh_size_of_raw_data > 0 &&
246 		    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 
261 static void
262 parse_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  */
311 static uint32_t
312 compute_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 
347 static void
348 parse_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 
397 static void
398 parse_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 
446 static void
447 parse_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 
467 static void
468 parse_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 
486 void
487 parse(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 
500 static off_t
501 append(struct executable *x, void *ptr, size_t len, size_t aligment)
502 {
503 	off_t off;
504 
505 	off = x->x_len;
506 	x->x_buf = realloc(x->x_buf, x->x_len + len + aligment);
507 	if (x->x_buf == NULL)
508 		err(1, "realloc");
509 	memcpy(x->x_buf + x->x_len, ptr, len);
510 	memset(x->x_buf + x->x_len + len, 0, aligment);
511 	x->x_len += len + aligment;
512 
513 	return (off);
514 }
515 
516 void
517 update(struct executable *x)
518 {
519 	uint32_t checksum;
520 	struct pe_certificate *pc;
521 	struct pe_directory_entry pde;
522 	size_t pc_len;
523 	size_t pc_aligment;
524 	off_t pc_off;
525 
526 	pc_len = sizeof(*pc) + x->x_signature_len;
527 	pc = calloc(1, pc_len);
528 	if (pc == NULL)
529 		err(1, "calloc");
530 
531 	if (pc_len % PE_ALIGMENT_SIZE > 0)
532 		pc_aligment = PE_ALIGMENT_SIZE - (pc_len % PE_ALIGMENT_SIZE);
533 	else
534 		pc_aligment = 0;
535 
536 #if 0
537 	/*
538 	 * Note that pc_len is the length of pc_certificate,
539 	 * not the whole structure.
540 	 *
541 	 * XXX: That's what the spec says - but it breaks at least
542 	 *      sbverify and "pesign -S", so the spec is probably wrong.
543 	 */
544 	pc->pc_len = x->x_signature_len;
545 #else
546 	pc->pc_len = pc_len;
547 #endif
548 	pc->pc_revision = PE_CERTIFICATE_REVISION;
549 	pc->pc_type = PE_CERTIFICATE_TYPE;
550 	memcpy(&pc->pc_signature, x->x_signature, x->x_signature_len);
551 
552 	pc_off = append(x, pc, pc_len, pc_aligment);
553 #if 0
554 	printf("added signature chunk at offset %zd, len %zd\n",
555 	    pc_off, pc_len);
556 #endif
557 
558 	free(pc);
559 
560 	pde.pde_rva = pc_off;
561 	pde.pde_size = pc_len + pc_aligment;
562 	memcpy(x->x_buf + x->x_certificate_entry_off, &pde, sizeof(pde));
563 
564 	checksum = compute_checksum(x);
565 	assert(sizeof(checksum) == x->x_checksum_len);
566 	memcpy(x->x_buf + x->x_checksum_off, &checksum, sizeof(checksum));
567 #if 0
568 	printf("new checksum 0x%x\n", checksum);
569 #endif
570 }
571