目標(biāo):hook幾個(gè)Linux內(nèi)核函數(shù)調(diào)用述呐,如打開文件和啟動(dòng)進(jìn)程五垮,并利用它來啟用系統(tǒng)活動(dòng)監(jiān)控并搶先阻止可疑進(jìn)程疯搅。
一膨处、方案比較
1. 使用Linux安全API
方法:內(nèi)核代碼的關(guān)鍵點(diǎn)包含安全函數(shù)調(diào)用崎场,這些調(diào)用可能觸發(fā)安全模塊安裝的回調(diào)佩耳,該模塊可以分析特定操作的上下文,并決定是允許還是禁止它谭跨。
限制:安全模塊無法動(dòng)態(tài)加載干厚,所以需要重新編譯內(nèi)核李滴。
2. 修改系統(tǒng)調(diào)用表
方法:所有Linux系統(tǒng)調(diào)用處理程序都存儲(chǔ)在sys_call_table表中,可以保存舊的處理程序值蛮瞄,并將自己的處理程序添加到表中所坯,這樣就能hook任何系統(tǒng)調(diào)用。
優(yōu)點(diǎn):一是能夠完全控制所有系統(tǒng)調(diào)用挂捅;二是性能開銷較小芹助,包含更新系統(tǒng)調(diào)用表、監(jiān)視闲先、調(diào)用原始系統(tǒng)調(diào)用處理程序状土;三是通用性較好,不依賴內(nèi)核版本伺糠。
缺點(diǎn):一是實(shí)現(xiàn)較復(fù)雜蒙谓,需查看系統(tǒng)調(diào)用表、繞過內(nèi)存寫保護(hù)训桶、確保處理程序的安全性累驮;二是有些處理程序無法替換,如有些優(yōu)化要求在匯編中實(shí)現(xiàn)系統(tǒng)調(diào)用處理程序舵揭;三是只能hook系統(tǒng)調(diào)用谤专,限制了入口點(diǎn)。
3.Kprobes
方法:可以為任何內(nèi)核指令琉朽、函數(shù)入口和函數(shù)返回點(diǎn)安裝處理程序毒租,處理程序可以訪問寄存器并更改它們。
優(yōu)點(diǎn):一是API很成熟箱叁;二是能跟蹤內(nèi)核中任意點(diǎn),kprobes通過在內(nèi)核代碼中嵌入斷點(diǎn)(int3指令)實(shí)現(xiàn)惕医。跟蹤函數(shù)內(nèi)部的特定指令很有用耕漱。
缺點(diǎn):一是技術(shù)復(fù)雜,若要獲取函數(shù)參數(shù)或局部變量值抬伺,需知道堆棧具體位置及所在寄存器螟够,并手動(dòng)取出,若要阻止函數(shù)調(diào)用峡钓,還需手動(dòng)修改進(jìn)程狀態(tài)妓笙;二是開銷太大,超過了修改系統(tǒng)調(diào)用表的成本能岩;三是禁用搶占寞宫,kprobes基于中斷和故障,所以為了執(zhí)行同步拉鹃,所有處理程序需以禁用的搶占方式執(zhí)行辈赋,導(dǎo)致的限制是鲫忍,處理程序中不能等待、分配大量內(nèi)存钥屈、處理輸入輸出悟民、在信號(hào)量和計(jì)時(shí)器中休眠。
4.拼接
方法:將函數(shù)開頭的指令替換為通向處理程序的無條件跳轉(zhuǎn)篷就,處理完成后再執(zhí)行原始指令射亏,再跳回截?cái)嗪瘮?shù)前執(zhí)行。類似于kprobes竭业。
優(yōu)點(diǎn):一是不需要設(shè)置內(nèi)核編譯選項(xiàng)鸦泳,可在任何函數(shù)開頭實(shí)現(xiàn);二是開銷低永品,兩次跳轉(zhuǎn)即可返回到原始點(diǎn)做鹰。
缺點(diǎn):技術(shù)復(fù)雜。
- 同步掛鉤安裝和刪除(如果在更換指令期間調(diào)用了該函數(shù))
- 使用可執(zhí)行代碼繞過內(nèi)存區(qū)域的寫保護(hù)
- 替換指令后使CPU緩存失效
- 拆卸已替換的指令鼎姐,以便將它們作為一個(gè)整體進(jìn)行復(fù)制
- 檢查替換后的函數(shù)是否沒有跳轉(zhuǎn)
- 檢查替換后的函數(shù)是否可以移動(dòng)到其他位置
二钾麸、使用Ftrace hook函數(shù)
1.簡介
ftrace提供很多函數(shù)集,可顯示調(diào)用圖炕桨、跟蹤函數(shù)調(diào)用頻率和長度饭尝、過濾特定函數(shù)。ftrace的實(shí)現(xiàn)基于編譯器選項(xiàng)-pg和-mfentry献宫,這些內(nèi)核選項(xiàng)在每個(gè)函數(shù)的開頭插入一個(gè)特殊跟蹤函數(shù)的調(diào)用—mcount()或fentry ()钥平,用于實(shí)現(xiàn)ftrace框架。
但是每個(gè)函數(shù)調(diào)用ftrace會(huì)使性能降低姊途,所以有一種優(yōu)化機(jī)制——?jiǎng)討B(tài)trace涉瘾。內(nèi)核知道調(diào)用mcount()或fentry ()的位置,在早期階段將機(jī)器碼替換為nop捷兰,當(dāng)打開Linux內(nèi)核跟蹤時(shí)立叛,ftrace調(diào)用會(huì)被添加到指定的函數(shù)中。
2.函數(shù)說明
以下結(jié)構(gòu)用于描述每個(gè)鉤子函數(shù):
/*
name: 被hook的函數(shù)名
function: 鉤子函數(shù)的地址(替代被hook函數(shù))
original: 指針贡茅,指向存儲(chǔ)被hook函數(shù)的地址的地方
address: 被hook函數(shù)的地址
ops: ftrace服務(wù)信息
*/
struct ftrace_hook {
const char *name;
void *function;
void *original;
unsigned long address;
struct ftrace_ops ops;
};
可以只填寫三個(gè)字段:name秘蛇、function、original顶考。
#define HOOK(_name, _function, _original) \
{ \
.name = (_name), \
.function = (_function), \
.original = (_original), \
}
static struct ftrace_hook hooked_functions[] = {
HOOK("sys_clone", fh_sys_clone, &real_sys_clone),
HOOK("sys_execve", fh_sys_execve, &real_sys_execve),
};
鉤子函數(shù)包裝的結(jié)構(gòu)如下:
/*
這是個(gè)指向原始execve()的指針赁还,可被wrapper調(diào)用。未改變參數(shù)順序和類型驹沿、返回值
asmlinkage:調(diào)用函數(shù)的時(shí)候參數(shù)不是通過棧傳遞艘策,而是直接放到寄存器里
*/
static asmlinkage long (*real_sys_execve)(const char __user *filename,
const char __user *const __user *argv,
const char __user *const __user *envp);
/*
fh_sys_execve函數(shù)將代替被hook函數(shù)執(zhí)行,它的參數(shù)和原始函數(shù)一樣甚负,返回值也正常返回柬焕。該函數(shù)可在被hook函數(shù)之前审残、之后或替代執(zhí)行。
*/
static asmlinkage long fh_sys_execve (const char __user *filename,
const char __user *const __user *argv,
const char __user *const __user *envp)
{
long ret;
pr_debug("execve() called: filename=%p argv=%p envp=%p\n",
filename, argv, envp);
ret = real_sys_execve(filename, argv, envp);
pr_debug("execve() returns: %ld\n", ret);
return ret;
}
3.初始化ftrace
第一步是查找被hook函數(shù)的地址斑举,通過kallsyms搅轿。
static int resolve_hook_address (struct ftrace_hook *hook)
hook->address = kallsyms_lookup_name(hook->name);
if (!hook->address) {
pr_debug("unresolved symbol: %s\n", hook->name);
return -ENOENT;
}
*((unsigned long*) hook->original) = hook->address;
return 0;
}
第二步,初始化ftrace_ops結(jié)構(gòu)富玷,需設(shè)置必要字段func\flags璧坟。
int fh_install_hook (struct ftrace_hook *hook)
int err;
err = resolve_hook_address(hook);
if (err)
return err;
hook->ops.func = fh_ftrace_thunk;
hook->ops.flags = FTRACE_OPS_FL_SAVE_REGS
| FTRACE_OPS_FL_IPMODIFY;
/* ... */
}
fh_ftrace_thunk()
是ftrace在跟蹤函數(shù)時(shí)的回調(diào)函數(shù),稍后討論赎懦。flags意義是告訴ftrace保存和恢復(fù)寄存器(以修改寄存器)雀鹃,我們可在回調(diào)函數(shù)中修改這些寄存器的內(nèi)容(RIP)。
第三步励两,開始hook黎茎,首先用ftrace_set_filter_ip()
為需要跟蹤的函數(shù)打開ftrace,再調(diào)用register_ftrace_function()
對(duì)被hook函數(shù)進(jìn)行注冊(cè)当悔。記得用ftrace_set_filter_ip()
關(guān)閉ftrace傅瞻。
int fh_install_hook (struct ftrace_hook *hook)
{
/* ... */
err = ftrace_set_filter_ip(&hook->ops, hook->address, 0, 0);
if (err) {
pr_debug("ftrace_set_filter_ip() failed: %d\n", err);
return err;
}
err = register_ftrace_function(&hook->ops);
if (err) {
pr_debug("register_ftrace_function() failed: %d\n", err);
/* Don’t forget to turn off ftrace in case of an error. */
ftrace_set_filter_ip(&hook->ops, hook->address, 1, 0);
return err;
}
return 0;
}
關(guān)閉鉤子如下,避免鉤子函數(shù)仍然在其他地方執(zhí)行:
void fh_remove_hook (struct ftrace_hook *hook)
{
int err;
err = unregister_ftrace_function(&hook->ops);
if (err)
pr_debug("unregister_ftrace_function() failed: %d\n", err);
}
err = ftrace_set_filter_ip(&hook->ops, hook->address, 1, 0);
if (err) {
pr_debug("ftrace_set_filter_ip() failed: %d\n", err);
}
}
4.用ftrace hook函數(shù)
原理:修改rip寄存器指向自定義的回調(diào)函數(shù)盲憎。
static void notrace fh_ftrace_thunk(unsigned long ip, unsigned long parent_ip,
struct ftrace_ops *ops, struct pt_regs *regs)
{
struct ftrace_hook *hook = container_of(ops, struct ftrace_hook, ops);
regs->ip = (unsigned long) hook->function;
}
// container_of(ptr,type,member): 已知結(jié)構(gòu)體type的成員member的地址ptr嗅骄,求解結(jié)構(gòu)體type的起始地址。也即返回ftrace_hook結(jié)構(gòu)的首地址饼疙。
// notrace說明符:如果不小心從ftrace回調(diào)中調(diào)用了一個(gè)函數(shù)溺森,系統(tǒng)就不會(huì)掛起,因?yàn)閒trace正在跟蹤這個(gè)函數(shù)窑眯。
5.防止遞歸調(diào)用
問題:當(dāng)包裝函數(shù)調(diào)用原始函數(shù)時(shí)屏积,原始函數(shù)將被ftrace再次跟蹤,從而導(dǎo)致無窮無盡的遞歸伸但。
解決:可利用parent_ip
(調(diào)用鉤子函數(shù)的返回地址)——ftrace回調(diào)參數(shù)之一肾请,該參數(shù)通常用于構(gòu)建函數(shù)調(diào)用圖秉颗,但也可以用來區(qū)分跟蹤函數(shù)是第一次調(diào)用還是重復(fù)調(diào)用岂昭。第一次調(diào)用時(shí)预茄,parent_ip
指向內(nèi)核某個(gè)位置,重復(fù)調(diào)用時(shí)却妨,parent_ip
指向包裝函數(shù)內(nèi)部,只有第一次執(zhí)行時(shí)執(zhí)行回調(diào)函數(shù)括眠,其他調(diào)用時(shí)需執(zhí)行原始函數(shù)彪标。
static void notrace fh_ftrace_thunk (unsigned long ip, unsigned long parent_ip,
struct ftrace_ops *ops, struct pt_regs *regs)
{
struct ftrace_hook *hook = container_of(ops, struct ftrace_hook, ops);
/* Skip the function calls from the current module. */
if (!within_module(parent_ip, THIS_MODULE))
regs->ip = (unsigned long) hook->function;
}
內(nèi)核中hook函數(shù)的整體執(zhí)行流程可參見Hooking linux內(nèi)核函數(shù)(二):如何使用Ftrace hook函數(shù)中的圖示。
三掷豺、Ftrace評(píng)價(jià)與配置
1.優(yōu)缺點(diǎn)
優(yōu)點(diǎn):一是API成熟捞烟,代碼簡單薄声;二是根據(jù)名稱就能跟蹤任何函數(shù);三是開銷較低题画。
缺點(diǎn):一是配置上有要求默辨,需支持kallsyms函數(shù)索引、ftrace框架苍息;二是ftrace只能在函數(shù)入口點(diǎn)工作缩幸。
2.配置
編譯內(nèi)核時(shí)需設(shè)置以下選項(xiàng):
-
CONFIG_FTRACE
——Ftrace -
CONFIG_KALLSYMS
——kallsyms -
CONFIG_DYNAMIC_FTRACE_WITH_REGS
——?jiǎng)討B(tài)寄存器修改 -
CONFIG_HAVE_FENTRY
——ftrace調(diào)用必須位于函數(shù)的開頭(x86_64架構(gòu)支持,但i386架構(gòu)不支持竞思,所以ftrace函數(shù)hooking不支持32位x86體系結(jié)構(gòu))
3.啟發(fā)
啟發(fā):可以hook某些函數(shù)表谊,如堆分配函數(shù)kmalloc,記錄參數(shù)和返回值盖喷。
參考
Hooking linux內(nèi)核函數(shù)(一):尋找完美解決方案