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

By ccoma | May 25, 2018

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


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


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



이제 정말 exploit 코드를 짜야 합니다. 두근두근
지난 글에서 leak을 할 수 있는 포인트를 찾았고, 내가 원하는 주소를 call 할 수 있는 취약점도 찾았었습니다.

정말 exploit을 하면 될 것 같은데!!

그 전에 Windows 환경에서 exploitdxf을 할 때 알고 있어야 하는 사전 지식이라기엔 거창하지만 어쨌든 필요한 개념을 몇개 짚고 가야할 것 같네요.




step.03 - Exploit 전초전!


Windows에는 dll 이라는 아이가 있는데, 이건 아마 저희 블로그를 뒤지면 누군가 쓴 글이 있을 것만 같았는데 역시나 있습니다 ㅎㅎㅎ


원양어선 renew 블로그 - 정상인이 쓴 dll 인젝션을 사용한 api 후킹


dll이 무엇인지는 위의 글에서 스리슬쩍 이해하고 오시면 될 것 같아요.


dll이 왜 중요하냐면!
바로 이 dll에서 rop 공격을 위한 가젯을 찾을 예정이기 때문입니다.


thing2 바이너리를 windbg를 통해 연 후, lmi를 입력 해 확인 해 보면 아래와 같은 dll을 사용하는 것을 알 수 있습니다.



dll의 역할이 궁금하시다면 아래의 위키백과를 참고하시면 될 것 같습니다.



여기에서 각 dll들의 시작 주소를 잘 봐주세요!


msvcr100msvcp10000000000 65**0000과 같은 형태의 주소값을 가지고 있고, thing2 바이너리의 base 주소와 kernerbase, kernel32, ntdll00007ff* ****0000의 형태를 가지고 있습니다.


이 값은 컴퓨터를 부팅할 때 할당하기 때문에 새로 부팅하지 않는 이상은 바뀌지 않습니다. 그나마 다행이죠 ㅠㅠ


내친김에 stackheap 주소의 범위도 찾아볼까요?

windbg에서 !address를 입력하면 어떤 용도로 어떤 주소를 사용하는지 알 수 있습니다.



확인해 본 결과 heap 주소는 00000*** ********의 형태로, 총 8바이트의 주소 중 6바이트를 사용하고 있습니다. stack의 경우에는 000000** ********의 형태로, 총 8바이트의 주소 중 5바이트를 사용하고 있네요! 리눅스에서 stack 주소는 00007ff* ********였던거 같은데 말이에요 ㅎㅎ


정리를 한 번 하면 아래와 같습니다 ㅎㅎ



잉 근데 exploit을 하는데 왜 주소 형태를 알아야 하는거죠...? 라고 궁금해 하실 수 있을거에요.


그 이유는!!

제가 이전 글에서 이 문제 바이너리에는 ASLR 보호기법이 걸려있다고 말씀 드렸었습니다.
때문에 heap 주소와 stack 주소는 바이너리를 실행할 때마다 바뀌게 됩니다.
그렇게 되면 어떤 주소에 어떤 값이 들어가는지 알 수 없기 때문에 exploit을 하기가 매우매우매우매우매우 어렵습니다.

그래서 제가 아까 dll에서 rop 공격을 위한 가젯을 찾는다고 말씀 드렸었는데요.


leak이 된 데이터에서 dll의 주소 혹은 특정 dll의 시작 주소를 계산할 수 있는 값이 나온다면, 그 주소를 기준으로 offset을 계산 해 가젯의 주소를 구할 수 있겠죠?

그리고 또 leak이 된 데이터에서 stack 혹은 heap 주소룰 찾을 수 있다면, 해당 주소에 rop payload를 넣고 그 주소를 call 하도록 해 rop payload를 실행할 수 있을겁니다.


그런데, 이 때 leak이 된 데이터에서 뭐가 heap 주소고, 뭐가 stack 주소고… 뭐가 dll의 주소인지 알아볼 수 없다면…? 문제를 풀 수 없겠죠 흑 ㅠㅠ(이미 다 알고 계시겠지만 그냥 한번 설명 드려봤어요 ㅎㅎㅎ)

