1

I'm trying to create a compiler and I will start doing the part of the code generation. To become more simple to generate the executable, I want the PE headers remain as small as possible.

For me understand better what I will do I am compiling and analyzing programs with OllyDbg.

I made a program using FASM and used some macros that I found on the Internet to reduce the size of imports. Everything is in one section as well.

The executable was generated with 1024 bytes. Then I used a PEiD plugin which reduced the file to 722 bytes.

I do not understand much about it yet, but looking at some articles on the internet seems to have some unnecessary things in headers of my file. So I want to help to reduce the size of headers without compromising the program. If possible I want help to reduce the size of imports too!

Here is the source code:

format PE console
entry start

include 'win32a.inc'

macro import_part1 library, [api]
{ 
  common
    library#_str: db `library 
  forward 
    if rva $ mod 2 = 0
     ; db 0
    end if
    ; When align is right, one byte from previous import name 
    ; is used as byte for next import's hint. 
    api#_str = $-1 
    db 0, `api 
  common
    db 0
} 

import_part2_first = 0

macro import_part2 library, [api]
{ 
  common
    if import_part2_first = 0
      align 4
      import_part2_first = 1
    else
      dd 0
    end if
    library#_import: 
  forward 
    api dd rva api#_str 
} 

macro import_part3 [library]
{ 
  common
    data import
  forward 
    dd 0, 0, 0, rva library#_str, rva library#_import 
  common
    rd 5
    end data 
} 

import_list equ
import_libraries equ

macro import library,[api]
{ 
  common
    import_list equ import_list import_#library 
    import_#library equ library,api 
    import_libraries equ import_libraries,library 
} 

macro importend 
{ 
  match a, import_list 
  \{ 
    irps b, a \\{ match c, b \\\{ import_part1 c \\\} \\} 
    irps b, a \\{ match c, b \\\{ import_part2 c \\\} \\} 
  \} 
  match =,a,import_libraries \{ import_part3 a \} 
}


start:

         push var
         call [printf]


         push 0
         call [ExitProcess]

         var db 'Test', 0

import kernel32.dll, ExitProcess, AttachConsole
import msvcrt.dll, printf, scanf
importend

I used RebuildPE plugin to reduce de exe size. Here is the compiled code:

CPU Disasm
Address   Hex dump                                       Command                           Comments
00401000   .  68 13104000                                PUSH 00401013                     ; /format = "Test"
00401005   .  FF15 80104000                              CALL DWORD PTR DS:[<&msvcrt.print ; \MSVCRT.printf
0040100B   .  6A 00                                      PUSH 0                            ; /ExitCode = 0
0040100D   .  FF15 74104000                              CALL DWORD PTR DS:[<&kernel32.Exi ; \KERNEL32.ExitProcess
00401013   .  54                                         PUSH ESP
00401014   .  65:73 74                                   JAE SHORT 0040108B                ; Superfluous segment override prefix
00401017   .  006B 65                                    ADD BYTE PTR DS:[EBX+65], CH
0040101A   .  72 6E 65 6C 33 32 2E 64 6C 6C 00           ASCII "rnel32.dll",0              ; ASCII "rnel32.dll"
00401025   .  45 78 69 74 50 72 6F 63 65 73 73 00        ASCII "ExitProcess",0             ; ASCII "ExitProcess"
00401031   .  41 74 74 61 63 68 43 6F 6E 73 6F 6C 65 00  ASCII "AttachConsole",0           ; ASCII "AttachConsole"
0040103F   .  6D 73 76 63 72 74 2E 64 6C 6C 00           ASCII "msvcrt.dll",0              ; ASCII "msvcrt.dll"
0040104A   .  70 72 69 6E 74 66 00                       ASCII "printf",0                  ; ASCII "printf"
00401051   .  73 63 61 6E 66 00                          ASCII "scanf",0                   ; ASCII "scanf"
00401057   .  70 75 74 73 00                             ASCII "puts",0                    ; ASCII "puts"
0040105C   .  75 73 65 72 33 32 2E 64 6C 6C 00           ASCII "user32.dll",0              ; ASCII "user32.dll"
00401067   .  4D 65 73 73 61 67 65 42 6F 78 41 00        ASCII "MessageBoxA",0             ; ASCII "MessageBoxA"
00401073      90                                         NOP
00401074   .  647FA577                                   DD 77A57F64
00401078   .  1878A577                                   DD 77A57818
0040107C   .  00000000                                   DD 00000000
00401080   .  C4D2B777                                   DD 77B7D2C4
00401084   .  BF16C077                                   DD 77C016BF
00401088   .  9C3BC077                                   DD 77C03B9C
0040108C   .  00000000                                   DD 00000000
00401090   .  9E278B77                                   DD 778B279E
00401094   .  00000000                                   DD 00000000                       ; Struct 'IMAGE_IMPORT_DESCRIPTOR'
00401098   .  00000000                                   DD 00000000
0040109C   .  00000000                                   DD 00000000
004010A0   .  18100000                                   DD 00001018
004010A4   .  74100000                                   DD 00001074
004010A8   .  00000000                                   DD 00000000                       ; Struct 'IMAGE_IMPORT_DESCRIPTOR'
004010AC   .  00000000                                   DD 00000000
004010B0   .  00000000                                   DD 00000000
004010B4   .  3F100000                                   DD 0000103F
004010B8   .  80100000                                   DD 00001080
004010BC   .  00000000                                   DD 00000000                       ; Struct 'IMAGE_IMPORT_DESCRIPTOR'
004010C0   .  00000000                                   DD 00000000
004010C4   .  00000000                                   DD 00000000
004010C8   .  5C100000                                   DD 0000105C
004010CC   .  90100000                                   DD 00001090
004010D0   .  00000000                                   DD 00000000                       ; Struct 'IMAGE_IMPORT_DESCRIPTOR'
004010D4   .  00000000                                   DD 00000000
004010D8   .  00000000                                   DD 00000000
004010DC   .  00000000                                   DD 00000000
004010E0   .  00000000                                   DD 00000000

And the headers:

CPU Disasm
Address   Hex dump                                       Command                           Comments
00400000  /.  4D 5A                                      ASCII "MZ"                        ; DOS_Signature[2] = "MZ"
00400002  |.  8000                                       DW 80                             ; DOS_PartPag = 128.
00400004  |.  0100                                       DW 1                              ; DOS_PageCnt = 1
00400006  |.  0000                                       DW 0                              ; DOS_ReloCnt = 0
00400008  |.  0400                                       DW 4                              ; DOS_HdrSize = 4
0040000A  |.  1000                                       DW 10                             ; DOS_MinMem = 16.
0040000C   .  50 45 00 00                                ASCII "PE",0,0                    ; IMAGE_NT_SIGNATURE[4] = "PE"
00400010  /.  4C01                                       DW 14C                            ; Machine = IMAGE_FILE_MACHINE_I386
00400012  |.  0100                                       DW 1                              ; DOS_ChkSum = 1
00400014  |.  76264555                                   DD 55452676                       ; DOS_ExeIP = 55452676
00400018  |.  00000000                                   DD 00000000                       ; DOS_RelocOffset = 0
0040001C  |.  00000000                                   DD 00000000                       ; DOS_Reserved1[4] = 00000000
00400020  |.  E000                                       DW 0E0                            ; SizeOfOptionalHeader = 224.
00400022  \.  0F01                                       DW 10F                            ; Characteristics = EXECUTABLE_IMAGE|32BIT_MACHINE|RELOCS_STRIPPED|LINE_NUMS_STRIPPED|LOCAL_SYMS_STRIPPED
00400024  /.  0B01                                       DW 10B                            ; DOS_OEM_ID = 10B
00400026  |.  0147                                       DW 4701                           ; DOS_OEM_Info = 4701
00400028  |.  00020000                                   DD 00000200                       ; SizeOfCode = 512.
0040002C  |.  00020000                                   DD 00000200                       ; SizeOfInitializedData = 512.
00400030  |.  00000000                                   DD 00000000                       ; SizeOfUninitializedData = 0
00400034  |.  00100000                                   DD 00001000                       ; AddressOfEntryPoint = 1000
00400038  |.  00100000                                   DD 00001000                       ; BaseOfCode = 1000
0040003C  |.  0C000000                                   DD 0000000C                       ; DOS_PEOffset = 0C
00400040  |.  00004000                                   DD 00400000                       ; ImageBase = 400000
00400044  |.  00100000                                   DD 00001000                       ; SectionAlignment = 1000
00400048  |.  00020000                                   DD 00000200                       ; FileAlignment = 200
0040004C  |.  0100                                       DW 1                              ; MajorOSVersion = 1
0040004E  |.  0000                                       DW 0                              ; MinorOSVersion = 0
00400050  |.  0000                                       DW 0                              ; MajorImageVersion = 0
00400052  |.  0000                                       DW 0                              ; MinorImageVersion = 0
00400054  |.  0300                                       DW 3                              ; MajorSubsystemVersion = 3
00400056  |.  0A00                                       DW 0A                             ; MinorSubsystemVersion = 10.
00400058  |.  00000000                                   DD 00000000                       ; Win32VersionValue = 0
0040005C  |.  E4100000                                   DD 000010E4                       ; SizeOfImage = 4324.
00400060  |.  00020000                                   DD 00000200                       ; SizeOfHeaders = 512.
00400064  |.  53C80000                                   DD 0000C853                       ; CheckSum = 0C853
00400068  |.  0300                                       DW 3                              ; Subsystem = IMAGE_SUBSYSTEM_WINDOWS_CUI
0040006A  |.  0000                                       DW 0                              ; DLLCharacteristics = 0
0040006C  |.  00100000                                   DD 00001000                       ; SizeOfStackReserve = 4096.
00400070  |.  00100000                                   DD 00001000                       ; SizeOfStackCommit = 4096.
00400074  |.  00000100                                   DD 00010000                       ; SizeOfHeapReserve = 65536.
00400078  |.  00000000                                   DD 00000000                       ; SizeOfHeapCommit = 0
0040007C  |.  00000000                                   DD 00000000                       ; LoaderFlags = 0
00400080  \.  10000000                                   DD 00000010                       ; NumberOfRvaAndSizes = 16.
00400084  /.  00000000                                   DD 00000000                       ; Export Table address = 0
00400088  |.  00000000                                   DD 00000000                       ; Export Table size = 0
0040008C  |.  94100000                                   DD 00001094                       ; Import Table address = 1094
00400090  |.  50000000                                   DD 00000050                       ; Import Table size = 80.
00400094  |.  00000000                                   DD 00000000                       ; Resource Table address = 0
00400098  |.  00000000                                   DD 00000000                       ; Resource Table size = 0
0040009C  |.  00000000                                   DD 00000000                       ; Exception Table address = 0
004000A0  |.  00000000                                   DD 00000000                       ; Exception Table size = 0
004000A4  |.  00000000                                   DD 00000000                       ; Certificate File pointer = 0
004000A8  |.  00000000                                   DD 00000000                       ; Certificate Table size = 0
004000AC  |.  00000000                                   DD 00000000                       ; Relocation Table address = 0
004000B0  |.  00000000                                   DD 00000000                       ; Relocation Table size = 0
004000B4  |.  00000000                                   DD 00000000                       ; Debug Data address = 0
004000B8  |.  00000000                                   DD 00000000                       ; Debug Data size = 0
004000BC  |.  00000000                                   DD 00000000                       ; Architecture Data address = 0
004000C0  |.  00000000                                   DD 00000000                       ; Architecture Data size = 0
004000C4  |.  00000000                                   DD 00000000                       ; Global Ptr address = 0
004000C8  |.  00000000                                   DD 00000000                       ; Reserved = 00000000
004000CC  |.  00000000                                   DD 00000000                       ; TLS Table address = 0
004000D0  |.  00000000                                   DD 00000000                       ; TLS Table size = 0
004000D4  |.  00000000                                   DD 00000000                       ; Load Config Table address = 0
004000D8  |.  00000000                                   DD 00000000                       ; Load Config Table size = 0
004000DC  |.  00000000                                   DD 00000000                       ; Bound Import Table address = 0
004000E0  |.  00000000                                   DD 00000000                       ; Bound Import Table size = 0
004000E4  |.  00000000                                   DD 00000000                       ; Import Address Table address = 0
004000E8  |.  00000000                                   DD 00000000                       ; Import Address Table size = 0
004000EC  |.  00000000                                   DD 00000000                       ; Delay Import Descriptor address = 0
004000F0  |.  00000000                                   DD 00000000                       ; Delay Import Descriptor size = 0
004000F4  |.  00000000                                   DD 00000000                       ; COM+/CLI Header address = 0
004000F8  |.  00000000                                   DD 00000000                       ; COM+/CLI Header size = 0
004000FC  |.  00000000                                   DD 00000000                       ; Reserved = 00000000
00400100  \.  00000000                                   DD 00000000                       ; Reserved = 00000000
00400104  /.  2E 66 6C 61 74 00 00 00                    ASCII ".flat",0,0,0               ; Name[8] = ".flat"
0040010C  |.  E4000000                                   DD 000000E4                       ; VirtualSize = 228.
00400110  |.  00100000                                   DD 00001000                       ; VirtualAddress = 1000
00400114  |.  D2000000                                   DD 000000D2                       ; SizeOfRawData = 210.
00400118  |.  00020000                                   DD 00000200                       ; PointerToRawData = 200
0040011C  |.  00000000                                   DD 00000000                       ; PointerToRelocations = 0
00400120  |.  00000000                                   DD 00000000                       ; PointerToLineNumbers = 0
00400124  |.  0000                                       DW 0                              ; NumberOfRelocations = 0
00400126  |.  0000                                       DW 0                              ; NumberOfLineNumbers = 0
00400128  \.  600000E0                                   DD E0000060                       ; Characteristics = CODE|INITIALIZED_DATA|EXECUTE|READ|WRITE

So my question: what can I remove from headers and improve on imports to make them smaller?

  • 1
    I don't think you can equate "reducing a PE header" with "making it as simple as possible". Reducing a PE header's size requires a lot of knowledge of its various parts, way beyond the regular documentation. – usr2564301 May 2 '15 at 23:16
4

You need to know about the PE format in detail. There are quite a few header entries which can be removed to make the PE as small as possible. However doing this is generally not recommended as this is an undocumented feature and may break compatibility across various Windows versions. Moreover, your file is more likely to trigger alerts from Anti-Virus products.

Here is a great article about creating the smallest possible PE. Much of the information had been obtained by fuzzing the file format, so the previous warnings apply.

0

Well, if your purpose is to generate small binaries, headers are definitely not what you should be playing with since most of the header entries, whatever the binary format is (PE, ELF, Mach-O, ...), are designed to handle certain weird cases which you might possibly never think of. And, you can't really go very far in terms of size optimization, at most you'll save couple hundred bytes.

On the other hand, if you want your compiler to generate small binary files you should optimize the generated code (the -Os optimization flag does that in GCC). You'll find helpful information in this link, and also this one (under the Aggressive Optimizations section). Another interesting thing would be to check GCC's or LLVM's size optimization pass. The source codes are freely available on the compilers' respective websites.

Usually, code size optimization is critical for embedded systems since they're most likely to pack a small memory. Here is a very interesting scientific article that tackles code size optimization for embedded systems by smartly picking instructions using a DAG (Directed Acyclic Graph) covering algorithm. Since you're writing a compiler, this sounds like a better option than cutting off entries in the binary format header. Your can reasonably implement the algorithm provided in the above publication in your compiler's back-end. For a lengthier and complete reference, check this document by Neil. E. Johnson from the University of Cambridge. It's a gold mine.

APPENDIX :

A pure reverse engineering trick would be to perform a differential analysis by compiling a simple program using code optimization flags (-O2 for GCC), then size optimization flags (-Os for GCC), and then comparing the two binaries. Using this techniques, you'll most likely be able to spot the differences in headers, if there are any, and also the different code size optimizations.

  • Surely executable headers are not optimized by gcc? I'm guessing the only relevant difference in the headers would be "this file has a larger amount of code". – usr2564301 May 5 '15 at 10:31
  • Well they are rarely reduced... the gain isn't considerably large compared to code. Some sections are deleted if not used, but it is mainly related to the binary and the compiler version. – yaspr May 5 '15 at 10:42

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.