Часть 67

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

Мой друг попросил у меня несколько разъяснений о методе, используемом для обхода COOKIE на 32-битных машинах, когда у нас есть переполнение стека в ядре, и мы можем перезаписать адрес возврата, но есть COOKIE, который мешает нам завершить выполнение кода.

Очевидно, что если у нас есть другая уязвимость, которая допускает утечку данных, мы могли бы прочитать значение COOKIE и затем использовать его для отправки наших данных, для перезаписи, но есть метод, который я никогда не использовал на практике, который немного стар и в системах, отличных от WINDOWS 7 32 бит не будет работать, но было бы хорошо взглянуть на него, чтобы уточнить для моего друга и того, кто читает меня (и для меня самого).

Мы уже знаем, что уязвимый драйвер можно скачать отсюда.

https://github.com/hacksysteam/HackSysExtremeVulnerableDriver/releases/download/v1.20/HEVD.1.20.zip

А сам инструмент для загрузки драйвера, можно загрузить отсюда.

http://www.osronline.com/OsrDown.cfm/osrloaderv30.zip?name=osrloaderv30.zip&id=157

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

Внутри ZIP архива находятся такие файлы.

HEVD.1.20\DRV\VULNERABLE\I386

Это драйвер и его символы.

При открытии драйвера в IDA, мы видим функцию DRIVERENTRY, первым аргументом которой всегда является указатель на объект _DRIVER_OBJECT.

Регистр ESI это указатель на объект _DRIVER_OBJECT.

Если мы перейдём во вкладку LOCAL TYPES.

Мы видим, что эта структура. Если мы поищем структуру _IRP, мы находим наиболее часто используемым для обращения структуры.

Мы будем отмечать структуры _DRIVER_OBJECT, _IRP, _DEVICE_OBJECT, _IO_STACK_LOCATION и PIRP и синхронизировать их, потому что именно их мы будем использовать чаще всего.

То что нам нужно сделать - это добавить структуру MAJORFUNCTION, которой нигде нет.

Напомним, что мы можем добавить структуру через вкладку LOCAL TYPES, используя правую кнопку мыши, пункт INSERT и вставить этот код.

struct __MajorFunction
{
SIZE_T _MJ_CREATE;
SIZE_T _MJ_CREATE_NAMED_PIPE;
SIZE_T _MJ_CLOSE;
SIZE_T _MJ_READ;
SIZE_T _MJ_WRITE;
SIZE_T _MJ_QUERY_INFORMATION;
SIZE_T _MJ_SET_INFORMATION;
SIZE_T _MJ_QUERY_EA;
SIZE_T _MJ_SET_EA;
SIZE_T _MJ_FLUSH_BUFFERS;
SIZE_T _MJ_QUERY_VOLUME_INFORMATION;
SIZE_T _MJ_SET_VOLUME_INFORMATION;
SIZE_T _MJ_DIRECTORY_CONTROL;
SIZE_T _MJ_FILE_SYSTEM_CONTROL;
SIZE_T _MJ_DEVICE_CONTROL;
SIZE_T _MJ_INTERNAL_DEVICE_CONTROL;
SIZE_T _MJ_SCSI;
SIZE_T _MJ_SHUTDOWN;
SIZE_T _MJ_LOCK_CONTROL;
SIZE_T _MJ_CLEANUP;
SIZE_T _MJ_CREATE_MAILSLOT;
SIZE_T _MJ_QUERY_SECURITY;
SIZE_T _MJ_SET_SECURITY;
SIZE_T _MJ_POWER;
SIZE_T _MJ_SYSTEM_CONTROL;
SIZE_T _MJ_DEVICE_CHANGE;
SIZE_T _MJ_QUERY_QUOTA;
SIZE_T _MJ_SET_QUOTA;
SIZE_T _MJ_PNP;
SIZE_T _MJ_PNP_POWER;
SIZE_T _MJ_MAXIMUM_FUNCTION;
};

Мы добавляем и синхронизируем структуру, и последнее что мы делаем – открываем во вкладке LOCAL TYPES структуру _DRIVER_OBJECT и изменяем ее так, чтобы последнее поле представляло собой структуру типа _MAJORFUNCTION.

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

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

Мы видим, что регистр ESI сохраняет значение указателя на объект _DRIVER_OBJECT в этой области, а по адресу 0x000160СF и это значение указателя затирается.

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

