API Hooking

#Maldev #Easy 🟢

|

Disclaimer :
This post is presented for educational purposes only. I do not take responsibility for any consequences or sanctions resulting from the use of the techniques described here.

Today, we are going to talk about a technique used in malware development called API hooking. It is a widely used technique because it allows the behavior of a legitimate API to be modified by redirecting its execution to another custom function.
Although API hooking is popular in malware development, it's also employed in legitimate contexts (e.g., debugging, monitoring, or extending functionalities)
There are different methods of hooking, such as inline hooking, IAT hooking, and EAT hooking, etc. For this post, we will talk about inline hooking !
Presentation :
During inline hooking, a jump instruction to the custom function is created by overwriting the legitimate function. As a result, the legitimate function is hijacked to directly execute the custom code.
For this technique, we will not use libraries such as Detours or Minhook or API's such as  SetWindowsHookEx because they are well known to security solutions and will therefore be flagged by them, even if the code is legitimate. Instead of that, we are going to manually create our own inline hooking code, which will be less likely to be flagged.
Exploitation Part
First, we will initialize the hook on the target API. To do this, we need the address of the legitimate function, the address of our malicious function that will be executed, and a structure for our hook. The structure is shown below:
This structure will allow us to store all the variables we need to create our functions for inline hooking. Note the declaration of the pOriginalBytes variable, which will be used to retrieve the original function once the inline hooking is complete.
After that, we can initialize our hook on the target function. To do this, we will populate our structure with the necessary information, and we must also ensure that write permissions are set on the target function. Since VirtualProtect API is most likely to be detected by EDR and antirviruses, you should create manually a function that do the same thing. You can see the initialize function bellow :
We will now place our trampoline (the jump instruction to our malicious function) in the legitimate function. To do this, we will dynamically retrieve the address of our function, cast it to the uint64_t type so that we can write the address into our shellcode, which will then be written into the legitimate function. Here is the code below:
You can note the first two bytes, 0x48 0xBA, at the beginning, which define the instruction "mov rdx, imm64", where imm64 is the 64 bits address of our malicious function. As for the last two bytes, 0xFF 0xE2, they define the instruction "jmp rdx", which allows jumping to the memory address stored in the rdx register.
From now on, every time we call our legitimate function, such as the Windows API MessageBoxA, our malicious function will execute in its place. However, we can still use the Windows API with the Unicode version if the sacrificed API is in ANSI format.
If we still want to retrieve the original legitimate function, we can simply copy the original bytes of the function back to the place where they were overwritten the first time. Here’s the code below:
When removing the hook, we can notice tha't the function works again.
Now, let's take a look at what happens at the assembly level to better understand what is going on at the lowest level possible.

In this image, we can see the basic assembly instructions for the legitimate GetMessageBox API before hooking it.

Let's now run the code and set a breakpoint after having placed our hook on the function.

After hooking our function, we can see that the two instructions which overwrite the beginning of the function are indeed present. As a result, the rest of the function is not executed because a jump instruction is performed to execute our malicious function.

After unhooking the API, we can see that we have successfully restored the original legitimate function. Here is the final code :

#include <windows.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#define TRAMPOLINE_SIZE		13



typedef struct HookStructure {

	PVOID	pFunctionToHook;                 
	PVOID	pMyExploitFunction;
	BYTE	pOriginalBytes[TRAMPOLINE_SIZE];
	DWORD	dwOldProtection;

}HookStructure, *PHookStructure;



BOOL InstallHookOnRemoteFunction(IN PHookStructure Hook) {


	uint8_t	uTrampoline[] = {
			0x48, 0xBA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // mov rdx, pMyExploitFunction
			0xFF, 0xE2													// jmp rdx
	};

	uint64_t uPatch = (uint64_t)(Hook->pMyExploitFunction);
	memcpy(&uTrampoline[2], &uPatch, sizeof(uPatch));

	// Placing the trampoline function
	memcpy(Hook->pFunctionToHook, uTrampoline, sizeof(uTrampoline));

	return TRUE;
}


BOOL RemoveHookFromRemoteFunction(IN PHookStructure Hook) {

	DWORD	dwOldProtection = NULL;

	// Copying the original bytes over
	memcpy(Hook->pFunctionToHook, Hook->pOriginalBytes, TRAMPOLINE_SIZE);
	// Cleaning up the buffer
	memset(Hook->pOriginalBytes, '\0', TRAMPOLINE_SIZE);
	// Setting the old memory protection
	if (!VirtualProtect(Hook->pFunctionToHook, TRAMPOLINE_SIZE, Hook->dwOldProtection, &dwOldProtection)) {
		printf("[!] VirtualProtect Failed With Error : %d \n", GetLastError());
		return FALSE;
	}

	// Setting all to null
	Hook->pFunctionToHook = NULL;
	Hook->pMyExploitFunction = NULL;
	Hook->dwOldProtection = NULL;

	return TRUE;
}



BOOL InitializeHookOnRemoteFunction(IN PVOID pFunctionToHook, IN PVOID pFunctiontoExec, OUT PHookStructure Hook) {

	Hook->pFunctionToHook = pFunctionToHook;
	Hook->pMyExploitFunction = pFunctiontoExec;

	memcpy(Hook->pOriginalBytes, pFunctionToHook, TRAMPOLINE_SIZE);

	// Make the destinate API writable
	if (!VirtualProtect(pFunctionToHook, TRAMPOLINE_SIZE, PAGE_EXECUTE_READWRITE, &Hook->dwOldProtection)) {
		printf("[!] VirtualProtect Failed With Error to make remote function writable: %d \n", GetLastError());
		return FALSE;
	}

	return TRUE;
}

BOOL MyExploitFunction() {
	printf("[+] My own function is working right now !\n[+] PRESS ENTER:\n");
	getchar();
	MessageBoxW(NULL, L"I can also make a message box with Unicode version :)\n", L"Exploited Function", MB_OK);
	return TRUE;
}



BOOL main() {
		
	HookStructure MyHookStructure = { 0 };
	printf("[+] Initializing Hook Structure at 0x%p\n[i] PRESS ENTER TO CONTINUE\n", &MyHookStructure);
	getchar();
	if (!InitializeHookOnRemoteFunction(&MessageBoxA, &MyExploitFunction, &MyHookStructure)) {
		return FALSE;
	}
	printf("[+] Overwriting Remote function at 0x%p\n[i] PRESS ENTER TO CONTINUE", MyHookStructure.pFunctionToHook);
	getchar();
	if (!InstallHookOnRemoteFunction(&MyHookStructure)) {
		return FALSE;
	}
	printf("[+] The function has been overwritten !\n");
	MessageBoxA(NULL, "The API won't work because it is hooked", "Hooked function", MB_OK);

	printf("[+] Removing the hook from the remote function\n[i] PRESS ENTER TO CONTINUE\n");
	getchar();
	if (!RemoveHookFromRemoteFunction(&MyHookStructure)) {
		return FALSE;
	}
	printf("[+] The Hook has been removed ! Let's try to use the legitimite function again !");
	MessageBoxA(NULL, "The legitimate function work again !!", "Hooked function", MB_OK);

	return TRUE;
}

Feel free to share your thoughts or ask questions about these techniques. Stay ethical and keep learning !