Windows API Hashing
Windows API Hashing is a technique used to dynamically resolve function addresses without storing their names as plain strings. Why? Because doing so allows attackers to avoid detection by antivirus or static analysis tools, since API names like LoadLibraryA
do not appear as plain text in the binary. It's also used for obfuscation, hiding the actual API calls being used. When trying to evade detection, avoiding hardcoded addresses and strings is crucial. This technique also hides functions from the Import Address Table (IAT), making analysis even harder.
Let's see an example without using API hashing. We’ll create a simple app that just allocates memory. After compiling, we can see the functions being used — for example, VirtualAlloc
and VirtualFree
— and nothing is hidden. These functions are visible in the import table.


Real-World Usage: REvil Ransomware
This technique isn't theoretical. It’s been used in the wild by threats like REvil (aka Sodinokibi), a notorious ransomware family.
Instead of calling APIs directly, REvil loops through all exported functions in modules like kernel32.dll
, hashes each name, and compares it against a precomputed list of hash values. When it finds a match (e.g., for VirtualAlloc
), it grabs the function’s address and calls it—completely bypassing the IAT and string detection.
This technique has been observed in many malware families and is one of the reasons analysis gets complicated fast.
You can read a deeper technical breakdown here:API Hashing – Why and How
How it work?
1. Hashing Algorithm
A custom hash function is used to convert API names like "VirtualAlloc"
or "LoadLibraryA"
into a unique integer. This function is simple and fast to allow runtime use.
2. Precomputed Hash
The hash of the target API name is computed ahead of time and hardcoded in the binary. The original function name is not stored, which avoids detection via string scanning.
3. Accessing PEB
The code directly accesses the Process Environment Block (PEB), which contains information about all loaded modules for the current process, including base addresses.
4. Traversing Modules
It loops through each module (DLL) listed in the PEB, such as kernel32.dll
, ntdll.dll
, or user32.dll
.
5. Parsing Export Table
For each module, it locates the Export Table (found via the module’s PE header). This table lists function names and their relative addresses.
6. Hash Matching
Each exported function name is hashed using the same algorithm, and the result is compared to the precomputed target hash.
7. Resolve Address
If a match is found, the corresponding function's address is resolved and stored for future use. The program then calls the function directly through the resolved pointer.
Code
get the full code here: https://github.com/mohe22/API-hashing
1#include "HashResolver.h"
2#include <windows.h>
3#include <iostream>
4
start with including the header and windows header file.
1
2// Typedef for LoadLibraryA
3typedef HMODULE(WINAPI *pLoadLibraryA_t)(LPCSTR);
4
5// Custom hash function for obfuscation
6DWORD CalculateHash(const char *functionName)
7{
8 DWORD hash = 0x35;
9 while (*functionName)
10 {
11 hash = (hash * 0xAB10F29F) + (*functionName);
12 hash &= 0xFFFFFF;
13 functionName++;
14 }
15 return hash;
16}
17
The typedef
defines a function pointer type for LoadLibraryA
. we will see why.
CalculateHash
takes a function name string and turns it into a 24-bit hash using multiplication and addition.The hash helps identify functions without using their real names, commonly used to evade detection or obfuscate APIs.
1pLoadLibraryA_t ResolveLoadLibraryA()
2{
3 HMODULE hKernel32 = GetModuleHandleA("kernel32.dll");
4 if (!hKernel32)
5 return nullptr;
6
7 auto dosHeader = (PIMAGE_DOS_HEADER)hKernel32;
8 auto ntHeaders = (PIMAGE_NT_HEADERS)((BYTE *)hKernel32 + dosHeader->e_lfanew);
9 DWORD exportDirRVA = ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
10 if (!exportDirRVA)
11 return nullptr;
12
13 auto exportDir = (PIMAGE_EXPORT_DIRECTORY)((BYTE *)hKernel32 + exportDirRVA);
14 DWORD *namesRVA = (DWORD *)((BYTE *)hKernel32 + exportDir->AddressOfNames);
15 WORD *ordinals = (WORD *)((BYTE *)hKernel32 + exportDir->AddressOfNameOrdinals);
16 DWORD *functions = (DWORD *)((BYTE *)hKernel32 + exportDir->AddressOfFunctions);
17
18 for (DWORD i = 0; i < exportDir->NumberOfNames; i++)
19 {
20 const char *funcName = (const char *)((BYTE *)hKernel32 + namesRVA[i]);
21 if (CalculateHash(funcName) == 6943297)
22 { // Precomputed LoadLibraryA hash
23 WORD ordinal = ordinals[i];
24 DWORD funcRVA = functions[ordinal];
25 return (pLoadLibraryA_t)((BYTE *)hKernel32 + funcRVA);
26 }
27 }
28
29 return nullptr;
30}
31
This function manually locates LoadLibraryA
by reading kernel32.dll
's export table and hashing each function name to find a match for 6943297
(the hash of "LoadLibraryA"
). If found, it returns the address as a function pointer; otherwise, it returns nullptr
.
We do this because later, LoadLibraryA
is needed inside ResolveFunctionByHash
, but calling it directly inside that function would require resolving it first — leading to a recursive call if not handled. By resolving LoadLibraryA
separately in its own function (ResolveLoadLibraryA
), we break the recursion and ensure it’s available beforehand.
1static pLoadLibraryA_t pLoadLibraryA = ResolveLoadLibraryA();
2
3// Function to resolve any function by hash
4FARPROC ResolveFunctionByHash(const char *moduleName, DWORD targetHash)
5{
6 HMODULE hModule = GetModuleHandleA(moduleName);
7 if (!hModule && pLoadLibraryA)
8 {
9 hModule = pLoadLibraryA(moduleName);
10 if (!hModule)
11 {
12 std::cout << "[-] Failed to load module: " << moduleName << std::endl;
13 return nullptr;
14 }
15 }
16
17 if (!hModule)
18 return nullptr;
19
20 auto dosHeader = (PIMAGE_DOS_HEADER)hModule;
21 auto ntHeaders = (PIMAGE_NT_HEADERS)((BYTE *)hModule + dosHeader->e_lfanew);
22 DWORD exportDirRVA = ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
23 if (!exportDirRVA)
24 return nullptr;
25
26 auto exportDir = (PIMAGE_EXPORT_DIRECTORY)((BYTE *)hModule + exportDirRVA);
27 DWORD *namesRVA = (DWORD *)((BYTE *)hModule + exportDir->AddressOfNames);
28 WORD *ordinals = (WORD *)((BYTE *)hModule + exportDir->AddressOfNameOrdinals);
29 DWORD *functions = (DWORD *)((BYTE *)hModule + exportDir->AddressOfFunctions);
30
31 for (DWORD i = 0; i < exportDir->NumberOfNames; i++)
32 {
33 const char *funcName = (const char *)((BYTE *)hModule + namesRVA[i]);
34 if (CalculateHash(funcName) == targetHash)
35 {
36 WORD ordinal = ordinals[i];
37 DWORD funcRVA = functions[ordinal];
38 return (FARPROC)((BYTE *)hModule + funcRVA);
39 }
40 }
41
42 return nullptr;
43}
44
The ResolveFunctionByHash
function locates a target API by comparing the hash of each exported function name in a given DLL. It first tries to get the module's base address using GetModuleHandleA
. If the module isn't loaded, it falls back to LoadLibraryA
(which was resolved earlier and stored in pLoadLibraryA
) to load it manually. This ensures the module is in memory before parsing its export table. The function then hashes each export name and compares it to the target hash. If a match is found, it returns the function's address. Resolving LoadLibraryA
separately prevents recursion and avoids using direct API imports.
How to use it in your code?
Simply include HashResolver.h
in your file to get access to the CalculateHash
and ResolveFunctionByHash
functions.
To calculate the hash of an API, use the CalculateHash("FunctionName")
call. For example, CalculateHash("LoadLibraryA")
returns the hash for that function name.
Next, get the function signature. Just Google the API name (e.g., "VirtualAlloc site:learn.microsoft.com") and open the first Microsoft Docs link. Copy the function prototype and convert it into a function pointer type like this:
1typedef LPVOID (WINAPI *pVirtualAlloc_t)(LPVOID, SIZE_T, DWORD, DWORD);
2
Finally, resolve the function using:
1pVirtualAlloc_t pVirtualAlloc = (pVirtualAlloc_t)ResolveFunctionByHash("kernel32.dll", /*your hash*/);
2
This way, you can dynamically call Windows APIs using their hashed names without importing them statically.
Conclusion
Windows API hashing is a stealthy technique used to evade detection and complicate analysis. It’s commonly seen in advanced malware where hiding API calls is key to avoiding static scans and reverse engineering.