Как только зона отмечена, мы нажимаем T.

Конечно, мы выбираем регистр ESI в качестве базового регистра структуры и смещение, которое мы устанавливаем в нуль, потому что оно указывает на начало структуры, и мы выбираем структуру _DRIVER_OBJECT, и IDA обнаруживает 4 поля которые используются.

Для нас важно то поле, которое обрабатывает IOCTL, т.е. _MJ_DEVICE_CONTROL.

Если бы у нас не было символов, такая же работа выполнялась бы путем импорта файла .H с 32-битными структурами для реверсинга драйверов.

Файл .H с 32-битными структурами находится здесь.

https://drive.google.com/file/d/1VXwR45uvw1FtvzW2b9eNO1DLid9CIdx8/view?usp=sharing

И он импортируется в IDA отсюда.

При этом мы увидим необходимые структуры DRIVER_OBJECT, _IRP, _DEVICE_OBJECT, _IO_STACK_LOCATION и PIRP и _MAJORFUNCTION в LOCAL TYPES. Мы синхронизируем их и получим таким же образом возможность распознавать функцию, которая обрабатывает IOCTL.

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

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

Вызов идет сюда.

И затем сюда.

Мы видим, что есть функция MEMCPY, которая копирует буфер в стек. MAXCOUNT - количество байтов. Полностью отреверсив функцию мы увидим, что у неё есть COOKIE. Хотя мы это уже видели до адреса возврата, который в отличие от другого переполнения стека, который мы уже эксплуатировали.

Перед инструкцией RETN есть такой вызов.

Эта проверка и в начале функции она то же есть.

Возвращаясь к началу функции с именем IRPDEVICEIOCTLHANDLER, которая обрабатывает IOCTL, в регистре EDI передается указатель на структуру IRP. Мы уже видели в предыдущих туториалах, что в 32х битных системах по смещению 0x60 это был указатель на структуру IO_STACK_LOCATION, которая останется в регистре ESI.

И я нажимаю T на ESI + 0xC.

Как мы уже видели, что структура IO_STACK_LOCATION варьируется в зависимости от функции, в которой она используется , поскольку здесь мы используем её в случае функции, которая обрабатывает IOCTL. Мы должны выбрать DEVICEIOCONTROL.

Я оставлю всё это так. В этой функции, регистр ESI имеет указатель на IO_STACK_LOCATION, регистр EDX – это код IOCTL (IOCONTROLCODE) и EDI указатель на _IRP.

Регистр ESI и регистр EDI – это два аргумента вызова функции STACKOVERFLOWGSIOCTLHANDLER.

Конечно, поскольку у нас есть символы, мы можем видеть эти два аргумента в определении функции. Первый - это указатель на _IRP, а второй - указатель на IO_STACK_LOCATION.

Опять же, пытаясь определить поле _IO_STACK_LOCATION, мы должны выбрать случай, когда используется DEVICEIOCONTROL.

Мы видим, что то, что мы определили при реверсинге, совпадает с тем, что нам показывают символы.

Поле INPUTBUFFERLENGHT - это размер входного буфера из режима пользователя, а TYPE3INPUTBUFFER - указатель на этот пользовательский входной буфер, который мы также передаем.

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

Мы видим, что обе переменные используются без проверки или изменения в функции MEMCPY.

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

Инициализируется только 0x1FF байт буфера. Также нет проблем, что некоторые байты ещё остались нетронутыми.

В начале функции мы видим, что выше адреса возврата есть структура CPPEH_RECORD.

Она находится чуть ниже буфера и поверх адреса возврата.

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

Однако мы видим, что IDA показывает нам, что чуть выше "R" находится сохраненный регистр EBP т.е. "S".

Также, если мы войдем в функцию __SEH_PROLOG4_GS.

Мы видим, что после помещения в регистр EAX значения переменной CONS_0x210, программа сохраняет здесь регистр EBP, так что на самом деле выше "R" , наконец, остается "S" или сохраненный регистр EBP.

Затем, над сохраненным регистром EBP, помещается указатель на эту структуру которая передается сразу после инструкции PUSH 0x210.

По этому адресу находится структура STRU_0x12218. Программа сохраняет её чуть выше "S".

И чуть выше "S" находится переменная MS_EXC, которая является структурой типа CPPEH_RECORD, так что этот адрес будет последним полем этой структуры, и мы увидим это.

