Post

IAT Hiding - Custom GetProcAddress

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;
}
This post is licensed under CC BY 4.0 by the author.