linux簡(jiǎn)單hook

linux hook原理

  • 在 Linux 系統(tǒng)中,要進(jìn)行函數(shù)鉤子需要使用動(dòng)態(tài)鏈接庫(kù)(Dynamic Link Library,DLL)技術(shù)。動(dòng)態(tài)鏈接庫(kù)是指在應(yīng)用程序運(yùn)行期間才被載入的庫(kù),它允許程序在運(yùn)行時(shí)動(dòng)態(tài)地連接到某個(gè)庫(kù)文件上,以執(zhí)行其中的函數(shù)。因?yàn)閯?dòng)態(tài)鏈接庫(kù)是在運(yùn)行時(shí)才被載入的,所以可以在運(yùn)行期間修改它們的代碼,實(shí)現(xiàn)函數(shù)的替換。
  • 實(shí)現(xiàn)hook的流程一般是:
    1. 編寫(xiě)一個(gè)動(dòng)態(tài)鏈接庫(kù),包含需要鉤子的函數(shù)以及它們的替換函數(shù)。
    2. 在動(dòng)態(tài)鏈接庫(kù)中,使用 LD_PRELOAD 環(huán)境變量指定要預(yù)先加載的庫(kù)。
    3. 在需要進(jìn)行函數(shù)鉤子的應(yīng)用程序中,重載需要鉤子的函數(shù),使其調(diào)用動(dòng)態(tài)鏈接庫(kù)中的替換函數(shù),而不是原始的函數(shù)。
    4. 在動(dòng)態(tài)鏈接庫(kù)中的替換函數(shù)中,執(zhí)行預(yù)定的操作,然后調(diào)用原始函數(shù)。

一個(gè)簡(jiǎn)單通過(guò)LD_PRELOAD 注入的代碼的例子

  • 創(chuàng)建一個(gè)簡(jiǎn)單的function.c文件
#include <stdio.h>
void my_printf() {
    printf("my_printf in function\n");
}
  • 這段代碼只進(jìn)行了一個(gè)簡(jiǎn)單的打印操作
  • 創(chuàng)建一個(gè)app程序main.c 調(diào)用這個(gè)函數(shù)
#include <stdio.h>
void my_printf();

int main()
{
    my_printf();
    return 0;
}
  • 我們進(jìn)行編譯運(yùn)行
gcc -shared -fPIC -o libfunction.so function.c 
gcc -o app main.c -L. -lfunction
export LD_LIBRARY_PATH=.
./app
  • 這樣我們得到一個(gè)打印 my_printf in function
  • 現(xiàn)在再創(chuàng)建一個(gè)注入的so hook.c
#include <stdio.h>
void my_printf() {
    printf("my_printf in hook\n");
}
  • 分別進(jìn)行編譯和運(yùn)行
gcc -shared -fPIC -o libhook.so hook.c 
LD_PRELOAD=./libhook.so ./app
  • 得到的結(jié)果為 my_printf in hook
  • 這時(shí)候我們就完成最簡(jiǎn)單的函數(shù)替換

hook原函數(shù)

  • 下面我們將使用修改got表來(lái)實(shí)現(xiàn)替換到原函數(shù)
  • 可執(zhí)行程序在調(diào)用一個(gè)so的的函數(shù)時(shí)候首先是訪問(wèn)plt的重定向表。然后轉(zhuǎn)到got里面取出位置。
  • 首先我們需要獲取三個(gè)重要的信息
  • 字符串表:包含以空字符結(jié)尾的字符序列,使用這些字符來(lái)描繪符號(hào)和節(jié)名;
  • 符號(hào)表:保存了一個(gè)程序在定位和重定位時(shí)需要的定義和引用的信息;
  • plt表:保存了需要重定位的符號(hào)的信息;
  • 然后我們遍歷plt表根據(jù)plt的信息找到對(duì)應(yīng)符號(hào)表信息和字符串表信息(主要是找出函數(shù)名字)。比對(duì)是否是我們需要替換的函數(shù)名字。如果是需要替換的名字那么根據(jù)plt里面保存的偏移信息找到got項(xiàng)。替換里面的地址為我們函數(shù)的地址。完成調(diào)用函數(shù)的替換
  #include <stdio.h>
