3 * Copyright (C) 2005 Filip Navara
4 * Copyright (C) 2020 Mark Jansen
6 * The purpose of this utility is fix PE binaries generated by binutils and
7 * to manipulate flags that can't be set by binutils.
9 * Currently one features is implemented:
11 * - Updating the PE header to use a LOAD_CONFIG,
12 * when the struct is exported with the name '_load_config_used'
22 #include "../../dll/win32/dbghelp/compat.h"
24 static const char* g_ApplicationName
;
25 static const char* g_Target
;
37 void *rva_to_ptr(unsigned char *buffer
, PIMAGE_NT_HEADERS nt_header
, DWORD rva
)
40 PIMAGE_SECTION_HEADER section_header
= IMAGE_FIRST_SECTION(nt_header
);
42 for (i
= 0; i
< nt_header
->FileHeader
.NumberOfSections
; i
++, section_header
++)
44 if (rva
>= section_header
->VirtualAddress
&&
45 rva
< section_header
->VirtualAddress
+ section_header
->Misc
.VirtualSize
)
47 return buffer
+ rva
- section_header
->VirtualAddress
+ section_header
->PointerToRawData
;
54 static void error(const char* message
, ...)
58 fprintf(stderr
, "%s ERROR: '%s': ", g_ApplicationName
, g_Target
);
60 va_start(args
, message
);
61 vfprintf(stderr
, message
, args
);
65 static void fix_checksum(unsigned char *buffer
, size_t len
, PIMAGE_NT_HEADERS nt_header
)
67 unsigned int checksum
= 0;
70 nt_header
->OptionalHeader
.CheckSum
= 0;
72 for (n
= 0; n
< len
; n
+= 2)
74 checksum
+= *(unsigned short *)(buffer
+ n
);
75 checksum
= (checksum
+ (checksum
>> 16)) & 0xffff;
78 checksum
+= (unsigned int)len
;
79 nt_header
->OptionalHeader
.CheckSum
= checksum
;
82 static int add_loadconfig(unsigned char *buffer
, PIMAGE_NT_HEADERS nt_header
)
84 PIMAGE_DATA_DIRECTORY export_dir
;
85 PIMAGE_EXPORT_DIRECTORY export_directory
;
86 PDWORD name_ptr
, function_ptr
;
90 export_dir
= &nt_header
->OptionalHeader
.DataDirectory
[IMAGE_DIRECTORY_ENTRY_EXPORT
];
91 if (export_dir
->Size
== 0)
93 error("No export directory\n");
97 export_directory
= rva_to_ptr(buffer
, nt_header
, export_dir
->VirtualAddress
);
98 if (export_directory
== NULL
)
100 error("Invalid rva for export directory\n");
104 name_ptr
= rva_to_ptr(buffer
, nt_header
, export_directory
->AddressOfNames
);
105 ordinal_ptr
= rva_to_ptr(buffer
, nt_header
, export_directory
->AddressOfNameOrdinals
);
106 function_ptr
= rva_to_ptr(buffer
, nt_header
, export_directory
->AddressOfFunctions
);
108 for (n
= 0; n
< export_directory
->NumberOfNames
; n
++)
110 const char* name
= rva_to_ptr(buffer
, nt_header
, name_ptr
[n
]);
111 if (!strcmp(name
, "_load_config_used"))
113 PIMAGE_DATA_DIRECTORY load_config_dir
;
114 DWORD load_config_rva
= function_ptr
[ordinal_ptr
[n
]];
115 PDWORD load_config_ptr
= rva_to_ptr(buffer
, nt_header
, load_config_rva
);
117 /* Update the DataDirectory pointer / size
118 The first entry of the LOAD_CONFIG struct is the size, use that as DataDirectory.Size */
119 load_config_dir
= &nt_header
->OptionalHeader
.DataDirectory
[IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG
];
120 load_config_dir
->VirtualAddress
= load_config_rva
;
121 load_config_dir
->Size
= *load_config_ptr
;
127 error("Export '_load_config_used' not found\n");
131 static int driver_fixup(enum fixup_mode mode
, unsigned char *buffer
, PIMAGE_NT_HEADERS nt_header
)
133 /* GNU LD just doesn't know what a driver is, and has notably no idea of paged vs non-paged sections */
134 for (unsigned int i
= 0; i
< nt_header
->FileHeader
.NumberOfSections
; i
++)
136 PIMAGE_SECTION_HEADER Section
= IMAGE_FIRST_SECTION(nt_header
) + i
;
138 /* LD puts alignment crap that nobody asked for */
139 Section
->Characteristics
&= ~IMAGE_SCN_ALIGN_MASK
;
141 /* LD overdoes it and puts the initialized flag everywhere */
142 if (Section
->Characteristics
& IMAGE_SCN_CNT_CODE
)
143 Section
->Characteristics
&= ~IMAGE_SCN_CNT_INITIALIZED_DATA
;
145 if (strncmp((char*)Section
->Name
, ".rsrc", 5) == 0)
147 /* .rsrc is discardable for driver images, WDM drivers and Kernel-Mode DLLs */
148 if (mode
== MODE_KERNELDRIVER
|| mode
== MODE_WDMDRIVER
|| mode
== MODE_KERNELDLL
)
150 Section
->Characteristics
|= IMAGE_SCN_MEM_DISCARDABLE
;
153 /* For some reason, .rsrc is made writable by windres */
154 Section
->Characteristics
&= ~IMAGE_SCN_MEM_WRITE
;
158 /* Known sections which can be discarded */
159 if (strncmp((char*)Section
->Name
, "INIT", 4) == 0)
161 Section
->Characteristics
|= IMAGE_SCN_MEM_DISCARDABLE
;
165 /* Known sections which can be paged */
166 if ((strncmp((char*)Section
->Name
, "PAGE", 4) == 0)
167 || (strncmp((char*)Section
->Name
, ".rsrc", 5) == 0)
168 || (strncmp((char*)Section
->Name
, ".edata", 6) == 0)
169 || (strncmp((char*)Section
->Name
, ".reloc", 6) == 0))
174 /* If it's discardable, don't set the flag */
175 if (Section
->Characteristics
& IMAGE_SCN_MEM_DISCARDABLE
)
178 Section
->Characteristics
|= IMAGE_SCN_MEM_NOT_PAGED
;
184 /* NOTE: This function tokenizes its parameter in place.
185 * Its format is: name[=newname][,[[!]{CDEIKOMPRSUW}][A{1248PTSX}]] */
186 static int change_section_attribs(char* section_attribs
, unsigned char* buffer
, PIMAGE_NT_HEADERS nt_header
)
188 char *sectionName
, *newSectionName
;
189 char *attribsSpec
, *alignSpec
;
191 DWORD dwAttribs
[2]; /* Attributes to [0]: set; [1]: filter */
193 PIMAGE_SECTION_HEADER SectionTable
, Section
;
195 if (!section_attribs
|| !*section_attribs
)
197 error("Section attributes specification is empty.\n");
201 sectionName
= section_attribs
;
203 /* Find the optional new section name and attributes specifications */
204 newSectionName
= strchr(section_attribs
, '=');
205 attribsSpec
= strchr(section_attribs
, ',');
206 if (newSectionName
&& attribsSpec
)
208 /* The attributes specification must be after the new name */
209 if (!(newSectionName
< attribsSpec
))
211 error("Invalid section attributes specification.\n");
216 *newSectionName
++ = 0;
220 /* An original section name must be specified */
223 error("Invalid section attributes specification.\n");
226 /* If a new section name begins, it must be not empty */
227 if (newSectionName
&& !*newSectionName
)
229 error("Invalid section attributes specification.\n");
233 /* The alignment specification is inside the attributes specification */
237 /* Check for the first occurrence of the 'A' separator, case-insensitive */
238 for (ptr
= attribsSpec
; *ptr
; ++ptr
)
240 if (toupper(*ptr
) == 'A')
250 /* But it's not supported at the moment! */
251 if (alignSpec
&& *alignSpec
)
253 fprintf(stdout
, "%s WARNING: '%s': %s", g_ApplicationName
, g_Target
,
254 "Section alignment specification not currently supported! Ignoring.\n");
257 /* Parse the attributes specification */
258 dwAttribs
[0] = dwAttribs
[1] = 0;
259 if (attribsSpec
&& *attribsSpec
)
261 for (i
= 0, ptr
= attribsSpec
; *ptr
; ++ptr
)
265 /* The next attribute needs to be removed.
266 * Any successive '!' gets collapsed. */
271 switch (toupper(*ptr
))
274 dwAttribs
[i
%2] |= IMAGE_SCN_CNT_CODE
;
277 dwAttribs
[i
%2] |= IMAGE_SCN_MEM_DISCARDABLE
;
280 dwAttribs
[i
%2] |= IMAGE_SCN_MEM_EXECUTE
;
283 dwAttribs
[i
%2] |= IMAGE_SCN_CNT_INITIALIZED_DATA
;
285 case 'K': /* Remove the not-cached attribute */
286 dwAttribs
[(i
+1)%2] |= IMAGE_SCN_MEM_NOT_CACHED
;
289 dwAttribs
[i
%2] |= IMAGE_SCN_LNK_REMOVE
;
292 dwAttribs
[i
%2] |= IMAGE_SCN_LNK_INFO
;
294 case 'P': /* Remove the not-paged attribute */
295 dwAttribs
[(i
+1)%2] |= IMAGE_SCN_MEM_NOT_PAGED
;
298 dwAttribs
[i
%2] |= IMAGE_SCN_MEM_READ
;
301 dwAttribs
[i
%2] |= IMAGE_SCN_MEM_SHARED
;
304 dwAttribs
[i
%2] |= IMAGE_SCN_CNT_UNINITIALIZED_DATA
;
307 dwAttribs
[i
%2] |= IMAGE_SCN_MEM_WRITE
;
311 error("Invalid section attributes specification.\n");
315 /* Got an attribute; reset the state */
318 /* If the state was not reset, the attributes specification is invalid */
321 error("Invalid section attributes specification.\n");
326 /* Find all sections with the given name, rename them and change their attributes */
328 SectionTable
= IMAGE_FIRST_SECTION(nt_header
);
329 for (i
= 0; i
< nt_header
->FileHeader
.NumberOfSections
; ++i
)
331 if (strncmp((char*)SectionTable
[i
].Name
, sectionName
, ARRAY_SIZE(SectionTable
[i
].Name
)) != 0)
334 Section
= &SectionTable
[i
];
336 if (newSectionName
&& *newSectionName
)
338 memset(Section
->Name
, 0, sizeof(Section
->Name
));
339 strncpy((char*)Section
->Name
, newSectionName
, ARRAY_SIZE(Section
->Name
));
342 /* First filter attributes out, then add the new ones.
343 * The new attributes override any removed ones. */
344 Section
->Characteristics
&= ~dwAttribs
[1];
345 Section
->Characteristics
|= dwAttribs
[0];
348 /* If no section was found, return an error */
351 error("Section '%s' does not exist.\n", sectionName
);
362 printf("Usage: %s <options> <filename>\n\n", g_ApplicationName
);
363 printf("<options> can be one of the following options:\n"
364 " --loadconfig Fix the LOAD_CONFIG directory entry;\n"
365 " --kernelmodedriver Fix code, data and resource sections for driver images;\n"
366 " --wdmdriver Fix code, data and resource sections for WDM drivers;\n"
367 " --kerneldll Fix code, data and resource sections for Kernel-Mode DLLs;\n"
368 " --kernel Fix code, data and resource sections for kernels;\n"
370 "and/or a combination of the following ones:\n"
371 " --section:name[=newname][,[[!]{CDEIKOMPRSUW}][A{1248PTSX}]]\n"
372 " Overrides the attributes of a section, optionally\n"
373 " changing its name and its alignment.\n");
376 int main(int argc
, char **argv
)
379 enum fixup_mode mode
= MODE_NONE
;
383 unsigned char *buffer
;
384 PIMAGE_DOS_HEADER dos_header
;
385 PIMAGE_NT_HEADERS nt_header
;
387 g_ApplicationName
= argv
[0];
389 /* Check for options */
390 for (i
= 1; i
< argc
; ++i
)
392 if (!(argv
[i
][0] == '-' && argv
[i
][1] == '-'))
394 /* We are out of options (they come first before
395 * anything else, and cannot come after). */
399 if (strcmp(&argv
[i
][2], "loadconfig") == 0)
401 if (mode
!= MODE_NONE
)
403 mode
= MODE_LOADCONFIG
;
405 else if (strcmp(&argv
[i
][2], "kernelmodedriver") == 0)
407 if (mode
!= MODE_NONE
)
409 mode
= MODE_KERNELDRIVER
;
411 else if (strcmp(&argv
[i
][2], "wdmdriver") == 0)
413 if (mode
!= MODE_NONE
)
415 mode
= MODE_WDMDRIVER
;
417 else if (strcmp(&argv
[i
][2], "kerneldll") == 0)
419 if (mode
!= MODE_NONE
)
421 mode
= MODE_KERNELDLL
;
423 else if (strcmp(&argv
[i
][2], "kernel") == 0)
425 if (mode
!= MODE_NONE
)
429 else if (strncmp(&argv
[i
][2], "section:", 8) == 0)
431 /* Section attributes override, will be handled later */
435 fprintf(stderr
, "%s ERROR: Unknown option: '%s'.\n", g_ApplicationName
, argv
[i
]);
438 fprintf(stderr
, "%s ERROR: Specific mode already set.\n", g_ApplicationName
);
444 /* Stop now if we don't have any option or file */
445 if ((i
<= 1) || (i
>= argc
))
453 /* Read the whole file to memory */
454 file
= fopen(g_Target
, "r+b");
457 fprintf(stderr
, "%s ERROR: Can't open '%s'.\n", g_ApplicationName
, g_Target
);
461 fseek(file
, 0, SEEK_END
);
463 if (len
< sizeof(IMAGE_DOS_HEADER
))
466 error("Image size too small to be a PE image\n");
470 /* Add one byte extra for the case where the input file size is odd.
471 We rely on this in our checksum calculation. */
472 buffer
= calloc(len
+ 1, 1);
476 error("Not enough memory available (Needed %lu bytes).\n", len
+ 1);
480 /* Read the whole input file into a buffer */
481 fseek(file
, 0, SEEK_SET
);
482 fread(buffer
, 1, len
, file
);
484 /* Check the headers and save pointers to them */
485 dos_header
= (PIMAGE_DOS_HEADER
)buffer
;
486 if (dos_header
->e_magic
!= IMAGE_DOS_SIGNATURE
)
488 error("Invalid DOS signature: %x\n", dos_header
->e_magic
);
492 nt_header
= (PIMAGE_NT_HEADERS
)(buffer
+ dos_header
->e_lfanew
);
493 if (nt_header
->Signature
!= IMAGE_NT_SIGNATURE
)
495 error("Invalid PE signature: %x\n", nt_header
->Signature
);
501 /* Apply mode fixups */
502 if (mode
!= MODE_NONE
)
504 if (mode
== MODE_LOADCONFIG
)
505 result
= add_loadconfig(buffer
, nt_header
);
507 result
= driver_fixup(mode
, buffer
, nt_header
);
510 /* Apply any section attributes override */
511 for (i
= 1; (i
< argc
) && (result
== 0); ++i
)
513 /* Ignore anything but the section specifications */
514 if (!(argv
[i
][0] == '-' && argv
[i
][1] == '-'))
516 if (strncmp(&argv
[i
][2], "section:", 8) != 0)
519 result
= change_section_attribs(&argv
[i
][10], buffer
, nt_header
);
524 /* Success. Recalculate the checksum only if this is not a reproducible build file */
525 if (nt_header
->OptionalHeader
.CheckSum
!= 0)
526 fix_checksum(buffer
, len
, nt_header
);
528 /* We could optimize by only writing the changed parts, but keep it simple for now */
529 fseek(file
, 0, SEEK_SET
);
530 fwrite(buffer
, 1, len
, file
);