Здесь после сохранения сохраненного регистра EBP в переименую CONST_0X210 программа помещает адрес указанной переменной в регистр EBP. Это будет более или менее похоже на начало функции PUSH EBP, MOV EBP, ESP.

Оба должны сохранить значение регистра EBP родительской функции TRIGGERSTACKOVERFLOWGS и установить новый регистр EBP для неё через инструкцию LEA.

Затем программа освобождает место для переменных, выполняя инструкцию SUB ESP, EAX.

И также мы видим, что по адресу EBP-4 XORится значение, которое было там с COOKIE, которое читается из секции данных.

Напомним, что по адресу EBP-4 находится значение 0x12218. С этим значением XORится COOKIE и сохраняется по этому адресу.

Кроме того, XORится COOKIE с регистром EBP и полученное значение сохраняется по адресу EBP-1C.

Это то, что программа будет проверять в эпилоге.

И внутри функции __SECURITY_CHECK_COOKIE.

Программа сравнивает значения. Если они одинаковы, то всё хорошо, а если нет мне выкидывает BSOD.

Мы создадим стек с самого начала в соответствии с порядком, с которым программа размещает значения перед входом в пролог:

PUSH 0x210
PUSH 0x12218

Затем программа входит в пролог, который заставляет её сохранять адрес возврата в стеке, куда она будет возвращаться. Это будет адрес 0x000148E9, поскольку при выходе из пролога программа вернется туда.

Таким образом, при входе в пролог мы имеем в стеке два аргумента и адрес возврата, куда вернется программа.

Давайте продолжим смотреть на то, как аргументы помещаются в стек.

Затем есть еще две инструкции PUSH - адрес функции EXCEPTION_HANDLER4 и значение, которое содержит регистр FS:0.

Над обратным адресом тогда будут эти два значения.

Затем 0x210 перезаписывается STORED_EBP.

Мы знаем, что под сохраненным регистром EBP был адрес возврата в функцию TRIGGERSTACKOVERFLOWGS. Мы добавили его в наше представление стека.

Текущий регистр EBP остается с адресом STORE_EBP (смотри на адрес, а не значение)

Из регистра ESP вычитается значение 0x210 для пространства переменных, другими словами над адресом FS:0 - 0x210 останется регистр ESP.

Затем выше есть еще три PUSH - EBX, ESI и EDI.

Затем содержимое EBP-4 XORится с COOKIE.

Поскольку ТЕКУЩИЙ EBP продолжает указывать на адрес STORE_EBP, EBP-4 указывает на значение 0x12218, это значение XORится с COOKIE.

ЗНАЧЕНИЕ EBX
ЗНАЧЕНИЕ ESI
ЗНАЧЕНИЕ EDI
FS:0
__EXCEPT_HANDLER4
0x148E9 <---- АДРЕС ВОЗВРАТА В ПРОЛОГ
0x12218 <--------XORится с COOKIE
STORED_EBP <----- ТЕКУЩИЙ EBP - АДРЕС STORED_EBP
АДРЕС ВОЗВРАТА В ФУНКЦИЮ TriggerStackOverflowGS

Если мы сделаем так, чтобы уточнить первый столбец, с адресами ссылающимися на значение ТЕКУЩЕГО EBP.

ЗНАЧЕНИЕ EBX
ЗНАЧЕНИЕ ESI
ЗНАЧЕНИЕ EDI
EBP-10 FS:0
EBP-C __EXCEPT_HANDLER4
EBP-8 0x148E9 <---- АДРЕС ВОЗВРАТА В ПРОЛОГ
EBP-4 0x12218 <--------XORИТСЯ С COOKIE
EBP STORED_EBP <----- ТЕКУЩИЙ EBP - АДРЕС STORED_EBP
АДРЕС ВОЗВРАТА В ФУНКЦИЮ TRIGGERSTACKOVERFLOWGS

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

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

В обычной функции регистр ESP при возврате равен тому же значения, что и перед передачей аргументов.

Но в данном конкретном случае эта специальная функция похожа на часть функции TRIGGERSTACKOVERFLOWGS, выполняемой в отдельном CALL.

Если принимать регистр ESP в качестве нуля в начале функции, я вижу, что при возврате из CALL регистр увеличивается на 0x234 байт, потому что внутри функции пролога было сделано несколько инструкций PUSH, была выполнена инструкция SUB ESP, 0x210, и был осуществлен возврат из функции без восстановления регистра ESP.

