鉤子
鉤子(hooking)是外掛開發中的常用技巧:把一小段機器碼(通常是一個jmp指令)放到內存中原本是正常函數的地方,就可以改變這個函數的行爲。這個技巧不僅在外掛開發中有用,在軟件測試當中也很有用,可以用來mock一個非虛函數。鉤子實現起來也非常簡單。
比如下面這段代碼:
#include <stdio.h>
int func()
{
printf("hello, beautiful world");
return 0;
}
int main()
{
return func();
}
雖然在這裏如果想要修改func函數很簡單,但是可以假設其實func函數是在一個動態鏈接庫裏面,或者一個靜態庫裏面,我們沒有其源代碼。這時候就可以利用鉤子在運行時修改func函數。
這裏以x64架構的Linux爲例。一般來說,代碼位於.text
段中,這裏的內存是不可以修改的,所以我們用mprotect函數修改這裏的內存屬性,允許寫入這段內存:
#include <unistd.h>
#include <sys/mman.h>
// 假設被鉤住的函數是orig_func
int page_size = getpagesize();
void *aligned_addr = (void *)(((uintptr_t)orig_func) & (~(page_size - 1)));
if (mprotect(aligned_addr,
page_size * 2,
PROT_EXEC | PROT_WRITE | PROT_READ ) != 0) {
err_log("failed to modify mem props");
exit(EXIT_FAILURE);
}
隨後寫入跳轉的機器碼,其彙編僞代碼如下:
mov rax, &hook_func
jmp rax
因爲rax寄存器在C/C++的ABI中是用來存儲返回值的,可以任意修改,所以這裏不用擔心修改rax寄存器會改變程序的行爲。
具體的程序的話是這樣:
uint8_t hook_mcode[] = {0x48, 0xB8, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xFF, 0xE0};
*(uintptr_t*)(hook_code + 2) = (uintptr_t)hook_func;
memcpy(orig_func, hook_mcode, 12);
最後我們得到了完整的代碼:
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <sys/mman.h>
int func()
{
printf("hello, beautiful world\n");
return 0;
}
int hook_func()
{
printf("farewell, cruel world\n");
return 0;
}
void hook(void *orig_func, void *hook_func, uint8_t *mcode_backup)
{
int page_size;
void *aligned_addr;
uint8_t hook_mcode[12] = {0x48, 0xB8, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0xFF, 0xE0};
page_size = getpagesize();
aligned_addr = (void *)(((uintptr_t)orig_func) & (~(page_size - 1)));
if (mprotect(aligned_addr,
page_size * 2,
PROT_EXEC | PROT_WRITE | PROT_READ ) != 0) {
fprintf(stderr, "failed to modify mem props\n");
exit(EXIT_FAILURE);
}
*(uintptr_t*)(hook_mcode + 2) = (uintptr_t)hook_func;
memcpy(mcode_backup, orig_func, 12);
memcpy(orig_func, hook_mcode, 12);
}
void restore(void *func, uint8_t *mcode)
{
memcpy(func, mcode, 12);
}
int main()
{
uint8_t mcode_backup[12] = {0};
func();
hook(func, hook_func, mcode_backup);
func();
restore(func, mcode_backup);
func();
return 0;
}
程序運行結果如下:
hello, beautiful world
farewell, cruel world
hello, beautiful world
其中,第一行是原本的函數的輸出結果,第二行是被hook之後的輸出結果,最後一行是回覆之後的輸出結果。