/*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2014 The FreeBSD Foundation * * This software was developed by Edward Tomasz Napierala under sponsorship * from the FreeBSD Foundation. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * */ /* * PE format reference: * http://www.microsoft.com/whdc/system/platform/firmware/PECOFF.mspx */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include "uefisign.h" #ifndef CTASSERT #define CTASSERT(x) _CTASSERT(x, __LINE__) #define _CTASSERT(x, y) __CTASSERT(x, y) #define __CTASSERT(x, y) typedef char __assert_ ## y [(x) ? 1 : -1] #endif #define PE_ALIGMENT_SIZE 8 struct mz_header { uint8_t mz_signature[2]; uint8_t mz_dont_care[58]; uint16_t mz_lfanew; } __attribute__((packed)); struct coff_header { uint8_t coff_dont_care[2]; uint16_t coff_number_of_sections; uint8_t coff_dont_care_either[16]; } __attribute__((packed)); #define PE_SIGNATURE 0x00004550 struct pe_header { uint32_t pe_signature; struct coff_header pe_coff; } __attribute__((packed)); #define PE_OPTIONAL_MAGIC_32 0x010B #define PE_OPTIONAL_MAGIC_32_PLUS 0x020B #define PE_OPTIONAL_SUBSYSTEM_EFI_APPLICATION 10 #define PE_OPTIONAL_SUBSYSTEM_EFI_BOOT 11 #define PE_OPTIONAL_SUBSYSTEM_EFI_RUNTIME 12 struct pe_optional_header_32 { uint16_t po_magic; uint8_t po_dont_care[58]; uint32_t po_size_of_headers; uint32_t po_checksum; uint16_t po_subsystem; uint8_t po_dont_care_either[22]; uint32_t po_number_of_rva_and_sizes; } __attribute__((packed)); CTASSERT(offsetof(struct pe_optional_header_32, po_size_of_headers) == 60); CTASSERT(offsetof(struct pe_optional_header_32, po_checksum) == 64); CTASSERT(offsetof(struct pe_optional_header_32, po_subsystem) == 68); CTASSERT(offsetof(struct pe_optional_header_32, po_number_of_rva_and_sizes) == 92); struct pe_optional_header_32_plus { uint16_t po_magic; uint8_t po_dont_care[58]; uint32_t po_size_of_headers; uint32_t po_checksum; uint16_t po_subsystem; uint8_t po_dont_care_either[38]; uint32_t po_number_of_rva_and_sizes; } __attribute__((packed)); CTASSERT(offsetof(struct pe_optional_header_32_plus, po_size_of_headers) == 60); CTASSERT(offsetof(struct pe_optional_header_32_plus, po_checksum) == 64); CTASSERT(offsetof(struct pe_optional_header_32_plus, po_subsystem) == 68); CTASSERT(offsetof(struct pe_optional_header_32_plus, po_number_of_rva_and_sizes) == 108); #define PE_DIRECTORY_ENTRY_CERTIFICATE 4 struct pe_directory_entry { uint32_t pde_rva; uint32_t pde_size; } __attribute__((packed)); struct pe_section_header { uint8_t psh_dont_care[16]; uint32_t psh_size_of_raw_data; uint32_t psh_pointer_to_raw_data; uint8_t psh_dont_care_either[16]; } __attribute__((packed)); CTASSERT(offsetof(struct pe_section_header, psh_size_of_raw_data) == 16); CTASSERT(offsetof(struct pe_section_header, psh_pointer_to_raw_data) == 20); #define PE_CERTIFICATE_REVISION 0x0200 #define PE_CERTIFICATE_TYPE 0x0002 struct pe_certificate { uint32_t pc_len; uint16_t pc_revision; uint16_t pc_type; char pc_signature[0]; } __attribute__((packed)); void range_check(const struct executable *x, off_t off, size_t len, const char *name) { if (off < 0) { errx(1, "%s starts at negative offset %jd", name, (intmax_t)off); } if (off >= (off_t)x->x_len) { errx(1, "%s starts at %jd, past the end of executable at %zd", name, (intmax_t)off, x->x_len); } if (len >= x->x_len) { errx(1, "%s size %zd is larger than the executable size %zd", name, len, x->x_len); } if (off + len > x->x_len) { errx(1, "%s extends to %jd, past the end of executable at %zd", name, (intmax_t)(off + len), x->x_len); } } size_t signature_size(const struct executable *x) { const struct pe_directory_entry *pde; range_check(x, x->x_certificate_entry_off, x->x_certificate_entry_len, "Certificate Directory"); pde = (struct pe_directory_entry *) (x->x_buf + x->x_certificate_entry_off); if (pde->pde_rva != 0 && pde->pde_size == 0) warnx("signature size is 0, but its RVA is %d", pde->pde_rva); if (pde->pde_rva == 0 && pde->pde_size != 0) warnx("signature RVA is 0, but its size is %d", pde->pde_size); return (pde->pde_size); } void show_certificate(const struct executable *x) { struct pe_certificate *pc; const struct pe_directory_entry *pde; range_check(x, x->x_certificate_entry_off, x->x_certificate_entry_len, "Certificate Directory"); pde = (struct pe_directory_entry *) (x->x_buf + x->x_certificate_entry_off); if (signature_size(x) == 0) { printf("file not signed\n"); return; } #if 0 printf("certificate chunk at offset %zd, size %zd\n", pde->pde_rva, pde->pde_size); #endif range_check(x, pde->pde_rva, pde->pde_size, "Certificate chunk"); pc = (struct pe_certificate *)(x->x_buf + pde->pde_rva); if (pc->pc_revision != PE_CERTIFICATE_REVISION) { errx(1, "wrong certificate chunk revision, is %d, should be %d", pc->pc_revision, PE_CERTIFICATE_REVISION); } if (pc->pc_type != PE_CERTIFICATE_TYPE) { errx(1, "wrong certificate chunk type, is %d, should be %d", pc->pc_type, PE_CERTIFICATE_TYPE); } printf("to dump PKCS7:\n " "dd if='%s' bs=1 skip=%zd | openssl pkcs7 -inform DER -print\n", x->x_path, pde->pde_rva + offsetof(struct pe_certificate, pc_signature)); printf("to dump raw ASN.1:\n " "openssl asn1parse -i -inform DER -offset %zd -in '%s'\n", pde->pde_rva + offsetof(struct pe_certificate, pc_signature), x->x_path); } static void parse_section_table(struct executable *x, off_t off, int number_of_sections) { const struct pe_section_header *psh; int i; range_check(x, off, sizeof(*psh) * number_of_sections, "section table"); if (x->x_headers_len <= off + sizeof(*psh) * number_of_sections) errx(1, "section table outside of headers"); psh = (const struct pe_section_header *)(x->x_buf + off); if (number_of_sections >= MAX_SECTIONS) { errx(1, "too many sections: got %d, should be %d", number_of_sections, MAX_SECTIONS); } x->x_nsections = number_of_sections; for (i = 0; i < number_of_sections; i++) { if (psh->psh_size_of_raw_data > 0 && psh->psh_pointer_to_raw_data < x->x_headers_len) errx(1, "section points inside the headers"); range_check(x, psh->psh_pointer_to_raw_data, psh->psh_size_of_raw_data, "section"); #if 0 printf("section %d: start %d, size %d\n", i, psh->psh_pointer_to_raw_data, psh->psh_size_of_raw_data); #endif x->x_section_off[i] = psh->psh_pointer_to_raw_data; x->x_section_len[i] = psh->psh_size_of_raw_data; psh++; } } static void parse_directory(struct executable *x, off_t off, int number_of_rva_and_sizes, int number_of_sections) { //int i; const struct pe_directory_entry *pde; //printf("Data Directory at offset %zd\n", off); if (number_of_rva_and_sizes <= PE_DIRECTORY_ENTRY_CERTIFICATE) { errx(1, "wrong NumberOfRvaAndSizes %d; should be at least %d", number_of_rva_and_sizes, PE_DIRECTORY_ENTRY_CERTIFICATE); } range_check(x, off, sizeof(*pde) * number_of_rva_and_sizes, "PE Data Directory"); if (x->x_headers_len <= off + sizeof(*pde) * number_of_rva_and_sizes) errx(1, "PE Data Directory outside of headers"); x->x_certificate_entry_off = off + sizeof(*pde) * PE_DIRECTORY_ENTRY_CERTIFICATE; x->x_certificate_entry_len = sizeof(*pde); #if 0 printf("certificate directory entry at offset %zd, len %zd\n", x->x_certificate_entry_off, x->x_certificate_entry_len); pde = (struct pe_directory_entry *)(x->x_buf + off); for (i = 0; i < number_of_rva_and_sizes; i++) { printf("rva %zd, size %zd\n", pde->pde_rva, pde->pde_size); pde++; } #endif return (parse_section_table(x, off + sizeof(*pde) * number_of_rva_and_sizes, number_of_sections)); } /* * The PE checksum algorithm is undocumented; this code is mostly based on * http://forum.sysinternals.com/optional-header-checksum-calculation_topic24214.html * * "Sum the entire image file, excluding the CheckSum field in the optional * header, as an array of USHORTs, allowing any carry above 16 bits to be added * back onto the low 16 bits. Then add the file size to get a 32-bit value." * * Note that most software does not care about the checksum at all; perhaps * we could just set it to 0 instead. * * XXX: Endianness? */ static uint32_t compute_checksum(const struct executable *x) { uint32_t cksum = 0; uint16_t tmp; int i; range_check(x, x->x_checksum_off, x->x_checksum_len, "PE checksum"); assert(x->x_checksum_off % 2 == 0); for (i = 0; i + sizeof(tmp) < x->x_len; i += 2) { /* * Don't checksum the checksum. The +2 is because the checksum * is 4 bytes, and here we're iterating over 2 byte chunks. */ if (i == x->x_checksum_off || i == x->x_checksum_off + 2) { tmp = 0; } else { assert(i + sizeof(tmp) <= x->x_len); memcpy(&tmp, x->x_buf + i, sizeof(tmp)); } cksum += tmp; cksum += cksum >> 16; cksum &= 0xffff; } cksum += cksum >> 16; cksum &= 0xffff; cksum += x->x_len; return (cksum); } static void parse_optional_32_plus(struct executable *x, off_t off, int number_of_sections) { #if 0 uint32_t computed_checksum; #endif const struct pe_optional_header_32_plus *po; range_check(x, off, sizeof(*po), "PE Optional Header"); po = (struct pe_optional_header_32_plus *)(x->x_buf + off); switch (po->po_subsystem) { case PE_OPTIONAL_SUBSYSTEM_EFI_APPLICATION: case PE_OPTIONAL_SUBSYSTEM_EFI_BOOT: case PE_OPTIONAL_SUBSYSTEM_EFI_RUNTIME: break; default: errx(1, "wrong PE Optional Header subsystem 0x%x", po->po_subsystem); } #if 0 printf("subsystem %d, checksum 0x%x, %d data directories\n", po->po_subsystem, po->po_checksum, po->po_number_of_rva_and_sizes); #endif x->x_checksum_off = off + offsetof(struct pe_optional_header_32_plus, po_checksum); x->x_checksum_len = sizeof(po->po_checksum); #if 0 printf("checksum 0x%x at offset %zd, len %zd\n", po->po_checksum, x->x_checksum_off, x->x_checksum_len); computed_checksum = compute_checksum(x); if (computed_checksum != po->po_checksum) { warnx("invalid PE+ checksum; is 0x%x, should be 0x%x", po->po_checksum, computed_checksum); } #endif if (x->x_len < x->x_headers_len) errx(1, "invalid SizeOfHeaders %d", po->po_size_of_headers); x->x_headers_len = po->po_size_of_headers; //printf("Size of Headers: %d\n", po->po_size_of_headers); return (parse_directory(x, off + sizeof(*po), po->po_number_of_rva_and_sizes, number_of_sections)); } static void parse_optional_32(struct executable *x, off_t off, int number_of_sections) { #if 0 uint32_t computed_checksum; #endif const struct pe_optional_header_32 *po; range_check(x, off, sizeof(*po), "PE Optional Header"); po = (struct pe_optional_header_32 *)(x->x_buf + off); switch (po->po_subsystem) { case PE_OPTIONAL_SUBSYSTEM_EFI_APPLICATION: case PE_OPTIONAL_SUBSYSTEM_EFI_BOOT: case PE_OPTIONAL_SUBSYSTEM_EFI_RUNTIME: break; default: errx(1, "wrong PE Optional Header subsystem 0x%x", po->po_subsystem); } #if 0 printf("subsystem %d, checksum 0x%x, %d data directories\n", po->po_subsystem, po->po_checksum, po->po_number_of_rva_and_sizes); #endif x->x_checksum_off = off + offsetof(struct pe_optional_header_32, po_checksum); x->x_checksum_len = sizeof(po->po_checksum); #if 0 printf("checksum at offset %zd, len %zd\n", x->x_checksum_off, x->x_checksum_len); computed_checksum = compute_checksum(x); if (computed_checksum != po->po_checksum) { warnx("invalid PE checksum; is 0x%x, should be 0x%x", po->po_checksum, computed_checksum); } #endif if (x->x_len < x->x_headers_len) errx(1, "invalid SizeOfHeaders %d", po->po_size_of_headers); x->x_headers_len = po->po_size_of_headers; //printf("Size of Headers: %d\n", po->po_size_of_headers); return (parse_directory(x, off + sizeof(*po), po->po_number_of_rva_and_sizes, number_of_sections)); } static void parse_optional(struct executable *x, off_t off, int number_of_sections) { const struct pe_optional_header_32 *po; //printf("Optional header offset %zd\n", off); range_check(x, off, sizeof(*po), "PE Optional Header"); po = (struct pe_optional_header_32 *)(x->x_buf + off); switch (po->po_magic) { case PE_OPTIONAL_MAGIC_32: return (parse_optional_32(x, off, number_of_sections)); case PE_OPTIONAL_MAGIC_32_PLUS: return (parse_optional_32_plus(x, off, number_of_sections)); default: errx(1, "wrong PE Optional Header magic 0x%x", po->po_magic); } } static void parse_pe(struct executable *x, off_t off) { const struct pe_header *pe; //printf("PE offset %zd, PE size %zd\n", off, sizeof(*pe)); range_check(x, off, sizeof(*pe), "PE header"); pe = (struct pe_header *)(x->x_buf + off); if (pe->pe_signature != PE_SIGNATURE) errx(1, "wrong PE signature 0x%x", pe->pe_signature); //printf("Number of sections: %d\n", pe->pe_coff.coff_number_of_sections); parse_optional(x, off + sizeof(*pe), pe->pe_coff.coff_number_of_sections); } void parse(struct executable *x) { const struct mz_header *mz; range_check(x, 0, sizeof(*mz), "MZ header"); mz = (struct mz_header *)x->x_buf; if (mz->mz_signature[0] != 'M' || mz->mz_signature[1] != 'Z') errx(1, "MZ header not found"); return (parse_pe(x, mz->mz_lfanew)); } static off_t append(struct executable *x, void *ptr, size_t len, size_t aligment) { off_t off; off = x->x_len; x->x_buf = realloc(x->x_buf, x->x_len + len + aligment); if (x->x_buf == NULL) err(1, "realloc"); memcpy(x->x_buf + x->x_len, ptr, len); memset(x->x_buf + x->x_len + len, 0, aligment); x->x_len += len + aligment; return (off); } void update(struct executable *x) { uint32_t checksum; struct pe_certificate *pc; struct pe_directory_entry pde; size_t pc_len; size_t pc_aligment; off_t pc_off; pc_len = sizeof(*pc) + x->x_signature_len; pc = calloc(1, pc_len); if (pc == NULL) err(1, "calloc"); if (pc_len % PE_ALIGMENT_SIZE > 0) pc_aligment = PE_ALIGMENT_SIZE - (pc_len % PE_ALIGMENT_SIZE); else pc_aligment = 0; #if 0 /* * Note that pc_len is the length of pc_certificate, * not the whole structure. * * XXX: That's what the spec says - but it breaks at least * sbverify and "pesign -S", so the spec is probably wrong. */ pc->pc_len = x->x_signature_len; #else pc->pc_len = pc_len; #endif pc->pc_revision = PE_CERTIFICATE_REVISION; pc->pc_type = PE_CERTIFICATE_TYPE; memcpy(&pc->pc_signature, x->x_signature, x->x_signature_len); pc_off = append(x, pc, pc_len, pc_aligment); #if 0 printf("added signature chunk at offset %zd, len %zd\n", pc_off, pc_len); #endif free(pc); pde.pde_rva = pc_off; pde.pde_size = pc_len + pc_aligment; memcpy(x->x_buf + x->x_certificate_entry_off, &pde, sizeof(pde)); checksum = compute_checksum(x); assert(sizeof(checksum) == x->x_checksum_len); memcpy(x->x_buf + x->x_checksum_off, &checksum, sizeof(checksum)); #if 0 printf("new checksum 0x%x\n", checksum); #endif }