Resolving function addresses manually
Resolving function addresses manually
GetProcAddress is for suckers, resolve imported functions' addresses manually instead ;)
It is common for malware to dynamically resolve function addresses for a number of reasons. One elementary reason being to hide suspicious imports from the import table. This also bypasses IAT hooking.
The amateur approach for dynamically resolving function addresses is to use GetModuleHandle to get the base address of a module, and then GetProcAddress to find the address of a function within said module. However these two functions are quite suspicious, especially if they're the only ones showing up.
The big boys resolve function addresses by essentially implementing GetModuleHandle and GetProcAddress manually. This is extremely difficult to detect if done properly.
Getting the module base address
This is the first step, which we need to do, so that we can parse the PE file, which starts at the module's base address. Notice that the HMODULE you get from GetModuleHandle is exactly the same as a pointer to the base address.
We will perform what is called a PEB walk. What this means, is we will basically get a pointer from the PEB to a list of modules, then we will iterate this list until we find the module we want. I've made a graph to visualize it. I think it is very helpful in grasping what is happening.

1. Get a pointer to the PEB
You can find the PEB through the TEB which holds a pointer to it.
On 64-bit Windows the GS register holds a pointer to the TEB, and the PEB is at a 0x60 offset from it. On 32-bit Windows the FS register holds a pointer to the TEB and the PEB at a 0x30 offset from it.
In C you can use the __readgsqword() function to read the GS register, or assembly.
2. Get the list of modules
We can find 3 lists of modules in the PEB_LDR_DATA structure pointed to by the Ldr member of the PEB. Each list is ordered differently, as the names describe. We will be using the InMemoryOrderModuleList__. This is a doubly-linked list linking each LDR_DATA_TABLE_ENTRY representing a module. Flink will let you access the structure describing the first module, which should be the main module (the .exe).
3. Check if it is the correct module
Check if the BaseDllName is equal to the module we are looking for. Notice that this is not a normal string, but a UNICODE_STRING. If the name was the one we are looking for, return the address found in the DllBase field of LDR_DATA_TABLE_ENTRY. Otherwise move on to the next module with the Flink from the current LDR_DATA_TABLE_ENTRY
Notice that the Flink in the InMemoryOrderModuleList does not point to the beginning of LDR_DATA_TABLE_ENTRY. To access the members you must take into account the existing 0x10 offset in your pointer arithmetic, or to make it easier to read you might want to save the pointer to LDR_DATA_TABLE_ENTRY, which is Flink - 0x10 and skip the messy pointer arithmetic all together.
Notice that these structs are defined in winternl.h__, if you're using C/C++. Also, the first module in the InMemoryOrderModuleList will be the main module, the .exe.
Finding the function address
Now that we have the base address of the module, we can parse the PE headers to find the Export Address Table (EAT). All .exe and .dll files are Portable Executable (PE) files, they have the same structure.
The EAT holds addresses of each function that the module exports. So for example in the EAT of the kernel32.dll module, we will find addresses of every kernel32.dll function.
A pointer to the EAT can be found in the DataDirectory list, which resides in the OptionalHeader inside the NT headers. Below is a graph describing the structure of a PE file.
1. Get the NT headers
The relative address of the NT headers is found in the e_lfanew member of the DOS header. As you can see in the graph, the DOS header starts at the module base. So basically NT headers are at moduleBase + dosHeaders->e_lfanew.
It is good practice to verify the signature here, from ntHeaders->Signature. It should be equal to 0x4550
2. Get the export table
Get the PIMAGE_EXPORT_DIRECTORY from the DataDirectory list, residing in the OptionalHeader. This can be done in one line like so moduleBase + ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress
3. Iterate through the export table
Now there are 3 important lists, describing each function: AddressOfFunctions, AddressOfNames and AddressOfNameOrdinals.
First we will iterate through AddressOfNames until we find the desired function. This list does not actually hold the names, but instead a relative address to it, so the name is found at moduleBase + AddressOfNames[i]
4. Get the function address
Now that we have found the correct function, with this same index in AddressOfNameOrdinals, we will find the ordinal of the function, which is the index into AddressOfFunctions. This means, that the actual function address is found at moduleBase + AddressOfFunctions[AddressOfNameOrdinals[i]]. The AddressOfFunctions holds a relative address.
Notice, in my testing (in C), accessing AddressOfNameOrdinals and AddressOfFunctions like a normal array (as in array[i]) did not work, instead i had to manually access the indexes, like so:
WORD ordinal = ((WORD)((ULONG_PTR)AddressOfNameOrdinals + i*sizeof(WORD)));
DWORD fRVA = ((DWORD)((ULONG_PTR)AddressOfFunctions + ordinal*sizeof(DWORD)));
return (PVOID)((ULONG_PTR)moduleBase+fRVA);