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

  1. Nope. ↩︎

  2. Nuh-uh. ↩︎