Sunday 4 January 2015

Win32 Hacks: Loading API functions from a process' Process Environment Block (PEB) Part 2 of 2

Win32 Hacks: Loading API functions from a process' Process Environment Block (PEB)

Preliminary

Last time in part 1 we figured out how to find the base address of persistent DLLs via the PEB. By reading the DLL image at the base address, we can read its export directory table and get the address to its functions. That's what we're going to do now.

PE memory layout

At the DLL base address, the DLL image is almost laid out the same way as in the DLL file on disk. The only difference is that sections are aligned and padded to larger sizes than in the file, and not all sections get mapped to memory at runtime. The latest PE specification can be found here. At runtime, the layout looks like this:

  • MSDOS header (starts with the magic value "MZ")
  • MSDOS stub
  • PE header
  • Optional header
  • Data directory table
  • Section headers
  • Section 1
  • Section 2
  • ...
  • Section N
Winnt.h has structures for all of these, and they are called IMAGE_DOS_HEADER and IMAGE_NT_HEADERS32. (IMAGE_NT_HEADERS64 for PE32+ executables)
IMAGE_NT_HEADERS32 contains both the PE file header and the optional header.

Our goal is to get the address to a function inside the library given the function's name, and that information is found in the export directory table. The address to the export directory table is found in the data directory table.

Validating the headers

We need to validate the headers in case some of the required fields don't exist, or the image is corrupt for some reason.

void* slGetModuleProcAddress(void* moduleBase, LPCSTR procName){
 //The MSDOS header starts directly at the first byte of the base address
 PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)moduleBase;
 //MSDOS images start with Magic value 'M' 'Z'
 if (dosHeader->e_magic != IMAGE_DOS_SIGNATURE)
  return NULL;
 //The field e_lfanew in the MSDOS header contains the
 //PE header offset
 PIMAGE_NT_HEADERS32 headers32 =
  (PIMAGE_NT_HEADERS32)((char*)moduleBase + dosHeader->e_lfanew);
 if (headers32->Signature != IMAGE_NT_SIGNATURE)
  return NULL;
 //A normal PE32 file has an optional header which is 96
 //bytes long
 //NumberOfRvaAndSizes holds the number of entries in the
 //data directory table. We need at least one.
 if (headers32->FileHeader.SizeOfOptionalHeader < 96 ||
  headers32->OptionalHeader.NumberOfRvaAndSizes == 0)
  return NULL;
 
 DWORD EdtOffset =
 headers32->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;

 if (!EdtOffset)
  return NULL;

 /* More code follows .. */

Pretty straight forward. The addresses and sizes of different tables are stored in the data directory table at specific positions. The size and address of the export directory table is located at index 0 of the data directory table. This is why NumberOfRvaAndSizes must be at least one. We checked all the magic values in the different headers, and we found the relative virtual address to the export directory table, which is EdtOffset. So what does the EDT look like?

Looking up a symbol from the Export Directory Table


typedef struct _EXPORT_DIRECTORY_TABLE {
 DWORD ExportFlags;
 DWORD TimeStamp;
 WORD MajorVersion;
 WORD MinorVersion;
 DWORD NameRVA;
 DWORD OrdinalBase;
 DWORD ExportAddressTableSize;
 DWORD NamePointerTableSize;
 DWORD ExportAddressTableRVA;
 DWORD NamePointerTableRVA;
 DWORD OrdinalTableRVA;
} EXPORT_DIRECTORY_TABLE, *PEXPORT_DIRECTORY_TABLE;

The interesting fields here are NameRVA, ExportAddressTableRVA, OrdinalTableRVA and OrdinalBase. The RVAs are relative addresses to the address-, ordinal- and string tables respectively, while OrdinalBase is an offset to add to ordinals.
NameRVA is an array of null-terminated ISO-8859-1 strings, and it is lexicographically sorted. Finding a symbol in the list is therefore O(log n) and pretty fast. If the symbol you searched for is at index 'i' in the name table, the symbol's ordinal is found at index 'i' in the ordinal table via OrdinalTableRVA. You use the ordinal as an index into the export address table, and that gives you the function address.

