kernel_pwn Double_fetch
wiki上kernel部分的第三道题目,来自0ctf2018 final_baby
这题不太一样的是没有给bzlmage,只给了.ko文件和core.cpio。通过ida打开ko文件查看hex view可以发现内核版本为4.15.0-22-generic SMP mod_unload。
我偷懒直接从p4nda师傅的github上搞了一个,写个shell文件就可以qemu起了。
.ko文件分析:
1 | __int64 init_module() |
init_module()里执行了misc_register()在/dev目录下建立了baby结点,并定义为杂项设备,该设备共享主设备号和open函数调用,有点类似上一篇的core。
1 | __int64 __fastcall baby_ioctl(__int64 a1, int a2) |
baby_ioctl()就类似于main函数了,主要完成的功能是对第二个参数进行检查,若为0x6666,就输出内核里flag的地址;如果为0x1337就会进行三个检查_chk_range_not_ok()函数就是检查a1+a2>a3是否成立。
寄存器状态:
1 | $rdx : 0x00007ffffffff000 → 0x00007ffffffff000 |
通过调试我们可以发现,v2/v7就是是我们传入的结构体,而a3则是一个固定的地址0x7ffffffff000,这个地址是用户空间和内核空间的边界地址,因此我们可以想到该函数就是判断a1+a2的是否在用户空间。
结构体:
1 | struct attr{ |
第一个check是我们输入的结构体是否在用户空间,第二个check是flag是否在用户空间,第三个check是我们结构体的flag_len是否和真正flag长度相等。
过了check之后会将我们输入的flag的地址里的内容和内核里的flag进行比较,相同最终就会输出flag。
分析下来我们发现,内核得到的是一个结构体的指针,而结构体的内容是存放在用户空间的。我们可以先通过0x6666得到flag在内核里的地址,但如果我们在传入的结构体里写入该地址的话就无法通过第二个check(因为该flag在内核空间)。这里就用到了doubel_fetch方法。
原理摘自CTF_WIKI:
1 | Double Fetch 从漏洞原理上属于条件竞争漏洞,是一种内核态与用户态之间的数据访问竞争。 |
因此我们可以先在结构体的flag位置写入用户空间的地址,在创建一个恶意线程,该线程的任务就是不断的修改我们的flag地址为内核地址,如果该修改操作发生在通过check后,那么就可以通过后面的依次比较从而输出flag。
exp:
1 |
|
补充:
编译时要多加-pthread选项gcc exp.c -pthread -static -o exploit
gef attach的命令gef-remote -q localhost:1234
关闭地址随机化方法:
在-append最后加上 nokaslr
参考:
https://ctf-wiki.github.io/ctf-wiki/pwn/linux/kernel/double-fetch-zh/
https://x3h1n.github.io/2019/08/27/20180ctf-final-baby/