【Linux內(nèi)核調(diào)試】使用Ftrace來Hook linux內(nèi)核函數(shù)

目標(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ù)(一):尋找完美解決方案

Hooking linux內(nèi)核函數(shù)(二):如何使用Ftrace hook函數(shù)

Hooking linux內(nèi)核函數(shù)(三):Ftrace的主要優(yōu)缺點(diǎn)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末爆办,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子课梳,更是在濱河造成了極大的恐慌距辆,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,198評(píng)論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件惦界,死亡現(xiàn)場離奇詭異挑格,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)沾歪,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門漂彤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人灾搏,你說我怎么就攤上這事挫望。” “怎么了狂窑?”我有些...
    開封第一講書人閱讀 167,643評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵媳板,是天一觀的道長。 經(jīng)常有香客問我泉哈,道長蛉幸,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,495評(píng)論 1 296
  • 正文 為了忘掉前任丛晦,我火速辦了婚禮奕纫,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘烫沙。我一直安慰自己匹层,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,502評(píng)論 6 397
  • 文/花漫 我一把揭開白布锌蓄。 她就那樣靜靜地躺著升筏,像睡著了一般撑柔。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上您访,一...
    開封第一講書人閱讀 52,156評(píng)論 1 308
  • 那天铅忿,我揣著相機(jī)與錄音,去河邊找鬼洋只。 笑死辆沦,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的识虚。 我是一名探鬼主播肢扯,決...
    沈念sama閱讀 40,743評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼担锤!你這毒婦竟也來了蔚晨?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,659評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤肛循,失蹤者是張志新(化名)和其女友劉穎铭腕,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體多糠,經(jīng)...
    沈念sama閱讀 46,200評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡累舷,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,282評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了夹孔。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片被盈。...
    茶點(diǎn)故事閱讀 40,424評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖搭伤,靈堂內(nèi)的尸體忽然破棺而出只怎,到底是詐尸還是另有隱情,我是刑警寧澤怜俐,帶...
    沈念sama閱讀 36,107評(píng)論 5 349
  • 正文 年R本政府宣布身堡,位于F島的核電站,受9級(jí)特大地震影響拍鲤,放射性物質(zhì)發(fā)生泄漏贴谎。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,789評(píng)論 3 333
  • 文/蒙蒙 一季稳、第九天 我趴在偏房一處隱蔽的房頂上張望赴精。 院中可真熱鬧,春花似錦绞幌、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評(píng)論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽谭确。三九已至,卻和暖如春票渠,著一層夾襖步出監(jiān)牢的瞬間逐哈,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評(píng)論 1 271
  • 我被黑心中介騙來泰國打工问顷, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留昂秃,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,798評(píng)論 3 376
  • 正文 我出身青樓杜窄,卻偏偏與公主長得像肠骆,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子塞耕,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,435評(píng)論 2 359