Многие скажут, но если регистр ESP не восстановлен, как найти адрес возврата в стеке, который намного ниже значения регистра ESP.

Мы говорили, что адрес EBP-8 указывает на адрес возврата, чтобы вернуться из функции пролога в TRIGGERSTACKOVERFLOWGS и ТЕКУЩИЙ ESP после того, как три PUSH из EBX, ESI и EDIостались выше.

Если мы посмотрим в функции пролога, увидим, что она возвращает адрес возврата с помощью инструкции PUSH - RET.

Помещенное в стек значение, указанное через адрес EBP-8, является адресом возврата. Программа помещает значение обратно в стек и затем выполняет инструкцию RET, и программа возвращается к функции TRIGGERSTACKOVERFLOWGS, не восстанавливая ESP и оставляя весь стек целым, как это было в прологе.

Между инструкцией PUSH и RET есть только инструкции MOV и LEA, поэтому стек не затрагивается, и это аналогично PUSH-RET.

Мы уже знаем, как функция начинается, как все устроено в стеке и как функция возвращается, у нас есть некоторые вещи которые находятся в середине после трех PUSH перед возвратом.

Мы создали стек здесь.

До этого момента он был создан так.

ЗНАЧЕНИЕ EBX
ЗНАЧЕНИЕ ESI
ЗНАЧЕНИЕ EDI
EBP-10 FS:0
EBP-C __EXCEPT_HANDLER4
EBP-8 0x148E9 &lt;---- АДРЕС ВОЗВРАТА В ФУНКЦИЮ ПРОЛОГ
EBP-4 0x12218 &lt;--------XORED COOKIE
EBP STORED_EBP &lt;----- ТЕКУЩИЙ EBP - АДРЕС STORED_EBP
АДРЕС ВОЗВРАТА В ФУНКЦИЮ TRIGGERSTACKOVERFLOWGS

Мы уже знаем, что ничего из этого не будет потеряно, все, что я добавлю или изменю в прологе в стеке, не будет удалено, поскольку PUSH-RET покинет стек, как это было для функции TRIGGERSTACKOVERFLOWGS.

Еще одна вещь, которая уже настроена для функции TRIGGERSTACKOVERFLOWGS, это регистр EBP.

С помощью LEA вычисляется база для переменных и аргументов не только пролога, но и функции TRIGGERSTACKOVERFLOWGS, поскольку начиная с этого момента её значение остается постоянным, даже после возвращения.

Я смотрю функцию TRIGGERSTACKOVERFLOWGS, чтобы попытаться увидеть, где это соответствует адресу EBP-1C, где программа хранит COOKIE.

Мы видим, что переменная MS_EXC находится по адрес EBP-0x18. Другими словами место, где программа хранит COOKIE, которое вы собираетесь проверить, находится чуть выше структуры MS_EXC.

Напомним, что буфер DST был инициализирован только с 0x1FF байтами, и мы сказали, что осталось несколько байтов чуть ниже него, поэтому, если мы поправим размер DST на 0x1FF, у нас будет переменная, в которой сохраняется COOKIE в стеке.

Здесь я назначаю новый размер и у меня остается четыре пустых байта между ними. Я нажимаю D, пока я не изменю на DWORD (DD), и переименую переменную в COOKIE.

Я вижу, что это по адресу EBP-1C (слева от названия есть позиция относительно EBP т.е. 0x0000001C).

Затем идет инструкция PUSH EAX и сохраняется текущее значение регистра ESP по адресу EBP-18, которое было внутри структуры MS_EXC, которая начинается здесь. Это первое поле той же структуры.

Если мы заглянем внутрь структуры, первым полем будет OLD ESP

Так что стек стал таким

ЗНАЧЕНИЕ EAX
ЗНАЧЕНИЕ EBX
ЗНАЧЕНИЕ ESI
ЗНАЧЕНИЕ EDI
EBP-10 FS:0
EBP-C __EXCEPT_HANDLER4
EBP-8 0x148E9 <---- АДРЕС ВОЗВРАТА В ПРОЛОГ
EBP-4 0x12218 <--------XORится с COOKIE
EBP STORED_EBP <----- ТЕКУЩИЙ EBP - АДРЕС STORED_EBP
АДРЕС ВОЗВРАТА В ФУНКЦИЮ TRIGGERSTACKOVERFLOWGS

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

