将系统调用劫持推广到任何内核符号

时间:2015-07-08 21:38:22

标签: c linux kernel-module

我知道如何在现代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_<system call name>之类的代码。但我怀疑这可能只是系统调用的情况,因为劫持它们涉及替换指向地址的指针。这会与其他符号一起使用吗?如果没有,我怎么能以一种相当简单的方式劫持它们呢?

2 个答案:

答案 0 :(得分:0)

简短的回答:您的方法不适用于通用功能,您希望查看kprobes

答案如下:

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

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

这样做的典型方法是通过调用函数来替换您希望劫持的函数的开头。如果你不关心每一个返回到原来的功能,这不是太困难,你基本上放置一个蹦床,所以无论什么时候预定的功能被称为第一件事而且预期的功能唯一的功能就是调用你的功能。如果你想返回预期的函数,即你希望每次调用某个目标函数时都调用一个函数,然后返回目标函数,事情会变得更加困难。这是因为您在现在需要的函数的开头替换了一些机器代码。这可以通过生成一些机器代码来处理,这些机器代码执行被替换的机器代码所做的事情,然后跳转到原始功能代码的其余部分。这基本上是kprobes正在做的事情,除了kprobes将调试指令(x86的int 3)放在函数的开头,然后调试处理程序调用probe函数而不是调用你的指令。

请注意,这是一个有点高级别的解释,因为细节是特定于体系结构的。例如,如果指令是指令指针相对指令,则当您更换指定指令时,事情会变得复杂,因为指令指针不是通常的指令。我建议查看kprobes以获取一些特定于体系结构的详细信息。

答案 1 :(得分:-1)

Linux内核符号地址可以在/ proc / kallsyms找到。如果访问该部分,则应该是root用户,因为许多内核使用安全性检查不向非特权用户公开内核符号。原因是它在构建攻击向量方面有很大帮助。

这些符号与系统调用表不同,并且完全存储在内核空间中,因此您无法像使用syscall表那样访问这些内存地址。如果您尝试 - 您会发现访问要更改的内存地址会导致程序崩溃。

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

我假设您拥有自己正在尝试此操作的计算机,因此切换到root和cat /proc/kallsyms | grep ns_capable(可能也可能不作为非特权,取决于内核)。现在你有了想要破解的功能开头的地址。

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

现在你必须在内核中找到一个安全性故障(一个弱点)并利用它,这样你就可以写入内核空间并用ns_capable和{{1}替换mov eax, 1的指令这意味着返回true。

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

浏览CVE列表很烦人,许多弱点无法帮助您获得对内核空间的写入权限,但请耐心等待,阅读摘要并发现漏洞利用。

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