修改ELF文件统计内存泄露

本篇不走寻常路,想要正常的内存调试手段请查阅内核相关的内存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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章