Эти 4 DWORDS являются 4 нижними полями структуры MS_EXC.

Помните, что последние 4 поля структуры - это другая структура размером 0x10 байтов, т.е. 16 в десятичной системе (4 DWORDS), поэтому на изображении отмечены только те 4 DWORDS.

Двумя важными переменными являются NEXT и EXCEPTION HANDLER. Мы уже знаем их положение в стеке. Мы видим, что переменная NEXT в структуре имеет значение FS:0, а EXCEPTIONHANDLER на данный момент имеет значение __EXCEPT_HANDLER4, хотя они еще не добавлены в цепочку SEH.

ЗНАЧЕНИЕ EAX
ЗНАЧЕНИЕ EBX
ЗНАЧЕНИЕ ESI
ЗНАЧЕНИЕ EDI
EBP-10 FS:0 - (NEXT)
EBP-C __EXCEPT_HANDLER4 - (EXCEPTION_HANDLER)
EBP-8 0x148E9 <---- АДРЕС ВОЗВРАТА В ПРОЛОГ -(SCOPETABLE)
EBP-4 0x12218 <--------XORится с COOKIE (TRYLEVEL)
EBP STORED_EBP <----- ТЕКУЩИЙ EBP - АДРЕС STORED_EBP
АДРЕС ВОЗВРАТА В ФУНКЦИЮ TRIGGERSTACKOVERFLOWGS

Хорошо. У нас создан стек, и мы видим справа синие поля структуры.

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

Мы видим, что в по адресу EBP-8 (SCOPETABLE) программа сохраняет значение COOKIE, XORит его со значением 0x12218, которое было в EBP-4, а затем в том же EBP-4, что является TRYLEVEL, сохраняет значение 0xFFFFFFFE.

В конце программа сохраняет адрес EBP-10 - NEXT в регистр FS:0 с настроенным обработчиком исключений.

Мы знаем, что регистр FS:0 указывает на последний элемент в списке цепочки исключений, т.е на верхнюю часть всей цепочки.

Помните, что добавление нового элемента в список осуществляется с помощью этого кода

PUSH OFFSET HANDLER
PUSH FS:[0]
MOV FS:[0], ESP

Т.е. поскольку здесь выполняется следующая инструкция MOV LARGE FS:0, EAX

Этот регистр EAX является адресом стека, где находится новый NEXT и ниже SEH.

Так как регистр EAX является адресом EBP-10, здесь будет переменная NEXT и чуть ниже SEH, как мы уже говорили.

Если я отлаживаю код и отправляю данные эксплойту, который обходит правильный IOCTL для достижения уязвимой функции (Позже мы увидим, как это сделать. Сейчас же это просто для проверки).

Я вижу, что регистр FS:0 указывает на верхний элемент цепочки SEH. В моем случае он равен 9CCEFCC0. Если я посмотрю, здесь должны быть переменные NEXT и SEH. Переменная NEXT равна 0xFFFFFFFF, потому что это последний NEXT в цепочке исключений.

Функция является типичным универсальным обработчиком. Если я собираюсь увидеть, что это за байты, то нажимаю C чтобы создать функцию.

Если я продолжу трассировать пролог, я попадаю туда, где регистр EAX сохранится в регистр FS:0.

Здесь мы видим новый драйвер, добавленный в цепочку.

Как мы уже рассматривали, адрес EBP-10 будет новым NEXT, а ниже находится SEH, который будет являться _EXCEPT_HANDLER4. Это то значение, которое мы должны будем переписать для эксплуатации

Хорошо. У нас уже все хорошо расположено. Пора начинать писать эксплойт.

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

Идея состоит в том, что, поскольку сбой происходит при доступе на чтение к буферу пользовательского режима, это приводит к тому, что он обрабатывается как сбой в режиме пользователя и происходит переходит к SEH, а не обрабатывается как сбой ядра, что вызывает BSOD.

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

Основное объяснение этого метода находится здесь:

http://poppopret.blogspot.com/2011/07/windows-kernel-exploitation-basics-part_16.html

И исходный код публичного эксплойта находится здесь:

https://github.com/hacksysteam/HackSysExtremeVulnerableDriver/tree/master/Exploit

Я не собираюсь делать все это также на PYTHON, потому что это того не стоит, но давайте посмотрим, как автор это объясняет и исправим, то что не работает.

