GACTF2020
babyqemu
漏洞分析
看下启动脚本,发现自定义了一个设备,应该洞就出在这里
1 |
|
逆一下找到mmio和pmio,发现有个带范围的地址读/写,调试一下发现0x20处有个elf的函数地址,借此可以得到proc_base进而得到system函数地址。
1 | unsigned __int64 __fastcall denc_mmio_read(__int64 a1, unsigned __int64 addr, int a3) |
因为好久没调试qemu的题了,这里还是写点备忘,调试的时候先写exp,gcc ./exp.c -static -o exp
静态编译后拿find . | cpio -o --format=newc > ../rootfs.cpio
打包到文件系统里,启动qemu后拿ps -aux | grep qemu
看下进程号,sudo gdb attach -q [pid]
跟进去,如果函数调用比较多可以先不attach,等到getchar执行时再跟。
之后打算找个函数调用的位置,开始没找到,因为IDA的问题,之后查看system_ptr的引用,发现了mmio_read的一处隐藏后门,在输入地址是0x660和0x664时会leak出system_compat函数的libc地址,到这里其实也没什么用,毕竟我们已经有system@plt了,但是这启发了我们去看汇编,终于最后在pmio_write里发现了另一处后门,当输入地址是0x660时触发调用刚才函数指针的位置。参数为偏移为0的地址内容。
1 | .text:00000000003AA14A cmp [rbp+var_20], 660h |
qemu启动时会通过读取随机数到heap上,每次赋值前要和对应的随机数异或,因此先通过异或0让它们的值leak出,随后写入system@plt以及cat flag
字符串
exp.c
1 |
|
踩坑
这里有个地方困扰我很久,在写入数据的时候只能写入0/0x20,而不能写入0x4/0x24。之后发现mmio_write函数写的有问题,把uint64_t改成32即可。
card
libc 2.31,典型的orw题目,这里记一下如何找好用的gadget以及orw的常规思路
程序逻辑
Add次数最多0x100次,读取的数据首先放到bss段,之后再strcpy拷贝到heap上。出题人可能只想到了strcpy造成的零字节截断,因而预期解是2.29下的off-by-null。实际上因为数据有残留,这里还可以造成off-by-one以及溢出。另外有个后门可以对chunk编辑三次。
1 | __int64 __fastcall get_input(__int64 a1, int sz) |
漏洞利用
通过off-by-one构造overlapping,配合后门改fd到stdout泄露libc,注意bss的长度其实是有限的,如果数据过长会导致写到非法内存区而失败。此后的做法有两种,一种是free_hook+setcontext,2.31下的setcontext参数由rdx控制,因而需要寻找一些magic_gadget。这里寻找的方式是通过IDA将libc的asm导出到文件中,直接find相应的指令,比如这里我通过vscode搜索mov rdx, [rdi+
,即可在libc中找到相关的汇编(这里大概有90条结果,挨个看下),筛选后得到一个好用的gadget如下。之后分配到free_hook改为magic_gadget并在后面布置frame,free(__free_hook)触发调用即可。1
2
3
4
5//here
loc_1547A0:
mov rdx, [rdi+8]
mov [rsp+0C8h+var_C8], rax
call qword ptr [rdx+20h]
另一种方式个人觉得实用性更好一点,首先将free_hook改为printf,通过栈上脏数据leak出返回地址位置,通过第三次的UAF改到返回地址处布置rop调用mprotect改栈区为可执行,并跳转到后面的shellcode orw读取flag。官方那边有第一种方式的exp,这里我贴下第二种。
exp.py
1 | #coding=utf-8 |
student_manager
程序逻辑 && 漏洞利用
这是个cpp的程序,不过还是常规的菜单题,环境是18.04,有double free,hook会自动更新,因此用18.04的IO_FILE攻击。这里的数据结构有点难操作,只能写fd前四个字节,0x18处的8个字节以及0x20处的4个字节。
1 | 00000000 node struc ; (sizeof=0x28, mappedto_10) |
先double free来leak处heap低四字节,劫持到tcache_perthread_struct的0x30处修改name上方的sz为0xb1,之前将count改为0xff,劫持到name这里释放从而leak出libc地址。
之后劫持tcache_bins[0x30]的值到_IO_2_1_stdout_的fp+0xe8,改为system,劫持fp+0xd8的vtable到_IO_str_jumps-0x28,最后add的时候使用一个shell注入语句触发system(“sh”)。之前的操作是把_IO_buf_end改成binsh的地址,这里直接在输入的时候触发调用,因为最后的fflush的参数就是输入缓冲区的地址。
exp.py
1 | #coding=utf-8 |
babyvm
程序逻辑 & 漏洞分析
题目模拟了一个vm,开了seccomp只能orw。有几个函数指针,分别是read/write/puts/free。和以往的题目不同,这里开始的code是作者指定的,如果输入的数据比较短就直接结束了。这里先fuzz一下,1*0x200
打过去发现了错误,查看输入数据(尽量多往后看一点),可以发现后面有个存储着code最后一次执行的地址,我们用第一次输出的机会leak出heap地址,改一下这个code地址,发现可以执行我们自己的代码,只有一次机会,这里先用0x90的指令把code地址改到堆上,然后就可以拿自定义指令做了。
先拿free把一个大堆块释放了,puts泄露出libc地址,通过修改v34[1]
两次可以将free_hook改成setcontext+53,输入sigframe再调用free即可。
1 | case 0x90: |
exp.py
1 | #coding=utf-8 |