【问题标题】:What security issues arise in the absence of a kernel stack?在没有内核堆栈的情况下会出现哪些安全问题?
【发布时间】:2020-02-03 16:40:27
【问题描述】:

内核代码使用进程的常规堆栈会导致哪些类型的安全问题?

【问题讨论】:

  • 用户模式代码可以将任何值加载到 e/rsp 中。它不需要是一个有效的地址。

标签: security linux-kernel x86 x86-64 callstack


【解决方案1】:

非特权用户空间使内核崩溃是微不足道的,并且很容易接管它或“只是”获得 root。

内核堆栈指针的用户空间控制(由中断异步使用)破坏了“非特权”代码的任何可能性,假设所述非特权代码是由潜在攻击者控制的机器代码。 或者只是用户空间中的错误会导致内核崩溃。

使内核崩溃可能就像xor esp,esp / int 0x80 或等待定时器中断一样简单。在 RSP 包装到 0xFF...8 之后,这可能会导致页面错误,因为尝试将异常帧推送到未映射的页面上。 (内核使用与用户空间相同的页表;每个 PTE 中都有一个位将其标记为仅内核或不是。)尝试传递该页错误的失败会导致另一个页错误或 GPF,并且繁荣你已经三重错误。

控制 RSP 还可以让您用异常帧轻松覆盖任意内核地址,这可能会影响其他内核上发生的事情。

请注意,我使用了int 0x80 而不是syscall,因为syscall 会跳转到存储在MSR 中的入口点 不会触及内存(或修改RSP)。在这种情况下,内核理论上可以在做任何事情之前检查它是否有效。但是真正的中断(包括软件中断)会在任何内核指令运行之前推送 CS:RIP 和 RFLAGS。 在实际的 x86-64 上,interrupts use a kernel-RSP value from the TSS。 如果这没有发生,用户空间将控制这些商店的虚拟地址。 (IDK,如果它甚至可以配置使用未经修改的用户空间 RSP,或者如果硬件有效地强制使用内核堆栈/每个任务的内核堆栈。)

(通常内核的syscall 入口点使用swapgs 和来自gs:0 的加载或其他东西从内核堆栈底部加载内核堆栈指针。)


接管内核(本地提权):

  • 在一个进程中启动多个线程,因此它们都共享相同的虚拟地址空间。 (或使用 POSIX 共享内存或其他任何东西并在那里设置 RSP)。

  • 一个线程将其堆栈指针存储到其他线程可以读取的全局位置。

  • 该线程进行系统调用; 内核将其堆栈用于内核空间返回地址和数据。选择像open() 或stat() 这样的sys_open() 或sys_stat() 内核函数需要一些时间才能返回,尤其是当它们在路径名解析或访问inode 期间阻塞磁盘I/O 时。

    或者更简单地说,nanosleep。 (休眠的系统调用将用户空间状态保存在内核堆栈中,在上下文切换回此任务并从调用返回到 schedule() 之后,它最终将返回到 ret。)磁盘 I/O 上的阻塞是不必要的复杂。尽管它确实公开了许多文件系统代码作为寄存器值的可能来源;您可以选择要覆盖的返回地址。

  • 在此过程中,另一个用户空间线程修改了该内存,从而控制了内核的 RIP/EIP 和堆栈上的数据。即使使用不可执行的内核堆栈,您也可以做很多事情。通过读取返回地址,您可以击败内核 ASLR,然后知道如何修改它们以跳转到您想要的任何内核代码。

内核使用与用户空间相同的页表,因此可以在进行系统调用之前由mprotect(PROT_EXEC) 设置读/写/执行。可执行堆栈页面将使代码注入变得微不足道。但是the SMEP bit (Supervisor Mode Execution Prevention) introduced in 2010 阻止了这一点,不允许用户空间页面的 ring 0 exec (页表条目中的 U/S 位将始终由用户空间“拥有”的任何页面设置)。 Another more recent blog post.

在create_module(2) 系统调用处理程序中进行权限检查后,您仍然可以将ret 转到某个地方,以从文件系统中加载一个模块,其中包含您在内核空间中运行的代码。 ROP 攻击的攻击面巨大,因为内核拥有每个系统调用的实现,包括特权调用。 更不用说系统调用的各种内部函数和其他东西了 使用,以及大量的驱动代码。


Broadwell 引入了另一个功能,SMAP (Supervisor Mode Access Prevention) 可以防御这种情况。当内核处于活动状态时,如果它尝试读取一个用户页面,它就会出错。必须在copy_to_user() 和copy_from_user() 周围禁用它,但使用指向用户空间内存的 RSP 来实现这些功能似乎不太可能。 call 在推送返回地址时会出错。可能在 32 位内核上,您可能能够在 1:3 用户/内核拆分上方使用 ESP 进行系统调用,因此只有一些嵌套函数调用会从 1G 内核页面的底部进入最高用户页面。但是如果copy_to/from_user 是叶函数(或者在禁用 SMAP 时不进行任何函数调用),我们可能无法攻击它们。

使用 SMAP 使内核崩溃仍然是微不足道的,但它会使非 DoS 漏洞利用更加困难。 (这就是真正的 x86-64 的目的:将可能的漏洞转化为错误。)仍然,在我们假设的没有内核堆栈的 x86 中,将 RSP 设置为内核地址并进行系统调用(并且不在用户空间中使用 [RSP])将允许内核指令覆盖内核数据,SMAP 不会停止。请参阅下面的回复:没有多任务处理。


或者,如果您实际上不想在内核模式下运行代码,您可以只 ret 将您的进程提升为根的代码,设置 EUID = 0。

当到达ret 时,您可以通过选择您进行的系统调用和传递的参数来控制寄存器中的值。以及在哪一层嵌套函数调用覆盖了返回地址。


请注意,即使在单核机器上,阻塞系统调用也使这种攻击成为可能,其中攻击线程无法与内核代码同时运行。它只需要在受害系统调用返回之前获得预定的核心,这就是阻塞的可能。

在没有多任务处理的玩具系统上(没有办法进入内核,在它离开之前运行任何其他用户空间代码),你能做的“所有”就是覆盖带有堆栈帧的任意内核内存地址。包括无效的系统调用(如在 Linux 上返回 -ENOSYS 的 RAX 值)以已知模式转储用户空间寄存器内容,然后在没有太多干扰的情况下返回更多堆栈空间!!!假设syscall 入口点写了类似于 Linux 的东西,它很早就检查了呼叫号码,没有一堆 call/ret 会乱写垃圾,如果你想接管而不是崩溃,你可能不想要它。

一旦您的无效 syscall 返回,您将 RSP 恢复为正常值,然后进行系统调用,利用您刚刚覆盖的任何数据,例如让通常不会成功的系统调用成功。例如chmod + chown 使 SUID-root 可执行,或者如果您设法将当前任务 UID 设置为零,则执行新的 shell。

【讨论】:

  • CR4 中的 SMEP 标志可防止在环 0 中执行用户可访问页面中的代码。
  • @prl:谢谢。因此,在使用它的系统上,您需要依赖 ROP 攻击和内核数据覆盖。 (已经在更新中添加更多关于东西的想法,包括甚至不需要多任务处理的数据覆盖,只是推送用户空间寄存器的系统调用入口点,即攻击者控制的值。
猜你喜欢
  • 2011-12-30
  • 2010-10-22
  • 2012-05-01
  • 2020-01-26
  • 2016-05-12
  • 2016-08-30
  • 2019-10-01
  • 1970-01-01
  • 2011-02-05
相关资源
最近更新 更多