4 minutes
Nim - Parsing PE File Headers
winim and memlib
If you’ve tried Windows system programming in Nim you’re probably familiar with Ward’s excellent winim and memlib libraries which make working with the very cool1 and normal2 Windows API and process memory relatively painless.
Winim contains Windows API structs and definitions, and even Windows COM support, whereas memlib is a drop-in replacement for the dynlib pragma that enables us to do stuff like load a DLL from memory.
If you haven’t heard of these, take the time to browse his GitHub page. There’s a lot of cool stuff in there.
Parsing a PE file header
Our goal for today is to parse a PE header to read the exported functions of a DLL. Luckily, winim already contains all the Windows struct definitions we’ll need so as long as we know what we’re looking for our work here is easy. Essentially, we’re going to do two things; offset calculations, and data structure casting.
The first step is to find the base address of the DLL which we can do using the LoadLibraryA
API function.
import winim
import strformat
let dllBase = LoadLibraryA("ntdll")
# Use the `:#x` format specifier to hex format the int.
echo fmt"DLL base address: {dllBase:#x}"
We then cast dllBase
to a pointer to an IMAGE_DOS_HEADER
struct:
let dosHeader = cast[PIMAGE_DOS_HEADER](dllBase)
Then to calculate the offset of PIMAGE_NT_HEADERS
we just add the New Executable address dosHeader.e_lfanew
to the dllBase
address.
let imageNTHeaders = cast[PIMAGE_NT_HEADERS](cast[DWORD_PTR](dllBase) + dosHeader.e_lfanew)
Next, we need to find the relative address of the IMAGE_DIRECTORY_ENTRY_EXPORT
and then calculate it’s offset from dllBase
to get the address of PIMAGE_EXPORT_DIRECTORY
.
let exportDirectoryRVA = cast[DWORD](imageNTHeaders.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress)
let imageExportDirectory = cast[PIMAGE_EXPORT_DIRECTORY](cast[DWORD_PTR](dllBase) + exportDirectoryRVA)
The last part is to cast the function name and ordinal from imageExportDirectory.AddressOfNames
and imageExportDirectory.AddressOfNameOrdinals
.
Before we put this all together, let’s quickly look at some extremely helpful functions and templates from memlib which will make our pointer arithmetic and casting much easier.
memlib helper functions to the rescue
These functions are straight out of Ward’s memlib. Unfortunately, they’re not exported so you’ll need to copy them into your script if you want to use the final syntax in this example. I’ve added examples for clarity.
The []
proc provides concise casting syntax. It simply casts x
to type U
.
import typetraits
proc `[]`[T](x: T, U: typedesc): U =
cast[U](x)
var a: uint32 = 1
echo a.type.name # uint32
var b = a[int]
echo b # 1
echo b.type.name # int
The {}
proc performs a zero extend cast which means we ensure the high bits in the int are zeros after casting. Notice that this proc uses the previous []
proc when casting.
import strformat
proc `{}`[T](x: T, U: typedesc): U =
when sizeof(x) == 1: x[uint8][U]
elif sizeof(x) == 2: x[uint16][U]
elif sizeof(x) == 4: x[uint32][U]
elif sizeof(x) == 8: x[uint64][U]
else: {.fatal.}
var
a: int8 = -127
b = a{uint16} # b uses zero extends cast
c = cast[uint16](a) # c does not
echo fmt"{b:#b}" # 0b10000001
echo fmt"{c:#b}" # 0b1111111110000001
Next, a {}
template that simplifies pointer arithmetic syntax. Again, notice the zero-extend cast in the proc.
import strformat
template `{}`[T](p: T, x: SomeInteger): T =
cast[T]((cast[int](p) +% x{int}))
let a: int = 0x1234
let b = a{1}
echo fmt"{b:#x}" # 0x1235
Finally, this just increments a pointer by sizeof(T)
.
template `++`[T](p: var ptr T) =
## syntax sugar for pointer increment
p = cast[ptr T](p[int] +% sizeof(T))
Exported functions
Put it all together, and our code prints out the exported functions and their ordinal from the PE header. Neat.
import winim, strformat
proc `[]`[T](x: T, U: typedesc): U =
cast[U](x)
proc `{}`[T](x: T, U: typedesc): U =
when sizeof(x) == 1: x[uint8][U]
elif sizeof(x) == 2: x[uint16][U]
elif sizeof(x) == 4: x[uint32][U]
elif sizeof(x) == 8: x[uint64][U]
else: {.fatal.}
template `{}`[T](p: T, x: SomeInteger): T =
cast[T]((cast[int](p) +% x{int}))
template `++`[T](p: var ptr T) =
p = cast[ptr T](p[int] +% sizeof(T))
let
dllBase = LoadLibraryA("ntdll")
dosHeader = cast[PIMAGE_DOS_HEADER](dllBase)
imageNTHeaders = cast[PIMAGE_NT_HEADERS](cast[DWORD_PTR](dllBase) + dosHeader.e_lfanew)
exportDirectoryRVA = cast[DWORD](imageNTHeaders.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress)
imageExportDirectory = cast[PIMAGE_EXPORT_DIRECTORY](cast[DWORD_PTR](dllBase) + exportDirectoryRVA)
var
nameRef = cast[pointer](dllBase){imageExportDirectory.AddressOfNames}[ptr uint32]
ordinal = cast[pointer](dllBase){imageExportDirectory.AddressOfNameOrdinals}[ptr uint16]
for i in 0 ..< imageExportDirectory.NumberOfNames:
let
name = cast[pointer](dllBase){nameRef[]}[cstring]
index = int ordinal[]
echo name
echo index
++nameRef
++ordinal