Linux内核:系统调用挂钩的例子

我试图写一些简单的testing代码来演示挂钩系统调用表。

“sys_call_table”在2.6中不再被导出,所以我只是从System.map文件中获取地址,而且我可以看到它是正确的(通过查找我发现的地址的内存,我可以看到指向系统调用)。

但是,当我尝试修改这个表时,内核给出了一个“Oops”和“无法在虚拟地址c061e4f4处理内核寻呼请求”,机器重新启动。

这是运行2.6.18-164.10.1.el5的CentOS 5.4。 有某种保护措施,还是只是有一个错误? 我知道它带有SELinux,我试过把它放到宽松模式,但是没有什么区别

这是我的代码:

#include <linux/kernel.h> #include <linux/module.h> #include <linux/moduleparam.h> #include <linux/unistd.h> void **sys_call_table; asmlinkage int (*original_call) (const char*, int, int); asmlinkage int our_sys_open(const char* file, int flags, int mode) { printk("A file was opened\n"); return original_call(file, flags, mode); } int init_module() { // sys_call_table address in System.map sys_call_table = (void*)0xc061e4e0; original_call = sys_call_table[__NR_open]; // Hook: Crashes here sys_call_table[__NR_open] = our_sys_open; } void cleanup_module() { // Restore the original call sys_call_table[__NR_open] = original_call; } 

我终于自己find了答案。

http://www.linuxforums.org/forum/linux-kernel/133982-cannot-modify-sys_call_table.html

内核在某个时刻被改变了,所以系统调用表是只读的。

cypherpunk:

即使是晚了,但解决scheme也可能让其他人感兴趣:在entry.S文件中你会发现:代码:

 .section .rodata,"a" #include "syscall_table_32.S" 

sys_call_table – > ReadOnly如果你想用sys_call_table“破解”你必须编译新的内核…

该链接也有一个更改内存可写的示例。

nasekomoe:

大家好。 感谢您的回复。 我很久以前通过修改对内存页面的访问来解决这个问题。 我已经实现了两个函数,为我的上一级代码:

 #include <asm/cacheflush.h> #ifdef KERN_2_6_24 #include <asm/semaphore.h> int set_page_rw(long unsigned int _addr) { struct page *pg; pgprot_t prot; pg = virt_to_page(_addr); prot.pgprot = VM_READ | VM_WRITE; return change_page_attr(pg, 1, prot); } int set_page_ro(long unsigned int _addr) { struct page *pg; pgprot_t prot; pg = virt_to_page(_addr); prot.pgprot = VM_READ; return change_page_attr(pg, 1, prot); } #else #include <linux/semaphore.h> int set_page_rw(long unsigned int _addr) { return set_memory_rw(_addr, 1); } int set_page_ro(long unsigned int _addr) { return set_memory_ro(_addr, 1); } #endif // KERN_2_6_24 

这是原始代码的修改版本,适用于我。

 #include <linux/kernel.h> #include <linux/module.h> #include <linux/moduleparam.h> #include <linux/unistd.h> #include <asm/semaphore.h> #include <asm/cacheflush.h> void **sys_call_table; asmlinkage int (*original_call) (const char*, int, int); asmlinkage int our_sys_open(const char* file, int flags, int mode) { printk("A file was opened\n"); return original_call(file, flags, mode); } int set_page_rw(long unsigned int _addr) { struct page *pg; pgprot_t prot; pg = virt_to_page(_addr); prot.pgprot = VM_READ | VM_WRITE; return change_page_attr(pg, 1, prot); } int init_module() { // sys_call_table address in System.map sys_call_table = (void*)0xc061e4e0; original_call = sys_call_table[__NR_open]; set_page_rw(sys_call_table); sys_call_table[__NR_open] = our_sys_open; } void cleanup_module() { // Restore the original call sys_call_table[__NR_open] = original_call; } 

谢谢Stephen,你的研究对我有帮助。 我有一些问题,因为我试图在2.6.32内核上,得到WARNING: at arch/x86/mm/pageattr.c:877 change_page_attr_set_clr+0x343/0x530() (Not tainted)后面跟着一个内核OOPS关于不能写入内存地址。

上面这行提到的评论说:

 // People should not be passing in unaligned addresses 

以下修改的代码工作:

 int set_page_rw(long unsigned int _addr) { return set_memory_rw(PAGE_ALIGN(_addr) - PAGE_SIZE, 1); } int set_page_ro(long unsigned int _addr) { return set_memory_ro(PAGE_ALIGN(_addr) - PAGE_SIZE, 1); } 

请注意,在某些情况下,这仍然不会将页面设置为读取/写入。 在set_memory_rw()内部调用的set_memory_rw() static_protections()函数会在_PAGE_RW移除_PAGE_RW标志:

  • 它在BIOS区域
  • 地址在.rodata里面
  • CONFIG_DEBUG_RODATA被设置,内核被设置为只读

我在debugging后发现,为什么在修改内核函数的地址时仍然“无法处理内核分页请求”。 我最终能够通过查找地址的页表项来手动解决这个问题,并手动将其设置为可写。 谢天谢地, lookup_address()函数在2.6.26+版本中被导出。 以下是我写的代码:

 void set_addr_rw(unsigned long addr) { unsigned int level; pte_t *pte = lookup_address(addr, &level); if (pte->pte &~ _PAGE_RW) pte->pte |= _PAGE_RW; } void set_addr_ro(unsigned long addr) { unsigned int level; pte_t *pte = lookup_address(addr, &level); pte->pte = pte->pte &~_PAGE_RW; } 

最后,虽然Mark的答案在技术上是正确的,但是在Xen内部运行时会遇到问题。 如果要禁用写保护,请使用读/写cr0function。 我macros观他们这样的:

 #define GPF_DISABLE write_cr0(read_cr0() & (~ 0x10000)) #define GPF_ENABLE write_cr0(read_cr0() | 0x10000) 

希望这可以帮助那些绊倒这个问题的人。

请注意,以下内容也将工作,而不是使用change_page_attr,不能折旧:

 static void disable_page_protection(void) { unsigned long value; asm volatile("mov %%cr0,%0" : "=r" (value)); if (value & 0x00010000) { value &= ~0x00010000; asm volatile("mov %0,%%cr0": : "r" (value)); } } static void enable_page_protection(void) { unsigned long value; asm volatile("mov %%cr0,%0" : "=r" (value)); if (!(value & 0x00010000)) { value |= 0x00010000; asm volatile("mov %0,%%cr0": : "r" (value)); } } 

如果您正在处理内核3.4及更高版本(它也可以使用较早的内核,我没有testing它),我会build议一个更聪明的方法来获取系统调用表的位置。

例如

 #include <linux/module.h> #include <linux/kallsyms.h> static unsigned long **p_sys_call_table; /* Aquire system calls table address */ p_sys_call_table = (void *) kallsyms_lookup_name("sys_call_table"); 

而已。 没有地址,在我testing过的每个内核上都能正常工作。

用同样的方法可以使用你的模块中没有导出的内核函数:

 static int (*ref_access_remote_vm)(struct mm_struct *mm, unsigned long addr, void *buf, int len, int write); ref_access_remote_vm = (void *)kallsyms_lookup_name("access_remote_vm"); 

请享用!