Введение в реверсинг с нуля, используя IDA PRO. Часть 67. Часть 1.

Дата публикации 24 апр 2019 | Редактировалось 4 июл 2019
Метод, который мы не рассмотрели, чтобы эксплуатировать одну из уязвимостей драйвера 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

1.png

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

2.png

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

3.png

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

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

4.png

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

5.png

Мы будем отмечать структуры _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;
};
6.png

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

7.png

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

8.png

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

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

9.png

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

10.png

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

11.png

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

12.png

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

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

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

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

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

13.png

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

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

14.png

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

15.png

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

16.png

И затем сюда.

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

17.png

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

18.png

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

19.png

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

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

20.png

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

21.png

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

22.png

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

23.png

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

24.png

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

25.png

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

26.png

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

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

27.png

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

28.png

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

29.png

30.png

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

31.png

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

32.png

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

33.png

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

34.png

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

35.png

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

36.png

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

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

37.png

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

38.png

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

39.png

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

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

40.png

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

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

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

41.png

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

42.png

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

43.png

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

44.png

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

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

PUSH 0x210
PUSH 0x12218

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

45.png

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

46.png

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

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

47.png

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

48.png

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

49.png

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

50.png

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

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

51.png

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

52.png

53.png

Затем содержимое 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 при возврате равен тому же значения, что и перед передачей аргументов.

54.png

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

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

55.png

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

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

56.png

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

57.png

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

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

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

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

58.png

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

ЗНАЧЕНИЕ 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.

59.png

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

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

60.png

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

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

61.png

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

62.png

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

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

63.png

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

64.png

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

ЗНАЧЕНИЕ 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" байты также являются полями указанной структуры.

65.png

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

66.png

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

67.png

Двумя важными переменными являются 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

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

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

68.png

Мы видим, что в по адресу 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 для достижения уязвимой функции (Позже мы увидим, как это сделать. Сейчас же это просто для проверки).

69.png

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

70.png

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

71.png

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

72.png

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

73.png

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

74.png

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

Метод заключается в том, что, когда мы копируем из пользовательского буфера, который является источником, и мы предоставляем, вместо того, чтобы сломать стек, заполняя его полностью, мы должны вычислить, какой источник копирует 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, вам придется изменить параметры по умолчанию.

75.png

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

76.png

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

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

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

77.png

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

78.png

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

79.png

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

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

80.png

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

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

81.png

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

82.png

83.png

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

84.png

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

85.png

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

86.png

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

87.png

88.png

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

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

89.png

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

90.png

91.png

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

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

92.png

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

93.png

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

94.png

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

95.png

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

96.png

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

97.png

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

98.png

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

99.png

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

100.png

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

0 3.949
yashechka

yashechka
Ростовский фанат Нарвахи

Регистрация:
2 янв 2012
Публикаций:
90