在没有内核堆栈的情况下会出现什么安全问题?

问题描述 投票:1回答:1

使用常规进程堆栈的内核代码可能导致什么类型的安全性问题?

security linux-kernel x86 x86-64 callstack
1个回答
4
投票

对于无特权的用户空间,如果崩溃会使内核崩溃,并且很容易将其接管或“仅仅”获得root用户,就很容易。

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

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

注意,我使用int 0x80而不是syscall,因为syscall跳转到存储在MSR without

触摸式内存中的入口点(或修改RSP)。在这种情况下,内核在做任何事情之前都可以检查它是否有效。但是真正的中断(包括软件中断)会在运行任何内核指令之前先推送CS:RIP和RFLAGS。 在实际的x86-64上,interrupts use a kernel-RSP value from the TSS。如果没有发生,则用户空间将控制这些存储的虚拟地址。 (IDK,甚至有可能将其配置为使用未经修改的用户空间RSP,或者如果HW有效地强制拥有内核堆栈/每个任务内核堆栈。)

(通常,内核的syscall入口点使用swapgs并从gs:0进行加载,或者使用某种方法从内核堆栈的底部加载内核堆栈指针。]


接管内核(本地特权升级):
  • 在一个进程中启动多个线程,因此它们都共享相同的虚拟地址空间。 (或者使用POSIX共享内存或其他任何东西,然后在其中设置RSP。)

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

  • 该线程进行系统调用; 内核将其堆栈用于内核空间返回地址和数据

  • 。选择open()stat()之类的将需要一些时间才能使sys_open()sys_stat()内核函数返回,尤其是如果它们在路径名解析或访问inode期间阻塞在磁盘I / O上时。

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

  • 正在发生这种情况,另一个用户空间线程修改了该内存

    ,从而控制了内核的RIP / EIP和堆栈上的数据。即使使用不可执行的内核堆栈,您也可以做很多事情。通过阅读返回地址,您可以击败内核ASLR,然后知道如何修改它们以跳转至所需的任何内核代码。
  • 内核使用与用户空间相同的页表,因此可以在进行系统调用之前由mprotect(PROT_EXEC)设置读/写/执行。可执行的堆栈页面会使代码注入变得微不足道。但是the SMEP bit (Supervisor Mode Execution Prevention) introduced in 2010阻止了此操作,不允许用户空间页面的环号0 exec(页面表项中的U / S位始终由用户空间“拥有”的任何页面设置)。 Another more recent blog post

    在对ret系统调用处理程序进行权限检查之后,您仍然可以将create_module(2)移到某个地方,以从文件系统中加载模块,其中包含在内核空间中运行的代码。 ROP攻击的攻击面是huge,因为内核具有每个系统调用的实现,包括特权的实现。

    更不用说系统调用和<< use >>的其他各种内部功能了,并且大量的驱动程序代码。

    Broadwell引入了另一个功能,SMAP (Supervisor Mode Access Prevention)可以防御此问题。在活动状态下,如果内核尝试甚至read

    用户页面,内核也会出错。必须在copy_to_user()copy_from_user()附近将其禁用,但似乎不太可能通过RSP指向用户空间存储器来实现这些功能。 call会在推送返回地址时出错。可能在32位内核上,您可能可以使用ESP在1:3用户/内核拆分上方进行系统调用,因此只有一些嵌套函数调用会从1G内核页面的底部进入最高用户页面。但是,如果copy_to/from_user是叶函数(或者在禁用SMAP的情况下不进行任何函数调用),我们可能无法攻击它们。使用SMAP崩溃内核仍然是微不足道的,但是这会使非DoS攻击更加困难。 (这是在真正的x86-64上的目的:将可能的利用变成错误。)但是,在我们假设的没有内核堆栈的x86中,将RSP设置为内核地址并进行系统调用(并且不在用户空间中使用[RSP])将允许通过内核指令覆盖内核数据,而SMAP不会停止。参见下面的re:无多任务处理。


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

    您可以在到达ret时控制寄存器中的值,方法是选择进行的系统调用以及传递的args。以及调用哪个嵌套函数级别来覆盖返回地址。


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

    在没有多任务处理的玩具系统上

    (无法进入运行任何其他用户空间代码的内核之前,您都可以做“ [全部覆盖任意内核内存”)带堆栈框架的地址。包括无效的系统调用(如在Linux上返回-ENOSYS的RAX值)以已知模式转储用户空间寄存器内容,然后返回而不会过多干扰更多堆栈空间!假设syscall入口点写的是类似Linux的入口点,它很早就检查了电话号码,而没有一堆电话/回拨,如果您想接管而不是仅仅崩溃,就会在不需要的地方乱写垃圾。[无效的syscall返回后,您立即将RSP恢复为正常值,然后进行系统调用,利用您刚刚覆盖的任何数据,例如让系统调用成功通常不会成功。例如chmod + chown创建SUID根可执行文件,或者如果您设法将当前任务的UID设置为零,则执行新的Shell。]
    © www.soinside.com 2019 - 2024. All rights reserved.