【问题标题】:Generalizing system call hijacking to any kernel symbol将系统调用劫持推广到任何内核符号
【发布时间】:2015-07-08 21:38:22
【问题描述】:

我知道如何在现代 Linux 内核中劫持系统调用,以便为它们设计简单的替换。我用来劫持系统调用的代码通常如下所示:

static unsigned long *sys_call_table = (unsigned long*)<address of system call table>;
…
int make_rw(unsigned long address) {
    unsigned int level;
    pte_t *pte = lookup_address(address, &level);
    if (pte->pte &~ _PAGE_RW) {
        pte->pte |= _PAGE_RW;
    }
    return 0;
}
int make_ro(unsigned long address) {
    unsigned int level;
    pte_t *pte = lookup_address(address, &level);
    pte->pte = pte->pte &~ _PAGE_RW;
    return 0;
}
…
asmlinkage long (*real_<system call name>)(<system call arguments>);

asmlinkage long hijacked_<system call name>(<hijacked system call arguments>) {
    // replacement code goes here
}
…
void hack(void) {
    make_rw((unsigned long)sys_call_table);
    real_<system call name> = (void*)*(sys_call_table + __NR_<system call name>);
    *(sys_call_table + __NR_<system call name>) = (unsigned long)hijacked_<system call name>;
    make_ro((unsigned long)sys_call_table);
}
void restore(void) {
    make_rw((unsigned long)sys_call_table);
    *(sys_call_table + __NR_<system call name>) = (unsigned long)real_<system call name>;
    make_ro((unsigned long)sys_call_table);
}

Linux 导出内核内部使用的其他函数(我认为它们被称为“符号”)。一个这样的符号是capable,在linux/capability.c 中定义为:

bool capable(int cap)
{
    return ns_capable(&init_user_ns, cap);
}

我的理论是我可以使用与劫持系统调用相同的代码,只是没有sys_call_table__NR_&lt;system call name&gt; 之类的位。但我怀疑这可能只是系统调用的情况,因为劫持它们涉及替换指向地址的指针。这可以与其他符号一起使用吗?如果没有,我怎样才能以相当简单的方式劫持它们?

【问题讨论】:

    标签: c linux kernel-module


    【解决方案1】:

    简短的回答:您的方法不适用于通用函数,您想查看kprobes

    长答案如下:

    劫持系统调用如此容易的原因是因为您正在用自己的函数替换原始系统调用的内存地址,因此当查找系统调用表时,您的函数就在那里而不是原始函数。系统调用函数基本上只能通过系统调用表间接调用。如果某些代码直接调用了系统调用函数,那么你的劫持将不起作用。

    对于劫持任何你没有这个简单方法的函数,通用函数可以通过多种方式调用。例如,您不能简单地扫描所有文本并将所有调用指令替换为对函数的调用,因为函数地址可能存储在数据中(想想 C 函数指针)。

    执行此操作的典型方法是将要劫持的函数的开头替换为对函数的调用。如果您不关心每次返回到原​​始函数,这并不太难,那么您基本上是在放置一个蹦床,因此每当调用预期的函数时,第一件事就是调用您的函数。如果您想返回到预期的函数,即您希望每次调用某个目标函数时都调用一个函数,然后再返回目标函数,事情就有点困难了。这是因为您在现在需要的函数的开头替换了一些机器代码。这可以通过生成一些机器代码来处理被替换的机器代码所做的事情,然后跳转到原始功能代码的其余部分。这基本上是 kprobes 所做的,除了 kprobes 将调试指令(x86 的 int 3)放在函数的开头,然后调试处理程序调用探针函数,而不是调用您的指令。

    请注意,这是一个有点高级的解释,因为细节是特定于架构的。例如,如果指令是指令指针相关指令,则当您替换预期的指令时,事情会变得复杂,因为指令指针不是它通常的样子。我建议查看kprobes 了解一些架构特定的细节。

    【讨论】:

      【解决方案2】:

      Linux 内核符号地址可以在 /proc/kallsyms 中找到。如果您访问该部分,您应该是 root,因为许多内核使用安全检查来不将内核符号暴露给非特权用户。原因是它对构建攻击向量有很大帮助。

      这些符号与系统调用表不同,它们完全存储在内核空间中,因此您无法像使用系统调用表那样访问这些内存地址。如果你尝试——你会发现访问你想要改变的内存地址会使你的程序崩溃。

      如果您想更改capable(或者,更深入地,ns_capable),您需要能够写入内核地址空间并覆盖您从 kallsyms 找到的ns_capabale 地址中的二进制代码。没有可以劫持的函数指针,只有实际的二进制文件。

      我假设你拥有你正在尝试这个的机器,所以切换到 root 和cat /proc/kallsyms | grep ns_capable(也可能会或可能不会像非特权一样工作,具体取决于内核)。现在您有了要破解的函数的开头地址。

      如果您在朋友家并且无法访问特权帐户,您仍然可以使用以下指南来查找 kallsyms 的弱点并改变其将符号导出给任何人的行为。

      现在您必须找到内核中的安全故障(一个弱点)并利用它,以便您可以写入内核空间并将ns_capable 的指令替换为mov eax, 1ret,这意味着返回true。

      这实际上是具有挑战性的部分。在这里,您必须找到目标内核在 thisthisthisthis 等网站中容易受到攻击的弱点(你明白了 - 这是公共知识 (;) 并开发一个漏洞利用做你需要的。

      浏览 CVE 列表很烦人,许多弱点不会帮助您获得对内核空间的写入权限,但请耐心等待,通读摘要并找到可利用的弱点。

      我对系统调用内部结构并不深入,但如果您从用户空间运行代码,这意味着您要更改的地址在运行它的进程中。除了直接访问之外没有看到任何其他内容,据我所知,您无法访问进程内存空间之外的内存地址,因为它会出错。

      【讨论】:

        猜你喜欢
        • 2013-03-30
        • 2019-05-16
        • 2012-11-18
        • 2018-09-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-01-06
        相关资源
        最近更新 更多