#define __USE_GNU
#include<dlfcn.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <limits.h>
#include <sys/mman.h>
#include <elf.h>
#include <link.h>

 void (*orig_func)();

void my_printf_hook() {
    printf("my_printf in hook on enter\n");
    orig_func();
    printf("my_printf in hook on level\n");
}
const Elf64_Dyn* find_dyn(const Elf64_Dyn* dyn, Elf64_Sxword tag) {
    while (dyn->d_tag != DT_NULL) {
        if (dyn->d_tag == tag) {
            return dyn;
        }
        dyn++;
    }
    return NULL;
}
void do_replace_function() {
    // 打開(kāi)我們的可執(zhí)行文件
    void* handle = dlopen(RTLD_DEFAULT, RTLD_LAZY);
    if (handle == NULL) {
        printf("open so error %s", dlerror());
        return;
    }
    struct link_map* map = NULL;
    //獲取可執(zhí)行文件的dlinfo
    if (dlinfo(handle, RTLD_DI_LINKMAP, &map) != 0) {
        printf("dlinfo error");
    }
    const char* plt_addr_base = (const char *)map->l_addr;
    const Elf64_Dyn* dyn = NULL;
    // 獲取符號(hào)表信息
    //.dynsym
    dyn = find_dyn(map->l_ld, DT_SYMTAB);
    if (dyn == NULL) {
        printf("failed to find DT_SYMTAB\n");
        return;
    }
    const Elf64_Sym* dyn_systab =(const Elf64_Sym*) dyn->d_un.d_ptr;
    //獲取字符串表
    //.dynstr
    dyn = find_dyn(map->l_ld, DT_STRTAB);
    if (dyn == NULL) {
        printf("failed to find DT_STRTAB\n");
        return;
    }
    const char* dyn_strtab = (const char*)dyn->d_un.d_ptr;
    //獲取plt
    dyn = find_dyn(map->l_ld, DT_JMPREL);
    if (dyn == NULL) {
        printf("failed to find DT_JMPREL\n");
        return;
    }
    const Elf64_Rela* plt = (const Elf64_Rela*)dyn->d_un.d_ptr;
    //獲取plt大小
    dyn = find_dyn(map->l_ld, DT_PLTRELSZ);
    if (dyn == NULL) {
        printf("failed to find DT_PLTRELSZ\n");
        return;
    }
    int plt_cont = dyn->d_un.d_val / sizeof(Elf64_Rela);
    int i = 0;
    size_t len = strlen("my_printf");
    //便利plt
    for ( i = 0; i < plt_cont; i++) {
        const Elf64_Rela* current_plt = plt + i;
        if (ELF64_R_TYPE(current_plt->r_info) != R_X86_64_JUMP_SLOT) {
            continue;
        }
        size_t index = ELF64_R_SYM(current_plt->r_info);//找到符號(hào)表的偏移
        size_t index2 = dyn_systab[index].st_name;//站到字符串表的偏移
        const char* name = dyn_strtab + index2;
        if (strncmp("my_printf", name, len) == 0) {
            void** addr = (void **)(plt_addr_base + current_plt->r_offset);//找到這個(gè)got表的偏移的位置
            orig_func = (void (*)())(*addr);//把記錄的值取出來(lái)
            *addr = (void *)my_printf_hook;//新的地址復(fù)制過(guò)去 完成替換
        }
    }
}

void __attribute__((constructor)) my_init(void) {
    printf("Hello from LD_PRELOAD!\n");
    do_replace_function();
}
  • 編譯hook.c 鏈接執(zhí)行
  gcc -shared   -fPIC -o libhook.so hook.c  -ldl
  LD_PRELOAD=./libhook.so ./app 
  • 得到結(jié)果為
Hello from LD_PRELOAD!
my_printf in hook on enter
my_printf in function
my_printf in hook on level
  • 成功的替換my_printf為my_hook_prinf
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容