『pwnable』刷题记录_WP
0x00 前言
pwnable的题挺好玩的,就是难度可能偏高,但是可以练基本功,知道自己的弱项在哪里,所以写WP来做刷题记录。
0x01 start
分析
这篇WP在距离第一次做这题之后一个多月时间又修改了一次,不同的是,这段时间我学了一点汇编,才发现自己之前做题还是处于很懵懂的状态。其实这道题很有意思,程序是用汇编写的,短小精悍,32位无保护。IDA有点无助,如果不熟悉汇编,可以用动调来看发生了什么。从代码上来看,就是两次系统调用,然后就会退出。第一个系统调用显然是write,第二个IDA看不出来,但是从系统调用号(al寄存器处)可以看出来是read函数。
在准备write之前,程序总共push了6次,第一次是把esp中的地址放到了栈上,然后是_exit函数的地址,接下来连续push五次放的是字符串到栈上。我们知道,每push一次sp寄存器就会自动减一个字长。接下来程序把esp的地址作为起始地址然后打印20个字节,刚好对应五次push的内容。所以如果我们定义字符串最后一个字符的地址是buf,那么esp一开始的地址就是buf+0x1C。
打印完之后ecx中的内容没变,紧接着就read60个字节。也就是说程序从buf处开始输入60字节,有溢出,因为此时buf距离ret地址只有 0x14。注意,这里和平时我们常接触的C语言编译的程序不太一样,它没有ebp的存在(整个程序都没出现),所以那个offset _exit其实就是返回地址了。我们又知道,ret完后sp寄存器会自动加一个字长,所以ret完后sp寄存器刚好指向一开始写esp值的栈地址。显然这个地址也是个栈地址,所以我们可以控制执行流,让程序返回到0x8048087处更新ecx后打印,这样我们就泄露了栈地址了。
栈地址有什么用呢?整个程序很简单,也没有后门,也没有libc这一说,所以只能通过syscall来getshell,但是又没有足够的gadget来控制寄存器,所以考虑写shellcode。正好,程序没开NX保护。shellcode只能写到栈上,所以我们需要栈地址。上一步打印完后,程序直接从当前esp处开始输入。但是这里要注意,输入完之后程序依然会执行add esp,14h
和retn
,所以我们需要利用这个retn返回到shellcode处。所以输入的60字节就被这个ret地址切割成了20字节和36字节。我们很难找到少于20字节的shellcode,所以shellcode要写在24个字节之后。别忘了我们接收到的地址在当前esp的地址还要+4,所以我们要返回的地址是接收到的地址再+20就行了。
如果不确定我们接收到的地址到底是哪,可以动调看看,然后手算一下。
EXP
1 | from pwn import * |
0x02 orw
分析
程序十分简单,32位,就是开了一个沙盒,然后读取shellcode后执行。题目也已经提示了要用orw。我们直接用seccomp-tools查看沙盒都开了些什么。
开了些白名单,可以用这些函数,也就是说其他的用不了。程序中read函数可以输入200字节,所以直接使用pwntools的shellcraft工具来生成shellcode就行。&shellcode位于bss段,我们读取的flag也可以存在bss段,注意不要和shellcode有冲突就行。
EXP
1 | from pwn import * |
0x03 calc
单独一篇文章分析
0x21 tcache_tear
分析
题目名字就很明显提示了要用tcachebin attack,给了libc附件,用ROPgadget工具查了一下libc中binsh的偏移,用libcdata网站查出libc是2.27版本的。
说到tcache在2.27,我的第一反应就是double free没有任何检查,后面肯定用得上。然后程序一开始就要你往bss段写一个name,info函数也只是把这个name打印出来,肯定有些倪端,应该是要拿来泄露libc地址了。free功能限制了总共只能释放8个chunk,但是有UAF漏洞,为double free奠定了基础。malloc功能限制了大小为0xFF,并且可以写入的字节数为size-0x10,所以没有堆溢出。整个程序只有一个ptr变量用来储存上一个被申请的chunk的指针,所以一旦申请了新的chunk之后,之前申请的chunk就没法再被释放了。
所以总的思路就是,先泄露libc地址,然后通过double free劫持fd申请chunk到__free_hook,写入system地址后释放一个chunk来getshell,当然这个方式有个前提是在这之前的free不能超过7个;也可以劫持malloc_hook为one_gadget;看到程序当中有个exit本来想着劫持got表为one_gadget,但是看保护开了got表不可写(full relro),所以这个方案没法实现。
泄露libc
这道题不止一种泄露方法,这里先看一种,另外一种有时间再试试。其实这题不太好leak。如果有指针变量的话就可以通过got表来泄露,很可惜这里没有。堆题里另一种常见的泄露方式是通过unsorted bin的fd泄露,所以我们可以伪造一个fake chunk释放后进入unsorted bin来打印,显而易见这个chunk要在name处构造,size要在largebin范围,因为smallbin范围会先进入tcachebin。
释放chunk的时候libc会对chunk有一些检查,我们伪造chunk的话需要绕过这些检查。源码如下:
1 | /* Lightweight tests: check whether the block is already the |
总而言之就是,libc会检查被释放的chunk的下一个chunk和下下个chunk的size字段,所以总的来说要伪造三个chunk,size字段分别为0x501,0x21,0x21就可以了。0x501在程序一开始就写入,下面两个size字段就需要通过double free申请到name+0x500处写入。这里要注意一个问题,我们可以同时写入下面两个chunk,总共需要写入8*4*2
个字节,但是申请一个chunk的时候只能写入size-0x10个字节,所以我们double free的size至少要0x50。
然后第二次double free就申请到name+0x10处(tcachebin链表存的是mem地址),然后释放掉之后0x501size的chunk就会进入unsorted bin,然后fd就是一个和main_arena有固定偏移的地址,打印出来就可以计算出libc地址。
代码:
1 | r.recvuntil(b'Name:') |
EXP
1 | from pwn import * |