IAT Hiding - Custom GetProcAddress
What is it?
A custom GetProcAddress replacement that resolves function addresses by walking a DLL’s PE export table manually. Used to call Windows APIs without importing them — no IAT entry, no plaintext function name string, just a pointer you resolve yourself at runtime.
How it works
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
CustomGetProcAddress(hModule, "NtAllocateVirtualMemory"):
pBase = (PBYTE)hModule
Parse headers:
pImgheader = (PIMAGE_DOS_HEADER)pBase
e_magic == IMAGE_DOS_SIGNATURE? ("MZ")
pImgNTheader = pBase + pImgheader->e_lfanew
Signature == IMAGE_NT_SIGNATURE? ("PE\0\0")
pImgOptheader = &pImgNTheader->OptionalHeader
DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress != 0?
Get export table:
pImgExportDir = pBase + ExportDir.VirtualAddress
Three parallel arrays in the export directory:
FuncNameArray = pBase + pImgExportDir->AddressOfNames
→ array of RVAs to function name strings
FuncAddressArray = pBase + pImgExportDir->AddressOfFunctions
→ array of RVAs to function code
FuncOrdinalArray = pBase + pImgExportDir->AddressOfNameOrdinals
→ maps name index → function address index
Walk NumberOfNames entries:
pFuncName = (CHAR*)(pBase + FuncNameArray[i])
pFuncAddress = (PVOID)(pBase + FuncAddressArray[FuncOrdinalArray[i]])
strcmp(lpApiName, pFuncName) == 0?
→ print: index, name, address array ptr, ordinal
→ return pFuncAddress
Verification in main():
printf("Original GetProcAddress: 0x%p",
GetProcAddress(GetModuleHandleA("NTDLL.DLL"), "NtAllocateVirtualMemory"))
printf("Replacement: 0x%p",
CustomGetProcAddress(GetModuleHandleA("NTDLL.DLL"), "NtAllocateVirtualMemory"))
→ Both should print the same address
The three-array lookup is the standard PE export resolution pattern. The indirection through OrdinalArray exists because exports can be added or removed without renumbering — the ordinal array maps a name’s index in the sorted name table to its position in the (potentially sparse) function address array.
Note: FuncOrdinalArray is declared as PDWORD (cast from the PWORD it should be) — this is a bug in the original code. AddressOfNameOrdinals is an array of WORDs (2 bytes each), not DWORDs (4 bytes). In practice on x64 this often works due to alignment, but it’s technically reading the wrong width. The correct declaration is PWORD FuncOrdinalArray = (PWORD)(pBase + pImgExportDir->AddressOfNameOrdinals).
This custom resolver is the foundation for static API hashing — replace the strcmp with a hash comparison and you get GetProcessAddrHash from the StaticAPI-Hashing post.
Custom-GetProcAddress.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#include <Windows.h>
#include <stdio.h>
#include <winternl.h>
FARPROC CustomGetProcAddress(_In_ HMODULE hModule, _In_ LPCSTR lpApiName) {
PBYTE pBase = (PBYTE)hModule;
// Getting DOS Header
PIMAGE_DOS_HEADER pImgheader = (PIMAGE_DOS_HEADER)pBase;
if (pImgheader->e_magic != IMAGE_DOS_SIGNATURE)
return NULL;
// Gettting NT Header
PIMAGE_NT_HEADERS pImgNTheader = (PIMAGE_NT_HEADERS)(pBase + pImgheader->e_lfanew);
if (pImgNTheader->Signature != IMAGE_NT_SIGNATURE)
return NULL;
// Getting Optional Header
PIMAGE_OPTIONAL_HEADER pImgOptheader = &pImgNTheader->OptionalHeader;
if (!pImgOptheader->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress)
return NULL;
// Getting Image Export Table
PIMAGE_EXPORT_DIRECTORY pImgExportDir = (PIMAGE_EXPORT_DIRECTORY)(pBase + pImgOptheader->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
// Getting function's array pointers
PDWORD FuncNameArray = (PDWORD)(pBase + pImgExportDir->AddressOfNames);
// Getting functions address array pointer
PDWORD FuncAddressArray = (PDWORD)(pBase + pImgExportDir->AddressOfFunctions);
// Getting ordinal array pointer
PWORD FuncOrdinalArray = (PDWORD)(pBase + pImgExportDir->AddressOfNameOrdinals);
// Find correct function
for (DWORD i = 0; i < pImgExportDir->NumberOfNames; i++) {
// Getting function name
CHAR* pFuncName = (CHAR*)(pBase + FuncNameArray[i]);
// Getting Address of function through ordinal
PVOID pFuncAddress = (PVOID)(pBase + FuncAddressArray[FuncOrdinalArray[i]]);
// compare provided functionname with the export
if (strcmp(lpApiName, pFuncName) == 0) {
printf("[ %0.4d ] NAME: %s -\t ADDRESS: 0x%p -\t ORDINAL: %d \n", i, pFuncName, FuncAddressArray, FuncOrdinalArray[i]);
return pFuncAddress;
}
}
return NULL;
}
int main() {
printf("[+] Original GetProcAddress 0x%p \n", GetProcAddress(GetModuleHandleA("NTDLL.DLL"), "NtAllocateVirtualMemory"));
printf("[+] GetProcAddress Replacement : 0x%p \n", CustomGetProcAddress(GetModuleHandleA("NTDLL.DLL"), "NtAllocateVirtualMemory"));
return 0;
}