home · archive · links · projects

鉤子

鉤子(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之後的輸出結果,最後一行是回覆之後的輸出結果。


© Licensed under CC BY-NC-SA 4.0 if not specified otherwise.
Email: dzshy [at] outlook [dot] com