关于官方的wp
还没有来得及看,这种方法利用的是越解读写问题导致的权限提升,但感觉竞争条件应该也是可以使用的。
相关代码及脚本下载
漏洞分析
题目实现了一个驱动程序,其中交互接口实现了ioctl
函数——hackme_ioctl
。
数据交互以0x20
大小的结构体作为交互接口,数据结构如下:
1 | 00000000 arg struc ; (sizeof=0x20, mappedto_3) |
在ioctl
程序中根据操作数的不同实现了四类功能:
0x30001 free
1 | if ( v3 == 0x30001 ) |
0x30002 write
1 | if ( v3 == 0x30002 ) |
0x30003 read
1 | else if ( v3 == 0x30003 ) |
0x30000 alloc
1 | v12 = v19.len; |
其中,程序维护了一个全局数组pool
,其第一个成员记录内核堆地址,第二个成员记录堆的大小,位于驱动的.bss
段。不难发现,对于这个数组的存取缺少锁的操作,并且内核以多线程启动,很明显存在竞争类漏洞,如释放内存后立刻竞争读写堆块,造成UAF
等。
此外,在read
和write
功能中存在明显的越界问题:
1 | if ( v10 && v19.offset + v19.len <= (unsigned __int64)v11[1] ) |
可见,v19.offset
为负数时,v19.offset + v19.len
可以向上越界读写任意长度的内存。
漏洞利用
可见该漏洞的品相是比较好的,因为可以申请任意大小的内存。
在系统保护层面,开启了kaslr
、smep
和smap
。
抛砖引玉
最开始看这题的时候觉得还是比较简单的,如果可以提前喷射大量的cred
在申请的内存前的话,通过向前越界读就很容易搜索到cred
结构体,然后再通过将cred
结构体的uid
等值置为0
,就可以提权了。但是在实际操作中发现了问题,正如之前WCTF
题目中提到的,内核cred
分配采用了cred_jar
这个新的kmem_cache
,与kmalloc
使用的kmalloc-xx
是隔离的,而且在尝试的过程中发现可以找到分配出来的cred
结构体,但是在覆写过程中貌似在内存里存在保护的hole
,当调用copy_from_user
从cred
覆写到我们用kmalloc
时,会出现kernel panic
,提示在写一块non whitelist
的内存,这时我们想到如果可以控制驱动模块bss
段上的size
成员时,就可以局部写,来直接修改结构体了。
强行插入了没有做出来的思路先立个flag
,这个cred_jar
的坑还没有填上,有空的时候再继续翻翻源码。
地址泄露
投机取巧的方法用不了了,那就找常规的漏洞利用思路吧,由于开启了kaslr
包含,就先从各个模块的地址入手。
堆地址
堆地址泄露相对来说容易,因为我们知道一种向上越界读的方法,那么就可在堆上进行构造了。
基于slub
分配器,其释放过的堆块类似于glibc
的fastbin
,首先是一种后入先出结构,并且其存在FD
指针指向下一块空闲的块。
那么,通过如下的方法可以很快得到堆地址:
1 | alloc(fd,0,mem,0x100); |
在释放1、3
以前,pool
中的内容:
1 | pwndbg> x /20gx 0xffffffffc0002400 |
释放1、3
之后,pool
中的内容:
1 | pwndbg> x /20gx 0xffffffffc0002400 |
释放的两块堆块中的内容:
1 | pwndbg> x /6gx 0xffff88800017a600 |
可见,利用4
号堆块向前越解读就很容易读出堆地址。
内核基址
内核基址的读取需要一点猜测的成分在,可知0
号内存0xffff88800017a500
之前是已经在用的系统块,那么一定存在一些内核的指针。
1 | read_from_kernel(fd,0,mem,0x200,-0x200); |
从0
号块向前越界搜索,可以发现一个内核地址0xffffffff81849ae0
1 | pwndbg> x /20gx 0xffff88800017a500-0x200 |
而这个内核地址利用/proc/kallsyms
可以很容易发现属于一个固定函数sysctl_table_root
。
1 | /home/pwn |
模块地址
在glibc
中的fastbin attack
常用的方法是劫持fd
指针,多次分配后达到任意地址读写的目的。在slub
中也可以这样来做。
想要劫持fd
指针,可以通过设置offset
为负的方式来修改空闲堆块的指针。
要修改到哪里是一个问题,利用已有的内核地址,可以找到一个包含模块指针的内存位置——mod_tree
1 | /home/pwn |
1 | pwndbg> x /20gx 0xffffffff81811000 |
溢出修改fd
以后,可以发现原有第3
块内存变为:
1 | pwndbg> x /10gx 0xffff88800017a800 |
连续alloc
两次以后可以拿到内核地址。
1 | pwndbg> x /20gx 0xffffffffc0002400 |
此时,我们就可以得到内核模块hackme
的基址了。其中在修改时尽量用负数越界读的方法泄露地址,防止复制毁坏数据。
1 | memset(mem,'A',0x100); |
内存任意写
当泄露了内核基址以后,利用同样的方法,我们可以将保持堆地址大小及堆地址的.bss
段上的pool
申请下来。
1 | delete(fd,2); |
可见,在第8
块,我们拿到了自己本身的地址。
1 | pwndbg> x /20gx 0xffffffffc0002400 |
这样一个交叠的结构,可以使我们向pool
项中增加任意想写的地址和大小,造成任意地址写。
权限提升
以上就解决了我们在抛砖引玉章节提出的控制写长度的问题了。在这个部分以后,提升权限的方法有很多,可以参考之前写过的【KERNEL PWN】从内存任意读写到权限提升一文。
在翻wp
的过程中,发现一种很有意思的解法,通过任意写修改modprobe_path
内容,利用一个非正确格式的ELF
文件触发。
1 | *((size_t *)(mem+0x8)) = 0x100; |
起初依稀记得哪里用到过,后来发现依然来源于dong-hoon you (x82)
的New Reliable Android Kernel Root Exploitation Techniques
,和之前写过的修改poweroff_cmd
异曲同工。
最终结果:
1 | ┌─[p4nda@ubuntu] - [~/Desktop/pwn/hackme] - [Wed May 01, 06:36] |
EXP
1 |
|
Reference
[1] https://kileak.github.io/ctf/2019/xctf-hackme/
[2] https://github.com/perfectblue/ctf-writeups/tree/master/midnightsun-ctf-2019-quals/HFSIPC
[3] New Reliable Android Kernel Root Exploitation Techniques: http://t.cn/Rftu7Dn