kernel_rop
参考文章:https://ctf-wiki.org/pwn/linux/kernel-mode/exploitation/rop/rop/#_4
内核态的 ROP 与用户态的 ROP 一般无二,只不过利用的 gadget 变成了内核中的 gadget,所需要构造执行的 ropchain 由 system("/bin/sh")
变为了 commit_creds(&init_cred)
或 commit_creds(prepare_kernel_cred(NULL))
,当我们成功地在内核中执行这样的代码后,当前线程的 cred 结构体便变为 init 进程的 cred 的拷贝,我们也就获得了 root 权限,此时在用户态起一个 shell 便能获得 root shell。
状态保存
通常情况下,我们的 exploit 需要进入到内核当中完成提权,而我们最终仍然需要着陆回用户态以获得一个 root 权限的 shell,因此在我们的 exploit 进入内核态之前我们需要手动模拟用户态进入内核态的准备工作——保存各寄存器的值到内核栈上,以便于后续着陆回用户态。
通常情况下使用如下函数保存各寄存器值到我们自己定义的变量中,以便于构造 rop 链
一个通用的pwn板子
编译时需要指定参数:-masm=intel
1 |
|
返回用户态
由内核态返回用户态只需要:
swapgs
指令恢复用户态 GS 寄存器sysretq
或者iretq
恢复到用户空间
那么我们只需要在内核中找到相应的 gadget 并执行swapgs;iretq
就可以成功着陆回用户态。
通常来说,我们应当构造如下 rop 链以返回用户态并获得一个 shell:
1 |
|
例题:强网杯 2018 - core
分析
题目给了 bzImage,core.cpio,start.sh 以及带符号表的 vmlinux 四个文件
前三个文件我们已经知道了作用,vmlinux 则是静态编译,未经过压缩的 kernel 文件,相对应的 bzImage 可以理解为压缩后的文件,更详细的可以看 stackexchange
vmlinux 未经过压缩,也就是说我们可以从 vmlinux 中找到一些 gadget,我们先把 gadget 保存下来备用。
这里建议使用ropper来查找可能会比较方便,或者使用ROPgadget将所有gadget导入一个文本文档
看一下 start.sh
1 |
|
发现内核开启了 kaslr 保护。
解压 core.cpio 后,看一下 init (这里我的脚本已经修改过了)
1 |
|
- 第 9 行中把 kallsyms 的内容保存到了 /tmp/kallsyms 中,那么我们就能从 /tmp/kallsyms 中读取 commit_creds,prepare_kernel_cred 的函数的地址了
- 第 10 行把 kptr_restrict 设为 1,这样就不能通过 /proc/kallsyms 查看函数地址了,但第 9 行已经把其中的信息保存到了一个可读的文件中,这句就无关紧要了
- 第 11 行把 dmesg_restrict 设为 1,这样就不能通过 dmesg 查看 kernel 的信息了
- 第 18 行设置了定时关机,为了避免做题时产生干扰,直接把这句删掉然后重新打包
同时还发现了一个 shell 脚本 gen_cpio.sh,主要是方便打包的脚本
这里运行kernel需要将start.sh内的64改为128,需要将分配给内核的内存调大,因为这里我很多都已经改过了就直接放wiki的操作了
1 |
|
接着分析core.ko
用ida查看,发现在core_copy_func中存在明显的栈溢出
1 |
|
其他函数功能分析可以在wiki中查看,wiki讲的很清楚
思路:
- 通过 ioctl 设置 off,然后通过 core_read() leak 出 canary
- 通过 core_write() 向 name 写,构造 ropchain
- 通过 core_copy_func() 从 name 向局部变量上写,通过设置合理的长度和 canary 进行 rop
- 通过 rop 执行 commit_creds(prepare_kernel_cred(0))
- 返回用户态,通过 system(“/bin/sh”) 等起 shell
exp:
1 |
|