3

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

原始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;
4

2 回答 2

2

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

因此,正确的模块卸载方案必须检查可能在挂钩系统调用中休眠的进程。仅当系统调用挂钩中没有一个进程处于休眠状态时才可能进行卸载。

UPD

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

http://pastebin.com/1yLBuMDY

于 2013-08-14T13:07:05.177 回答
1

这里有一些随机的杂乱无章,我不确定任何/所有这些都有意义,但是已经很晚了,我宁愿把它写下来然后上床睡觉,而不是试图弄清楚到底是哪个(如果有的话)问题。希望有帮助:

我认为您已经检查过您的恢复是否确实恢复了指针 - 例如打印的内容sys_call_table[__NR_read]

我肯定会通过 or-ing 恢复您清除的位来恢复 CR0,而不是恢复旧值 - 大多数时候这可能无关紧要,但 CR0 中的其他位可能会不时改变 - 可能只是真的TS 位,但这已经够糟糕了 - 随机恢复陈旧的浮点数或丢失浮点数恢复是一件坏事 [猜猜想找出一些长时间运行的数学突然得到完全不正确结果的原因是多么容易,因为你的代码几个小时前卸载了?]。这几乎肯定不是您的代码崩溃的原因,但如果您加载/卸载模块的次数足够多,它几乎肯定会在某一时刻引起问题。[还,sys_call_table东西]。

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

正如我最初所说的那样,这比实际研究内核内部的工作方式等等更多的是漫无边际。我的美容觉时间到了 - 我需要尽可能多的东西......;)

于 2013-08-14T00:48:17.080 回答