Прежде всего, если бы мы сделали это в PYTHON, у нас была бы проблема, которую можно решить, но, компилируя его в C++, у нас уже есть модуль, который помимо запуска и экслуатации повышения привилегий, мы можем скомпилировать его по своему вкусу, например, без SAFESEH, или DEP или ASLR. В опциях VISUAL STUDIO позволят нам выбрать то что нужно, поэтому, если кто-то загружает решение, т.е. файл SLN в VISUAL STUDIO, вам придется изменить параметры по умолчанию.

Чуть выше находится это.

Хорошо. Я приложу скомпилированный файл с его символами HACKSYSEVDEXPLOIT.EXE и HACKSYSEVDEXPLOIT.PDB, чтобы его было легко увидеть в IDA.

Исполняемый файл скомпилирован для всех уязвимостей, которые есть у драйвера, и его выполнение в консоли в WINDOWS 7 32 с аргументами -G -C XXX.EXE достаточно, так как я уже добавляю в конце этого метода выполнение калькулятора c правами SYSTEM после поднятия прав. В остальных вместо XXX.EXE придется подставить CALC.EXE или CMD.EXE

Хорошо. Мы переходим к функции, которая использует эту уязвимость. В данном случае - STACKOVERFLOWGSTHREAD.

После исправления некоторых проблем консоли, которые не имеют отношения, давайте проанализируем эксплойт, который мы открываем в IDA, и видим, что эксплойт начинается здесь:

Внутри мы видим вызов функции CREATEFILE для получения дескриптора драйвера.

Все это так же, как и случаи, которые мы видели в предыдущих ядрах.

Регистр EBX остается с дескриптором драйвера, он используется только при вызове DEVICEIOCONTROL ниже.

Хорошо. Затем идет вызов функции CREATEFILEMAPPING, что является пространством виртуальной памяти, которое будет связано с содержимым файла. (Функция не резервирует память, только создает объект и возвращает дескриптор)

https://docs.microsoft.com/en-us/windows/desktop/memory/file-mapping

Но если мы посмотрим в описание функции CREATEFILEMAPPING, мы увидим, что первым аргументом является дескриптором файла, но нам также говорится, что может быть передан аргумент INVALID_HANDLE_VALUE. В этом случае, программа создаст отображение файла, не связывая его с файлом, и это будет общая анонимная память.

Хорошо. Это тот случай, поэтому мы видим, что когда вызывается эта API, вы передаете значение 0xFFFFFFFF, которое является INVALID_HANDLE_VALUE.

В исходном коде, под названием SHARED MEMORY, созданном здесь, мы видим, что функции передаются разрешение на выполнение, чтение и запись.

Хорошо. Она возвращает нам дескриптор файлового отображения.

Затем программа вызывает функцию MAPVIEWOFFILE, которая отображает объект в памяти, зарезервировав необходимое для него пространство.

Хорошо. Функция возвращает адрес начала секции, созданного для отображения файлов.

Чтобы отладить эксплойт в режиме пользователе, несмотря на драйвер, я копирую сервер IDA - WIN32_REMOTE.EXE в целевую машину и запускаю его.

Я запускаю его на сервере с правами администратора в целевой системе, а на машине, где я реверсил эксплойт, меняю отладчик на удаленный отладчик WINDOWS. В PROCESS OPTIONS я указываю IP-адрес и порт.

Напомним, что мы можем отлично отладить этот эксплойт в пользовательском режиме, но на шелл-код, который вызывается из ядра, мы не сможем поставить BP или что-либо еще, потому что это вызовет исключение INT3 в ядре, которое не обрабатывается как в пользовательском режиме, и будет создан BSOD.

Если мы запустим файл без аргументов, он покажет нам опции.

Я запускаю эксплойт с аргументами -G, чтобы задействовать уязвимость STACK OVERFLOW GS,

Теперь я ожидаю.

Так что я могу теперь прикрепить IDA, ту где я реверсил эксплойт (не ту, которой был проанализирован драйвер)

Нажав клавишу в целевой машине, чтобы пропустить паузу, мы останавливаемся на BP, который я ставлю после паузы.

Я дохожу до функции CREATEFILEMAPPING.

Пройдя вызов с помощью F8, мне возвращается дескриптор файла.

Как мы уже говорили, этот дескриптор передается в регистре ESI.

