这一篇讲 ret2usr 攻击和 smep 保护。主要参考了 Pratical SMEP bypass techniques on Linux。
ret2usr
ret2usr 攻击利用了 用户空间的进程不能访问内核空间,但内核空间能访问用户空间 这个特性来定向内核代码或数据流指向用户控件,以 ring 0
特权执行用户空间代码完成提权等操作。
2018 强网杯 - core
上一篇分析了使用 kernel rop 完成提权拿 shell 的步骤,这一篇分析一下使用 ret2usr 手法获取 root shell。
题目就不再分析了,直接分析 exp。
|
|
比较一下和 kernel rop 做法的异同。
- 通过读取
/tmp/kallsyms
获取commit_creds
和prepare_kernel_cred
的方法相同,同时根据这些偏移能确定 gadget 的地址。 - leak canary 的方法也相同,通过控制全局变量
off
读出 canary。 - 与 kernel rop 做法不同的是 rop 链的构造
- kernel rop 通过 内核空间的 rop 链达到执行
commit_creds(prepare_kernel_cred(0))
以提权目的,之后通过swapgs; iretq
等返回到用户态,执行用户空间的system("/bin/sh")
获取 shell - ret2usr 做法中,直接返回到用户空间构造的
commit_creds(prepare_kernel_cred(0))
(通过函数指针实现)来提权,虽然这两个函数位于内核空间,但此时我们是ring 0
特权,因此可以正常运行。之后也是通过swapgs; iretq
返回到用户态来执行用户空间的system("/bin/sh")
- kernel rop 通过 内核空间的 rop 链达到执行
从这两种做法的比较可以体会出之所以要 ret2usr
,是因为一般情况下在用户空间构造特定目的的代码要比在内核空间简单得多。
SMEP
为了防止 ret2usr
攻击,内核开发者提出了 smep
保护,smep 全程(Supervisor Mode Execution Protection),是内核的一种保护措施,作用是当 CPU 处于 ring0
模式时,执行 用户空间的代码
会触发页错误;这个保护在 arm 中被称为 PXN
。
通过 qemu 启动内核时的选项可以判断是否开启了 smep 保护。
|
|
也可以通过
|
|
检测该保护是否开启。
smep 和 CR4 寄存器
系统根据 CR4 寄存器的值判断是否开启 smep 保护,当 CR4 寄存器的第 20 位是 1 时,保护开启;是 0 时,保护关闭。
例如,当
|
|
时,smep 保护开启。而 CR4 寄存器是可以通过 mov 指令修改的,因此只需要
|
|
即可关闭 smep 保护。
搜索一下从 vmlinux
中提取出的 gadget,很容易就能达到这个目的。
- 如何查看 CR4 寄存器的值?
- gdb 无法查看 cr4 寄存器的值,可以通过 kernel crash 时的信息查看。为了关闭 smep 保护,常用一个固定值
0x6f0
,即mov cr4, 0x6f0
。
- gdb 无法查看 cr4 寄存器的值,可以通过 kernel crash 时的信息查看。为了关闭 smep 保护,常用一个固定值
CISCN2017 - babydriver
之前已经分析过使用 uaf 改 cred 的做法,这次换一种方法,通过关闭 smep 保护和 ret2usr 来提权。
这里选取的方法是先通过 uaf 控制一个 tty_struct
结构,在 open("/dev/ptmx", O_RDWR)
时会分配这样一个结构体
tty_struct
的 源码 如下:
|
|
为什么要控制这个结构体呢?因为其中有另一个很有趣的结构体 tty_operations
,源码 如下:
|
|
大把的函数指针(pwn 手的风水宝地),因此设想构造下图所示结构体
|
|
那么我们就可以通过不同的操作(如 write, ioctl
等)来跳转到不同的 evil 了。
对这道题目而言,因为开启了 smep 保护,因此如果想 ret2usr 提权,需要修改 cr4 的值,而控制函数指针是不够的,可以控制函数指针进行 stack pivot 等操作到我们排布 rop 链的空间,通过 rop 关闭 smep,进而进行后续操作。
这道题目没给 vmlinux,需要使用 extract-vmlinux 解压内核镜像。
关闭 smep 保护后,就可以通过 rop 来为所欲为了,最终的 exp 如下:
|
|