linux 2.6.22-14 动态添加syscall - [Linux]

    版权声明:转载时请以超链接形式标明文章原始出处和作者信息及本声明
    http://technica.blogbus.com/logs/18945123.html

    Linux System Call Table


    http://bluemaster.iu.hio.no/edu/dark/lin-asm/syscalls.html

          这两天在做系统软件大作业,写一个kernel module,添加自己的syscall,实现通过访问内核全局数据获得关于所有进程和CPU等信息。作业本身不难,但是被kernel的各个版本搞死了,书上网上很多东西都不对的或者过时的,这里记录下我所做大致过程,以备不时之需,也为其他人提供参考。

         添加syscall有两种办法,一种静态的,需要修改和重新编译内核,另一种就是动态加载。动态加载有的hack的味道,实际上就是劫持篡改内存里的syscall_table。图省事,不高兴每次修改syscall都重新编译内核,我就用动态加载syscall来实现添加syscall。当然kernel module是通过insmod动态加载的。在module初始化时,获取syscall_table并修改相应的offset(offset为__NR_newhandler * 4)处的值为新的handler的函数地址,就完成了添加syscall。具体如何获得syscall_table的地址网上有很多文章,但是这些文章的代码都是抄来抄去,其中都有一个相同的小错误,即搜索call (,%eax,4)的机器码时,应该是p[i]而不是p[0],我一开始没仔细看,结果深受其害,莫名了半天。执行insmod,模块初始化时就完成了syscall的添加。接下来就是如何调用这个新syscall的问题。通常syscall都是通过调用glibc中的库函数间接调用的,但我们自己写的syscall glibc当然是不认识也不向用户提供相应接口的。在老版本kernel中(2.6.15之前),可以通过syscall0(...), syscall1(...)这种来调用syscall,syscallx(...)实际上是定义在<asm/unistd.h>中的一个内嵌asm的macro,x86中就是通过int 0x80直接进入内核态执行系统调用(如下面的代码所示)。我一开始按照这种方法来做,但是发现编译器不认syscall0(...),察看asm/unistd.h发现里面根本没有这个macro,后来URSA告诉我2.6.15之后的版本要用syscall(__NR_xxx, param1,param2,...)这样来直接调syscall,参数可以有也可以没有,看具体的syscall而定。这样就实现了添加syscall以及在用户程序里直接调用syscall。整个做的过程还是碰到了不少零碎地麻烦,kernel的版本比较乱,也没有一个authority来统一说说哪个版本有些什么变化。网上查到的方法大多数都是针对老的版本的,在2.6.22-14下几乎都不能用,当然这个过程也还是学到了不少东西,比如一些gcc扩展 asmlikage,__attribute__((packed)) ,kernel的一些乱七八糟的macro, gcc内嵌汇编,AT&T汇编写法。最后记录下大致过程,也怕时间一长自己忘记。

    #define _syscall0(type,name) \
    type name(void) \
    { \
       long __res; \
       __asm__ volatile ("int $0x80" \
            : "=a" (__res) \
            : "0" (__NR_##name)); \
       __syscall_return(type,__res); \
    }

    1. 做一个kernel module,在这个kernel module的init中,hijack sys_call_table

    这一步网上有现成的代码,但是几乎所有的代码都是转载的,都犯了一个小错误。我一开始没注意,加载模块时一直segment fault。下面是hijack sys_call_table的代码。

    #include <linux/init.h>
    #include <linux/module.h>
    #include <linux/sched.h>

    MODULE_LICENSE("Dual BSD/GPL");

    #define __NR_newhandler 223

    struct { 
        unsigned short limit; 
        unsigned int base; 
    } __attribute__((packed)) idtr;

    struct {
        unsigned short off1; 
        unsigned short sel; 
        unsigned char none, flags; 
        unsigned short off2; 
    } __attribute__((packed)) idt;

    unsigned int* syscall_table;
    unsigned int old_handler; 

    unsigned int get_sys_call_table(void) 

        unsigned int sys_call_off; 
        unsigned int sys_call_table; 
        char* p; 
        int i; 
        // 获取中断描述符表寄存器的地址 
        asm("sidt %0":"=m"(idtr)); 
        printk("addr of idtr: %x\n", &idtr); 
        // 获取0x80中断处理程序的地址 
        memcpy(&idt, idtr.base+8*0x80, sizeof(idt)); 
        sys_call_off=((idt.off2<<16)|idt.off1); 
        printk("addr of idt 0x80: %x\n", sys_call_off); 
        // 从0x80中断服务例程中搜索sys_call_table的地址 
        p=sys_call_off; 
        for (i=0; i<100; i++) 
        { 
            if (p[i]=='\xff' && p[i+1]=='\x14' && p[i+2]=='\x85') 
            { 
               sys_call_table=*(unsigned int*)(p+i+3); 
               printk("addr of sys_call_table: %x\n", sys_call_table); 
               return sys_call_table; 
            } 
        } 
    }

    static int mymod_init(void) {    
        syscall_table = (unsigned int*)get_sys_call_table();
        old_handler = syscall_table[__NR_newhandler];

        //篡改内存中的syscall_table中ofset为__NR_newhandler处的内容,也就是编号

        //为  __NR_newhandler的syscall的处理程序的入口
        syscall_table[__NR_newhandler] = (unsigned long)sys_newhandler;
        return 0;
    }

    把makefile也贴出来。

    # Makefile2.6
    MODNM := syshj1
    ifneq ($(KERNELRELEASE),)
    #kbuild syntax. dependency relationshsip of files and target modules are listed here.
    #mymodule-objs := file1.o file2.o
    obj-m += $(MODNM).o
    else
    PWD  := $(shell pwd)
    KVER ?= $(shell uname -r)
    KDIR := /lib/modules/$(KVER)/build
    all:
     $(MAKE) -C $(KDIR) M=$(PWD) 
    clean:
     rm -rf .*.cmd *.o *.mod.c .tmp_versions *~ *.symvers 
    install:
     insmod $(MODNM).ko
    remove:
     rmmod $(MODNM)
    endif

    2. 在kernel module中实现syscall

    // syscall的实现,这里的代码是sys要做的事情,可以替换为需要的内容

    asmlinkage long sys_newhandler(void) {

        printk(KERN_ALERT "Inside new_handler().\n");
        return 0;
    }

    3. 在用户程序中调用syscall

    在需要调用的地方用syscall(__NR_xxx);  其实也是一个macro,可惜我在一堆代码里没找到 :(

    最后推荐一个好东西 http://lxr.linux.no/  可以在线察看各个版本的kernel source,很方便。

    ps.之所有文章命名为《linux 2.6.22-14 动态添加syscall》是因为觉得linux kernel各个版本太搞了,可能我写的这些在2.6.22-14中可用,也不一定适合别的版本。//~~~


    收藏到:Del.icio.us



     

    引用地址:

    评论

  • 稍微高一点的版本,syscall_table已置为只读了,用modules改syscall_table将引起kernel的page异常!
    aktoon回复wronged说:
    改之前把cr0寄存器的WP位清掉就可以了
    2009-12-27 17:32:49
  • 好,学习了。真厉害。
    现在大学里如果都这么布置作业就强了。
    还是说楼主格外的牛人?
    或者我坐井观天以前把大学计算机教育看扁了?
  • 人民的大救星阿 我搞了一个晚上的获取call_table 火死了 现拷贝下了 再来看看究竟哪里错了

    我在ubuntu 8.04 (kernel 2.6.24) 测试成功
  • 我猜是在google中搜索aktoon的名字可以得到了
  • aktoon的风水宝地
    我开始正式动手了
    版本的确是个大问题,之前许多22.14下的东西都和查到的资料不符合
    革新得厉害啊
    现在统一都用20.15做东西了
    aktoon加油,继续老乱下去
    aktoon回复yaol说:
    @@, 你怎么知道我这个blog啊? 
    最近都没心思写了,等有空把做病毒的东西也整理一篇出来
    2008-06-11 16:21:57
 posted on 2011-08-26 13:48  chingliuyu  阅读(956)  评论(0编辑  收藏  举报