使用LKM的系统调用表

时间:2013-08-13 23:33:51

标签: c linux module linux-kernel

我从Linux(3.x)的syscall表中覆盖SYS_READ但是在卸载模块本身时遇到了一些麻烦。 我首先加载我找到syscall表的模块,然后启用RW,用我自己的SYS_READ函数覆盖SYS_READ(实际上除了调用原始SYS_READ之外什么都不做。 1}}),然后我等一会儿,然后卸载模块。在我的模块的卸载方法中,我将原始的SYS_READ函数恢复到syscall表中,并将系统调用表重新设置为RO

原始的SYS_READ功能已正确恢复,但是当我卸载模块时我得到了这个:http://pastebin.com/JyYpqYgL

我错过了什么?恢复真实的SYS_READ后,我应该做些什么吗?

编辑:GitHub链接到项目:https://github.com/alexandernst/procmon

编辑:

这是我获取系统调用表地址的方式:

void **sys_call_table;

struct idt_descriptor{
    unsigned short offset_low;
    unsigned short selector;
    unsigned char zero;
    unsigned char type_flags;
    unsigned short offset_high;
} __attribute__ ((packed));


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


void *get_sys_call_table(void){
    struct idtr idtr;
    struct idt_descriptor idtd;
    void *system_call;
    unsigned char *ptr;
    int i;

    asm volatile("sidt %0" : "=m" (idtr));
    memcpy(&idtd, idtr.base + 0x80 * sizeof(idtd), sizeof(idtd));
    system_call = (void*)((idtd.offset_high<<16) | idtd.offset_low);
    for(ptr=system_call, i=0; i<500; i++){
        if(ptr[0] == 0xff && ptr[1] == 0x14 && ptr[2] == 0x85)
            return *((void**)(ptr+3));
        ptr++;
    }

    return NULL;
}

sys_call_table = get_sys_call_table();

这就是我设置RW / RO的方式:

unsigned long set_rw_cr0(void){
    unsigned long cr0 = 0;
    unsigned long ret;
    asm volatile("movq %%cr0, %%rax" : "=a"(cr0));
    ret = cr0;
    cr0 &= 0xfffffffffffeffff;
    asm volatile("movq %%rax, %%cr0" : : "a"(cr0));
    return ret;
}

void set_ro_cr0(unsigned long val){
    asm volatile("movq %%rax, %%cr0" : : "a"(val));
}

最后,这就是我定义系统调用并更改系统调用表的方法:

asmlinkage ssize_t (*real_sys_read)(unsigned int fd, char __user *buf, size_t count);
asmlinkage ssize_t hooked_sys_read(unsigned int fd, char __user *buf, size_t count);

//set my syscall
real_sys_read = (void *)sys_call_table[__NR_read];
sys_call_table[__NR_read] = (void *)hooked_sys_read;

//restore real syscall
sys_call_table[__NR_read] = (void *)real_sys_read;

2 个答案:

答案 0 :(得分:2)

如果您希望卸载拦截系统调用的模块,请注意当系统调用处理程序中的某些进程和您的代码(模块的文本段)远离内存时的情况。这会导致页面错误,因为当进程从某个内核函数(睡眠)返回到代码时,代码就不再存在了。

因此,正确的模块卸载方案必须检查可能在挂钩系统调用中休眠的进程。只有在没有一个进程在syscall挂钩中休眠时才可以卸载。

<强> UPD

请看,证明我的理论的补丁。它添加了在hooked_sys_read调用时递增和递减的原子计数器。因此我假设有一个进程在模块卸载时仍在read_sys_read中等待。此补丁显示使用printk(read_counter)并为我打印1,这意味着某人不会减少read_counter

http://pastebin.com/1yLBuMDY

答案 1 :(得分:1)

这里有一些随机的谣言,我很不确定任何/所有这些都是有道理的,但它已经很晚了,我宁可把它写下来然后上床睡觉,而不是试着找出究竟是哪一个(如果有的话) )实际上是问题。希望有所帮助:

我认为你已经检查过你的恢复实际上恢复了指针 - 例如打印sys_call_table[__NR_read]的内容?

我肯定会通过或者恢复你清除的位来恢复CR0,而不是恢复旧值 - 大多数时候这可能无关紧要,但CR0中的其他位可能会不时发生变化 - 可能实际上只有TS位,但这已经足够糟糕了 - 获得一些随机恢复过时的浮点或丢失浮点恢复是一件坏事[并猜测是多么容易弄清楚一些长时间运行的数学突然完全得到的原因不正确的结果,因为您的代码提前几个小时卸载了?]。这几乎肯定不是你的代码崩溃的原因,但如果你加载/卸载模块足够多次,它几乎肯定会在某个时刻引起问题。 [另外,确保在更改CR0时不要在处理器之间进行交换 - 可能最好进行某种锁定,以确保在执行整个更新sys_call_table时保持同一处理器。

我认为你的代码崩溃的原因是缺少缓存刷新(操作系统不希望这个内存改变 - 并且进程将其视为只读,因此不需要检查它对于sys_call_table条目,你需要刷新所有处理器上的缓存。我不确定最简单/最好的方法是什么。我认为void flush_icache_range(unsigned long start, unsigned long end)是你需要的电话 - 但是我我不确定这是当前还是旧功能。从这里:https://www.kernel.org/doc/Documentation/cachetlb.txt

正如我最初所说的那样,这比实际调查内核中的内容是如何工作的更多是漫无目的等等。我的美丽睡眠的时间 - 我需要尽可能多的内容......;)