Home avatar

Wings

Kernel Pwn Heap Basics

专门用来分配以页为单位的大内存空间, 且大小必须是 2 的整数次幂, $2^{order} \times PGSIZE$. 分配时会找 order 对应的块, 如果没有, 则向上找更大的去分割一半, 直到分割出一块 order 对应大小的 (线段树动态开点!). 释放时, 如果可以, 会将相邻的两个合并成一个大的, 一直往上合并.

TODO, 学到再说, 估计学不到 kernel heap 风水了

Slub allocator 最初是 slab allocator, 但是效率不高, 现在普遍是用的一个优化版本 slub allocator. slub allocator 是面向数据对象 (结构体) 的堆分配器, 某个或某些对象 (结构体) 的分配使用对应的某个 slub allocator. 最初 slub allocator 会向 buddy system 申请一个或多个连续页面, 这一整个空间称为一个 slub. 一个 slub 会被划分成多个大小相等的 object, 分配给特定的对象 (结构体) 使用.

slub allocator 的实现在 kernel 里是 kmem_cache 这个结构体, 可以简单认为他是 kmem_cache_cpu + kmem_cache_node. kmem_cache 管理多个 slub, 这些 slub 的 object 大小都相同. cat /proc/slabinfo 可以查看这些 slub allocator. slulb allocator 可以通过 kmem_cache_create() 来创建

Kernel Pwn ROP bypass KPTI

在不开 KPTI 的情况下, 每个进程的页表用时有 kernel space 和 user space 的映射, 但是处于用户态时没有权限访问 kernel space. 陷入内核后不需要切换页表和刷新 TLB, 可以避免很大的开销. 不过在 Intel 的 CPU 上, 存在可以侧信道攻击的硬件漏洞 (在 “用户态” 访问内核态?) 于是产生了这种缓解措施: 内核页表隔离KPTI.

KPTI 的想法也很简单, bug 无法避免, 那就让 bug 不能被利用. 每个进程维护两个页表, 一个是用户态使用的, 另一个是内核态使用的. 用户态使用的页表不再包含 kernel space 的所有映射, 仅仅包含如用于处理系统调用等必要的部分. 而内核态使用的页表则包含所有 kernel space 的映射, 以及所有 user space 的映射 (要 copy from, copy to). 既然内核态用了另一张页表, 那么显然给 user space 读写权限就够了, 所以即使是代码段 (对应的页), 在内核态的这张表中, 也没有可执行权限.

copy from/to user 时的 SMAP

某天突然想到开了 SMAP 那怎么进行 copy 呢?

Kernel Pwn ret2usr

在不开启 SMEP, SMAP, KPTI 的情况下, 内核态是可以执行用户空间的代码的. 于是我们只需要在内核态中控制程序流, 使其跳转到用户空间, 执行我们写好的代码, 如 commit_creds(prepare_kernel_cred(NULL)) 便可提取.

不过, 最终的目的是起一个 shell, 但是在内核态无法完成这件事, 所以还需要返回用户态.

iretq 是 x64 的中断返回指令. 当发生中断 (系统调用除外) 陷入内核时, 会发生这些事:

  1. 切换到内核栈. kernel 在 任务状态段Task State Segment (TSS) 这个数据结构上保存了内核栈段起始地址 (ss0) 和 内核栈顶指针 (rsp0).
  2. 接着在内核栈上压入用户空间的 ss, rsp, rflags, cs, rip.
  3. 之后将用户态的通用寄存器值保存起来, 通用寄存器赋值为 0, 保存用户态 gs 寄存器的值并设置内核的 gs .gs 寄存器在内核态时指向一个进程在内核中相关的数据结构, 其中就包含有 kernel stack canary.

返回用户态时, 发生的事情就反了过来:

  1. 恢复通用寄存器的值
  2. 使用 swapgs 指令恢复用户态 gs 的值
  3. 使用 iretq 指令, 内核将栈上用户态 rip, cs, rflags, rsp, ss 恢复

所以, 我们只需要 swapgsiretq, 就能够回到用户态. 寄存器的值是否恢复对我们的利用来说并不重要, 不恢复也是可以的.