分类: LINUX
2011-09-30 11:45:14
必须要对内核那个大的image进行修改吗?未必。
内核模块的符号链接过程是这样的,引用本人不久前在学校bbs上的一篇回复:
--------------------------------------开始
内核在载入模块后,解析模块中符号的函数在
kernel/module.c::find_symbol->each_symbol
首先在这几个section区域寻找符号
如果找不到就会遍历模块链表,在各模块的符号中寻找解析。如果仍然找不到就会报函
数未定义错误。
所以模块中函数要能解析,则函数对应的(addr,name)对要出现在__ksymtab(_..)等
section中,或是在已载入的模块中有导出。
而对于_ksymtab这个内核自定义section,EXPORT_SYMBOL这个宏的任务正是插入(addr,
name)对到这个section中
include/linux/module.h::EXPORT_SYMBOL
于是只有EXPORT_SYMBOL的函数才可能出现在_ksymtab(_...)section中,才可能在模块
init的时候通过名字解析符号的时候被解析到。
当然,整个解析的逻辑就是限制内核态函数的可见性,通过section来规约。通过不同的
section来细分各种场景,比如unused,及各种协议(gpl...)。实现name->addr的查找。
当然,通过/proc/kallsyms我们可找到我们需要的符号地址,手动解析addr, just to
find addr, just a hack :)
---------------------------------------------------------结束
模块链接的机理就是从一个人认识的symbol name到机器认识的addr的过程,内核的导出函数帮我们完成了这一功能。通过/proc/kallsyms我们也可以手动解析,kdb, kprobe等内核debug/trace工具的存在也让内核主动暴露了
一个从任意模块名到地址的函数,它叫kallsyms_lookup_name。声明在linux/kallsyms.h, 依托CONFIG_KALLSYMS的支持,我们可以将任意内核态函数转换为其地址,无论它有没有EXPORT_SYMBOL出来。
目的达成1: 拿到任意内核symbol的address, 于是我们拿到了sys_call_table的地址,以及我们需要的任意内核函数的地址
但是sys_call_table在内核ro区域,这部分区域在内核初始化结束后会readonly掉。
Init_post -> mark_rodata_ro: 会将.text区域和.rodata区域设置readonly,内核在这里调用的set_pages_ro -> set_memory_ro, 希望它反方向的函数set_pages_rw和set_memory_rw能够帮我们把sys_call_table那一页暂时可写下,
但我实验了下,不行。。
每个页面的属性是在页表最后一层pte的最后12位复用表达的,里面表达了此页可否写,可否执行等很多信息。略去表层的api浮云,我们要让我们需要的地方可写,最终都要动这一页的pte属性。而page,页表信息是可写的:)
直捣黄龙,拿着addr去page table里面走一遭,走到最后的pte, pte |= _PAGE_RW,通过已有可写区域扩大可写区域, 搞定!!
目的达成2: 可对内核任意地址进行改写
最关键的任务达成,篡改linux系统调用就很简单easy了。Init模块时:
1. 找到sys_call_table我们要改的系统调用地址 sys_call_table + __NR_xxx * sizeof(long),保存原始值。
2. 让那一个addr可写
3. *(long *)addr = (long)our_func
4. 把addr再改回只读
Exit模块是init时的拟过程,把原始值再改回去。由于系统调用是用栈传参数,而内核默认参数传递都用寄存器传递。因此必须用asmlinkage声明,否则就取错东西咯。