linux內核態hook模塊

linux內核支持動態加載module,今天不聊正常的module,只簡單看一下實現Hook的module. hook通常翻譯做劫持,不過這個翻譯聽起來讓人不舒服,感覺有點恐怖,所以大家都是喊行話:hook.
hook-flow1
上圖是經典的堆棧式hook,也是splice典型的做法,在原有的流程中插入hook,更加典型的做法是棧在調用過程中從funcA->funcB變成了funcA->hook->funcB
hook-flow2.jpg
這個做法和第一張圖的做法是一致的,區別在於hook function調用完畢後棧恢復成了剛從funcA出來要到funB的樣子,但是中間加了一點點代碼,根據hook返回值判斷是否應該攔截該過程,如果要攔截則直接返回錯誤值,如果允許則和funcA->funcB過程完全一樣。

hook的實現思路

修改系統調用表指針

因爲大部分需要監控的是來自用戶程序產生的動作,所以可以在系統調用層次上想想辦法。系統調用的原理就是用戶程序通過設置寄存器,之後通過異常指令主動觸發異常,在異常處理中根據設置的寄存器查找一張表,就是系統調用號,每個系統都不一樣,但是基本都是通過系統調用表實現的,區別只是這個表是否連續的。系統調用的規則可以通過man syscall來查看詳細信息,下面是常見架構下的系統調用指令和返回值:

arch/ABI   instruction          syscall #   retval Notes
───────────────────────────────────────────────────────────────────
arm/OABI   swi NR               -           a1     NR is syscall #
arm/EABI   swi 0x0              r7          r0
arm64      svc #0               x8          x0
i386       int $0x80            eax         eax
x86_64     syscall              rax         rax    See below
x32        syscall              rax         rax    See below

Linux系統調用表都存儲在sys_call_table位置,是一個連續的數組,通過改變這個表的內容就可以改變系統的行爲。
我們可以通過保存原始的系統調用處理地址,並且把我們自己的hook代碼地址放到系統調用表上。
更多內容

檢測方法:直接檢測系統調用表中的地址和符號表中進行對比就知道是否被劫持了

使用LSM框架

LSM是內核官方的接口,在內核代碼執行的關鍵點上埋下了hook點,安全模塊可以註冊這些hook方法,之後在執行過程中被回調,hook方法可以知道上下文並且也能決定是否禁止這個操作。

檢測方法:直接查看/sys/kernel/security/

劫持LSM指針

LSM註冊過的module最核心完成的一件事是註冊LSM的回調,static struct security_operations *security_ops指針指向回調函數集,所以我們可以通過查找符號表找到該符號,然後像修改系統調用表一樣修改指針值進行Hook。

檢測方法:對比安全模塊的Ops地址和LSM ops的指針地址

使用kprobe

我們剩餘的選項中有一個是kprobe,最初設計用來作內核的tracing和debugging.Kprobe允許你安裝pre-handlers和post-handlers到任何的內核指令上,通常是函數的入口和返回處。處理函數訪問寄存器並且操作他們。這個方式可以讓我們有機會來監控和修改事件。

它最早由elfmaster挖掘出hook的用途,同時也從這個這裏看到,我們並不能直接通過返回值這種方式進行影響,而是操縱一些指針和其裏面的內容來迂迴的達到hook的目的,要實現一個功能會需要比較複雜的實現。所以它儘管能夠在理論上hook所有的代碼位置(標記__kprobes的除外),但是選用的比較少。

kprobe是內核的標準框架,主要用於trace,所以辨別這類的hook難度較大,理論上它不是一個病毒。

redirfs

堆棧類文件系統的代表,它通過正常的文件系統註冊和某些文件路徑下的hook操作,可以劫持所有的文件操作調用。通常在內核中是VFS下面掛接物理文件系統,而redirfs在中間插入了一層,相當於一層代理。
redirfs.jpg

缺點:它只能劫持file,dentry,inode,address_space的ops指針,對於網絡流的控制不太強.
他目前由slavaim維護,但是裏面有一些隱晦的bug需要自己處理,而且不能卸載,但是它管理的filter可以卸載。

優點:系統支持的接口,和lsm一樣都不屬於hacker的方式,應該不會被認爲是一個病毒。它還有一個姐妹版的實現ecryptfs,是一個主要用來進行在文件系統層進行加密的文件系統,實現原理是相似的,不過ecryptfs掛載實例就是一個掛載點,原有的內容都會因爲掛載而隱藏掉;而redirfs不影響現有的文件系統結構,只做hook。

