Метод, который мы не рассмотрели, чтобы эксплуатировать одну из уязвимостей драйвера 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 <---- АДРЕС ВОЗВРАТА В ФУНКЦИЮ ПРОЛОГ
EBP-4 0x12218 <--------XORED COOKIE
EBP STORED_EBP <----- ТЕКУЩИЙ 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, а EXCEPTION HANDLER на данный момент имеет значение __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.
![]()
Здесь нам вернется адрес отображаемого файла.
![]()
Это секция будет из 0x1000 байт.
Введение в реверсинг с нуля, используя IDA PRO. Часть 67. Часть 1.
Дата публикации 24 апр 2019
| Редактировалось 4 июл 2019