ByteCTF2020 部分pwn&re
比赛打了两天,挺激烈的,尤其最后时刻大家都疯狂冲分,最后没能守住掉到23了🤦
这场比赛给我的最深的感受是我的逆向经验太少了,导致花费了很多时间在没用的函数里,值得好好总结。
gun
第一道pwn题,多亏ruan师傅眼疾手快帮我改好了最后的rop,拿到了二血,不然我还得调半天,这种最后的orw可以当作模板来用,好好体会一下。
题目是道菜单题,2.31的libc,功能大概是buy(购买子弹)–add
1 | __int64 buy() |
load(填充子弹,即将heap链接一起)
1 | __int64 sub_16E1() |
shoot(发射子弹)–dele。
1 | __int64 sub_15F8() |
分析heap的结构体为:
1 | struct container{ |
漏洞点在于load后使用next_heap指针将load的堆块连接起来,但shoot之后next_heap并不会清除,而且heap_ptr也未置0,只是将其free并修改status改为0,但之后我们可以只add回来一个但shoot两个,shoot第二个时会通过next_heap找到那个已经free的堆,由于没有对status的检查导致可以二次dele,但由于2.31加入了tecache的key机制导致tecache_attack无法使用。
构造二次dele的poc:
1 | add(0x60,'a'*8) |
tecache的double free无法使用了,但是fastbin的可以鸭,先填满tecache,再用上面的方法在fastbin构造double free,之后在add时会先取出tecache里的bin,当tecache取完之后会从fastbin取,当第一次从fastbin取时,会把剩下的fastbin放回tecache,因此我们就能利用tecache attack打到free_hook。
1 | pwndbg> bins |
之后取完tecache
1 | pwndbg> bins |
第一次取fastbin时(也即改fd):
1 | pwndbg> bins |
可以看到放回tecache了,由于没size检查,可以打free_hook。
exp:
1 | from pwn import * |
easyheap
这道是ruan师傅做的,我赛后复现了一下
经典菜单题,洞在add里:
1 | printf("Size: "); |
可以看到,v2是第一次输入的size,如果这个size是不符合条件的,就能在执行*((_BYTE *)ptr + v2 - 1) = 0;
时向上写入一个’\0’,可以用来改bin的fd,以构造overlop。
leak是先构造tecache_dup(即一个bin既在tecache,又在fastbin,利用的就是上面的思路)通过触发malloc_consoildate将fastbin放入unsoretdbin,从而leak main_arena。
1 | add(0x50,'a'*8) |
之后还是上面的思路构造overlop。通过改fd的低字节为’\x00’使得chunk->fd指向的地址在chunk下面(距离小于chunk的size),导致overlop。
之后tecache attack打free_hook。
exp:
1 | from pwn import * |
QIAO
这题肝了好久,并不是它难,而是吃了逆向经验匮乏的亏。
首先这题花指令很多”jmp $+5”导致ida无法反编译,那就直接上手调了。
main函数开始先把一个大数放入stack里:
1 | mov dword ptr [rbp-34h], 0B63C9FE1h |
之后通过调试会不断的将这个数和其他的数进行比较,相等的话就跳转。
1 | mov eax, [rbp-38h] |
这个跳转到loc_401ADB进行的是比较的操作,分析得是对argc的检查,如果为2就通过。
之后更新这个大数为501DC785h,并继续和其他数比较,相等的话就跳转。
1 | .text:0000000000401A94 mov eax, [rbp-38h] |
跳到loc_401B1D,call了strlen,完成的是对args的长度进行比较,为0x20即通过。
大数更新为874FBE3Fh,之后再比较跳到了:
1 | .text:0000000000401B79 loc_401B79: ; CODE XREF: .text:0000000000401997↑j |
感觉啥也没干,就更新了大数为8F7A1A66h,再跳:
1 | .text:0000000000401C0C loc_401C0C: ; CODE XREF: .text:00000000004019C3↑j |
这个地方是关键部分。
一般这种要我们输入的逆向题会对我们的输入进行变形,然后进行比较,经分析发现sub_401180完成的就是对输入进行变形。
如我们输入’a’*0x20,内存中’a’占1个字节,而变形后’a’表是16进制的0xa,只占4bit,从而变形为长度为0x10的’aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa’(内存中)。
1 | pwndbg> x/20gx 0x424290 |
而sub_4018C0理论上应该是比较函数,而且分析发现函数的返回后eax会和1比较,为1就表示通过。但这个函数反编译后十分复杂,感觉不像人写的,所以我直接放放弃静态分析(实际上静态分析了好久分析了个寂寞)。
通过调试发现sub_4018C0执行完后会释放一个堆块,而且堆中多了好多奇怪的内容,并且和我们的输入没有关系。
于是我猜测就是和堆里的内容比较,于是在free下断得到堆里的数据为7fa392e666d78abbb655165528fff33f
(经过大小端变换后的)
1 | pwndbg> x/20gx 0x4242b0 //将要free的堆 |
将其作为输入果然返回为1,之后就顺利拿到flag了。🚩
还有一道go的pwn题还没复现好,之后会单独写一篇,ruan师傅tql✨
这场比赛打下来收获颇多,但看着大把的安卓逆向实属难受,其实我们堆的pwn和web解出的题目数量和前面的队伍差不多的,主要吃亏在re,crypto和misc,说到底还是自己不够强。keep it up🐱👤