0x00 前言
这是一道CTF-WIKI
对于Fastbin Attack
的一道例题,本题利用的技术:堆溢出、Fastbin Attack、Arbitrary Alloc
参考链接:
这里感谢看雪论坛和CTF-WIKI的师傅的详细讲解。
题目下载地址:下载地址
0x01 分析
1 | Arch: i386-32-little |
程序功能及漏洞分析
程序有6个基本功能
1 | 1. Add new rifle |
在功能1. Add
中
1 | unsigned int add() |
可以看到,两次fgets都存在堆溢出,我们的name大小为27字节,description大小为25字节,但是却都允许输入56字节。
堆块最后四个字节存放的是指向下一个堆块的指针,记为next。
可以通过输入name来覆盖掉next。
当前步枪总数riflesNum位于bss段。
功能2show rifles
能打印出当前添加的所有
步枪的name和description功能3order rifles
能够free掉所有
add时申请的chunk,但是却没有置为NULL功能4leave message
能够向0x0804A2A8
处的指针所指向
的地址写入128个字节,注意这里是不是往0x0804A2A8
位置写数据,而是往0x0804A2A8
里面存的指针所指向的位置写128个字节的数据。功能5show stats
能够打印出当前add的数量和order的数量以及message(如果有的话)功能6 Exit
退出程序
分析完程序的功能了,可以发现漏洞点存在于add函数(堆溢出)和order函数(没置NULL)中。
这里我们主要利用add函数里面的堆溢出,配合Fastbin Attack、Arbitrary Alloc。
思路
既然我们能够通过堆溢出覆盖掉next指针,那我们可以把next覆盖成任意值,也就能指向任意位置,也就能通过2.show函数
打印出任意位置的内容,实现任意读。
我们可以先通过这种这任意读来泄露libc地址,从而获得system函数地址。
而我们的4. leave message
函数能够向0x0804A2A8
处的指针所指向
的地址写入128个字节,那么我们可以想办法修改0x0804A2A8
处的指针,然后就能实现任意写。
可以通过这任意写来修改一些函数的got表,将其修改为system,进而想办法执行system(‘/bin/sh’),从而getshell。
这里还会有一些细节,等下会说。
0x02 Let’s do it!
前期模板
1 | from pwn import * |
比较诡异的是,如果加上了c.recvuntil,哪怕已经加上了timeout,程序也会卡住….看各位大佬的WP里面都没加recvuntil… 迷惑行为…
Leak libcbase
这里我们选择puts函数来进行泄露,这里有两个细节:
第一:
我们进行泄露的原理是: add一个步枪,通过堆溢出覆盖它的next为puts_got,然后show。
也就是说,这样程序会把从puts_got开始的0x38个字节当做一个chunk,而由add函数申请的chunk最后四个字节是代表next的,我们需要确保我们所选择的用来泄露的函数其got表对应next的位置(也就是从这个函数got表开始的0x35~0x38字节的位置)的内容应该是0,这样当我们order(也就是delete)的时候,不会有多余的堆块被释放。
第二:
根据linux延迟绑定机制,只有第一次运行后got表才会绑定该函数的实际内存地址
这里比较幸运,puts函数满足我们上面所说的小细节,而且程序一开始就已经调用过puts了。
1 | # Leak |
想办法修改0x0804A2A8位置所存的指针
我们可以通过把0x0804A2A8附近伪造成一个chunk,然后利用Arbitrary alloc
技术修改其存放的指针。
可以发现在 0x0804A2A8 - 4 = 0x0804A2A4 的位置存放的正好是当前add的步枪的总数
而正常的chunk(32位),在data区-4的位置,存放的正好就是chunk的size
由于Arbitrary alloc
技术必然要先经过free掉这个伪造chunk,而程序的add功能申请的chunk的大小是0x38,加上chunk_head就是0x40大小,那么我们就把这个伪造chunk的size也设置成0x40就好了,设置的方法也很简单,总共add 40次就行了。
这里有个细节
:为了我们执行order函数时不把那些不必要的chunk也都free进fastbin,我们把这些为了凑数而申请的chunk的next都覆盖成0
1 | for i in range(0x40 - 1 - 1): |
注意这里 0x40-1-1,因为最开始已经add了一个了,而且待会我们还要再add一个起关键功能的chunk,所以这里是0x40-1-1
我们还需要一个chunk来指向我们的伪造chunk,不然执行order的时候…伪造chunk不会被free进fastbin
1 | payload = 'b'*27 + p32(message) |
算上刚刚这个,正好0x40个。
由于Arbitrary alloc
技术还需要和伪造chunk物理相邻的下一个chunk的size满足不能小于 2 * SIZE_SZ
,同时也不能大于av->system_mem
,我们来看
0x0804A2A8的位置在最开始存的指针是指向0x0804A2C0的,那么我们可以先leave message来修改伪造chunk的nextchunk(实际上这也是个伪造chunk,不是个真的chunk..)的size
0x0804A2C0 - 0x0804A2A8 = 24,也就是说有24个字节的间隔。
而伪造chunk的大小是0x38
我们0x38 - 24 = 0x20,我们只需要写0x20字节来先填充伪造chunk,然后就能修改nextchunk的prevsize和size了。
1 | msg = 'a'*((0x38 - (0xc0 - 0xa8)) - 4) #padding |
这个size只要满足不能小于 2 * SIZE_SZ
,同时也不能大于av->system_mem
就行。
构造完毕,我们来order一下
order函数比较巧妙,它会先free掉我们第0x40个add的chunk,然后根据第0x40个chunk的next找到我们的fakechunk再free掉,然后依次根据next找下去,可惜我们构造的fakechunk的next为0,这样方便再malloc。
来看一下结果
1 | pwndbg> fastbin |
可以看到我们伪造的fakechunk已经进入fastbin了,当我们再次add的时候会在原位置得到这个堆块,进而可以修改里面的内容。 咱们的目的是修改0x0804A2A8位置存放的指针。
为了方便getshell,我们这里选择了strlen函数,因为在sub_80485EC这个函数里面调用了它,比较好利用。
那么我们把0x0804A2A8存放的指针改成strlen_got吧
1 | payload = p32(elf.got['strlen']) |
getshell
然后我们把strlen
改成system
吧
这里有大佬一气呵成,修改成system
的同时也给它传入了参数/bin/sh
,因为leavemessage函数
里面也用了strlen函数
这是我目前凭自己想不到的一点,还是太菜了。。。
先列一下大佬的方法吧
1 | leavemsg(p32(system) + ';/bin/sh\x00') |
而我的方法比较笨一些,先将strlen
修改成system
,然后再add
一个,在name或者description位置传入/bin/sh
1 | leavemsg(p32(system)) |
0x03 完整EXP
1 | from pwn import * |