즉, 문제를 풀기 위해서는 다음과 같은 값들이 필요합니다.


1. (내가 임의의 값을 쓸 수 있는) heap 혹은 stack의 주소
2. dll의 주소
3. rop 공격을 하기 위한 가젯과 함수의 주소


지난 글에서는 leak이 되는지만 확인하고, 어떤 데이터가 leak 되는지는 확인하지 않았었습니다.
이제 어떤 주소가 어떤 영역인지를 알아볼 수 있으니까!
leak 된 데이터에 뭐가 있는지 확인하기 위해 간단하게 아래처럼 코드를 구현 해 실행 시켜 보았습니다.


import socket
import struct
import time
import sys
import re

print "[+] thing2 Exploit Start"

ip = "192.168.18.139"
port = 4141

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

print "[+] Connect OK with", ip

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

print data

print "[+] End"


그 결과 아래와 같은 값이 leak 되는 것을 확인할 수 있습니다.



음… 언뜻 보니 dll 주소의 형태는 보이는데, heap이나 stack 주소로 보이는 값은 없습니다. ㅠㅠ

내가 알고있는 주소에 내가 넣고싶은 데이터를 넣고 그 주소를 call 하도록 해야 exploit이 될텐데 말이에요…

그래서 이것저것을 시도해 보다 1 command를 통해서 먼저 값을 입력한 다음에 2 command를 통해 leak을 시키면 heap 주소가 나온다는 것을 확인할 수 있었습니다.


import socket
import struct
import time
import sys
import re

print "[+] thing2 Exploit Start"

ip = "192.168.18.139"
port = 4141

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

print "[+] Connect OK with", ip

raw_input()

test = "1" + "a" * 0x10+ "\n"
s.send(test)
data = s.recv(1024)
data = s.recv(1024)

leaker = b"2\n288\n"
leaker += b"37\n112\n32\n" * 0x60

s.send(bytearray(leaker))
data = s.recv(10000)

print data

print "[+] End"


위와 같이 소스코드를 구현 해서, a0x10개 만큼 넣고 이 후 2 commad를 통해서 0x60개의 주소를 leak을 시켜 보았습니다.



아까의 결과값과 비교 해 보니, 확실히 heap 주소 처럼 보이는 값이 많이 나왔네요!
그런데, 중간에 제가 block으로 지정 한 값이 보이시나요?
windbg를 통해 보면 저 주소에 제가 넣은 값이 들어있는 것을 확인할 수 있습니다.



그렇다면! 드디어 제가 임의의 값을 쓸 수 있는 heap의 주소를 발견했네요!
이제 dll의 주소를 구해 볼까요?


leak된 데이터에서 주변을 둘러보니 아까 dll의 주소 형태라고 했던 00000000 65**0000 / 00007ff* ****0000 형태의 주소가 있는 것을 알 수 있습니다.


그런데 이 때 출력되는 dll 형태의 주소들은 바이너리를 실행할 때마다 항상 같은 값입니다.
그렇다면, 이 주소가지고 offset을 계산 해 손쉽게 각 dll의 시작 주소를 알 수 있겠죠!


와 드디어 마지막으로 rop payload에 사용하는 가젯의 주소, 정확히는 dll 시작 주소로부터의 offset을 구하면 exploit 코드를 구현 할 준비가 끝이 납니다.


하… 여기까지 오는데 너무 오래걸렸어요 ㅠㅠ
원래 한편에 다~~ 끝내려고 했는데 아직도 가야할 길이 구만리인지라… 오늘 다 하면 스크롤 압박이 엄청날 것 같아요 ㅠㅠ
그래서! 다시 조금만 쉬었다가 다음편에서 본격적으로 exploit을 해 보겠습니다ㅎㅎ


FLAG도 보고싶고 엄마도 보고싶고 내 침대에 우통이도 보고싶다…ㅠㅠ

comments powered by Disqus