BSides Noida CTF 2021
周末和学弟学妹一起打的一场比赛,比较基础,但涉及的知识面较广,有必要整理查漏补缺 url: https://ctftime.org/event/1397 rank 8 with lilac
- babystack: static link, find gadget to trigger your own syscall and ROP
- warmup: glibc2.32 (lastest version) tcache exploitation, xor bypass
- khop: basic linux kernel exploits, use-after-free and zero deference
- babymusl: musl libc heap exploits, unlink vulnerability and ROP
- suscall: basic linux kernel exploits, bugged syscall implemented by host
- trash: glibc2.32 off-by-null, trigger heap overlapping and double free
- Interpreter: virtual machine exploits, out-of-bound read and write
babystack
明显的栈溢出。
静态编译,什么gadget都有,syscall只开了orw,但是并没有找到open。
那就自己凑syscall吧
|
|
warmup
2.32的新版本堆,明显的uaf,增删改打印功能齐全。还是改tcache的fd,打到free_hook,但是需要绕过tcache的异或检测。只需要在分配第一个tcache时uaf泄露出fd位置,便是异或的key(一开始的fd是0)。
详见 https://cloud.tencent.com/developer/article/1643954
|
|
khop
全局变量message uaf,开两个fd,close其中一个,另外一个还可以接着用。
|
|
启动脚本特意关掉了mmap_min_addr,所以直接mmap到0地址处,控制内容导致copy_to_user时造成内核栈溢出。cr4的gadget没得,只能在内核栈上提权了。
需要绕过canary、smep、KPTI,直接swapgs_restore_regs_and_return_to_usermode一把梭
|
|
baby_musl
给的是docker,看一下dockerfile,server就是拿ubuntu2004直接apt装的musl
|
|
所以本地就拿2004做就行,musl libc版本信息如下
|
|
musl堆题,保护全开,有两个洞。
前面add的时候要求idx<4,但show没检查idx
|
|
而chunk和size又是挨在一起的,向size处填充地址,在show的时候将其解析为地址,实现任意地址读。
|
|
free的时候也没有清空指针
|
|
这里简单总结一下musl与glibc的堆管理区别:
- musl没有hook
- musl作为轻量级libc,在分配堆块时会首先考虑libc上的空闲页,不会直接开在heap段上,但这样也使得堆上到处都是libc地址。
- musl类似于只有smallbin和largebin,一般unlink是十分奏效的利用方式
最后思路为:堆上拿到残留的libc地址,利用任意地址读environ拿到栈地址,再在栈上拿到elf加载地址。unlink打到bss段堆指针,实现任意地址写,最后写rop即可。
|
|
teensum
草 直球栈溢出,这题没人做?
给了docker和libc,实际上就是ubuntu20.04,libc也没做什么手脚。
|
|
开了pie,但是栈上本来就有残留地址,直接日就完事了。
|
|
suscall
题目提供了一个任意函数执行的漏洞系统调用,把reboot关了跑一下。妈的直接暴毙
|
|
看了一眼发现开完gzip太大了,所以启动脚本还是不能直接打包成cpio就送进去。顺便改了一下题目启动脚本(甚至有错别字) 这是题目给的脚本,paneic=1是什么鬼?
|
|
改了一下,能跑了
|
|
开了kaslr,但是并没有开kptr_restrict
|
|
所以每次读出来地址就行,然后调用它的syscall在内核态拿到root
|
|
成功拿到…
|
|
看了下discord有位老哥贴的exp,思路一样,证明了这题是多么哈批。
|
|
trash
直接给了堆地址,用于绕过2.32的异或检测。
C++堆,用allocator开的堆,但并没有考常规c++析构函数的double free,而是一个明显的off-by-null。
|
|
但这个'\x00’也导致了后续利用的困难,因为打印函数会在此截断。
|
|
总的来说这题比较麻烦的点在于:
- 所有的读入后面都会添加\x00,同时\n也会读入
- 没有free,只有类似于realloc改变大小时顺带的free
- 开了sandbox,只能orw
- glibc2.32 在输入堆地址时记得mask一下
令人痛心的是,新版本的setcontext已经不用rdi了,导致只能rop
|
|
所以核心思路是off-by-null使chunk的inuse位置0,标志着前一个chunk使空闲的,导致在free当前chunk时触发unlink向前合并。在前一个块精心布局绕过unlink检查,我们就可以向前合并导致堆块重叠。可以直接动态调试绕过unlink检测。
|
|
堆块合并拿到libc基址后,直接分配到environ去泄露stack地址,还是因为有个\x00
看来还得打stdout拿到栈地址,我们知道在IO FILE的flag为某些值时write_base到write_end会打印出来。所以打到stdout,利用write_base拿到栈地址。然后去做栈溢出。
这里发现stdout本身的flag不对,还得从头开始改,这样如果只改一半它在刚分配过去的时候会全部清零引发错误,所以必须得足够大的块一次性把整个stdout伪造出来(只把write指针全改也不行)
exp2.31,加上mask异或就能打远程。总的来说细节还是比较繁琐。
|
|
全场唯一解(2.32)已公布,大体思路一致,但暂时还没明白为什么他需要爆破,没有新版本环境。
|
|
Interpreter
C++实现的vmpwn,逆指令就完事了。
|
|
在解析指令时有明显的逻辑漏洞,本意是只有0-3个寄存器,但是只要不都大于3就不会报错
|
|
所以能以v13为base,以任意一字节为offset,进行读写等操作。
|
|
把v14改成3p1cl337-k3yw0rd就能拿到re的flag(flag1.txt)
pwn题要求拿到flag2.txt,在函数sub_2B47(),c++很难辨认,但是还是能依稀看出open了flag2.txt,并且该函数没有被任何函数调用。
|
|
不管怎么样先执行到这个函数试试吧,考虑直接改返回地址。
改了,发现直接把flag打印出来了。。
|
|
官方writeup如下,这。。成功率50%我表示不能理解。。
|
|