Splicing

有一個非常傳統的方法來配置內核的方法hook:通過替換函數開始地址的指令爲一個爲條件的跳轉到你自己的處理函數。這個原始的指令挪到一個不同的位置並在返回到上一級函數時重新被調用。通過兩次的跳轉,可以拼接你的代碼到這個過程。裏面的實現非常靈活,通常有以下幾種劫持方式,主要描述指令執行流程和棧的變化:
splice-hook1.jpg
流程簡述:
1.使用module_alloc申請內存,它是有EXEC權限的,當然也可以自己封裝__vmalloc_node_range申請區域位於kernel區域。
2.拷貝原始函數的第一條指令到新的內存上,隨後附加jump指令
3.在hook函數中,通過函數指針賦值和函數指針調用,編譯器自動生成call function調用關係
4.替換原始函數第一條指令爲jump hook指令

splice-hook2.jpg
流程簡述:
1.使用module_alloc申請內存。
2.拷貝原始函數的全部指令到新的內存上,然後處理其中的相對指令。
在arm64上adr,adrp,bl,blr,prfm,ldr,ldrsw等,然後根據地址偏移重新計算偏移地址。
3.在hook函數中,通過函數指針賦值和函數指針調用,編譯器自動生成call function調用關係
4.替換原始函數第一條指令爲jump hook指令

splice-hook3.jpg
流程簡述:
1.使用module_alloc申請內存
2.填充新的內存,順序分別是:
備份參數寄存器的內容到棧上,因爲hook和原始函數兩個需要使用同樣的參數,而通過寄存器傳參在調用過程中寄存器可能會被hook函數更改,所以需要保存到棧上。
保存lr寄存器到棧上(x86在call時將返回地址壓到棧上,所以不需要這個操作),這樣能夠正確返回caller層
調用hook函數,返回值會放在寄存器上
判斷返回值寄存器,如果成功則通過b指令到轉到後面正常的恢復棧指令處,如果失敗則簡單的縮減棧並根據lr返回caller
從棧上恢復寄存器指令,參數和lr寄存器的值和caller第一次調用時完全一樣,下面執行原始函數的第一條指令,然後跳轉到原始函數執行後續指令。
3.替換原始函數的第一條指令爲jump新內存地址

檢測方法:掃描內核的只讀代碼段,和vmlinux之類的靜態elf代碼段進行對比

ftrace

目前基於ftrace的livepatch功能已經成功合併到內核中。在ftrace實現中,在每一條函數開頭的位置都有一條nop指令,當打開ftrace開關的時候,可以更改nop指令爲jump指令,直接跳轉到新的函數位置
livepatch1.png

檢測方法:掃描內核的只讀代碼段,和vmlinux之類的靜態elf代碼段進行對比

hook在不同體系下的實現原理

x86_64:
他有兩種指令:call/jmp,因爲是變長指令,所以它的跳轉長度足夠長,一條指令就可以解決;同時變長,解析稍微麻煩一點。

commander大神的hijack

arm64:
arm64中b/bl指令可以進行±128 MB跳轉,而它的內存佈局是module在kernel下方64M的空間內,而內核在kernel的上放地址空間,一般情況下128M的跳轉是足夠的了,這樣就簡化了跳轉時的工作量,一條指令搞定。

我寫的hijack

mips64:
它和arm一樣也是定長指令,但是地址空間分佈:module在0xffffffffc0000000,而內核在0xffffffff80000000地址,而跳轉指令最多256M,所以跳不過去。方法有兩個:一個是合成絕對地址到寄存器,然後通過寄存器絕對跳轉;另一個是直接通過__vmalloc_node_range在內核地址上分配新的指令內存,這樣從原始函數跳轉到新的指令內存一條指令就搞定,而在新的指令內存上跳轉到hook函數這樣的操作就不需要關係跳轉指令長度了。

待完成

參考:

https://www.howtoforge.com/history-of-linux-kernel-live-patching/#7
https://poppopret.org/2013/01/07/suterusu-rootkit-inline-kernel-function-hooking-on-x86-and-arm/#arm
http://www.ksplice.com/doc/ksplice.pdf
https://stackoverflow.com/questions/45279810/arm64-assembly-branch-to-function-address/45283531#45283531

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章