Здесь нам вернется адрес отображаемого файла.

0.png

Это секция будет из 0x1000 байт.

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

Я поставлю эту IDA на паузу на одну минуту и открою другую, где у меня есть драйвер.

Одна вещь, которую я не видел и допустил ошибку - это то, что буфер назначения

Мы видим, что он инициализируется байтами 0x1FF, но непосредственно перед обнулением байта, который находится чуть выше, и место назначения начинается с VAR_21C, поэтому вам нужно исправить буфер назначения, чтобы иметь возможность хорошо вычислять размер, и теперь начинать с VAR_0x21C, и он будет равен 0x200 байт.

Теперь, если всё в порядке, я переименую его в BUFFER_DESTINO.

Это выглядит хорошо. Здесь прямо в функции MEMCPY, и мы можем скопировать количество байтов, которые нам нужны.

Очевидно, что мы не должны копировать с начала секции файлового отображения, потому что данные должны быть копированы только до SEH. Я должен увидеть, сколько байтов я должен скопировать.

Мы должны скопировать 0x200 байт, чтобы заполнить буфер. Еще 4, чтобы перезаписать COOKIE, а затем ещё идёт структура MS_EXC.

Внутри структуры есть 8 байтов, а затем NEXT и SEH, поэтому было бы так.

Всего cкопировано = 0x200 + 4 + 8 + NEXT + SEH.

Т.е. 0x214 байтами мы перезаписываем SEH.

Поскольку длина секции составляет 0x1000 байт, чтобы узнать, какое адрес передать, чтобы начать копирование, в начальный адрес секции я добавляю 0x1000, а затем вычитаю 0x214. При этом программа будет использовать этот новый адрес в качестве входного буфера, просто, чтобы перезаписать SEH и вызвать сбой при чтении.

Давайте посмотрим на это в отладчике, который был остановлен на эксплойте.

Мы видим, что адрес файлового отображения, хранится в регистре ESI и к нему добавляется значение 0xDEC. Т.е. 0x10000x214 = 0xDEC.

На моей машине 0x2C0DEC будет адресом, с которого начнется копирование 0x214 байт. Источник функции MEMCPY в стек.

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

С помощью функции MEMSET заполняется вcя секция буквами A (0x41)

Здесь заполняется входной буфер.

Мы видим, что в позицию 0x0204 записывается значение 0x42424242. Это предположительно говорит о том, что программа перезаписала COOKIE, так как буфер занимает 0x200, а COOKIEнаходится ниже. Для меня, так как это значение 0x204, перезаписывается DWORD чуть ниже COOKIE - первое поле структуры MS_EXC.

Затем программа записывает по адресу ESI + 4 значение 0x43434343.

Затем добавляет значение 8 к исходному регистру ESI и записывает NEXT и SEH.

Мы видим, что, как я уже сказал, COOKIE не перезаписался значением 0x41414141, и я затер чуть ниже 4 DWORD структуры MS_EXC.

Это 4 DWORD которые я перезаписал, поэтому последний DWORD - это SEH.

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

Я устанавливаю BP перед функцией DEVICEIOCONTROL, и когда я нажимаю RUN, мне нужно нажать клавишу в целевой маишине, чтобы пройти следующую системную паузу.

Давайте посмотрим аргументы, которые передаются функции.

Указатель на возвращенные байты передаются с помощью инструкции LEA. Затем идет PUSH 0 и PUSH 0 для выходного буфера. Его размер не имеет значения. Затем мы видим, что количество байтов, которые мы передает функции, чтобы она скопировала из входного буфера, равно 0x218 или на 4 байт больше, чем длина входного буфера, который был равен 0x214. Это приведет к сбою при чтении в конце источника.

Адрес входного буфера был оставлен в регистре EDI.

И затем передается код IOCTL 0x222007 от этой ошибки STACK OVERFLOW GS и дескриптор устройства, который находится в регистре EBX.

Мы уже проанализировали эксплойт. Поэтому теперь мы можем закрыть его и присоединить IDA с анализом драйвера к ЯДРУ и посмотреть, как копируются данные.

Прежде чем прикрепить IDA, я поставлю BP в начале уязвимой функции.

Готово. IDA уже обнаружила, что это тот же файл, который я проанализировал, и если я соглашусь и перемещу базу секции, IDA скажет, что это тот же файл.