But all of these fields are relative virtual addresses. How do we turn them into absolute virtual addresses? Well, we did it before when we set up the MSDOS and PE headers. They are relative to the DLL image base, so just add the image base address to them.

 //slGetModuleProcAddress() from previous code snippet
 //continues here.

 //moduleBase + offset gives us the real VA to the EDT
 PEXPORT_DIRECTORY_TABLE EdtPtr =
 (PEXPORT_DIRECTORY_TABLE)((char*)moduleBase + EdtOffset);
 //Again, add base address to all the RVAs
 PVOID OrdinalTable = (PBYTE)moduleBase + EdtPtr->OrdinalTableRVA;
 PVOID NamePointerTable = (PBYTE)moduleBase + EdtPtr->NamePointerTableRVA;
 PVOID ExportAddressTable = (PBYTE)moduleBase + EdtPtr->ExportAddressTableRVA;
 //We're lazy and do a linear search for the function name
 //Real code should probably use binary search
 for (DWORD i = 0; i < EdtPtr->NamePointerTableSize; i++){  
  DWORD NameRVA = ((PDWORD)NamePointerTable)[i];
  const char* NameAddr = (char*)moduleBase + NameRVA;
  //If string comparison fails, skip to next iteration
  //Note that these strings are ISO-8859-1, not UTF-8
  //or UTF-16/USC2!
  if (slStrCompare(NameAddr, procName))
   continue;

  //The weird stuff with OrdinalBase is explained later
  WORD Ordinal = ((PWORD)OrdinalTable)[i] + (WORD)EdtPtr->OrdinalBase;
  WORD RealOrdinal = Ordinal - (WORD)EdtPtr->OrdinalBase;  
  DWORD ExportAddress = ((PDWORD)ExportAddressTable)[RealOrdinal];  
  void* FinalAddr = (char*)moduleBase + ExportAddress;
  return FinalAddr;
 }
 return NULL;
}

In the code above, we start with computing the actual virtual addresses to all the tables. Then for simplicity's sake we do a linear search as opposed to binary search. When we find a string match in the string table, we use the current index to look up the ordinal, and the ordinal is used as lookup in the export address table. Finally, we add the image base to the export address to make a real virtual address.

But one thing remains to explain. What's up with the OrdinalBase variable? As it's used in the code, it looks completely superfluous.
According to Microsoft's COFF/PE specification, one is supposed to subtract OrdinalBase from the ordinal value prior lookup into the export address table. But in reality, the entries in the ordinal array is already offset with OrdinalBase. So if you want to compare ordinal values with other tools, you have to add OrdinalBase to the ordinal. In the code above Ordinal is the ordinal value you would compare with other programs, while RealOrdinal is the ordinal you use for lookup.

The final code combined with a simple example looks like this (structures omitted):

void* slGetModuleBase(LPCWSTR moduleName){
 void* pebPtr = slGetPEB(); 
 PPEB32 peb = (PPEB32)pebPtr;
 PLIST_ENTRY moduleListTail = &peb->Ldr->InMemoryOrderModuleList;
 PLIST_ENTRY moduleList = moduleListTail->Flink;
 do {
  char* modulePtrWithOffset = (char*)moduleList;
  PLDR_DATA_TABLE_ENTRY module =
   (PLDR_DATA_TABLE_ENTRY)modulePtrWithOffset;
  if (!slWStrCompare(module->FullDllName.Buffer, moduleName)){
   void* DllBase = module->Reserved2[0];
   return DllBase;
  }
  moduleList = moduleList->Flink;
 } while (moduleList != moduleListTail);
 return NULL;
}

