kernel_pwn UAF
内核的UAF和用户态的差不多,不同的是内核使用的是slab/slub分配器来管理堆块。
题目来源:ciscn2017_babydriver
查看启动脚本:
1 | #!/bin/bash |
开启了smep保护(内核态不可执行用户态的代码)。
查看init:
1 | #!/bin/sh |
看到加载了babydriver.ko模块,也就是存在漏洞的lkm了,ida打开开始干活:
关键函数babyopen():
1 | int __fastcall babyopen(inode *inode, file *filp) |
首先看到全局的结构体babydev_struct有两个成员:
1 | babydevice_t struc ; (sizeof=0x10, align=0x8, copyof_429) |
每当打开一个设备时,都会通过kmalloc申请大小为0x40的内存其ptr赋给device_buf,0x40给到device_buf_len。由于它是定义在全局的结构体,我们每次open都会更新该struct的内容。
接下来baby_ioctl():
1 | __int64 __fastcall babyioctl(file *filp, unsigned int command, unsigned __int64 arg) |
先kfree掉struct里device_buf指向的内存,之后kamlloc大小可控的内存并更新结构体。
baby_write():
1 | ssize_t __fastcall babywrite(file *filp, const char *buffer, size_t length, loff_t *offset) |
可以向结构体里的device_buf写入内容。
baby_release():
1 | int __fastcall babyrelease(inode *inode, file *filp) |
kfree掉device_buf,但没置0。
分析完我们可以发现问题就在于我们无论是什么操作都是基于这个babydev_struct结构体,但这个结构体是全局的,如果我们open两次的话第一次kmalloc的ptr就会被第二次kmalloc的ptr覆盖,也就是说我们无论是对fd1进行操作还是对fd2进行操作将会是同一块内存,如果close(fd1)而对fd2进行操作就可实现UAF的效果。
那该如何提权呢?容易想到劫持cred结构体改写uidgid,具体思路如下:
1.open两次得到fd1.fd2
2.利用ioctl改struct保存0xc8大小的堆块(cred结构体大小为0xa8)
3.close(fd1)此时0xa8大小的内存被kfree()
4.fork出子进程,在进程创建时会申请存放cred结构体的内存,此时刚被kfree的堆块被申请出来。
5.通过对fd2进行write操作改写cred结构体的uidgid=0完成提权。
调试:
执行完第二步的结构体:
1 | gef➤ x/20gx 0xffffffffc00024c9-1 |
write前(已经劫持cred):
1 | gef➤ x/20gx 0xffff880003ce16c0-0x10 |
write后:
1 | gef➤ x/20gx 0xffff880003ce16c0-0x10 |
对照内核cred结构体源码:
1 | struct cred { |
exp:
1 |
|