What is PE files?
PE files refers to Portable Executable files in Windows which may have any extension of the listed below
Windows Executable files Extensions:
- .exe → Executable File
- .dll → Dynamic Link Library
- .sys/.drv → System File / Kernel Driver
- .ocx → ActiveX Control
- .cpl → Control Panel
- .scr → ScreenSaver
Environment Setup
To follow along with me you should install pefile module
pip3 install pefile
OR clone the repository and follow the setup instructions
https://github.com/erocarrera/pefile
PE File Structure
DOS Header:
- e_magic → magic number of DOS header is ‘MZ’ (0x5a4d) and ‘MZ’ refers to Mark Zbikowski the designer of MS-DOS executable file format.
- e_lfnew → a pointer to the PE header (NT Header).
For most Windows programs DOS header contains a DOS program which does nothing but prints “This program cannot be run in DOS mode”.
Note that in the picture above e_magic == 0x4d5a (because of little endian)
https://en.wikipedia.org/wiki/Endianness
Get all information about PE header with pefile python module
import pefile pe = pefile.PE("path_to_your_executable") pe.print_info() # Prints all Headers in a human readable format
OUTPUT:
import pefile pe = pefile.PE("path_to_your_executable") print("e_magic : " + hex(pe.DOS_HEADER.e_magic)) # Prints the e_magic field of the DOS_HEADER print("e_lfnew : " + hex(pe.DOS_HEADER.e_lfanew)) # Prints the e_lfnew field of the DOS_HEADER
OUTPUT:
e_magic : 0x5a4d
e_lfnew : 0xd8
PE Header (NT Headers):
The only field we care about in the PE Header (NT_HEADER) is Signature which identify the file as a PE file and two other structures (FILE_HEADER and OPTIONAL_HEADER)
- Signature == 0x5045 (‘PE’ in ASCII)
- FILE_HEADER
- OPTIONAL_HEADER
import pefile pe = pefile.PE("path_to_your_executable") print("Signature : " + hex(pe.NT_HEADERS.Signature)) # Prints the Signature field of the NT_HEADERS
OUTPUT:
Signature : 0x4550
File Header:
- Machine: the architecture this binary is supposed to run on (0x014C == x86 binary and 0x8664 == x86-x64 binary)
- TimeDateStamp: UNIX timestamp (seconds since epoch or 00:00:00 1/1/1970)
- NumberOfSections: number of section headers
- Characteristics: specify some characteristics of the PE file
import pefile # Loading an executable pe = pefile.PE("path_to_your_executable") print("Machine : " + hex(pe.FILE_HEADER.Machine)) # Check if it is a 32-bit or 64-bit binary if hex(pe.FILE_HEADER.Machine) == '0x14c': print("This is a 32-bit binary") else: print("This is a 64-bit binary") print("TimeDateStamp : " + pe.FILE_HEADER.dump_dict()['TimeDateStamp']['Value'].split('[')[1][:-1] ) print("NumberOfSections : " + hex(pe.FILE_HEADER.NumberOfSections)) print("Characteristics flags : " + hex(pe.FILE_HEADER.Characteristics))
OUTPUT:
Machine : 0x14c
This is a 32-bit binary
TimeDateStamp : Tue Jan 30 03:57:45 2018 UTC
NumberOfSections : 0x5
Characteristics flags : 0x10
Optional Header:
It is not optional at all, the following are the interesting fields in the Optional Header
- Magic: depending on this value the binary will be interpreted as a 32-bit or 64-bit binary (0x10B == 32 bit and 0x20B == 64 bit)
- AddressOfEntryPoint: specifies the RVA (relative virtual address)
- ImageBase: specifies the preferred virtual memory location where the beginning of the binary should be placed
- SectionAlignment: specifies that sections must be aligned on boundaries which are multiples of this value
- FileAlignment: if the data was written to the binary into chunks no smaller than this value
- SizeOfImage: the amount of contigous memory that must be reserved to load the binary into memory
- DllCharacteristics: specify some security characteristics for the PE file
- DataDirectory[IMAGE_NUMBER_OF_DIRECTORY_ENTRIES]: an array of Data Entries
import pefile # Loading an executable pe = pefile.PE("path_to_your_executable") print("Magic : " + hex(pe.OPTIONAL_HEADER.Magic)) # Check if it is a 32-bit or 64-bit binary if hex(pe.OPTIONAL_HEADER.Magic) == '0x10b': print("This is a 32-bit binary") elif hex(pe.OPTIONAL_HEADER.Magic) == '0x20b': print("This is a 64-bit binary") print("ImageBase : " + hex(pe.OPTIONAL_HEADER.ImageBase)) print("SectionAlignment : " + hex(pe.OPTIONAL_HEADER.SectionAlignment)) print("FileAlignment : " + hex(pe.OPTIONAL_HEADER.FileAlignment)) print("SizeOfImage : " + hex(pe.OPTIONAL_HEADER.SizeOfImage)) print("DllCharacteristics flags : " + hex(pe.OPTIONAL_HEADER.DllCharacteristics)) print("DataDirectory: ") print("*" * 50) # print name, size and virtualaddress of every DATA_ENTRY in DATA_DIRECTORY for entry in pe.OPTIONAL_HEADER.DATA_DIRECTORY: print(entry.name + "\n|\n|---- Size : " + str(entry.Size) + "\n|\n|---- VirutalAddress : " + hex(entry.VirtualAddress) + '\n') print("*" * 50)
OUTPUT:
Magic : 0x10b
This is a 32-bit binary
ImageBase : 0x400000
SectionAlignment : 0x1000
FileAlignment : 0x200
SizeOfImage : 0x46000
DllCharacteristics flags : 0x8540
DataDirectory:
**************************************************
IMAGE_DIRECTORY_ENTRY_EXPORT
|
|---- Size : 0
|
|---- VirutalAddress : 0x0
IMAGE_DIRECTORY_ENTRY_IMPORT
|
|---- Size : 160
|
|---- VirutalAddress : 0x8534
IMAGE_DIRECTORY_ENTRY_RESOURCE
|
|---- Size : 53856
|
|---- VirutalAddress : 0x38000
IMAGE_DIRECTORY_ENTRY_EXCEPTION
|
|---- Size : 0
|
|---- VirutalAddress : 0x0
IMAGE_DIRECTORY_ENTRY_SECURITY
|
|---- Size : 10808
|
|---- VirutalAddress : 0x5b6b0
IMAGE_DIRECTORY_ENTRY_BASERELOC
|
|---- Size : 0
|
|---- VirutalAddress : 0x0
IMAGE_DIRECTORY_ENTRY_DEBUG
|
|---- Size : 0
|
|---- VirutalAddress : 0x0
IMAGE_DIRECTORY_ENTRY_COPYRIGHT
|
|---- Size : 0
|
|---- VirutalAddress : 0x0
IMAGE_DIRECTORY_ENTRY_GLOBALPTR
|
|---- Size : 0
|
|---- VirutalAddress : 0x0
IMAGE_DIRECTORY_ENTRY_TLS
|
|---- Size : 0
|
|---- VirutalAddress : 0x0
IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG
|
|---- Size : 0
|
|---- VirutalAddress : 0x0
IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT
|
|---- Size : 0
|
|---- VirutalAddress : 0x0
IMAGE_DIRECTORY_ENTRY_IAT
|
|---- Size : 664
|
|---- VirutalAddress : 0x8000
IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT
|
|---- Size : 0
|
|---- VirutalAddress : 0x0
IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR
|
|---- Size : 0
|
|---- VirutalAddress : 0x0
IMAGE_DIRECTORY_ENTRY_RESERVED
|
|---- Size : 0
|
|---- VirutalAddress : 0x0
**************************************************
Sections Header:
Sections are group of code or data that have similar permissions in memory
Common Section Names:
- .text → the actual code the binary runs
- .data → read/write data (globals)
- .rdata → read-only data (strings)
- .bss → Block Storage Segment (uninitialzed data format), often merged with the .data section
- .idata → import address table, often merged with .text or .rdata sections
- .edata → export address table
- .pdata → some architectures like ARM, MIPS use these sections structures to aid in stack-walking at run-time
- PAGE* → code/data which it’s fine to page out to disk if you’re running out of memory
- .reolc → relocation information for where to modify the hardcoded addresses
- .rsrc → resources like icons, other embedded binaries, this section has a structure organizing it like a filesystem
Common Structure of a Section Header:
- Name
- VirtualSize
- VirtualAddress
- SizeOfRawData
- PointerToRawData
- Characterisitcs
import pefile # Loading an executable pe = pefile.PE("path_to_your_executable") # Parsing every section from Sections Header print("Sections Info: \n") print("*" * 50) for section in pe.sections: print(section.Name.decode().rstrip('\x00') + "\n|\n|---- Vitual Size : " + hex(section.Misc_VirtualSize) + "\n|\n|---- VirutalAddress : " + hex(section.VirtualAddress) + "\n|\n|---- SizeOfRawData : " + hex(section.SizeOfRawData) + "\n|\n|---- PointerToRawData : " + hex(section.PointerToRawData) + "\n|\n|---- Characterisitcs : " + hex(section.Characteristics)+'\n') print("*" * 50)
OUTPUT:
Sections Info:
**************************************************
.text
|
|---- Vitual Size : 0x628f
|
|---- VirutalAddress : 0x1000
|
|---- SizeOfRawData : 0x6400
|
|---- PointerToRawData : 0x400
|
|---- Characterisitcs : 0x60000020
.rdata
|
|---- Vitual Size : 0x1354
|
|---- VirutalAddress : 0x8000
|
|---- SizeOfRawData : 0x1400
|
|---- PointerToRawData : 0x6800
|
|---- Characterisitcs : 0x40000040
.data
|
|---- Vitual Size : 0x25518
|
|---- VirutalAddress : 0xa000
|
|---- SizeOfRawData : 0x600
|
|---- PointerToRawData : 0x7c00
|
|---- Characterisitcs : 0xc0000040
.ndata
|
|---- Vitual Size : 0x8000
|
|---- VirutalAddress : 0x30000
|
|---- SizeOfRawData : 0x0
|
|---- PointerToRawData : 0x0
|
|---- Characterisitcs : 0xc0000080
.rsrc
|
|---- Vitual Size : 0xd260
|
|---- VirutalAddress : 0x38000
|
|---- SizeOfRawData : 0xd400
|
|---- PointerToRawData : 0x8200
|
|---- Characterisitcs : 0x40000040
**************************************************
For more usage examples of pefile to get you familiar, please refer to the following link
https://github.com/erocarrera/pefile/blob/wiki/UsageExamples.md
Resources:
- https://docs.microsoft.com/en-us/windows/win32/debug/pe-format
- https://code.google.com/archive/p/corkami/wikis/PE.wiki
- http://www.opensecuritytraining.info/LifeOfBinaries.html
- https://github.com/erocarrera/pefile
- https://winitor.com/index.html
- https://www.aldeid.com/wiki/PEiD
- https://github.com/hasherezade/pe-sieve
To the next time, Happy learning!
Leave a Reply