void* slGetModuleProcAddress(void* moduleBase, LPCSTR procName){
 PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)moduleBase;
 if (dosHeader->e_magic != IMAGE_DOS_SIGNATURE)
  return NULL; 
 PIMAGE_NT_HEADERS32 headers32 =
  (PIMAGE_NT_HEADERS32)((char*)moduleBase + dosHeader->e_lfanew);
 if (headers32->Signature != IMAGE_NT_SIGNATURE)
  return NULL; 
 if (headers32->FileHeader.SizeOfOptionalHeader < 96 ||
  headers32->OptionalHeader.NumberOfRvaAndSizes == 0)
  return NULL;
 
 DWORD EdtOffset =
 headers32->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
 if (!EdtOffset)
  return NULL;

 PEXPORT_DIRECTORY_TABLE EdtPtr =
  (PEXPORT_DIRECTORY_TABLE)((char*)moduleBase + EdtOffset);
 PVOID OrdinalTable = (PBYTE)moduleBase + EdtPtr->OrdinalTableRVA;
 PVOID NamePointerTable = (PBYTE)moduleBase + EdtPtr->NamePointerTableRVA;
 PVOID ExportAddressTable = (PBYTE)moduleBase + EdtPtr->ExportAddressTableRVA;
 
 for (DWORD i = 0; i < EdtPtr->NamePointerTableSize; i++){  
  DWORD NameRVA = ((PDWORD)NamePointerTable)[i];
  const char* NameAddr = (char*)moduleBase + NameRVA;

  if (slStrCompare(NameAddr, procName))
   continue;

  WORD Ordinal = ((PWORD)OrdinalTable)[i] + (WORD)EdtPtr->OrdinalBase;
  WORD RealOrdinal = Ordinal - (WORD)EdtPtr->OrdinalBase;
  DWORD ExportAddress = 0;  
  ExportAddress = ((PDWORD)ExportAddressTable)[RealOrdinal];  
  void* FinalAddr = (char*)moduleBase + ExportAddress;
  return FinalAddr;
 }
 return NULL;
}

//kernel32
typedef HMODULE (WINAPI *LOADLIBRARYWPROC)(LPCWSTR);
typedef BOOL (WINAPI *FREELIBRARYPROC)(HMODULE);
typedef PVOID (WINAPI *GETPROCADDRESSSPROC)(HMODULE, LPCSTR);
typedef VOID (WINAPI *EXITPROCESSPROC)(UINT);
//user32
typedef int (WINAPI *MESSAGEBOXWPROC)(HWND, LPCWSTR, LPCWSTR, UINT);


void main(void){
 //kernel32
 EXITPROCESSPROC slExitProcess = NULL;
 LOADLIBRARYWPROC slLoadLibraryW = NULL;
 FREELIBRARYPROC slFreeLibrary = NULL;
 GETPROCADDRESSSPROC slGetProcAddress = NULL;
 //user32
 MESSAGEBOXWPROC slMessageBoxW = NULL;
 HMODULE m;

 void* kernel32 = slGetModuleBase(L"KERNEL32.DLL");

 slExitProcess = slGetModuleProcAddress(kernel32, "ExitProcess");
 slLoadLibraryW = slGetModuleProcAddress(kernel32, "LoadLibraryW");
 slFreeLibrary = slGetModuleProcAddress(kernel32, "FreeLibrary");
 slGetProcAddress = slGetModuleProcAddress(kernel32, "GetProcAddress");

 m = slLoadLibraryW(L"USER32.DLL");
 slMessageBoxW = slGetProcAddress(m, "MessageBoxW");

 slMessageBoxW(0, L"SelfLoader Example", L"Test", MB_OK);
 slFreeLibrary(m);

 slExitProcess(0);
}


Hopefully it's clear now that we use this trick to only get a few functions from kernel32.dll, and let GetProcAddress() do the rest. Our program has no external dependencies at link time.

Win32 Hacks: Loading API functions from a process' Process Environment Block (PEB) Part 1 of 2

Win32 Hacks: Loading API functions from a process' Process Environment Block (PEB)

Preliminary

Part 2 is here.

