xref: /dragonfly/usr.sbin/uefisign/pe.c (revision 8e6c217bed2417c878d70765a454bf178418d761)
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  * $FreeBSD: head/usr.sbin/uefisign/pe.c 342253 2018-12-19 22:47:37Z mw $
32  */
33 
34 /*
35  * PE format reference:
36  * http://www.microsoft.com/whdc/system/platform/firmware/PECOFF.mspx
37  */
38 
39 #include <assert.h>
40 #include <err.h>
41 #include <errno.h>
42 #include <stddef.h>
43 #include <stdio.h>
44 #include <stdint.h>
45 #include <stdlib.h>
46 #include <string.h>
47 #include <unistd.h>
48 
49 #include "uefisign.h"
50 
51 #ifndef CTASSERT
52 #define CTASSERT(x)           _CTASSERT(x, __LINE__)
53 #define _CTASSERT(x, y)                 __CTASSERT(x, y)
54 #define __CTASSERT(x, y)      typedef char __assert_ ## y [(x) ? 1 : -1]
55 #endif
56 
57 #define PE_ALIGMENT_SIZE      8
58 
59 struct mz_header {
60           uint8_t                       mz_signature[2];
61           uint8_t                       mz_dont_care[58];
62           uint16_t            mz_lfanew;
63 } __attribute__((packed));
64 
65 struct 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 
73 struct 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 
85 struct 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 
95 CTASSERT(offsetof(struct pe_optional_header_32, po_size_of_headers) == 60);
96 CTASSERT(offsetof(struct pe_optional_header_32, po_checksum) == 64);
97 CTASSERT(offsetof(struct pe_optional_header_32, po_subsystem) == 68);
98 CTASSERT(offsetof(struct pe_optional_header_32, po_number_of_rva_and_sizes) == 92);
99 
100 struct 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 
110 CTASSERT(offsetof(struct pe_optional_header_32_plus, po_size_of_headers) == 60);
111 CTASSERT(offsetof(struct pe_optional_header_32_plus, po_checksum) == 64);
112 CTASSERT(offsetof(struct pe_optional_header_32_plus, po_subsystem) == 68);
113 CTASSERT(offsetof(struct pe_optional_header_32_plus, po_number_of_rva_and_sizes) == 108);
114 
115 #define   PE_DIRECTORY_ENTRY_CERTIFICATE          4
116 
117 struct pe_directory_entry {
118           uint32_t  pde_rva;
119           uint32_t  pde_size;
120 } __attribute__((packed));
121 
122 struct 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 
129 CTASSERT(offsetof(struct pe_section_header, psh_size_of_raw_data) == 16);
130 CTASSERT(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 
135 struct 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 
142 void
range_check(const struct executable * x,off_t off,size_t len,const char * name)143 range_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 
165 size_t
signature_size(const struct executable * x)166 signature_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 
184 void
show_certificate(const struct executable * x)185 show_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 
225 static void
parse_section_table(struct executable * x,off_t off,int number_of_sections)226 parse_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 
261 static void
parse_directory(struct executable * x,off_t off,int number_of_rva_and_sizes,int number_of_sections)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
compute_checksum(const struct executable * x)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
parse_optional_32_plus(struct executable * x,off_t off,int number_of_sections)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
parse_optional_32(struct executable * x,off_t off,int number_of_sections)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
parse_optional(struct executable * x,off_t off,int number_of_sections)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
parse_pe(struct executable * x,off_t off)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
parse(struct executable * x)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
append(struct executable * x,void * ptr,size_t len,size_t aligment)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
update(struct executable * x)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