Xnuca2020线上赛
比赛时pwn题都是ruan解的tttttql😍,我TM直接化身ruan师傅迷弟。不亏是中科院,收获满满。
defile
这题在听了ruan师傅讲解之后才终于搞明白了🙆♂️
题目给出了源码,编译好的elf文件,makefile,查看makefile发现gcc defile.c -o defile -lseccomp -g
,开了seccomp以及给出了调试符号,意味着我们可以源码调试了。
接下来就是阅读源代码了,逻辑大概是这样的(如有错误请务必指正):
程序首先mmap一块较大(4096)的shared空间,接下来fork出子进程。该子进程的任务是首先让我们向shared输入4094大小的内容,之后循环(while(1))的fork出孙子进程(相对于父进程而言),fork出的孙子进程会执行我们的输入也即shellcode,执行完后就exit(0),之后子进程在继续fork,循环上述的执行。
这里要注意的一点是再孙子进程跳去执行shellcode的时候:
1 | uint64_t targetaddr = (uint64_t)shared + (i % 2) * ((4096 - 2) / 2); |
可见是有两个入口地址的一个是shared,另一个是shared+2046,也即4096的空间里应该有我们两部分相同的shellcode。
父进程所作的就是通过random生成一段128长度的随机数,并且进行128轮次的循环。每一轮所做的就是比较shared+4095和key[i]的大小,如果相等就往shared+offset(也是随机数)写入0xcccccccccccccccc来破坏我们的shellcode,然后再比较shared+0x4094是否为1,相等则才进行下一轮的比较。执行完128轮之后程序会从pipe里读出128的内容于key比较,相同就给我们flag了。
这样看下来我们的shellcode所要完成的功能为:
1.shared+4095==key[i],需要循环爆破。
2.shared+4095==1
3.write(pipe,&key[i],1)。
由于0xcccccccccccccccc的写入我们可以有这样的思路:
首先从0开始循环增加的向shared+4095写入,每次写入之后都对shellcode进行检查,如果没有0xcccccccccccccccc的出现说明还没爆破到,若有0xcccccccccccccccc说明爆破到了key,之后由于我们再该空间内有两端相同的shellcode,因此可以通过偏移的相对位置利用未被写入0xcccccccccccccccc的那段来修复写入0xcccccccccccccccc的那段shellcode,之后构造write(pipe,&key[i],1)向管道写入Key值,之后exit()。
对应的汇编如下:
1 | code = """ |
开始的时候我们只call check了一次,这样会出现一个问题就是当我们爆破到了key但call check时,父进程还没完成0xcccccccccccccccc的写入导致call check返回0,使得写入的shared+4095会比key大1,为了解决父子进程这种可能的速度差异,我们选择使用了两次call check。
完整exp:
1 | from pwn import * |
cpp
Heap exploiting is so boring .
一道c++写的菜单题,由于没有对指针的清零导致我们可以一直dele,因此我们可以一直dele来填满tecache,从而进入fastibin。
题目限制了malloc的size,但我们可以利用basic_string扩容的规则来触发malloc_consolidate,来得到存放有main_arena的bins。
1 | create_des() |
效果:
1 | pwndbg> bins |
之后就是劫持stdout泄露了,利用small bin和0x40的tecache的overlop改main_arena的后低两个字节,但要先把0x20的tecache申请出来。
leak的时候有个问题要注意:
1 | pwndbg> bins |
在改了main_arena之后unsortedbin里的bin和0x40的第一个tecache是overlop的,这时如果我们直接取0x40的tecache和话就应注意不要改坏unsorted bin的结构,或者我们可以先把unsortedbin申请空在取0x40的tecache,这样就没什么问题了。
exp:
1 | from pwn import * |