Edit: The original post became a bit too long and elaborate, so I split it into two posts. This first part shows how to get the base address of a persistently loaded library, and part 2 shows how to parse a module given its base addres and access its imports (get function pointers).
Occasionally one might have good reasons to make a program without any imports. That is, you want to make an executable or have pure code without any dependencies at link time (freestanding code) Or maybe one has code which is injected into another process (debuggers, game trainers, diagnostic tools, etc), in which case one needs a way to find the function pointers to the API functions one wants to use.

This can be done by reading the Process Environment Block, and it can be found via a pointer entry inside the Thread Information Block. Once you have the PEB, one can find module information for kernel32.dll, kernelbase.dll, and ntdll.dll in a linked list there, as these libraries are shared between all processes. They are always loaded into userspace memory when a new process is created.
There's plenty of information in the module list, but the only thing you'll need to load a library's functions is the image base address. When you have the base address, the only thing left to do is to parse the library image and its export address table.

A word of warning here. The TIB and PEB structures are highly susceptible for change between Windows versions. Memory layouts explained later might be completely different between my machine and your machine. If someone have definite information on the TIB and PEB layouts for all Windows versions, I'd be grateful for information on that. I would also appreciate more information on the layouts for 64-bit processes.
If you already are familiar with the TIB and PEB, skip the two following headings.

What is the Thread Information Block?

The TIB is an internal and undocumented/unofficial Windows data structure that contains information about a process' currently running thread, and which lives in user space memory.  For a general layout, Wikipedia has a nice overview. In 32-bit executables, the selector fs with some offset is used to access the TIB, while in 64-bit executables one uses the gs selector. (From now on, I assume 32-bit x86 executables unless specifically said otherwise.) If one wants to do some more heavy work, it might be more convenient with a direct pointer to the TIB. A direct pointer can be found by reading fs:[0x18]. Other information you can access via the TIB is the current thread ID, the current locale, the last error reported by GetLastError(), and so on. Remember that there is one TIB per thread. We're only interested in one field though, and that is the pointer to the Process Environment Block, and the PEB pointer in the TIBs is the same for all TIBs, as there is only one PEB per process. You can get that pointer by reading fs:[0x30].


;NASM x86 functions for getting the TIB
GLOBAL _GetTIB
SECTION .text;

_GetTIB: mov eax, [fs:0x18]
ret

What is the Process Environment Block?

The PEB is an internal and undocumented (but more documented than TIB) data structure that contains information about the process itself, and which lives in user space memory. Notably it contains information about modules the process shares with other processes. Again, Wikipedia to the rescue. To get the address to the PEB, access the PEB pointer field of any TIB. Its offset from the TIB start is 48 bytes. So fs:[0x30] gets you the pointer to the PEB.

;NASM x86 functions for getting the PEB
GLOBAL _GetPEB
SECTION .text;

_GetPEB: mov eax, [fs:0x30]
ret

Once we have the PEB, the Ldr field is the interesting one. It is itself a structure and contains a InMemoryOrderModuleList member. InMemoryOrderModuleList is of type LIST_ENTRY from winnt.h, line 1049. The list is an intrusive list, i.e the forward and back pointers are a part of the objects themselves. The larger structure is of type LDR_DATA_TABLE_ENTRY. MSDN has more information.

/* Direct member of the PEB */
typedef struct _PEB_LDR_DATA {
 BYTE       Reserved1[8];
 PVOID      Reserved2[3];
 LIST_ENTRY InMemoryOrderModuleList;
} PEB_LDR_DATA, *PPEB_LDR_DATA;

/* Already defined in wint.h (1049) */
/*
typedef struct _LIST_ENTRY {
struct _LIST_ENTRY *Flink;
struct _LIST_ENTRY *Blink;
} LIST_ENTRY, *PLIST_ENTRY, *RESTRICTED_POINTER PRLIST_ENTRY;
*/

