模块添加syscall

在《添加一个syscall》一文中需要修改内核源码和重新编译安装内核才能添加自己的系统调用,这太浪费时间了,调试也很不方便,这次准备实现一个内核模块来添加syscall,这样就不再需要漫长的等待了。
一旦内核编译完成后,其系统调用表大小已确定下来,如果在其后追加,很容易造成内存溢出问题,故而这里只是替换《添加一个syscall》里的314号新增系统调用,既然要替换,那么先要获得系统调用表的起始位置,可由如下方法获得:
1.编译后的Linux源码目录下的System.map文件;
2./proc/kallsyms;
3./boot/System.map-3.13.6 (具体对应自己的版本,我的是3.13.6);
在上面任一文件搜索sys_call_table,即可得到系统调用表的入口位置:
 ffffffff818013c0 R sys_call_table
其中, ffffffff818013c0是64位系统对应的系统调用表入口(我机器上的),R表示该地址是只读的,我们肯定是没有写的权限了,但是Intel的相关手册里有说到CR0(控制寄存器0)的bit 16(第17位)是写保护标志,当该位置位(为1)时只读,清零(为0)时则可读写,那我们玩一个花样就是在修改314号系统调用对应的处理函数时,让bit16为0,改好后恢复原来值,卸载模块时修改314号系统调用为原来对应的函数,这里面会用到内核源码里的宏X86_CR0_WP和read_cr0()以及write_cr0()两函数,均在源码里有注明,模块源码如下:
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/unistd.h>
#include <linux/time.h>
#include <asm/uaccess.h>
#include <linux/sched.h>
#include <uapi/asm/processor-flags.h> //X86_CR0_WP
#include <asm/special_insns.h> //read_cr0(),write_cr0()

#define SYS_SLAM 314
#define SYS_CALL_TABLE_ADDR 0xffffffff818013c0

/*Write Protect (bit 16 of CR0)
* When set, inhibits supervisor-level procedures from writing into read-only pages;
* when clear, allows supervisor-level procedures to write into read-only pages.
*/
//reference:arch/x86/include/uapi/asm/processor-flags.h
//X86_CR0_WP

unsigned long * sys_call_table = 0;
static long (*orig_saved)(void);

MODULE_AUTHOR("X-SLAM");
MODULE_LICENSE("GPL");

asmlinkage long sys_new_slam(void)
{
        printk("I'm new_slam.current->comm=%s\n", current->comm);
        return current->pid;
}

static __init int syscall_module_init(void)
{
        unsigned long cr0;
        cr0 = read_cr0();
        write_cr0(cr0 & ~X86_CR0_WP);

        sys_call_table = (unsigned long *)SYS_CALL_TABLE_ADDR;
        orig_saved = (long(*)(void))(sys_call_table[SYS_SLAM]);
        sys_call_table[SYS_SLAM] = (unsigned long)&sys_new_slam;

        write_cr0(cr0);
        return 0;
}

static __exit void syscall_module_exit(void)
{
        unsigned long cr0;
        cr0 = read_cr0();
        write_cr0(cr0 & ~X86_CR0_WP);

        sys_call_table[SYS_SLAM] = (unsigned long)orig_saved;

        write_cr0(cr0);
}

module_init(syscall_module_init);
module_exit(syscall_module_exit);
相应的Makefile文件内容如下:
obj-m += syscall_module_example.o

CUR_PATH:=$(shell pwd)
LINUX_KERNEL_PATH:=/home/xinu/linux-3.13.6

all:
        make -C $(LINUX_KERNEL_PATH) M=$(CUR_PATH) modules

clean:
        make -C $(LINUX_KERNEL_PATH) M=$(CUR_PATH) clean
相应的源码文件目录树如下:
/home/xinu/xinu/linux_kernel_driver_l1/syscall_module_example/
 ├── Makefile
 └── syscall_module_example.c
 好了,模块部分已完成,make后insmod就可以了,接下来再放出相应测试应用程序源码:
#include <linux/unistd.h>
#include <sys/syscall.h>
#include <stdio.h>

int main(void)
{
        long cur_pid = syscall(314);
        printf("The current PID is %ld\n", cur_pid);
        return 0;
}
其对应的Makefile文件内容如下:
all:
        gcc -o slam_syscall slam_syscall.c

clean:
        rm -rf slam_syscall
相应的应用程序源码文件目录树如下:
/home/xinu/xinu/linux_kernel_driver_l1/syscall_module_test_app/
├── Makefile
└── slam_syscall.c
至此,整个模块及测试应用程序都体验了一次,测试的部分就自行体验对比《添加一个syscall》中添加的系统调用,看看我们的模块加载前后的dmesg区别吧。
到这儿有没有一点“黑客”的味道,我们绕过了系统写保护,强制更改系统调用的处理函数,别说你没感受到,但是有点强调,我们这样做只是图调试方便。
其实insmod时也需要root权限,那么我们仍可以使用之前的内核态与用户态数据交换方式,没必要绕这么大个圈。反正多尝试多学习,但原则还是要的。
参考网址:
http://www.lupaworld.com/home.php?mod=space&uid=401174&do=blog&id=229115
http://www.cnblogs.com/wuyuegb2312/archive/2012/05/20/2510668.html
http://blog.csdn.net/keenite/article/details/20108585
http://blog.csdn.net/dog250/article/details/6446192
http://blog.csdn.net/dog250/article/details/6451762
http://en.wikipedia.org/wiki/Inline_assembler
http://en.wikipedia.org/wiki/Control_register#CR0
http://badishi.com/kernel-writing-to-read-only-memory/
http://books.google.com.hk/books?id=c3SlgrqMid4C&pg=PA438&lpg=PA438&dq=Write+Protect+(bit+16+of+CR0)&source=bl&ots=wVJua3TVVg&sig=pMT4-Hhuo_F-Js_n1fJ_5ee2BeM&hl=en&sa=X&ei=JChrU9j_DoXc8AWm7oKoDg&ved=0CF0Q6AEwBg#v=onepage&q=Write%20Protect%20(bit%2016%20of%20CR0)&f=false

评论

此博客中的热门博文

I/O映射之I/O端口

通过Netlink检测网线插拔

使用seq_file