使用常规进程堆栈的内核代码可能导致什么类型的安全性问题?
崩溃内核可能很简单,例如xor esp,esp
/ int 0x80
或等待计时器中断。在RSP包装到0xFF...8
之后,尝试将异常帧推送到未映射的页面上可能会导致页面错误。 (内核使用与用户空间相同的页面表;每个PTE中都有一点将其标记为仅内核)。尝试传递该页面错误的失败会导致另一个页面错误或GPF,并且繁荣您三重错误。
通过RSP的控制,您还可以使用异常帧轻松覆盖任意内核地址,从而可能影响其他内核上发生的事情。
注意,我使用int 0x80
而不是syscall
,因为syscall
跳转到存储在MSR without
(通常,内核的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,因为内核具有每个系统调用的实现,包括特权的实现。
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。]