本篇不走尋常路,想要正常的內存調試手段請查閱內核相關的內存debug功能.
程序開發了很長時間,參與開發的人也很多,今天我想用實驗統計來證明我們寫的內核module沒有產生內存泄露,通常的做法是封裝內存的api,中間加上統計邏輯,但是我不想改他們的代碼,有什麼辦法嗎?
我翻了一下code,裏面有上百處申請內存的位置,主要使用了兩種api,一種是kmalloc,另外一種是創建自己的kmem_cache的方式.後一種自帶內存泄露檢查,當module卸載時嘗試destroy kmem_cache,如果還有slab對象沒有歸還時會自動報warning,這也是一種方式,創建自己的kmem_cache,通過slabinfo查看使用情況,唯一不好的是創建一大堆kmem_cache.
而kmalloc/kzalloc有什麼方法嗎?剛開始我考慮藉助gcc宏的方式,可以考慮下面這種實現是否可以成功:
.#define hook_kmalloc kmalloc
hook_kmalloc() {
do_something();
#include <linux/slab.h>
kmalloc();
}
這種當然是不成功的,它最終展開的邏輯想要展示成下面這樣,當然在宏展開後會發生重複定義的錯誤:
func1() {
do_something();
func1();
}
沒有辦法在一個module中不修改代碼的情況將直接進行func調用的情況變成進行func->hook_func->func
的操作.
最後採用一個折中的方法:
1.寫個調試module1,封裝kmalloc成hook_kmalloc,kfree->hook_kfree,加上調試邏輯
2.module中把不必要的<linux/slab.h>都去掉,然後放到唯一的一個頭文件中,大部分都不關心頭文件,只要不報警應該沒問題.
之後在這個頭文件中做文章,想調試時不調用內核原生的而是調用module1中的接口.
#ifndef HOOK_SLAB_H
#define HOOK_SLAB_H
#ifdef DEBUG
#define hook_kmalloc kmalloc
#define hook_kfree kfree
#else
# include <linux/slab.h>
#endif
#endif
有沒有辦法一點代碼都不修改的情況下進行替換呢?二進制編輯替換
這種hack方式是進行二進制格式的替換,只需要掌握一點ELF格式的基礎就可以,so easy!!!
kmalloc默認是inline的,它實際調用__kmalloc,我們可以根據module的.rela.text的信息進行替換,所有調用__kmalloc的地方變成hook_kmalloc,我當然不會傻傻的修改重定位段,只需要簡單的修改一下字符串就可以完成替換.下面只是提供原型,將__kmalloc替換成1_kmalloc,不寫程序直接用工具編輯二進制文件.
module原始的信息:
readelf -r test.ko |grep _kmalloc
000000006203 051300000002 R_X86_64_PC32 0000000000000000 __kmalloc - 4
......
0000000278f2 051300000002 R_X86_64_PC32 0000000000000000 __kmalloc - 4
總共有n個使用__kmalloc的位置,下面是重定位段的信息:
readelf -S test.ko
There are 54 section headers, starting at offset 0x1109688:
Section Headers:
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
...
[ 3] .rela.text RELA 0000000000000000 00978450
00000000000282c0 0000000000000018 I 51 2 8
[51] .symtab SYMTAB 0000000000000000 0096ab28
0000000000007c98 0000000000000018 52 366 8
[52] .strtab STRTAB 0000000000000000 009727c0
0000000000005c90 0000000000000000 0 0 1
# nm test.ko |grep kmalloc
U __kmalloc
U kmalloc_caches
U kmalloc_order_trace
重定位段的link記錄符號表的index,info記錄重定位段應用在哪個section上.
簡單捋一捋整個過程:在insmod的過程中會應用重定位段,其中查找需要的符號位置也就是符號表中的項,符號並沒有直接保存函數名稱而是保存了相關的字符串表的索引和偏移,我們只需要簡單對應的字符串的內容就可以完成.
在這裏需要對使用__kmalloc的位置進行重定位,修改字符串表中__kmalloc爲1_kmalloc之後,安裝時module就不再依賴__kmalloc而是1_kmalloc,我們就成功通過修改二進制程序來完成hook.
#readelf -r test.ko |grep _kmalloc
000000006203 051300000002 R_X86_64_PC32 0000000000000000 1_kmalloc - 4
......
0000000278f2 051300000002 R_X86_64_PC32 0000000000000000 1_kmalloc - 4
#nm zsdefend.ko |grep kmalloc
U 1_kmalloc
#dmesg:
[175853.839949] test: Unknown symbol 1_kmalloc (err 0)
限制:
1.修改二進制module在開啓簽名之後因爲修改內容,也就是數字簽名必然會不一致,所以在開啓強安全功能的系統上就不能這樣搞了
2.修改的函數名還是有限制的,修改後的函數名長度>原始的函數名長度,否則就越界了.
當然也可以取任意長度的hook名稱,但是需要做一些額外的工作,過程如下:
2.1.在module的.strtab中追加想要的hook名稱,函數名就是一個普通的字符串,以0結尾.
objcopy --dump-section .strtab=strtab.sec test.ko
echo -ne "hook_kmalloc\x00" >>strtab.sec
objcopy --update-section .strtab=strtab.sec test.ko
2.2.修改.symtab中的指針,指向追加的hook名稱
objcopy --dump-section .symtab=symtab.sec test.ko
我們從這裏看符號的偏移051300000002>>32=0x513,之後編輯這個偏移的符號表的st_name
readelf -r test.ko |grep _kmalloc
000000006203 051300000002 R_X86_64_PC32 0000000000000000 1_kmalloc - 4
vim -b symtab.sec # %!xxd %!xxd -r
更新符號表
objcopy --update-section .symtab=symtab.sec test.ko