Linux下HOOK动态链接库中API的方法
? ? ? ? 2012年,我寫了一篇介紹Windows系統下Ring3層API的hook方案——《一種注冊表沙箱的思路、實現——Hook Nt函數》,其在底層使用了微軟的Detours庫。5年后,我又遇到這么一個問題,但是系統變成了Linux。我最開始的想法是找一個Linux下的Detours庫,于是找到了subhook。其原理是:修改被Hook函數起始地址處的匯編代碼,讓執行流程跳到我們定義的函數中。但是在實際使用中,我發現通過該庫調用原始函數有錯誤——地址違例,導致進程崩潰,所以最終放棄了subhook的方案。(轉載請指明出于breaksoftware的csdn博客)
? ? ? ? 后來發現,Linux用戶層Hook非常簡單。我們只要定義一個和被Hook的API相同名稱、參數、返回值的函數即可。比如我們需要Hook獲取用戶UID的函數getuid(原來是在libc.so中實現的),則需要定義如下函數:
uid_t getuid(void) {return 800;
}
? ? ? ? 我們在main函數中調用之
int main() {printf("get_uid:%d\n", getuid());
}
? ? ? ? 函數返回
? ? ? ? 我使用work賬戶登錄的,其真實uid是502。而我們重寫了程序中的getuid,則返回的是我們“指定”的800。
? ? ? ? 如果我們希望在被hook中的函數中調用原始函數,怎么做呢?這兒有個比較尷尬的問題,那就是我們定義的getuid地址將對應于符號getuid,那么原始的getuid(以后稱libc中的getuid)地址將對應什么符號?我們怎么找到它?
? ? ? ? 可以想象libc中的getuid對應的符號不會因為我們的程序而被改變,那么就意味著程序運行中,將有兩個getuid。事實也的確如此。
? ? ? ? 第一個getuid就是我們重定義的hook的函數體,第二個是動態鏈接庫libc.so中的。于是我們在重定義的函數體中,使用
dlsym(RTLD_NEXT, "getuid")
? ? ? ? 就可以獲得原始的函數地址。
? ? ? ? 所以這種方案的精髓就是RTLD_NEXT參數。我們看下dlsym函數參數的說明:
? ? ? ??There are two special pseudo-handles, RTLD_DEFAULT and RTLD_NEXT. The former will find the first occurrence of the desired symbol using the default library search order. The latter will find the next occurrence of a function in the search order after the current library. This allows one to provide a wrapper around a function in another shared library.
? ? ? ? 這段文字意思是:在默認的庫查找順序下,RTLD_DEFAULT是用于查找第一個符號匹配的函數地址,RTLD_NEXT是用于查找第二個符號匹配的函數地址。這種方式就提供了一種針對動態鏈接庫中函數替換的功能。
? ? ? ? 以我們例子,RTLD_DEFAULT將找到我們自己定義的getuid,而RTLD_NEXT將找到libc.so中的。
? ? ? ? 為了方便使用這種方式,我封裝了相關調用
#ifndef HOOK_BASE
#define HOOK_BASE#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif#include <dlfcn.h>#define HOOK_FUNC_TEMPLATE(function_name) function_name##_func_t#define HOOK_FUNC_ORI_NAME(function_name) function_name##_ori#define HOOK_FUNC_INIT(function_name) static HOOK_FUNC_TEMPLATE(function_name) HOOK_FUNC_ORI_NAME(function_name);#define HOOK_FUNC(function_name) \if (!HOOK_FUNC_ORI_NAME(function_name)) {\HOOK_FUNC_ORI_NAME(function_name) = (HOOK_FUNC_TEMPLATE(function_name)) dlsym(RTLD_NEXT, #function_name);\}\#define ORIGINAL_FUNC(function_name) ((HOOK_FUNC_TEMPLATE(function_name)) HOOK_FUNC_ORI_NAME(function_name))#endif
? ? ? ? 我們只要關注HOOK_FUNC_INIT、HOOK_FUNC和ORIGINAL_FUNC三個宏。HOOK_FUNC_INIT方法聲明了一個全局函數指針變量,其在HOOK_FUNC宏中被指定為被HOOK函數的原始地址。ORIGINAL_FUNC則是將這個指針進行類型轉換,從而方便調用。
? ? ? ? 下一步我們要定義被HOOK的函數的類型
#ifndef HOOK_DEF
#define HOOK_DEF#include "hook_base.h"
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>typedef uid_t (*HOOK_FUNC_TEMPLATE(getuid))(void);
#endif
? ? ? ? 然后重定義我們要HOOK的函數
#include "hook_def.h"
#include <stdio.h>
#include <sys/types.h>HOOK_FUNC_INIT(getuid);uid_t getuid(void) {HOOK_FUNC(getuid);int uid = ORIGINAL_FUNC(getuid)();printf("getuid original:%d\n", uid);return 800;
}
? ? ? ? 這段代碼,我們先調用原始的getuid函數,并打印出它的值。最后才返回一個我們定義的值——800。
? ? ? ? 在main函數中,我們只調用getuid。并使用?gcc src/*.c -ldl -o main 編譯
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include "hook_def.h"int main() {printf("get_uid:%d\n", getuid());return 0;
}
? ? ? ? 其返回結果如下
總結
以上是生活随笔為你收集整理的Linux下HOOK动态链接库中API的方法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 代码打补丁的利器——diff和patch
- 下一篇: C++拾趣——C++11的语法糖auto