1 // SPDX-License-Identifier: BSD-2-Clause-Patent
3 * post-process-pe.c - fix up timestamps and checksums in broken PE files
4 * Copyright Peter Jones <pjones@redhat.com>
20 #include <sys/param.h>
22 #include <sys/types.h>
26 #define PAGE_SIZE 4096
34 #define MIN_VERBOSITY ERROR
35 #define MAX_VERBOSITY NOISE
36 #define debug(level, ...) \
38 if (verbosity >= (level)) { \
39 printf("%s():%d: ", __func__, __LINE__); \
40 printf(__VA_ARGS__); \
45 static bool set_nx_compat
= false;
47 typedef uint8_t UINT8
;
48 typedef uint16_t UINT16
;
49 typedef uint32_t UINT32
;
50 typedef uint64_t UINT64
;
52 typedef uint16_t CHAR16
;
54 typedef unsigned long UINTN
;
63 #include "include/peimage.h"
65 #if defined(__GNUC__) && defined(__GNUC_MINOR__)
66 #define GNUC_PREREQ(maj, min) \
67 ((__GNUC__ << 16) + __GNUC_MINOR__ >= ((maj) << 16) + (min))
69 #define GNUC_PREREQ(maj, min) 0
72 #if defined(__clang__) && defined(__clang_major__) && defined(__clang_minor__)
73 #define CLANG_PREREQ(maj, min) \
74 ((__clang_major__ > (maj)) || \
75 (__clang_major__ == (maj) && __clang_minor__ >= (min)))
77 #define CLANG_PREREQ(maj, min) 0
80 #if GNUC_PREREQ(5, 1) || CLANG_PREREQ(3, 8)
81 #define add(a0, a1, s) __builtin_add_overflow(a0, a1, s)
82 #define sub(s0, s1, d) __builtin_sub_overflow(s0, s1, d)
83 #define mul(f0, f1, p) __builtin_mul_overflow(f0, f1, p)
85 #define add(a0, a1, s) \
87 (*s) = ((a0) + (a1)); \
90 #define sub(s0, s1, d) \
92 (*d) = ((s0) - (s1)); \
95 #define mul(f0, f1, p) \
97 (*p) = ((f0) * (f1)); \
101 #define div(d0, d1, q) \
103 unsigned int ret_ = ((d1) == 0); \
105 (*q) = ((d0) / (d1)); \
110 image_is_64_bit(EFI_IMAGE_OPTIONAL_HEADER_UNION
*PEHdr
)
112 /* .Magic is the same offset in all cases */
113 if (PEHdr
->Pe32Plus
.OptionalHeader
.Magic
==
114 EFI_IMAGE_NT_OPTIONAL_HDR64_MAGIC
)
120 load_pe(const char *const file
, void *const data
, const size_t datasize
,
121 PE_COFF_LOADER_IMAGE_CONTEXT
*ctx
)
123 EFI_IMAGE_DOS_HEADER
*DOSHdr
= data
;
124 EFI_IMAGE_OPTIONAL_HEADER_UNION
*PEHdr
= data
;
125 size_t HeaderWithoutDataDir
, SectionHeaderOffset
, OptHeaderSize
;
126 size_t FileAlignment
= 0;
127 size_t sz0
= 0, sz1
= 0;
130 debug(NOISE
, "datasize:%zu sizeof(PEHdr->Pe32):%zu\n", datasize
,
131 sizeof(PEHdr
->Pe32
));
132 if (datasize
< sizeof(PEHdr
->Pe32
))
133 errx(1, "%s: Invalid image size %zu (%zu < %zu)", file
,
134 datasize
, datasize
, sizeof(PEHdr
->Pe32
));
137 "DOSHdr->e_magic:0x%02hx EFI_IMAGE_DOS_SIGNATURE:0x%02hx\n",
138 DOSHdr
->e_magic
, EFI_IMAGE_DOS_SIGNATURE
);
139 if (DOSHdr
->e_magic
!= EFI_IMAGE_DOS_SIGNATURE
)
141 "%s: Invalid DOS header signature 0x%04hx (expected 0x%04hx)",
142 file
, DOSHdr
->e_magic
, EFI_IMAGE_DOS_SIGNATURE
);
144 debug(NOISE
, "DOSHdr->e_lfanew:%u datasize:%zu\n", DOSHdr
->e_lfanew
,
146 if (DOSHdr
->e_lfanew
>= datasize
||
147 add((uintptr_t)data
, DOSHdr
->e_lfanew
, &loc
))
148 errx(1, "%s: invalid pe header location", file
);
150 ctx
->PEHdr
= PEHdr
= (EFI_IMAGE_OPTIONAL_HEADER_UNION
*)loc
;
151 debug(NOISE
, "PE signature:0x%04x EFI_IMAGE_NT_SIGNATURE:0x%04x\n",
152 PEHdr
->Pe32
.Signature
, EFI_IMAGE_NT_SIGNATURE
);
153 if (PEHdr
->Pe32
.Signature
!= EFI_IMAGE_NT_SIGNATURE
)
154 errx(1, "%s: Unsupported image type", file
);
156 if (image_is_64_bit(PEHdr
)) {
157 debug(NOISE
, "image is 64bit\n");
158 ctx
->NumberOfRvaAndSizes
=
159 PEHdr
->Pe32Plus
.OptionalHeader
.NumberOfRvaAndSizes
;
161 PEHdr
->Pe32Plus
.OptionalHeader
.SizeOfHeaders
;
162 ctx
->ImageSize
= PEHdr
->Pe32Plus
.OptionalHeader
.SizeOfImage
;
163 ctx
->SectionAlignment
=
164 PEHdr
->Pe32Plus
.OptionalHeader
.SectionAlignment
;
165 FileAlignment
= PEHdr
->Pe32Plus
.OptionalHeader
.FileAlignment
;
166 OptHeaderSize
= sizeof(EFI_IMAGE_OPTIONAL_HEADER64
);
168 debug(NOISE
, "image is 32bit\n");
169 ctx
->NumberOfRvaAndSizes
=
170 PEHdr
->Pe32
.OptionalHeader
.NumberOfRvaAndSizes
;
171 ctx
->SizeOfHeaders
= PEHdr
->Pe32
.OptionalHeader
.SizeOfHeaders
;
172 ctx
->ImageSize
= (UINT64
)PEHdr
->Pe32
.OptionalHeader
.SizeOfImage
;
173 ctx
->SectionAlignment
=
174 PEHdr
->Pe32
.OptionalHeader
.SectionAlignment
;
175 FileAlignment
= PEHdr
->Pe32
.OptionalHeader
.FileAlignment
;
176 OptHeaderSize
= sizeof(EFI_IMAGE_OPTIONAL_HEADER32
);
179 if (FileAlignment
% 2 != 0)
180 errx(1, "%s: Invalid file alignment %zu", file
, FileAlignment
);
182 if (FileAlignment
== 0)
183 FileAlignment
= 0x200;
184 if (ctx
->SectionAlignment
== 0)
185 ctx
->SectionAlignment
= PAGE_SIZE
;
186 if (ctx
->SectionAlignment
< FileAlignment
)
187 ctx
->SectionAlignment
= FileAlignment
;
189 ctx
->NumberOfSections
= PEHdr
->Pe32
.FileHeader
.NumberOfSections
;
192 "Number of RVAs:%"PRIu64
" EFI_IMAGE_NUMBER_OF_DIRECTORY_ENTRIES:%d\n",
193 ctx
->NumberOfRvaAndSizes
, EFI_IMAGE_NUMBER_OF_DIRECTORY_ENTRIES
);
194 if (EFI_IMAGE_NUMBER_OF_DIRECTORY_ENTRIES
< ctx
->NumberOfRvaAndSizes
)
195 errx(1, "%s: invalid number of RVAs (%lu entries, max is %d)",
196 file
, (unsigned long)ctx
->NumberOfRvaAndSizes
,
197 EFI_IMAGE_NUMBER_OF_DIRECTORY_ENTRIES
);
199 if (mul(sizeof(EFI_IMAGE_DATA_DIRECTORY
),
200 EFI_IMAGE_NUMBER_OF_DIRECTORY_ENTRIES
, &sz0
) ||
201 sub(OptHeaderSize
, sz0
, &HeaderWithoutDataDir
) ||
202 sub(PEHdr
->Pe32
.FileHeader
.SizeOfOptionalHeader
,
203 HeaderWithoutDataDir
, &sz0
) ||
204 mul(ctx
->NumberOfRvaAndSizes
, sizeof(EFI_IMAGE_DATA_DIRECTORY
),
207 if (mul(sizeof(EFI_IMAGE_DATA_DIRECTORY
),
208 EFI_IMAGE_NUMBER_OF_DIRECTORY_ENTRIES
, &sz0
))
210 "sizeof(EFI_IMAGE_DATA_DIRECTORY) * EFI_IMAGE_NUMBER_OF_DIRECTORY_ENTRIES overflows\n");
213 "sizeof(EFI_IMAGE_DATA_DIRECTORY) * EFI_IMAGE_NUMBER_OF_DIRECTORY_ENTRIES = %zu\n",
215 if (sub(OptHeaderSize
, sz0
, &HeaderWithoutDataDir
))
217 "OptHeaderSize (%zu) - HeaderWithoutDataDir (%zu) overflows\n",
218 OptHeaderSize
, HeaderWithoutDataDir
);
221 "OptHeaderSize (%zu) - HeaderWithoutDataDir (%zu) = %zu\n",
222 OptHeaderSize
, sz0
, HeaderWithoutDataDir
);
224 if (sub(PEHdr
->Pe32
.FileHeader
.SizeOfOptionalHeader
,
225 HeaderWithoutDataDir
, &sz0
)) {
227 "PEHdr->Pe32.FileHeader.SizeOfOptionalHeader (%d) - %zu overflows\n",
228 PEHdr
->Pe32
.FileHeader
.SizeOfOptionalHeader
,
229 HeaderWithoutDataDir
);
232 "PEHdr->Pe32.FileHeader.SizeOfOptionalHeader (%d)- %zu = %zu\n",
233 PEHdr
->Pe32
.FileHeader
.SizeOfOptionalHeader
,
234 HeaderWithoutDataDir
, sz0
);
236 if (mul(ctx
->NumberOfRvaAndSizes
,
237 sizeof(EFI_IMAGE_DATA_DIRECTORY
), &sz1
))
239 "ctx->NumberOfRvaAndSizes (%ld) * sizeof(EFI_IMAGE_DATA_DIRECTORY) overflows\n",
240 (unsigned long)ctx
->NumberOfRvaAndSizes
);
243 "ctx->NumberOfRvaAndSizes (%ld) * sizeof(EFI_IMAGE_DATA_DIRECTORY) = %zu\n",
244 (unsigned long)ctx
->NumberOfRvaAndSizes
, sz1
);
246 "space after image header:%zu data directory size:%zu\n",
249 errx(1, "%s: image header overflows data directory", file
);
252 if (add(DOSHdr
->e_lfanew
, sizeof(UINT32
), &SectionHeaderOffset
) ||
253 add(SectionHeaderOffset
, sizeof(EFI_IMAGE_FILE_HEADER
),
254 &SectionHeaderOffset
) ||
255 add(SectionHeaderOffset
,
256 PEHdr
->Pe32
.FileHeader
.SizeOfOptionalHeader
,
257 &SectionHeaderOffset
)) {
258 debug(ERROR
, "SectionHeaderOffset:%" PRIu32
" + %zu + %zu + %d",
259 DOSHdr
->e_lfanew
, sizeof(UINT32
),
260 sizeof(EFI_IMAGE_FILE_HEADER
),
261 PEHdr
->Pe32
.FileHeader
.SizeOfOptionalHeader
);
262 errx(1, "%s: SectionHeaderOffset would overflow", file
);
265 if (sub(ctx
->ImageSize
, SectionHeaderOffset
, &sz0
) ||
266 div(sz0
, EFI_IMAGE_SIZEOF_SECTION_HEADER
, &sz0
) ||
267 (sz0
<= ctx
->NumberOfSections
)) {
268 debug(ERROR
, "(%" PRIu64
" - %zu) / %d > %d\n", ctx
->ImageSize
,
269 SectionHeaderOffset
, EFI_IMAGE_SIZEOF_SECTION_HEADER
,
270 ctx
->NumberOfSections
);
271 errx(1, "%s: image sections overflow image size", file
);
274 if (sub(ctx
->SizeOfHeaders
, SectionHeaderOffset
, &sz0
) ||
275 div(sz0
, EFI_IMAGE_SIZEOF_SECTION_HEADER
, &sz0
) ||
276 (sz0
< ctx
->NumberOfSections
)) {
277 debug(ERROR
, "(%zu - %zu) / %d >= %d\n", (size_t)ctx
->SizeOfHeaders
,
278 SectionHeaderOffset
, EFI_IMAGE_SIZEOF_SECTION_HEADER
,
279 ctx
->NumberOfSections
);
280 errx(1, "%s: image sections overflow section headers", file
);
283 if (sub((uintptr_t)PEHdr
, (uintptr_t)data
, &sz0
) ||
284 add(sz0
, sizeof(EFI_IMAGE_OPTIONAL_HEADER_UNION
), &sz0
) ||
286 errx(1, "%s: PE Image size %zu > %zu", file
, sz0
, datasize
);
289 if (PEHdr
->Pe32
.FileHeader
.Characteristics
& EFI_IMAGE_FILE_RELOCS_STRIPPED
)
290 errx(1, "%s: Unsupported image - Relocations have been stripped", file
);
292 if (image_is_64_bit(PEHdr
)) {
293 ctx
->ImageAddress
= PEHdr
->Pe32Plus
.OptionalHeader
.ImageBase
;
295 PEHdr
->Pe32Plus
.OptionalHeader
.AddressOfEntryPoint
;
296 ctx
->RelocDir
= &PEHdr
->Pe32Plus
.OptionalHeader
.DataDirectory
297 [EFI_IMAGE_DIRECTORY_ENTRY_BASERELOC
];
298 ctx
->SecDir
= &PEHdr
->Pe32Plus
.OptionalHeader
.DataDirectory
299 [EFI_IMAGE_DIRECTORY_ENTRY_SECURITY
];
301 ctx
->ImageAddress
= PEHdr
->Pe32
.OptionalHeader
.ImageBase
;
303 PEHdr
->Pe32
.OptionalHeader
.AddressOfEntryPoint
;
304 ctx
->RelocDir
= &PEHdr
->Pe32
.OptionalHeader
.DataDirectory
305 [EFI_IMAGE_DIRECTORY_ENTRY_BASERELOC
];
306 ctx
->SecDir
= &PEHdr
->Pe32
.OptionalHeader
.DataDirectory
307 [EFI_IMAGE_DIRECTORY_ENTRY_SECURITY
];
310 if (add((uintptr_t)PEHdr
, PEHdr
->Pe32
.FileHeader
.SizeOfOptionalHeader
,
312 add(loc
, sizeof(UINT32
), &loc
) ||
313 add(loc
, sizeof(EFI_IMAGE_FILE_HEADER
), &loc
))
314 errx(1, "%s: invalid location for first section", file
);
316 ctx
->FirstSection
= (EFI_IMAGE_SECTION_HEADER
*)loc
;
318 if (ctx
->ImageSize
< ctx
->SizeOfHeaders
)
320 "%s: Image size %"PRIu64
" is smaller than header size %lu",
321 file
, ctx
->ImageSize
, ctx
->SizeOfHeaders
);
323 if (sub((uintptr_t)ctx
->SecDir
, (uintptr_t)data
, &sz0
) ||
324 sub(datasize
, sizeof(EFI_IMAGE_DATA_DIRECTORY
), &sz1
) ||
327 "%s: security direcory offset %zu past data directory at %zu",
330 if (ctx
->SecDir
->VirtualAddress
> datasize
||
331 (ctx
->SecDir
->VirtualAddress
== datasize
&&
332 ctx
->SecDir
->Size
> 0))
333 errx(1, "%s: Security directory extends past end", file
);
337 set_dll_characteristics(PE_COFF_LOADER_IMAGE_CONTEXT
*ctx
)
339 uint16_t oldflags
, newflags
;
341 if (image_is_64_bit(ctx
->PEHdr
)) {
342 oldflags
= ctx
->PEHdr
->Pe32Plus
.OptionalHeader
.DllCharacteristics
;
344 oldflags
= ctx
->PEHdr
->Pe32
.OptionalHeader
.DllCharacteristics
;
348 newflags
= oldflags
| EFI_IMAGE_DLLCHARACTERISTICS_NX_COMPAT
;
350 newflags
= oldflags
& ~(uint16_t)EFI_IMAGE_DLLCHARACTERISTICS_NX_COMPAT
;
351 if (oldflags
== newflags
)
354 debug(INFO
, "Updating DLL Characteristics from 0x%04hx to 0x%04hx\n",
356 if (image_is_64_bit(ctx
->PEHdr
)) {
357 ctx
->PEHdr
->Pe32Plus
.OptionalHeader
.DllCharacteristics
= newflags
;
359 ctx
->PEHdr
->Pe32
.OptionalHeader
.DllCharacteristics
= newflags
;
364 fix_timestamp(PE_COFF_LOADER_IMAGE_CONTEXT
*ctx
)
368 if (image_is_64_bit(ctx
->PEHdr
)) {
369 ts
= ctx
->PEHdr
->Pe32Plus
.FileHeader
.TimeDateStamp
;
371 ts
= ctx
->PEHdr
->Pe32
.FileHeader
.TimeDateStamp
;
375 debug(INFO
, "Updating timestamp from 0x%08x to 0\n", ts
);
376 if (image_is_64_bit(ctx
->PEHdr
)) {
377 ctx
->PEHdr
->Pe32Plus
.FileHeader
.TimeDateStamp
= 0;
379 ctx
->PEHdr
->Pe32
.FileHeader
.TimeDateStamp
= 0;
385 fix_checksum(PE_COFF_LOADER_IMAGE_CONTEXT
*ctx
, void *map
, size_t mapsize
)
388 uint32_t checksum
= 0;
392 if (image_is_64_bit(ctx
->PEHdr
)) {
393 old
= ctx
->PEHdr
->Pe32Plus
.OptionalHeader
.CheckSum
;
394 ctx
->PEHdr
->Pe32Plus
.OptionalHeader
.CheckSum
= 0;
396 old
= ctx
->PEHdr
->Pe32
.OptionalHeader
.CheckSum
;
397 ctx
->PEHdr
->Pe32
.OptionalHeader
.CheckSum
= 0;
399 debug(NOISE
, "old checksum was 0x%08x\n", old
);
401 for (size_t i
= 0; i
< mapsize
- 1; i
+= 2) {
402 word
= (data
[i
+ 1] << 8ul) | data
[i
];
404 checksum
= 0xffff & (checksum
+ (checksum
>> 0x10));
406 debug(NOISE
, "checksum = 0x%08x + 0x%08zx = 0x%08zx\n", checksum
,
407 mapsize
, checksum
+ mapsize
);
412 debug(INFO
, "Updating checksum from 0x%08x to 0x%08x\n",
415 if (image_is_64_bit(ctx
->PEHdr
)) {
416 ctx
->PEHdr
->Pe32Plus
.OptionalHeader
.CheckSum
= checksum
;
418 ctx
->PEHdr
->Pe32
.OptionalHeader
.CheckSum
= checksum
;
432 PE_COFF_LOADER_IMAGE_CONTEXT ctx
= { 0, 0 };
434 fd
= open(f
, O_RDWR
| O_EXCL
);
436 err(1, "Could not open \"%s\"", f
);
438 rc
= fstat(fd
, &statbuf
);
440 err(1, "Could not stat \"%s\"", f
);
442 sz
= statbuf
.st_size
;
444 map
= mmap(NULL
, sz
, PROT_READ
| PROT_WRITE
, MAP_SHARED
, fd
, 0);
445 if (map
== MAP_FAILED
)
446 err(1, "Could not map \"%s\"", f
);
448 load_pe(f
, map
, sz
, &ctx
);
450 set_dll_characteristics(&ctx
);
454 fix_checksum(&ctx
, map
, sz
);
456 rc
= msync(map
, sz
, MS_SYNC
);
458 warn("msync(%p, %zu, MS_SYNC) failed", map
, sz
);
461 rc
= munmap(map
, sz
);
463 warn("munmap(%p, %zu) failed", map
, sz
);
468 warn("close(%d) failed", fd
);
475 static void __attribute__((__noreturn__
)) usage(int status
)
477 FILE *out
= status
? stderr
: stdout
;
480 "Usage: post-process-pe [OPTIONS] file0 [file1 [.. fileN]]\n");
481 fprintf(out
, "Options:\n");
482 fprintf(out
, " -q Be more quiet\n");
483 fprintf(out
, " -v Be more verbose\n");
484 fprintf(out
, " -N Disable the NX compatibility flag\n");
485 fprintf(out
, " -n Enable the NX compatibility flag\n");
486 fprintf(out
, " -h Print this help text and exit\n");
491 int main(int argc
, char **argv
)
494 struct option options
[] = {
501 {.name
= "disable-nx-compat",
504 {.name
= "enable-nx-compat",
517 while ((i
= getopt_long(argc
, argv
, "hNnqv", options
, &longindex
)) != -1) {
521 usage(longindex
== -1 ? 1 : 0);
524 set_nx_compat
= false;
527 set_nx_compat
= true;
530 verbosity
= MAX(verbosity
- 1, MIN_VERBOSITY
);
533 verbosity
= MIN(verbosity
+ 1, MAX_VERBOSITY
);
541 for (i
= optind
; i
< argc
; i
++)
547 // vim:fenc=utf-8:tw=75:noet