Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1753468
  • 博文数量: 199
  • 博客积分: 10
  • 博客等级: 民兵
  • 技术积分: 6186
  • 用 户 组: 普通用户
  • 注册时间: 2012-10-30 11:01
个人简介

Linuxer.

文章存档

2015年(4)

2014年(28)

2013年(167)

分类: LINUX

2013-11-05 10:33:54

 linux内核模块中,我们可以使用很多内核export出的函数,来实现我们的功能,作为内核附属功能的扩展。但是内核export出的函数很多时候对我们是不够的。常常需要对内核的一些核心数据结构进行查看和修改。这个时候

必须要对内核那个大的image进行修改吗?未必。

内核模块的符号链接过程是这样的,引用本人不久前在学校bbs上的一篇回复:

--------------------------------------开始

内核在载入模块后,解析模块中符号的函数在 
kernel/module.c::find_symbol->each_symbol 

首先在这几个section区域寻找符号 

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

而对于_ksymtab这个内核自定义sectionEXPORT_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. #define EXPORT_SYMBOL(sym) \
  12.     __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 拿到任意内核symboladdress,  于是我们拿到了sys_call_table的地址,以及我们需要的任意内核函数的地址

 

但是sys_call_table在内核ro区域,这部分区域在内核初始化结束后会readonly掉。

Init_post -> mark_rodata_ro:  会将.text区域和.rodata区域设置readonly,内核在这里调用的set_pages_ro -> set_memory_ro 希望它反方向的函数set_pages_rwset_memory_rw能够帮我们把sys_call_table那一页暂时可写下,

但我实验了下,不行。。

每个页面的属性是在页表最后一层pte的最后12位复用表达的,里面表达了此页可否写,可否执行等很多信息。略去表层的api浮云,我们要让我们需要的地方可写,最终都要动这一页的pte属性。而page,页表信息是可写的:)

直捣黄龙,拿着addrpage 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声明,否则就取错东西咯。


阅读(1480) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~