API Inline Hooking

簡單來看,hook就是修改目標函數內容導致其執行流改變,跑去執行其他函數,而unhook就是把目標函數內容修改回來,讓其執行流恢復正常。
hook可好可壞,全看改變流程後執行的函數是做什麼事情。

inline hooking就是修改函數A,把A開頭的內容改成跳到B之類的指令,導致A被呼叫時,會先去執行B,然後B執行完功能後會unhook,把A開頭的內容恢復原先的樣子,再把流程給A執行其原本的功能,像B這樣的函數可稱作detour function。

跳轉指令如下。

PUSH B_addr
RET

MOV eax, B_addr
JMP eax

inline hooking關鍵在於B的參數與calling convention要和A一致,要讓B把A的參數當成自己的參數的樣子。
這個也很好理解,因為當實際執行到A的instruction時,等於已經進入A函數體,而在這之前參數早就已經擺好了,進入函數體A後,會執行hook的跳轉指令跳到B去執行,如果B的參數與calling convention與A不一致,可能會搞爆stack上擺放的參數,例如B清理stack時會多清或少清(假設calling convention規定由callee清理參數),導致之後程式會執行錯誤。

以下會hook MessageBoxA為例子,步驟大致是

  • 先取得要hook的MessageBoxA地址
  • 保存MessageBoxA內前幾個bytes,因為hook要修改這幾個bytes,而後面unhook時需要恢復
  • 取得hook function的地址
  • 把MessageBoxA內前幾個bytes修改成PUSH hook_func_addr RET兩條指令
  • hook function執行完功能後(還沒return),unhook恢復原先API前幾個bytes
  • return到MessageBoxA執行原先該執行的作業

在尚未hook前,原來MessageBoxA的前六個byte如下圖所示
before hook

hook後,MessageBoxA的前六個byte,被修改成push HookedMessageBox的地址和ret兩個指令,當執行MessageBoxA時,就會執行這兩個指令,從而改變flow,先去執行HookedMessageBox
after hook

可以看到PUSH的值就是HookedMessageBox的地址
Address of HookedMessageBox

當執行到HookedMessageBox中unhook的地方,unhook前,MessageBoxA的內容還是PUSH & RET
before unhook

unhook後,MessageBoxA又恢復成原來的樣子了,之後呼叫MessageBoxA都是正常流程了
after unhook

執行
api-hooking

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include <Windows.h>
#include <stdio.h>

FARPROC APIAddr = NULL;
BYTE OriginBytes[6] = {};
SIZE_T written;

int __stdcall HookedMessageBox(HWND hWnd, LPCSTR lptext, LPCSTR lptitle, UINT uType) {

printf("This is hook function\n");
printf("Origin text: %s\n", lptext);
printf("Origin title: %s\n", lptitle);

WriteProcessMemory(GetCurrentProcess(), (LPVOID)APIAddr, OriginBytes, sizeof(OriginBytes), &written);

return MessageBoxA(hWnd, lptext, lptitle, uType);
}

int main() {

BYTE patch[6] = {};
SIZE_T red;

// get address of MessageBoxA
APIAddr = GetProcAddress(LoadLibraryA("USER32.DLL"), "MessageBoxA");

// save origin bytes of MessageBoxA
ReadProcessMemory(GetCurrentProcess(), APIAddr, OriginBytes, sizeof(OriginBytes), &red);

// get address of HookedMessageBox
VOID* HookAddr = &HookedMessageBox;

/* patch content:
push HookedMessageBox
ret
*/
memcpy_s(patch, 1, "\x68", 1);
memcpy_s(patch + 1, 4, &HookAddr, 4);
memcpy_s(patch + 5, 1, "\xc3", 1);

// patch to beginning of MessageBoxA
WriteProcessMemory(GetCurrentProcess(), APIAddr, patch, sizeof(patch), &written);

MessageBoxA(NULL, "hello", "world", MB_OK);

return 0;
}

如果想在HookedMessageBox中正常呼叫MessageBoxA的話,就一定要先在HookedMessageBox中unhook,如果沒有先unhook就直接呼叫MessageBoxA,會導致無限遞迴。
MessageBoxA(因為PUSH & RET) -> HookedMessageBox -> MessageBoxA -> HookedMessageBox ->…..
例如我把line 14拿掉,就會這樣= =。
no-unhook

參考資料
https://www.ired.team/offensive-security/code-injection-process-injection/how-to-hook-windows-api-using-c++
https://labs.nettitude.com/blog/windows-inline-function-hooking/
https://guidedhacking.com/threads/how-to-hook-functions-code-detouring-guide.14185/