Since the PE loader in Windows is too flexible, most of the PE Header information can be discarded.
As the Tiny PE project proved, it's possible to get a 97 bytes PE! It also proved a valid PE can't be smaller, as 97 bytes is the minimum size to fit all the structures until OPTIONAL_HEADER.Subsystem, the last compulsory element.
In my short one-section file header (which I use in my helloworld.asm example), I define a minimum (not an absolute minimum, though) amount of elements of the PE structure, to have a file with Imports, Section and EntryPoint (none of them is strictly necessary):
IMAGE_DOS_HEADER
e_magic (constant)
e_lfanew
IMAGE_NT_HEADERS
Signature (constant)
IMAGE_FILE_HEADER
Machine (almost constant)
NumberOfSections (not strictly necessary)
SizeOfOptionalHeader
Characteristics
IMAGE_OPTIONAL_HEADER32
Magic (almost constant)
AddressOfEntryPoint (not strictly necessary)
ImageBase
SectionAlignment
FileAlignment
MajorSubsystemVersion (almost constant)
SizeOfImage
SizeOfHeaders
Subsystem
NumberOfRvaAndSizes (not strictly necessary)
IMAGE_DATA_DIRECTORY
ImportsVA (not strictly necessary)
IMAGE_SECTION_HEADER
VirtualAddress (not strictly necessary)
SizeOfRawData (not strictly necessary)
PointerToRawData (not strictly necessary)
So, in total, a PE can be defined by only 16 elements, 6 of which are not strictly necessary, excluding 3 almost constants (always the same under a modern 32 bit windows) and 2 constants signatures.
Since only a few elements are defined, the file is full of gaps (HelloWorld.exe is made of 67% of 00s).
...that's a lot of space...let's use it !
In collapsed.asm, all the elements that are not part of the header (code, data, imports) are moved into those gaps.
Among others, even the initial signature is a part of the code:
IMAGE_DOS_HEADER
e_magic dw 'MZ' ; will decode as dec ebp, pop edx
and the section name is used as an Import thunk:
SECTION_0:
kernel32.dll_THUNK:
__imp__ExitProcess:
.AddressOfData
DD iExitProcess - IMAGEBASE
DD 0
It makes quite an odd PE, but it still works! some tools may crash or fail loading it though: EntryPoint at 0, non-linear import table, etc...
HelloWorld: Binary Source
Collapsed: Binary Source
[...]
Les trous des en-têtes PE / les remplir
Puisque le système de chargement des PE sous Windows est trop flexible, la plupart de l'en-tête PE peut être oubliée.
Comme l'a prouvé le projet Tiny PE, il est possible d'obtenir un PE de 97 octets ! Il a également été prouvé qu'un PE valide ne peut être plus petit, car 97 octets est la taille minimum pour aller jusqu'au OPTIONAL_HEADER.Subsystem, le dernier élément indispensable.
Dans mon en-tête pour fichier à une seule section (que j'utilise dans l'exemple helloworld.asm), je définis un minimum (pas un minimum absolu cependant) d'éléments de la structure du PE, pour avoir un fichier avec Imports, Section et EntryPoint (aucun des trois n'étant indispensable) :
IMAGE_DOS_HEADER
e_magic (constant)
e_lfanew
IMAGE_NT_HEADERS
Signature (constant)
IMAGE_FILE_HEADER
Machine (quasi constant)
NumberOfSections (facultatif)
SizeOfOptionalHeader
Characteristics
IMAGE_OPTIONAL_HEADER32
Magic (quasi constant)
AddressOfEntryPoint (facultatif)
ImageBase
SectionAlignment
FileAlignment
MajorSubsystemVersion (quasi constant)
SizeOfImage
SizeOfHeaders
Subsystem
NumberOfRvaAndSizes (facultatif)
IMAGE_DATA_DIRECTORY
ImportsVA (facultatif)
IMAGE_SECTION_HEADER
VirtualAddress (facultatif)
SizeOfRawData (facultatif)
PointerToRawData (facultatif)
Donc en tout, un PE peut être défini par 16 éléments uniquement, dont 6 facultatifs, si on exclut les 3 quasi-constants (invariables sous un Windows 32 bits moderne) et 2 signatures constantes.
Puisque seulement quelques éléments sont définis, le fichier est plein de trous (HelloWorld.exe est vide à 67%).
...tout cet espace...utilisons-le !
Dans collapsed.asm, tous les éléments qui ne font pas partie de l'en-tête (code, données, imports) ont été déplacés dans ces trous.
Entre autre, même la signature initiale fait partie du code :
IMAGE_DOS_HEADER
e_magic dw 'MZ' ; sera interprété comme dec ebp, pop edx
et le nom de section est utilisé comme THUNK d'Import :
SECTION_0:
kernel32.dll_THUNK:
__imp__ExitProcess:
.AddressOfData
DD iExitProcess - IMAGEBASE
DD 0
Ça donne un binaire bien bizarre, mais ça marche encore ! Cela dit, certains programmes peuvent planter ou refuser de le charger : EntryPoint à 0, table d'imports non linéaire, etc...
HelloWorld : Binaire Source
Collapsed : Binaire Source
No comments:
Post a Comment