How Injected Code Finds Exported Functions

By Gal Badishi | September 13, 2012

Sometimes, you find yourself writing code that’s going to get injected into another process. Assuming we’re not talking about DLL injection, where everything is nice and clean, your code needs to operate in an unfamiliar environment. This may happen if you’re writing a payload for an exploit, if you’re using WriteProcessMemory to inject your code, or even if you’re writing a sophisticated packer. Your code will probably need to find the memory addresses of some Windows API functions to execute properly. How can it do that?

The simplest solution is to hardcode the functions’ addresses in the call/jmp statements in your code. This technique was prominent in early exploitation of Windows XP systems, and is still viable today on Windows XP. There are two problems with hardcoded addresses:

  1. Every patch applied to the system might change some functions’ addresses. Thus, the hardcoded address is relevant only for a particular patch level (or a set thereof).
  2. On systems employing ASLR (e.g., Windows 7), hardcoded addresses simply won’t work.

A better solution is to use a combination of LoadLibrary and GetProcAddress to dynamically locate the address of the function we want to import. However, even that requires us to have the addresses of LoadLibrary and GetProcAddress, so we need to find a way of doing so. Of course, if we develop a way of finding addresses of API functions, we’re not restricted to using it only for LoadLibrary and GetProcAddress.

Fortunately, we can use the PEB (accessible through the TIB) to find the list of loaded modules through the PEB_LDR_DATA structure. The official documentation for PEB_LDR_DATA is missing some information, so to get the complete picture you should also read the unofficial documentation. The PEB_LDR_DATA struct contains three doubly-linked lists, InLoadOrder, InMemOrder, and InInitOrder, each of them pointing to a struct we call LDR_MODULE. This latter struct contains information on the loaded module, including its name and base address.

Some things to note:

  • The order of the modules in each list might not be the same (otherwise, there’s no need for three lists). The first module in InInitOrder is ntdll.dll, so in our case it’s safe to skip it.
  • Each pointer in the list points to a different offset in LDR_MODULE (the offset of the corresponding LIST_ENTRY for that list). Therefore, offsets in LDR_MODULE shouldn’t be calculated relative to LDR_MODULE’s starting address, but rather relative to the appropriate LIST_ENTRY struct’s starting address.
  • Unicode strings are composed of 8 bytes: 2 bytes for the string’s length, 2 bytes for the string’s maximum length, and 4 bytes for the address of the actual string.

Here’s an illustration of the important relationships we talked about:

Since virtually every program loads kernel32.dll, and kernel32.dll exports both LoadLibrary and GetProcAddress, we can simply walk one of the modules’ lists, find kernel32.dll’s base address, and follow the PE structure to go to the export table and get the address of, say, GetProcAddress. Here’s some code that does just that (note that it’s not optimized):