使用内核模块hook内核系统调用(just for fun)

 

使用内核模块hook内核系统调用(just for fun)

 

在linux内核模块中,我们可以使用很多内核export出的函数,来实现我们的功能,作为内核附属功能的扩展。但是内核export出的函数很多时候对我们是不够的。常常需要对内核的一些核心数据结构进行查看和修改。这个时候
必须要对内核那个大的image进行修改吗?未必。
内核模块的符号链接过程是这样的,引用本人不久前在学校bbs上的一篇回复:
--------------------------------------开始
内核在载入模块后,解析模块中符号的函数在
kernel/module.c::find_symbol->each_symbol

首先在这几个section 区域寻找符号
并在module.c 中声明,段中存放(addr, name) 对。
如果找不到就会遍历模块链表,在各模块的符号中寻找解析。如果仍然找不到就会报函
数未定义错误。
所以模块中函数要能解析,则函数对应的(addr,name )对要出现在__ksymtab(_..)
section 中,或是在已载入的模块中有导出。

而对于_ksymtab 这个内核自定义section ,EXPORT_SYMBOL 这个宏的任务正是插入(addr,
name) 对到这个section
include/linux/module.h::EXPORT_SYMBOL
  1. #define __EXPORT_SYMBOL(sym, sec) \
  2. extern typeof(sym) sym; \
     
  3. __CRC_SYMBOL(sym, sec) \
     
  4. static const char __kstrtab_##sym[] \
     
  5. __attribute__((section("__ksymtab_strings"), aligned(1))) \
     
  6. = MODULE_SYMBOL_PREFIX #sym; \
     
  7. static const struct kernel_symbol __ksymtab_##sym \
     
  8. __used \
     
  9. __attribute__((section("__ksymtab" sec), unused)) \
     
  10. = { (unsigned long)&sym, __kstrtab_##sym }
     

  11.  
  12. #define EXPORT_SYMBOL(sym) \
     
  13. __EXPORT_SYMBOL(sym, "")
于是只有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声明,否则就取错东西咯。
  1. const struct symsearch arr[] = {
  2. { __start___ksymtab, __stop___ksymtab, __start___kcrctab,
     
  3. NOT_GPL_ONLY, false },
     
  4. { __start___ksymtab_gpl, __stop___ksymtab_gpl,
     
  5. __start___kcrctab_gpl,
     
  6. GPL_ONLY, false },
     
  7. { __start___ksymtab_gpl_future, __stop___ksymtab_gpl_future,
     
  8. __start___kcrctab_gpl_future,
     
  9. WILL_BE_GPL_ONLY, false },
     
  10. #ifdef CONFIG_UNUSED_SYMBOLS
     
  11. { __start___ksymtab_unused, __stop___ksymtab_unused,
     
  12. __start___kcrctab_unused,
     
  13. NOT_GPL_ONLY, true },
     
  14. { __start___ksymtab_unused_gpl, __stop___ksymtab_unused_gpl,
     
  15. __start___kcrctab_unused_gpl,
     
  16. GPL_ONLY, true },
     
  17. #endif
     
  18. };
     
  19. _start_..., _stop_... 分别在vmlinux.lds.h中定义:
     
  20. /* Kernel symbol table: Normal symbols */ \
     
  21. __ksymtab : AT(ADDR(__ksymtab) - LOAD_OFFSET) { \
     
  22. VMLINUX_SYMBOL(__start___ksymtab) = .; \
     
  23. *(__ksymtab) \
     
  24. VMLINUX_SYMBOL(__stop___ksymtab) = .; \
     
  25. } \
     
  26. \
     
  27. /* Kernel symbol table: GPL-only symbols */ \
     
  28. __ksymtab_gpl : AT(ADDR(__ksymtab_gpl) - LOAD_OFFSET) { \
     
  29. VMLINUX_SYMBOL(__start___ksymtab_gpl) = .; \
     
  30. *(__ksymtab_gpl) \
     
  31. VMLINUX_SYMBOL(__stop___ksymtab_gpl) = .; \
     
  32. }

你可能感兴趣的:(linux,hook内核系统)