This lab is a quick look into how userland WinAPIs can be hooked. A MessageBoxA
function will be hooked in this instance, but it could be any.
API hooking is a technique by which we can instrument and modify the behavior and flow of APIcalls.
https://resources.infosecinstitute.com/api-hooking/
Windows API hooking is one of the techniques used by AV/EDR solutions to determine if code is malicious. You can read some of my notes on bypassing EDRs by leveraging unhooking - Bypassing Cylance and other AVs/EDRs by Unhooking Windows APIs
For this lab, I will write a simple C++ program that will work follows:
- Get memory address of the
MessageBoxA
function - Read the first 6 bytes of the
MessageBoxA
- will need these bytes for unhooking the function - Create a
HookedMessageBox
function that will be executed when the originalMessageBoxA
is called - Get memory address of the
HookedMessageBox
- Patch / redirect
MessageBoxA
toHookedMessageBox
- Call
MessageBoxA
. Code gets redirected toHookedMessageBox
HookedMessageBox
executes its code, prints the supplied arguments, unhooks theMessageBoxA
and transfers the code control to the actualMessageBoxA
Pop the message box before the function is hooked - just to make sure it works and to prove that no functions are hooked so far - it's the first instruction of the program:
Get the memory address of the MessageBoxA
function:
If we dissasemble the bytes at that address, we can definitely see that there is code for MessageBoxA
:
Note the first 6 bytes 8b ff 55 8b ec 6a
(mind the endian-ness). We need to save these bytes for future when we want to unhook MessageBoxA
:
Let's now build the patch (hook) bytes:\
...that will translate into the following assembly instructions:
// push HookedMessageBox memory address onto the stack
push HookedMessageBox
// jump to HookedMessageBox
ret
We can now patch the MessageBoxA
- memory pane in the bottom right shows the patch being written to the beginning of MessageBoxA
function and the top right shows the beginning of the same function is re-written with a push 3e1474h; ret
instructions:
If we disassemble the address 3e1474h
, we can see it contains a jmp to our HookedMessageBox
:
The HookedMessageBox
intercepts and prints out the arguments supplied to MessageBoxA
, then unhooks by swaping back the first 6 bytes to the original bytes of the MessageBoxA
MessageBoxA
function and then calls the MessageBoxA
with the supplied arguments:
Once the function is hooked, we can call the MessageBoxA(NULL, "hi", "hi", MB_OK);
which will invoke the HookedMessageBox
, print the intercepted values and display the original message box:
{% code title="api-hooking.cpp" %}
#include "pch.h"
#include <iostream>
#include <Windows.h>
FARPROC messageBoxAddress = NULL;
SIZE_T bytesWritten = 0;
char messageBoxOriginalBytes[6] = {};
int __stdcall HookedMessageBox(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType) {
// print intercepted values from the MessageBoxA function
std::cout << "Ohai from the hooked function\n";
std::cout << "Text: " << (LPCSTR)lpText << "\nCaption: " << (LPCSTR)lpCaption << std::endl;
// unpatch MessageBoxA
WriteProcessMemory(GetCurrentProcess(), (LPVOID)messageBoxAddress, messageBoxOriginalBytes, sizeof(messageBoxOriginalBytes), &bytesWritten);
// call the original MessageBoxA
return MessageBoxA(NULL, lpText, lpCaption, uType);
}
int main()
{
// show messagebox before hooking
MessageBoxA(NULL, "hi", "hi", MB_OK);
HINSTANCE library = LoadLibraryA("user32.dll");
SIZE_T bytesRead = 0;
// get address of the MessageBox function in memory
messageBoxAddress = GetProcAddress(library, "MessageBoxA");
// save the first 6 bytes of the original MessageBoxA function - will need for unhooking
ReadProcessMemory(GetCurrentProcess(), messageBoxAddress, messageBoxOriginalBytes, 6, &bytesRead);
// create a patch "push <address of new MessageBoxA); ret"
void *hookedMessageBoxAddress = &HookedMessageBox;
char patch[6] = { 0 };
memcpy_s(patch, 1, "\x68", 1);
memcpy_s(patch + 1, 4, &hookedMessageBoxAddress, 4);
memcpy_s(patch + 5, 1, "\xC3", 1);
// patch the MessageBoxA
WriteProcessMemory(GetCurrentProcess(), (LPVOID)messageBoxAddress, patch, sizeof(patch), &bytesWritten);
// show messagebox after hooking
MessageBoxA(NULL, "hi", "hi", MB_OK);
return 0;
}
{% endcode %}
{% embed url="https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-messageboxa" %}