/* Not used by us, but you need it to have a complete PEB structure */
typedef struct _RTL_USER_PROCESS_PARAMETERS {
 BYTE           Reserved1[16];
 PVOID          Reserved2[10];
 UNICODE_STRING ImagePathName;
 UNICODE_STRING CommandLine;
} RTL_USER_PROCESS_PARAMETERS, *PRTL_USER_PROCESS_PARAMETERS;

/* The actual module structure which is a part of the intrusive list */
typedef struct _LDR_DATA_TABLE_ENTRY {
 PVOID Reserved1[2];
 LIST_ENTRY InMemoryOrderLinks;
 PVOID Reserved2[2];
 PVOID DllBase;
 PVOID EntryPoint;
 PVOID Reserved3;
 UNICODE_STRING FullDllName;
 BYTE Reserved4[8];
 PVOID Reserved5[3];
 union {
  ULONG CheckSum;
  PVOID Reserved6;
 };
 ULONG TimeDateStamp;
} LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;

typedef void(__stdcall * PPS_POST_PROCESS_INIT_ROUTINE)(void);

typedef struct _PEB32 {
 BYTE                          Reserved1[2];
 BYTE                          BeingDebugged;
 BYTE                          Reserved2[1];
 PVOID                         Reserved3[2];
 PPEB_LDR_DATA                 Ldr;
 PRTL_USER_PROCESS_PARAMETERS  ProcessParameters;
 BYTE                          Reserved4[104];
 PVOID                         Reserved5[52];
 PPS_POST_PROCESS_INIT_ROUTINE PostProcessInitRoutine;
 BYTE                          Reserved6[128];
 PVOID                         Reserved7[1];
 ULONG                         SessionId;
} PEB32, *PPEB32;
Because the list is intrusive, you have to iterate over the LIST_ENTRY list and cast the pointer to PLDR_DATA_TABLE_ENTRY each time. Also, the list is a circular list, so make sure you compare the current pointer against a reference point.
LDR_DATA_TABLE_ENTRY is the structure which contains information about a loaded module, for example kernel32.dll. We can now finally write a function which gives us the base address of a library.

//Look up base address for an module (for example "kernel32.dll")
void* slGetModuleBase(LPCWSTR moduleName){
 //Get Pointer to PEB structure
 void* pebPtr = slGetPEB(); 
 PPEB32 peb = (PPEB32)pebPtr;
 //Reference point / tail to compare against, since the list is circular
 PLIST_ENTRY moduleListTail = &peb->Ldr->InMemoryOrderModuleList;
 PLIST_ENTRY moduleList = moduleListTail->Flink;
 //Traverse the list until moduleList gets back to moduleListTail
 do {
  char* modulePtrWithOffset = (char*)moduleList;
  //List is intrusive, a part of a larger LDR_DATA_TABLE structure,
  //so cast the pointer
  PLDR_DATA_TABLE_ENTRY module = (PLDR_DATA_TABLE_ENTRY)modulePtrWithOffset;
  //Compare the name of the entry against our parameter name
  //Note that the name is a wide string
  if (!slWStrCompare(module->FullDllName.Buffer, moduleName)){
   //The actual position of the image base address inside
   //the LDR_DATA_TABLE_ENTRY seems to change *a lot*.
   //Apparently on Windows 8.1 it wasn't located in the
   //correct place according to my structures defined above.
   //It should have been "DllBase", but apparently it
   //was 8 bytes back, inside Reserved2[0]
   void* DllBase = module->Reserved2[0];
   return DllBase;
  }
  moduleList = moduleList->Flink;
 } while (moduleList != moduleListTail);
 return NULL;
}

//Now we can get the base address like so:
int main(void){
 void* kernel32Base = slGetModuleBase(L"KERNEL32.DLL");
 /* ... */
 return 0;
}
This post became longer than I expected, which is why I split it up. In part 2 I'll show you how to get the address of a function from the DLLs export table, by parsing the image DOS and PE headers from the beginning of the base address. Part 2 is here.

Subscribe to RSS feed