Linux Kernel ROP

来源:互联网 发布:淘宝护肤品有真的吗 编辑:程序博客网 时间:2024/06/11 17:30

介绍

在本教程的第1部分中,我们已经演示了如何找到有用的ROPGadget,并为我们的测试系统(3.13.0-32内核 - Ubuntu 12.04.5 LTS)构建一个特权升级ROP链。我们还开发了一个易受攻击的内核驱动程序,允许任意代码执行。在这部分中,我们将使用这个内核模块在实践中演示ROP链:升级权限,修复系统并对用户空间执行干净的“退出”。

以下是第1部分的ROP链的要求清单:

1 执行特权升级有效载荷2 可以引用驻留在用户空间中的数据(即,允许从用户空间获取数据)3 驻留在用户空间中的指令可能无法执行

第1部分演示的易受攻击的内核模块允许将功能指针设置为任意内存地址,因为缺少绑定检查。我们的简单触发代码如下所示:offset

这里写图片描述
在上面的代码片段中,我们控制在我们易受攻击的内核模块中offset声明的请求值unsigned long。使用这个offset值,我们可以引用任何内核或用户空间的内存地址。

堆栈枢轴

因为我们不能将内核控制流重定向到用户空间地址,所以我们需要在内核空间中寻找合适的Gadget。这个想法是在用户空间中准备我们的ROP链,然后将堆栈指针设置到此ROP链的开头。这样,我们不直接执行驻留在用户空间中的指令,而是从用户空间中获取指向内核空间中的指令。

在入侵点设置我们的弱点功能的断点device_ioctl(),我们可以检查device_ioctl()在取消引用函数指针之前,我们可以控制的寄存器是“static”(在调用之间有一些固定的值)

这里写图片描述

在[1]中,$rax寄存器包含要执行的指令的地址。我们可以提前计算这个地址,因为我们知道ops数组基地址和offset用于计算函数指针地址的传递值fn()。例如,给定的ops基地址0xffffffffaaaaaaaf和offset = 0x6806288,该fn地址变为0xffffffffdeadbeef。

我们可以反转这个逻辑,并尝试找到将给我们在内核空间中执行所需的目标地址的偏移值。有很多堆栈枢纽Gadget。例如,以下是用户空间ROP链中遇到的常见堆栈枢轴:

mov %rsp, %rXx ; retadd %rsp, ...; retxchg %rXx, %rsp ; ret

在内核空间中使用任意代码执行,我们需要将我们的堆栈指针设置为我们控制的用户空间地址。即使我们的测试环境是64位,我们对最后一个堆栈枢纽Gadget感兴趣,但使用32位寄存器,即xchg %eXx, %esp ; ret或xchg %esp, %eXx ; ret。如果我们rXx包含有效的内核内存地址(例如,0xffffffffXXXXXXXX),则该堆栈转移指令将将较低的32位rXx(0xXXXXXXXX作为用户空间地址)设置为新的堆栈指​​针。由于该$rax值在执行之前已知fn(),所以我们知道我们的新用户空间堆栈将在哪里,并相应地mmap。

使用第1部分中的ROPGadget工具,我们可以看到xchg内核映像中有很多合适的堆栈枢轴:

这里写图片描述

选择堆栈枢纽Gadget时,唯一的注意事项是需要对齐8个字节(因为ops是8个字节指针的数组,并且其基址正确对齐)。以下简单的脚本可用于查找合适的Gadget:

这里写图片描述

上面的堆栈地址表示ROP链需要mmaped(fake_stack)的用户空间地址:

这里写图片描述

ret所选堆栈枢轴中的指令具有数字操作数。ret没有参数的指令会将返回地址从堆栈中弹出并跳转到该堆栈。然而,在某些调用约定(例如Microsoft __stdcall)中,被调用方函数负责清理堆栈。在这种情况下,ret使用一个操作数来表示在获取下一条指令后表示从堆栈中弹出的字节数。因此,堆栈枢轴之后的第二个ROPGadget位于偏移位置0x11e8 + 8:一旦执行堆栈枢轴,控件将被传输到下一个Gadget,但堆栈指针将处于$rsp + 0x11e8。

有效载荷

参考第1部分的堆栈布局,我们可以在用户空间中准备ROP链,如下所示:

这里写图片描述

我们对第1部分的ROP链进行了一些修改。特别地,commit_creds()地址被移位了2个指令。这样做的原因是我们正在使用该call指令执行commit_creds()。call在将控制转移到第一条指令之前,该指令将返回地址保存在堆栈上commit_creds()。作为任何其他功能,commit_creds具有序列和结尾,将推送堆栈上的值,然后在返回之前将其弹出堆栈。因此,一旦执行功能,控制将被转移到保存的返回地址。但是,我们希望将其转移到ROP链中的下一个Gadget。要使用该call指令作为ROPGadget,我们可以简单地跳过push序言中的一个说明:

这里写图片描述

跳过push $rbp(和第一个nop)允许使用调用指令作为ROPGadget:堆栈上保存的返回地址将被commit_creds()结尾弹出,ret并将控件转移到链中的下一个Gadget。

固定

上述ROP链将给出我们的调用进程超级用户权限。然而,一旦所有的ROPGadget被执行,控制将被转移到堆栈上的下一条指令,这是一些未初始化的内存值。我们需要以某种方式恢复堆栈指针并将控制权转移回我们的用户空间进程。

您可能会意识到系统调用会一直切换内核/用户空间上下文。一旦进程执行系统调用,它需要恢复其状态,以便它可以在系统调用之后继续执行下一条指令。这通常使用iret(特权返回)指令从内核空间返回到用户空间进程。但是iret(或iretq在我们的情况下为64位操作数)期望一定的堆栈布局如下所示:

这里写图片描述

我们需要扩展我们的ROP链,以包括一个新的用户空间指令指针(RIP),mmaped用户空间堆栈指针(RSP),代码和堆栈段选择器(CS和SS)以及具有各种状态信息的EFLAGS寄存器。可以使用以下save_state()功能从主叫用户空间进程获取CS,SS和EFLAGS值:

这里写图片描述

iretq内核.text段中的指令地址可以通过以下方式获得objdump:

这里写图片描述

最后要注意的是,在执行之前iret,swapgs需要在64位系统上。该指令通过其中一个MSR中的值交换GS寄存器的内容。在进入内核空间例行程序(例如,系统调用)时,swpags执行该操作以获取指向内核数据结构的指针,因此swapgs在返回到用户空间之前需要进行匹配。

我们现在可以将所有的ROP链条放在一起:

这里写图片描述

结果

Ubuntu 12.04.5(x64)的完整漏洞可以在GitHub上找到。首先,我们需要使用基地址获取数组偏移量:

这里写图片描述

然后,将基地址和偏移地址传递给ROP漏洞利用:

这里写图片描述

我们是否提到这将绕过SMEP?:)有更简单的方法绕过SMEP。例如,将CR4位清除为ROP链Gadget,然后在用户空间中执行其余的特权升级有效负载(即,commit_creds(prepare_kernel_cred(0))与iret)。本教程的目标不是绕过一定的保护机制,而是演示内核ROP(整个有效载荷)可以像用户空间中的ROP一样容易地在内核空间中执行。内核ROP有明显的缺点:主要是能够获取对内核引导映像的访问(默认为0600)。这不是库存内核的问题,但是如果没有其他内存泄漏,那么对于自定义内核可能会有问题。

原创粉丝点击