Basic 64bit PE headers
The Portable Executable (PE) format is a type of file format utilized for executables, object code, DLLs, and other files within both 32-bit and 64-bit Windows operating systems. The PE format serves as a data structure that contains essential information required by the Windows OS loader to oversee the enclosed executable code.
Important headers
The PE format is a complex executable format. For the brevity of this article, we will only cover the essential aspects needed for unpacking. There are a few differences between the 32-bit and 64-bit formats. In this article, we will describe the 64-bit format. Most of the relevant structures are mentioned in the winnt.h
header file
The PE format starts with the DOS header. The first field is the 0x4D5A signature, and the pointer to the PEHeader leads us to the base of the FileHeader within a PE header.
which is encapsulated in
typedef struct _IMAGE_NT_HEADERS64 {
DWORD Signature;
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER64 OptionalHeader;
} IMAGE_NT_HEADERS64, *PIMAGE_NT_HEADERS64;
IMAGE_OPTIONAL_HEADER64 Fields
IMAGE_OPTIONAL_HEADER64.Machine
can be used to determine the machine the binary is supposed to run on.
IMAGE_OPTIONAL_HEADER64
has four fields of different sizes than the IMAGE_OPTIONAL_HEADER
(32-bit):
ImageBase
SizeOfStackReserve
SizeOfStackCommit
SizeOfHeapReserve
SizeOfHeapCommit
StackReserve: Reserving memory refers to the process of setting aside a portion of the virtual address space for a specific application or process. This reserved memory does not necessarily correspond to actual physical RAM; instead, it establishes a region in the process's virtual address space that can be used for future memory allocations.
StackCommit: Committing memory is an explicit request by the application to make use of the previously reserved virtual address space. Once committed, the operating system ensures that the required physical memory is available for storing data. If there is not enough physical RAM available, the OS might use a combination of RAM and disk space (paging or swapping) to fulfill the commitment.
If values are not present, the Windows kernel sets the default values as 4000h
and 0FFFFh
, which is done in RtlCreateUserStack
.
nt!RtlCreateUserStack+0x61:
fffff806`76e29231 498b4308 mov rax,qword ptr [r11+8]
fffff806`76e29235 4885c0 test rax,rax
fffff806`76e29238 0f8477010000 je nt!RtlCreateUserStack+0x1e5 (fffff806`76e293b5)
fffff806`76e2923e 498b4b10 mov rcx,qword ptr [r11+10h]
fffff806`76e29242 4885c9 test rcx,rcx
fffff806`76e29245 0f846a010000 je nt!RtlCreateUserStack+0x1e5 (fffff806`76e293b5)
fffff806`76e2924b ba00400000 mov edx,4000h
fffff806`76e29250 4885c0 test rax,rax
nt!RtlCreateUserStack+0xa1:
fffff806`76e29271 488d99ffff0000 lea rbx,[rcx+0FFFFh]
fffff806`76e29278 4881e30000ffff and rbx,0FFFFFFFFFFFF0000h
fffff806`76e2927f 498b8518030000 mov rax,qword ptr [r13+318h]
fffff806`76e29286 4889442448 mov qword ptr [rsp+48h],rax
fffff806`76e2928b eb05 jmp nt!RtlCreateUserStack+0xc2 (fffff806`76e29292)
fffff806`76e2928d e910010000 jmp nt!RtlCreateUserStack+0x1d2 (fffff806`76e293a2)
fffff806`76e29292 4885c0 test rax,rax
fffff806`76e29295 0f8598010000 jne nt!RtlCreateUserStack+0x263 (fffff806`76e29433)
Following is an example in C to parse headers of a PE 64bit file
int parsePEHeader(const char* peFilePath) {
FILE* file = fopen(peFilePath, "rb");
if (file == NULL) {
perror("Unable to open file");
return 1;
}
IMAGE_DOS_HEADER dosHeader;
IMAGE_NT_HEADERS64 ntHeaders;
// Read the DOS header
fread(&dosHeader, sizeof(IMAGE_DOS_HEADER), 1, file);
// Check for the DOS signature
if (dosHeader.e_magic != IMAGE_DOS_SIGNATURE) {
fclose(file);
fprintf(stderr, "Not a valid PE file (DOS header signature not found)\n");
return 1;
}
// Seek to the offset of IMAGE_NT_HEADERS64
fseek(file, dosHeader.e_lfanew, SEEK_SET);
// Read the IMAGE_NT_HEADERS64 structure
fread(&ntHeaders, sizeof(IMAGE_NT_HEADERS64), 1, file);
// Check for the NT signature
if (ntHeaders.Signature != IMAGE_NT_SIGNATURE) {
fclose(file);
fprintf(stderr, "Not a valid PE file (NT header signature not found)\n");
return 1;
}
// Access the information from IMAGE_OPTIONAL_HEADER64
printf("ImageBase: 0x%llX\n", ntHeaders.OptionalHeader.ImageBase);
printf("SizeOfImage: %lu\n", ntHeaders.OptionalHeader.SizeOfImage);
printf("SizeOfHeaders: %lu\n", ntHeaders.OptionalHeader.SizeOfStackReserve);
printf("SizeOfCommit: %lu\n", ntHeaders.OptionalHeader.SizeOfStackCommit);
fclose(file);
return 0;
}