> For the complete documentation index, see [llms.txt](https://yutewiyof.gitbook.io/intro-rev-ida-pro/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://yutewiyof.gitbook.io/intro-rev-ida-pro/chast-56.md).

# Часть 56

[\[Используемые материалы\]](https://github.com/yutewiyof/intro-rev-ida-pro/tree/e1367e11cc661f3d69c02ae5f733b7dd168bc5ab/.gitbook/assets/files/56.zip)

Давайте теперь рассмотрим драйвер, который запрограммирован с различными уязвимостями, чтобы понять, как их эксплуатировать. Как и всегда мы будем использовать **WINDOWS 7 SP1** без какого-либо патча безопасности. Мы знаем, что здесь всё будет работать. Затем мы увидим, какие изменения есть ниже и какие другие возможности существуют в новых системах. Но мы будем идти потихоньку и никуда не будем спешить.

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

<https://github.com/hacksysteam/HackSysExtremeVulnerableDriver>

Реализованные уязвимости:

* Двойная выборка
* Переполнение пула
* Использование после освобождения
* Путаница в типах
* Переполнение стека
* Целочисленное переполнение
* Переполнение стека с флагом GS(Buffer Security Check)
* Перезапись арбитражного кода
* Разыменование нулевого указателя
* Неинициализированная переменная кучи
* Неинициализированная переменная стека
* Небезопасный доступ к ресурсам ядра

Мы будем начинать потихоньку. Сначала сделаем анализ переполнения стека.

Конечно, Вам нужно скопировать драйвер на целевую машину и загрузить его с помощью **OSR DRIVER LOADER**.

Мы копируем его с его **IDB** в локальную папку и открываем его в **IDA** чтобы начать анализ.

Хорошо. Как мы уже знаем здесь, у нас есть символы, которые облегчают нам много вещи. Но первое, что мы должны найти и что почти всегда распознаётся с символами или без, это структура **\_DRIVER\_OBJECT**, которая передается как аргумент в функцию **DRIVERENTRY**.

В этом случае у нас не так много проблем.

![](/files/-Ljttv_6VmDeLyRx6R3y)

Здесь хорошо видна точка входа, и ее аргументы хорошо обнаружились.

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

![](/files/-LjrT6klRCRBzKr6k_Oh)

Напомним, что первым аргументом был указатель на структуру **UNICODE\_STRING**. Здесь мы видим **PUNICODE\_STRING**, т.е. указатель на структуру **UNICODE\_STRING**.

![](/files/-LjrT6ktEJah_BdBp3vG)

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

![](/files/-Ljttv_kF3di7bsWzgQd)

Сначала инициализируется в нуль структура **DOSDEVICENAME**, которая также имеет тип **UNICODE\_STRING**.

![](/files/-LjrT6l4PGWYq6BtOMdh)

Драйвер помещает в поле **LENGTH** нуль, помещая в него значение регистра **AX**, которое равно здесь **0**. А затем идет инструкция **STOSD**, которая копирует значение из регистра **EAX**, т.е. помещает нуль в адрес, куда указывает регистр **EDI**, т.е. в поле **MAXIMUMLENGHT**. А затем следующая инструкция **STOSW** копирует из регистра **AX** значение, т.е. нуль в два следующих байтах, т.е. помещая нуль в **6** байтов, т. е. инициализирует два оставшихся поля структуры, которые занимают **6** байтов (**1 WORD** и **DWORD**).

Компилятор только инициализирует структуру **DOSDEVICENAME**. Другая переменная, которая называется **DEVICENAME**, не равна нулю. Драйвер использует ее напрямую.

![](/files/-Ljttva1WbXvflQBIGAY)

Другими словами, **DEVICENAME** - это строка, которая преобразуется в тип структуры **UNICODE\_STRING**. Другими словами, в её трех полях будет длина, максимальная длина и указатель, который мы передаем в строку источник. Он будет скопирован в третье поле.

![](/files/-LjttvaAg8FOTerqT_X3)

В моей машине указатель находится по адресу **0x00016938**. Это смещение скопирует его в третье поле структуры.

![](/files/-LjttvaHa5BmLMbYJaQG)

В **DOSDEVICENAME** вы будете создавать другой указатель **UNICODE\_STRING**. Я использую как источник эту другую строку.

Затем идёт вызов функции **IOCREATEDEVICE**. Мы помним, что вам нужно было создать **DEVICE OBJECT**, чтобы иметь возможность общаться с программами из пользовательского режима.

![](/files/-LjrT6lPhOzRf7zKfg4n)

![](/files/-LjttvabIh6rpbQPu0-_)

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

![](/files/-LjrT6lZXVf_wt2lUshG)

Последний аргумент, это указатель на вновь созданную структуру **DEVICE\_OBJECT**.

![](/files/-LjttvatpYVB_pBm4t5g)

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

![](/files/-Ljttvb0f68IbMpUbxjB)

Затем структура **DRIVEROBJECT** будет инициализироваться из регистра **ESI + 38**. Поскольку **ESI** указывает на **DRIVEROBJECT**, я нажимаю **T**. Я могу посмотреть, какое это поле (но это **DRIVER\_OBJECT** идём в **LOCAL TYPES** и синхронизируем типы)

![](/files/-LjrT6lmmxdALNiY9-HX)

![](/files/-LjttvbGmRjz5rgtJlZS)

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

![](/files/-LjttvbOFTt1VqAslym6)

Первый указатель, т.е. тот, который находится в позиции **0**, является **IRP\_MJ\_CREATE** и драйвер будет переходить на него, когда вы вызываете функцию **CREATEFILE**, чтобы открыть дескриптор устройства. Второй указатель, т.е. со значением **0x1** находится в положении 4, так как он являются **DWORD** и т.д. Это означает, что обратно пропорционально, если у меня есть поле этой структуры по её смещению, чтобы знать, какой указатель нам нужен надо делить его на четыре. Из примера, который мы использовали в предыдущих драйверах давайте вспомним.

![](/files/-LjrT6m0q_INpYdvkoIQ)

Это соответствует значению **0x38/4**, т.е.

```
Python>hex(0x38/4)
0xE
```

![](/files/-LjttvbfNtpmGC7ng-l5)

Т.е. **0xE** соответствует **IRP\_MJ\_DEVICE\_CONTROL**, когда мы передали код **IOCTL** из режима пользователя. Этот указатель мы перезаписали с помощью обработчика, так что в соответствии с тем, какой код **IOCTL**, различные действия будут выполняться с помощью конструкции **SWITCH**. Например так.

В текущем случае, мы видим, что драйвер инициализирует значения начиная с указателя на начало таблицы **MAJORFUNCTION**. Он копирует значение регистра **EAX**, в которое помещается смещение функции, которая называется **\_IRP\_NOTIMPLEMENTEDHANDLERS**. Копирование происходит **0x1C** раз. Это значение передаётся в регистре **ECX**. Оно равно количеству указателей, которые нужно инициализировать.

![](/files/-MX3mIVOpsHvsBKaUBQf)

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

![](/files/-Ljttvc-tfYbuDPRUdkG)

Поскольку регистр **EDX** хранит указатель на начало таблицы **MAJORFUNCTION**, его содержимым является позиция **0**, т.е.

```
+#define IRP_MJ_CREATE 0x00
+#define IRP_MJ_CREATE_NAMED_PIPE 0x01
+#define IRP_MJ_CLOSE 0x02
+#define IRP_MJ_READ 0x03
+#define IRP_MJ_WRITE 0x04
+#define IRP_MJ_QUERY_INFORMATION 0x05
+#define IRP_MJ_SET_INFORMATION 0x06
+#define IRP_MJ_QUERY_EA 0x07
+#define IRP_MJ_SET_EA 0x08
+#define IRP_MJ_FLUSH_BUFFERS 0x09
+#define IRP_MJ_QUERY_VOLUME_INFORMATION 0x0a
+#define IRP_MJ_SET_VOLUME_INFORMATION 0x0b
+#define IRP_MJ_DIRECTORY_CONTROL 0x0c
+#define IRP_MJ_FILE_SYSTEM_CONTROL 0x0d
+#define IRP_MJ_DEVICE_CONTROL 0x0e
+#define IRP_MJ_INTERNAL_DEVICE_CONTROL 0x0f
+#define IRP_MJ_SCSI 0x0f
+#define IRP_MJ_SHUTDOWN 0x10
+#define IRP_MJ_LOCK_CONTROL 0x11
+#define IRP_MJ_CLEANUP 0x12
+#define IRP_MJ_CREATE_MAILSLOT 0x13
+#define IRP_MJ_QUERY_SECURITY 0x14
+#define IRP_MJ_SET_SECURITY 0x15
+#define IRP_MJ_POWER 0x16
+#define IRP_MJ_SYSTEM_CONTROL 0x17
+#define IRP_MJ_DEVICE_CHANGE 0x18
+#define IRP_MJ_QUERY_QUOTA 0x19
+#define IRP_MJ_SET_QUOTA 0x1a
+#define IRP_MJ_PNP 0x1b
+#define IRP_MJ_PNP_POWER 0x1b
+#define IRP_MJ_MAXIMUM_FUNCTION 0x1b
```

Мы будем создавать структуру **MAJORFUNCTION**.

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

Я знаю, что это указатели, но для нашего случая я буду использовать тип **UNSIGNED INT** и это будет работать. Проблема состоит в том, что это локальные типы. При использовании операции **INSERT**, **IDA** не принимают структуру. Поэтому я буду экспортировать её. Я добавляю структуру и перезагружаю ее с помощью **FILE→ LOAD FILE→ PARSE C HEADER FILE**

![](/files/-LjrT6mQMOmNBZCRY0lL)

Я добавил структуру в **ЗАГОЛОВОЧНЫЙ ФАЙЛ**.

![](/files/-LjttvcM6JagZcxdYkIV)

Теперь возникает вопрос

![](/files/-LjttvcTBBGYnn-z1H5o)

Я позволял себе редактировать внутри структуры **DRIVER\_OBJECT**, тип **MAJORFUNCTION** в **LOCAL TIPES** ?

![](/files/-LjrT6mkVloemaJW7ahJ)

Мы видим, что я изменил структуре определение поля **MAJORFUNCTION**. Внутри структуры **DRIVER\_OBJECT** для того, чтобы она была типа **\_\_MAJORFUNCTION**, который я определил.

![](/files/-Ljttvco8-Th7skqf2vl)

![](/files/-LjrT6mqD6QEVs4eX755)

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

Когда мы нажимаем **T**, мы не можем выбрать структуру **DRIVER\_OBJECT**, потому что регистр **EDX** указывает на таблицу **MAJORFUNCTION**, поэтому я выбираю последнюю.

![](/files/-LjttvdBUlwOtC5PJqmG)

![](/files/-LjrT6myI3JBPvXRLITB)

Сейчас стало намного лучше. Все в порядке. Я определил функции, которые будут использоваться, т. е. **\_MJ\_CREATE**, **\_MJ\_CLOSE**, **\_MJ\_DEVICE\_CONTROL** и те, которые будет вызываться, когда драйвер останавливается через функцию **DRIVERUNLOAD**.

Очевидно, когда из режима пользователя мы вызвали функцию **CREATEFILE**, вызывается функция, которая перезаписывает поле **\_MJ\_CREATE**. Когда мы передаем **IOCTL** код в функцию **DEVICEIOCONTROL**, вызывается **\_MJ\_DEVICE\_CONTROL**. Когда вызывается функция **CLOSEHANDLE**, драйвер вызывает ту, которая перезаписывает поле **\_MJ\_CLOSE**. И когда драйвер останавливается, вызывается та, которая перезаписывает функцию **DRIVERUNLOAD**.

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

Мы синхронизируем структуру **IRP** из вкладки **LOCAL TYPES**.

Как мы видели в части **53**, поле **60** из **IRP** указывает на структуру **\_IO\_STACK\_LOCATION**.

![](/files/-LjrT6n2W-TEFQR-LqlU)

Регистр **ESI** указывает на **\_IO\_STACK\_LOCATION**, поэтому все, что равно **ESI** + **XXX**, будет полем вышеупомянутой структуры. После синхронизации из вкладки **LOCAL TYPES**.

Напомним, что у структуры **\_IO\_STACK\_LOCATION** есть несколько опций. Я выберу ту, которая соответствует **IOCONTROLCODE**.

![](/files/-LjttvdfLPTRsVIowYYB)

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

![](/files/-LjrT6nG4aSUDlv9KDNy)

![](/files/-LjrT6nJbrLJ8XhABsL3)

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

![](/files/-Ljttve7yLQ32zdjd1Ok)

Мы видим, что есть два аргумента, которые передают в регистре **EDI** структуру **IRP** а в регистре **ESI IRPSP** - это имя переменной типа **\_IO\_STACK\_LOCATION**, которая была в регистре **ESI**.

![](/files/-LjttveFpjltezGHNUxp)

![](/files/-LjrT6nchCAdq0nyGv6l)

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

![](/files/-LjttveVrAwEC84RwuvM)

Мы видим, что этот **SIZE** и этот буфер передаются в функцию **\_TRIGGERSTACKOVERFLOW**.

![](/files/-LjttvecqzepvH9XRs6a)

Мы видим, что драйвер помещает нуль с помощью регистра **ESI** в первый **DWORD** буфера **KERNELBUFFER** и затем с помощью функции **MEMSET** помещает нуль в следующий **DWORD**, так как происходит сложение **KERNELBUFFER + 4**, и получается размер **0x7FC**.

![](/files/-LjrT6npGyLaR2LFd7bj)

Вышеупомянутый буфер имеет длину **512** \* **4**, так как это массив **DWORD** (**DD**), поэтому общая длина в десятичной системе равна:

```
512 * 4
Out[64]: 2048
```

В **HEX** это

```
hex(2048)
Out[65]: '0x800'
```

Поэтому, поместив в первый **DWORD** нуль, а затем в оставшиеся **0x7FC байт**. Действительно, драйвер заполнит весь буфер размером **0x800** нулями. (**0x7FC** + **4** = **0x800)**

Затем драйвер вызовет функцию **PROBEFORREAD**, которая проверит, выровнен ли входной буфер в пользовательском режиме и находится ли он в пользовательском пространстве.

![](/files/-LjrT6nvFkoKyPzg75AZ)

![](/files/-Ljttvf2Q1khj2sOYiER)

Затем драйвер печатает указатели буферов и их размеры.

![](/files/-LjrT6o7fiYdTlHZ69gt)

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

![](/files/-LjttvfNXVfodLwyOVpi)

![](/files/-LjttvfZ1U67dtwtKOyr)

Здесь мы видим, что при печати размера буфера ядра, драйвер используйте тот, который находится в регистре **ESI**, который является константой **0x800**, но при выполнении функции **MEMCPY** он используйте аргумент **SIZE**, который я передал ему, без каких-либо проверок, которые будут приводить к переполнению стека и поскольку здесь нет **COOKIE**, он будет легко переполняться.

В следующей части мы будем делать скрипт с эксплуатацией. На этом здесь, мы закончим анализ.

Автор оригинального текста — Рикардо Нарваха.

Перевод и адаптация на русский язык — Яша Яшечкин.

Перевод специально для форума системного и низкоуровневого программирования - WASM.IN

06.11.2018

[**Источник: ricardonarvaja.info**](http://ricardonarvaja.info/WEB/IDA%20DESDE%20CERO/CURSO%20DE%20IDA%20TUTES/56-INTRODUCCION%20AL%20REVERSING%20CON%20IDA%20PRO%20DESDE%20CERO.docx)


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://yutewiyof.gitbook.io/intro-rev-ida-pro/chast-56.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
