利用模块添加系统调用(不重新编译内核)

其实用这个标题随便baidu、Google出来都是一大堆,大部分都是转来转去,代码无非那么几种。可是真正编译通过还是费了不少功夫,我在双系统的Ubuntu10.04和虚拟机里的Red Hat9里来来回回不知折腾了多少次。所以本文更多的是记录下自己调试的细节,而不是简单的粘代码和转载。

  目的是在不重新编译内核的前提下添加系统调用,思路倒是很简单,修改映射在内存中的系统调用表,把一个空闲的系统调用表项指向自己写的模块中的函数,如果是已使用的表项,甚至可以实现系统调用劫持。

  分配的空闲的系统调用号依然要在源码的asm/unistd.h中去找,只是不用修改。如果没有unused的,怕是还是得重新编译内核了,毕竟系统调用表在编译后大小就固定了,映射到内存中也是固定的,在内存中这个表的最后新增加表项难免会溢出。

  找到系统调用号后,还要找系统调用表在内存中的位置。当前系统可以查看/proc/kallsyms的。对于不同的内核,它放在编译后的System.map中,同样可以在/boot/System.map.X.X.XX.XX中查看。我的是c057e110,由于它是十六进制数,这在源代码中前面还要加0x。而且通过R标志可以看出它是只读的。

  为了修改内存中的表项,还要修改寄存器写保护位,否则是不能修改的。它位于cr0的16位,其修改和保存由模块初始化函数实现。

  模块代码基本是参考http://www.lupaworld.com/home.php?mod=space&uid=401174&do=blog&id=229115,几乎没有改动,只是系统调用提供的功能不同,这不是重点。

syscall.c


#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/unistd.h>
#include <linux/time.h>
#include <asm/uaccess.h>
#include <linux/sched.h>


#define __NR_syscall 223   
#define SYS_CALL_TABLE_ADDRESS 0xc057e110


unsigned int clear_and_return_cr0(void);
void setback_cr0(unsigned int val);


int orig_cr0;
unsigned long *sys_call_table = 0;
static int (*anything_saved)(void);
 


unsigned int clear_and_return_cr0(void)
{
    unsigned int cr0 = 0;
    unsigned int ret;


    asm volatile ("movl %%cr0, %%eax"
            : "=a"(cr0)
         );
    ret = cr0;


    cr0 &= 0xfffeffff;
    asm volatile ("movl %%eax, %%cr0"
            :
            : "a"(cr0)
         );
    return ret;
}


void setback_cr0(unsigned int val)
{
    asm volatile ("movl %%eax, %%cr0"
            :
            : "a"(val)
         );
}


asmlinkage long sys_mycall(long arg1, long arg2, char func, long *ret_val)
{
    switch(func) {
        case '+':*ret_val = arg1 + arg2;return 1;
        case '-':*ret_val = arg1 - arg2;return 1;
        case '*':*ret_val = arg1 * arg2;return 1;
        case '/':if (arg2 == 0) {*ret_val = 0;return 0;}
                else {*ret_val = arg1 / arg2;return 1;}
        default: *ret_val = 0;return -1;
        }
}




int init_addsyscall(void)
{
    sys_call_table = (unsigned long *)SYS_CALL_TABLE_ADDRESS;
    anything_saved = (int(*)(void))(sys_call_table[__NR_syscall]);
    orig_cr0 = clear_and_return_cr0();
    sys_call_table[__NR_syscall] = (unsigned long)&sys_mycall;
    setback_cr0(orig_cr0);
    return 0;
}


void exit_addsyscall(void)
{
    orig_cr0 = clear_and_return_cr0();
    sys_call_table[__NR_syscall] = (unsigned long)anything_saved;
    setback_cr0(orig_cr0);
    printk("call exit....\n");
}




module_init(init_addsyscall);
module_exit(exit_addsyscall);


MODULE_AUTHOR("WY");
MODULE_LICENSE("GPL");

  测试代码

#include <stdio.h>
#include <stdlib.h>

int main()
{
    long x = 0;
    syscall(223,1,1,'+',&x);
    printf("syscall result is %ld\n", x);
    return 0;
} 
复制代码

 

  最初用带参数的gcc编译时总是出错,看了一些帖子,照猫画虎的写了一个Makefile,现在觉得它是很有必要的:

复制代码
obj-m := syscall.o
KERNELDIR := /lib/modules/2.6.32.22/build 
PWD := $(shell pwd) 

modules: 
    $(MAKE) -C $(KERNELDIR) M=$(PWD) modules 

modules_install: 
    $(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install
复制代码

  模块倒是正确生成了syscall.ko,但是insmod时终端直接显示“段错误”,lsmod里看却是加载了,但是系统调用的功能无法实现,而且这个模块还卸载不掉,总是显示in use。编译的时候也没有把rmmod的-f功能打开,不能强制卸载,只能重启,相当郁闷。

  没办法,只能找问题所在了。Linux下的调试不是很熟悉,打算用printk手动找bug。以前没用过printk,查询了一下,使用dmesg就可以看到它的输出了。于是在模块里加了一些。首先定位出内联汇编那里执行不下去了,但是与内联汇编的代码比照,反复看都没有问题。不经意间删除了一些空格,替换成了Tab(其他位置也有这样做),再次重启、make、insmod,居然没有提示并加载成功了。运行测试函数,输出了正确结果,这么看来代码本身是没有问题的,问题应该出在复制代码时的空格上。

  这里可以看出,这个模块如果放到别的环境中是不能运行的,移植时,系统调用号和系统调用表地址必须做出相应的修改。

作者:五岳 
出处:http://www.cnblogs.com/wuyuegb2312 
对于标题未标注为“转载”的文章均为原创,其版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。


编译了一个模块,在内核空间启动一个实时任务。
insmod 后直接提示“已杀死”。
lsmod 后发现这个模块正在被使用(used 一栏显示 1),但是后面没有显示是被哪个模块使用...(by一栏是空的),所以也没办法卸载,rmmod -f 也不好用...
请问这种情况一般是什么原因造成的?

 

 

 

从汇编看有可能是gcc优化导致在访问0xc05fd5dc时,cr0还没有被修改.把gcc的编译优化关掉,即在makefile中加一行:
EXTRA_CFLAGS= -O0,再编译试下.


  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值