Kernel Pwn ROP bypass KPTI
KPTI
在不开 KPTI 的情况下, 每个进程的页表用时有 kernel space 和 user space 的映射, 但是处于用户态时没有权限访问 kernel space. 陷入内核后不需要切换页表和刷新 TLB, 可以避免很大的开销. 不过在 Intel 的 CPU 上, 存在可以侧信道攻击的硬件漏洞 (在 “用户态” 访问内核态?) 于是产生了这种缓解措施: 内核页表隔离.
KPTI 的想法也很简单, bug 无法避免, 那就让 bug 不能被利用. 每个进程维护两个页表, 一个是用户态使用的, 另一个是内核态使用的. 用户态使用的页表不再包含 kernel space 的所有映射, 仅仅包含如用于处理系统调用等必要的部分. 而内核态使用的页表则包含所有 kernel space 的映射, 以及所有 user space 的映射 (要 copy from, copy to). 既然内核态用了另一张页表, 那么显然给 user space 读写权限就够了, 所以即使是代码段 (对应的页), 在内核态的这张表中, 也没有可执行权限.
某天突然想到开了 SMAP 那怎么进行 copy 呢?
Kernel Pwn ret2usr
ret2usr
在不开启 SMEP, SMAP, KPTI 的情况下, 内核态是可以执行用户空间的代码的. 于是我们只需要在内核态中控制程序流, 使其跳转到用户空间, 执行我们写好的代码, 如 commit_creds(prepare_kernel_cred(NULL))
便可提取.
不过, 最终的目的是起一个 shell, 但是在内核态无法完成这件事, 所以还需要返回用户态.
iretq
是 x64 的中断返回指令. 当发生中断 (系统调用除外) 陷入内核时, 会发生这些事:
- 切换到内核栈. kernel 在 任务状态段 (TSS) 这个数据结构上保存了内核栈段起始地址 (ss0) 和 内核栈顶指针 (rsp0).
- 接着在内核栈上压入用户空间的 ss, rsp, rflags, cs, rip.
- 之后将用户态的通用寄存器值保存起来, 通用寄存器赋值为 0, 保存用户态 gs 寄存器的值并设置内核的 gs .gs 寄存器在内核态时指向一个进程在内核中相关的数据结构, 其中就包含有 kernel stack canary.
返回用户态时, 发生的事情就反了过来:
- 恢复通用寄存器的值
- 使用
swapgs
指令恢复用户态 gs 的值 - 使用
iretq
指令, 内核将栈上用户态 rip, cs, rflags, rsp, ss 恢复
所以, 我们只需要 swapgs
和 iretq
, 就能够回到用户态. 寄存器的值是否恢复对我们的利用来说并不重要, 不恢复也是可以的.
笔记「计算机安全导论」
笔记「离散数学 II」
笔记「微机原理」
笔记「数据库系统概论」
2023 N1CTF Junior Pwn ShellcodeMaster
👴 是 Master