Я запускаю эксплойт на целевой машине.

Мы останавливаемся на BP.

Как мы уже говорили, будут скопированы NEXT и SEH, которые мы должны будем перезаписать позже в функции MEMCPY.

Потому что, когда мы нажимаем RUN, мы будем копировать данные и продолжим, не имея возможности остановить этот процесс. Мы можем установить BPM ON WRITE, чтобы остановить отладчик, когда скопируется NEXT прямо перед сбоем. Поэтому мы посмотрим, все ли в порядке.

Я вставил это в панель WINDBG.

Я создаю сегмент и преобразовываю его в код.

Или если мне хочется посмотреть на сегмент в панели WINDBG, я делаю так.

Напомним, что когда я armada источник мы помещаем значение 0x44444444, чтобы перезаписать значение NEXT.

Это изображение выше было источником. Давайте посмотрим, хорошо ли оно перезаписано в стеке.

Вот оно. Я останавливаюсь на BPM ON WRITE сразу после копирования NEXT, и теперь я скопирую SEH. Я нажимаю F7.

Сюда я скопирую наш SEH.

Также я могу посмотреть на него в IDA.

Конечно, модуль эксплойта, куда будет переходить отладчик, скомпилирован без DEP и без SAFE SEH, поскольку это является частью эксплуатации. Если бы он был создан с PYTHON, проблем не было бы. Вам нужно было бы создать область памяти, которая позволила бы ему работать с функцией VIRTUALALLOC, скопировать туда код и указать адрес этой области как SEH.

Давайте не будем забывать, что это PRIVILEGE ESCALATION, поэтому у нас уже есть выполнение кода на машине, но с обычным пользователем. Идея состоит в том, чтобы масштабироваться до прав SYSTEM, чтобы мы могли выполнять действия на компьютере, ограниченные нашими привилегиями, но запускать EXE с теми же привилегией. Также, если бы это был PYTHON, мы должны установить PYTHON для запуска .PY.

Давайте посмотрим на процедуру обработчика исключений.

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

Здесь я не могу поставить BP в отладчике в пользовательском режиме на целевой машине, но могу сделать так.

Я помещаю эту команду в WINDBG. Затем я нажимаю RUN.

Отладчик остановился здесь. Наш обработчик исключений работает.

Конечно, этот код - это шелл-код который называется как TOKEN STEALER, который уже был проанализирован в предыдущих туториалах.

Ошибка в исходном коде была в отмеченной области после возврата, чтобы украсть системный токен и сохранить его, чтобы повысить привилегии нашему процессу. Есть инструкция POPAD, которая восстанавливает регистры, сохраненные в начале с помощью инструкции PUSHAD, И потом не так легко вернуться из ядра исключения к режиму пользователю без прерывания, поэтому мы следовали советам, используя инструкцию SYSEXIT.

В регистр EDX вы должны поместить регистр EIP, куда программа вернетесь, когда вернетесь в пользовательский режим, а в регистр ECX - регистр ESP. В моем случае, поскольку файл компилирован без ASLR, я помещаю в регистр EDX адрес чуть ниже вызова DEVICEIOCONTROL, а в регистр ESP адрес основания стека 0x12FF00, который не совпадает со значением, которое выполнялось до вызова функции DEVICEIOCONTROL, но, поскольку я сохранил адрес в секции данных в регистре ESP, которое у меня был, когда я вернусь, я смогу восстановить правильный регистр ESP.

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

В текущей IDA, в которой я отлаживаю, я вижу ту же часть кода.

Я могу поставить здесь BP.

Я удаляю предыдущие BP.

И нажимаю RUN.

Я вижу, что программа вернулась в режим пользователя с регистрами EIP и ESP, которые я установил до вызова SYSEXIT.

Здесь восстанавливается регистр ESP. Идет чтение с того места, где вы его сохранили, в секции данных 0x4212B0.

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

Я удаляю все BP и нажимаю RUN.

Давайте посмотрим, какой пользователь является владельцем.

Готово. Я закончил. Я могу повышать привилегии до SYSTEM.

Я приложил ZIP файл с измененным исходным кодом и скомпилированным исполняемым файлом. Помните, что если вы скомпилируете его самостоятельно, вы должны сделать это без DEP, без SAFESEH и без ASLR, и он будет работать только в 32-разрядной версии WINDOWS 7 и отрегулировать.