✧ ✧ ✧
What are Windows APIs?
APIs (Application Programming Interfaces) are ready-made functions that Windows provides for programs. For example, if you want to open a window, you don't need to program from scratch how to draw pixels on the screen. You simply call the CreateWindow API that Windows already provides ready-to-use.
How Do We Traditionally Use APIs?
When you write a program in C++, C# or Python and want to use a Windows API, you typically do it like this:
#include <windows.h>
MessageBox(NULL, "Daniel is beautiful", "Cool Title", 0);
This is called static import. "Static" means it happens at compile time. When you compile your program using static import, the compiler creates an import table (IAT - Import Address Table) inside the executable.
What is IAT?
The IAT is like an index at the beginning of a book. It lists all the APIs that your program will use. It's literally a list that says:
My program uses:
- MessageBox from user32.dll
- CreateFile from kernel32.dll
- RegOpenKey from advapi32.dll
Why is this a problem?
Antivirus and security tools read your program's IAT before it even executes. They perform static analysis, meaning they analyze the file while it's sitting on disk. If the security tool sees in the IAT:
- VirtualAlloc (allocates memory)
- WriteProcessMemory (writes to another process)
- CreateRemoteThread (creates remote thread)
It thinks: "This program has code injection behavior" and blocks it.
Beyond the IAT, the problem includes:
- Plain text strings: "cmd.exe", "powershell.exe" are visible in the file
- Function names: appear literally in the compiled code
- Arguments: passed parameters are readable
D/Invoke
D/Invoke or Dynamic Invoke, completely changes the approach. Instead of declaring in the code "I'm going to use function X from DLL Y", you:
- At runtime (program already running), ask Windows: "where is the user32 DLL?"
- Then ask: "inside this DLL, where is the MessageBox function?"
- Receive a memory address (pointer)
- Execute the function through that address
The advantage is your executable's IAT is practically empty. Static analysis tools look and see "this program doesn't import anything suspicious" because the imports happen dynamically afterwards.
Dynamic Invocation
Dynamic Invocation is the fundamental technique that D/Invoke uses. Let's break it down into three mandatory steps.
1. Obtain the DLL Handle.
Handle is an identifier number that Windows uses to reference objects. Think of the handle as a protocol number. When you open a support ticket, you receive a number like "Protocol #12345". That number isn't the ticket itself, but allows Windows to locate the ticket. For DLLs, the handle is the "protocol number" that identifies where the DLL is loaded in memory.
What is a DLL?
DLL (Dynamic Link Library) is a file that contains ready-made functions. It's like a physical library: inside it has several books (functions). To get a specific book, you first need to know where the library is (the DLL handle). Examples of important DLLs:
- kernel32.dll
- user32.dll
- advapi32.dll
- ntdll.dll
Option A: GetModuleHandle
HMODULE hModule = GetModuleHandleA("user32.dll");
Option B: LoadLibrary
HMODULE hModule = LoadLibraryA("user32.dll");
What is HMODULE? HMODULE is a type defined by Windows that represents a module handle (DLL). Internally it's just a pointer (memory address), but Windows uses a typedef to make the code more readable:
typedef HANDLE HMODULE; // Windows definition
2. Obtain the function address.
Now that we have the DLL handle (we know where the library is), we need to find a specific function inside it. Let's use the GetProcAddress API:
FARPROC pFunction = GetProcAddress(hModule, "MessageBoxA");
Every DLL has an internal structure called Export Table. It's like a book's index:
user32.dll Export Table:
- MessageBoxA: address 0x7FFA1234
- MessageBoxW: address 0x7FFA1238
- CreateWindowA: address 0x7FFA1240
GetProcAddress reads the DLL's Export Table, searches for the name "MessageBoxA", finds the corresponding address, and returns that address. So here's a complete example so far:
HMODULE hUser32 = LoadLibraryA("user32.dll");
if (hUser32 == NULL) {
printf("Error loading DLL\n");
return -1;
}
FARPROC pMessageBox = GetProcAddress(hUser32, "MessageBoxA");
if (pMessageBox == NULL) {
printf("Error finding function\n");
return -1;
}
printf("MessageBoxA is at: 0x%p\n", pMessageBox);
Remember that here we're not executing MessageBoxA, we just have its address.
3. Execute the function.
Now we have a memory address (pointer) pointing to the function. But we can't simply call pMessageBox() because the compiler doesn't know the function's signature. Signature is the function's "format": what parameters it receives and what it returns. MessageBoxA has this signature:
int MessageBoxA(
HWND hWnd, // Parent window handle
LPCSTR lpText, // Message text
LPCSTR lpCaption, // Window title
UINT uType // Button/icon type
);
We need to inform the compiler: "this pointer points to a function with THIS specific signature". Using typedef to Define Signature:
typedef int (WINAPI* PMESSAGEBOX)(HWND, LPCSTR, LPCSTR, UINT);
Cast is type conversion. We're saying: "this generic address is, in fact, a MessageBox function".
PMESSAGEBOX pMessageBox = (PMESSAGEBOX)GetProcAddress(hUser32, "MessageBoxA");
pMessageBox(NULL, "Daniel is beautiful", "Cool Title", 0);
Is This Enough?
Even using the 3 steps above, we still have detection problems. When you compile the code above, the strings remain readable in the executable:
LoadLibraryA("user32.dll"); // "user32.dll" is visible
GetProcAddress(h, "MessageBoxA"); // "MessageBoxA" is visible
Problem 2: Known API Names. Your code still uses the real names: LoadLibraryA and GetProcAddress. Security tools look for exactly this pattern: "program that uses LoadLibrary + GetProcAddress is suspicious".
Problem 3: Import Table Still Has Traces. Even though MessageBoxA isn't in the IAT, LoadLibraryA and GetProcAddress still are.
How Can We Solve This?
To solve the problems above, we apply two additional techniques. We can encrypt our strings with XOR. So we have: MessageBoxW.
Convert to Hexadecimal:
M = 0x4D
e = 0x65
s = 0x73
s = 0x73
a = 0x61
g = 0x67
e = 0x65
B = 0x42
o = 0x6F
x = 0x78
W = 0x57
{ 0x4D, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x42, 0x6F, 0x78, 0x57 }
Set a Secret Key:
char mySecretKey[] = "gohacking";
g = 0x67
o = 0x6F
h = 0x68
a = 0x61
c = 0x63
k = 0x6B
i = 0x69
n = 0x6E
g = 0x67
Apply XOR Byte by Byte:
M (0x4D) XOR g (0x67) = 0x2A
e (0x65) XOR o (0x6F) = 0x0A
s (0x73) XOR h (0x68) = 0x1B
s (0x73) XOR a (0x61) = 0x12
a (0x61) XOR c (0x63) = 0x02
g (0x67) XOR k (0x6B) = 0x0C
e (0x65) XOR i (0x69) = 0x0C
B (0x42) XOR n (0x6E) = 0x2C
o (0x6F) XOR g (0x67) = 0x08
x (0x78) XOR g (0x67) = 0x1F
W (0x57) XOR o (0x6F) = 0x38
Encrypted Result:
unsigned char eMessageBoxW[] = {
0x2A, 0x0A, 0x1B, 0x12, 0x02, 0x0C, 0x0C, 0x2C, 0x08, 0x1F, 0x38
};
Implement XOR Function:
void XOR(char* data, size_t data_len, char* key, size_t key_len) {
int j = 0; // Key index
for (int i = 0; i < data_len; i++) {
if (j == key_len - 1) {
j = 0;
}
data[i] = data[i] ^ key[j];
j++;
}
}
How the function works: "j" controls which position of the key we're using. For each byte of the string, XOR with corresponding key byte. If key ends, restart from beginning (allows keys smaller than data). Modifies data in place.
Now let's use it in code:
char mySecretKey[] = "gohacking";
unsigned char eMessageBoxW[] = {
0x2A, 0x0A, 0x1B, 0x12, 0x02, 0x0C, 0x0C, 0x2C, 0x08, 0x1F, 0x38, 0x00
};
XOR((char*)eMessageBoxW, sizeof(eMessageBoxW) - 1, mySecretKey, sizeof(mySecretKey) - 1);
GetProcAddress(hModule, (char*)eMessageBoxW);
We have the advantage that if someone opens our executable in a hex editor, they'll see:
2A 0A 1B 12 02 0C 0C 2C 08 1F 38 // MessageBoxW with our super secret key.
Instead of:
4D 65 73 73 61 67 65 42 6F 78 57 // "MessageBoxW"
Of course, normally we'll take this super secret key and put it on our server, so it won't be directly in the code.
And that's how we can use D/Invoke and obfuscate code strings.
I Love God. Made by Daniel Andrade.
✧ Return