> 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-52.md).

# Часть 52

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

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

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

Чтобы получать информацию из пользовательского режима, мы должны научить наш драйвер отвечать на входные и выходные управляющие коды устройства (**IOCTL**), которые могут быть доставляться из пользовательского режима используя **API DEVICEIOCONTROL**. Мы уже видели, как наш драйвер может изменить процедуру загрузки, используя структуру **DRIVER\_OBJECT** и изменять указатель, который там храниться. Обработка **IOCTL** очень похожа. Нам просто нужно подготовить еще несколько процедур.

![](/files/-Ljtu2ZCKfKqkx1Ha9DN)

Первое, что мы должны сделать в нашей точке входа - создать **DEVICE OBJECT**.

Я не буду объяснять всю теорию об этом. Кто хочет узнать больше, почитайте это:

<https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/introduction-to-device-objects>

![](/files/-LjrT85hkuezwtt2sFyR)

И это должно быть так. В нашем первом драйвере, мы могли только запускать и останавливать его и не могли получать управляющие команды из пользовательского режима. Поэтому теперь мы должны создать **DEVICE OBJECT**, используя **API IOCREATEDEVICE**.

Функция, которую вызывает наша **DRIVERENTRY**, аналогична

`status = IoCreateDevice(DriverObject,0,&deviceNameUnicodeString,FILE_DEVICE_HELLOWORLD, 0,TRUE,&interfaceDevice);`

![](/files/-Ljtu2Zq7lUWtxqJuEyE)

**Parameters**

> **DriverObject \[in]**
>
> Pointer to the driver object for the caller. Each driver receives a pointer to its driver object in a parameter to its [DriverEntry](https://msdn.microsoft.com/es-es/library/windows/hardware/ff544113) routine.

`NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)`

Как мы увидели, **DRIVERENTRY** получает два аргумента. Первый - указатель на структуру **DRIVER OBJECT**, которая передается в качестве первого аргумента функции **IOCREATEDEVICE**.

> **DeviceName \[in, optional]**
>
> Optionally points to a buffer containing a null-terminated Unicode string that names the device object.
>
> WCHAR deviceNameBuffer\[] = L"\Device\HelloWorld";
>
> UNICODE\_STRING deviceNameUnicodeString;

В нашем коде **DEVICENAME** соответствует имени устройства, а затем копируется в переменную **DEVICENAMEUNICODESTRING**, который передается как аргумент **API**.

> **DeviceType \[in]**
>
> Specifies one of the system-defined FILE\_DEVICE\_XXX constants that indicate the type of device (such as FILE\_DEVICE\_DISK or FILE\_DEVICE\_KEYBOARD) or a vendor-defined value for a new type of device.

В нашем случае, это значение, определяется нами в начале кода.

`#define FILE\_DEVICE\_HELLOWORLD 0x00008337`

> **DeviceObject \[out]**
>
> Pointer to a variable that receives a pointer to the newly created [DEVICE\_OBJECT](https://msdn.microsoft.com/es-es/library/windows/hardware/ff543147) structure. TheDEVICE\_OBJECT structure is allocated from nonpaged pool.

Это указатель на **DWORD**, где **API** будет содержать указатель. Поэтому система говорит, что **OUT** – используется для выходного параметра.

Это самые важные функции. Давайте теперь посмотрим на код в **IDA,** теперь, когда мы знаем эти **API**.

Мы видим, что функция, которая вызывает наш **DRIVERENTRY**, аналогична

![](/files/-LjrT85qcOn47x08wWpF)

Давайте посмотрим на часть нашего кода.

![](/files/-Ljtu2_QS-nuLACTNgmF)

Функция начинается с тех же двух указателей на структуры типа **\_DRIVER\_OBJECT** и **\_UNICODE\_STRING**.

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

В переменную **VAR\_4** сохраняется **COOKIE** для защиты стека.

![](/files/-LjrT865DR51tWaKI2cB)

![](/files/-Ljtu2aLfGaLefuKkY1o)

Здесь программа копирует имя устройства **UNICODE** размером **9 DWORD** (**0x24** байта) в назначение, которым является переменная **DEVICENAMEBUFFER**, длина которой составляет **19 WORDS**, т.е. **19** \* **2**, всего **38** байт в десятичном формате или **0x26** байт в шестнадцатеричном, поэтому всё, что копируется, намного меньше, чем буфер.

![](/files/-LjrT86KjKgkYMTeWb_S)

```
Python>hex(0x19 * 2)
0x32
```

![](/files/-MX3mJerpiIHHTcINdy9)

Затем копируется **DOS\_DEVICE\_NAME** размером **0xB DWORDS,** т.е. **0xB** \* **4** - это **0x2C** байт в шестнадцатеричной системе в общей сложности

![](/files/-Ljtu2amIeQjCtAFpvox)

И буфер назначения это **DEVICELINKBUFFER**. Давайте посмотрим его длину.

![](/files/-Ljtu2b3pWFjCBIfg0xC)

Это **23** \* **2** в десятичной системе, т.е. **46** байт, т.е **0x2E** в шестнадцатеричной системе, так что здесь тоже нет переполнения.

![](/files/-Ljtu2bNpd1HLngxzNNN)

Проблема в том, что в **DEVICENAMEBUFFER** находится имя устройства, а в **DEVICELINKBUFFER** - имя устройства **DOS**.

![](/files/-Ljtu2bY4tPYuoqgs38w)

Затем идёт вызов функции **DBGPRINT**, которая печатает сообщение "**DRIVERENTRY CALLED**".

![](/files/-Ljtu2blRs4TBtYIL6vD)

Давайте продолжим со следующего: преобразуем строку **UNICODE** в ту, которая имеет тип **\_UNICODE\_STRING**. Для этого существует следующий **API RTLINITUNICODESTRING**.

![](/files/-LjrT86elIIAeB1RJbrS)

У нас есть вызов в **RTLINITUNICODESTRING**

![](/files/-LjrT86i9tNnwyku7sfg)

> WCHAR deviceNameBuffer\[] = L"\Device\HelloWorld";

Источник **DEVICENAMEBUFFER** является указателем на буфер, который имеет строку юникода, а назначение - указатель на структуру **UNICODE\_STRING**. Эта структура, которую мы уже видели, имеет три поля, два слова (**LENGHT** и **MAXIMUMLENGHT**, а третья должна быть указателем на строку юникода.

Это означает, что **API** скопирует адрес этого исходного буфера в третье поле структуры, добавит **LENGHT** и **MAXIMUMLENGHT** в соответствующие поля и преобразует общий буфер со строкой **UNICODE** в структуру **UNICODE\_STRING**.

![](/files/-Ljtu2cIbOuWb2j1ptRf)

![](/files/-Ljtu2cRByK62iifnJdm)

![](/files/-LjrT86s9FJiDhQHFayb)

Это структура типа **UNICODE\_STRING** из **8** байт. Так как они представляют собой два слова для **LENGHT** и **DWORD** для копирования указателя на буфер с помощью строки юникода.

Тогда есть вызов к **API IOCREATEDEVICE**, про которую мы говорили.

![](/files/-LjrT86xPKDM3WIbp4Y5)

Мы видели, что самый дальний аргумент, т.е. последний, был указателем на **DWORD**, который использовался как выход. Так что **API** хранит там указатель. Мы видим, что программа устанавливает нуль в переменную **INTERFACEDEVICE**, а затем с помощью инструкции **LEA** находит указатель на эту переменную, где будет записан указатель.

![](/files/-Ljtu2d0hClWmpVr4JLP)

Затем идет инструкция **PUSH 1**, которая является исключительным аргументом, который мы не видели раньше, потому что это не имело большого значения. Затем появляется инструкция **PUSH EDI**.Мы видим, что в регистре **EDI** есть нуль, поскольку раньше была выполнена инструкция **XOR EDI, EDI**.

![](/files/-LjrT878QzppRRLTRYNN)

Это также не очень важно. Затем идёт инструкция **PUSH 8337H**, которая является константой **DEVICETYPE**, которую мы определили в исходном коде.

> \#define FILE\_DEVICE\_HELLOWORLD 0x00008337

Затем появляется указатель на структуру с **\_UNICODE\_STRING** с **DEVICENAME**

![](/files/-Ljtu2dTlwphx4P80037)

Затем идет другая инструкция **PUSH EDI**, которая равна нулю **DEVICEEXTENSIONSIZE** и в конце регистр **EBX** является указателем на **DRIVEROBJECT**.

![](/files/-Ljtu2descwloa-ZSUY2)

Давайте запомним, что это указатель на структуру **\_DRIVER\_OBJECT**.

![](/files/-Ljtu2dpMUWgD5QcOh3x)

Хорошо. При выходе из **API** будет создан **DEVICEOBJECT**.

![](/files/-LjrT87OkfF4g9cbczlg)

Если регистр **EAX** имеет отрицательное значение, будет сбой и инструкция **JS** будет переходить на зеленую стрелку. Но у нас всё будет нормально и программа перейдет к функции **DBGPRINT**, которая напечатает "**SUCESS**"

Затем программа будет делать то же самое с другой строкой **UNICODE** при преобразовании ее из буфера со строкой **UNICODE** в структурную форму **\_UNICODE\_STRING**, как и раньше, с помощью **APIRTLINITUNICODESTRING**.

Поэтому **DEVICELINKUNICODESTRING** теперь будет иметь тип **\_UNICODE\_STRING** и будет иметь в своем третьем поле указатель на буфер со строкой **UNICODE L"\DOSDEVICES\HELLOWORLD"**.

![](/files/-LjrT87S5R5zu85PW_yN)

Затем, передаются указатели на два **\_UNICODE\_STRING** в функцию **IOCREATESYMBOLICLINK**. Мы создаем символическую связь между **DEIVCEOBJECT** и пользовательским режимом.

Регистр **EBX** имеет указатель на структуру **DRIVER\_OBJECT**

![](/files/-LjrT87V5nHeydm-o2y0)

Если объект не находится в структурах как раньше, мы переходим в **LOCAL TYPES** и синхронизируем программа так, чтобы объект отображался. Мы нажимаем **T** в каждом из этих полей.

Как и в предыдущем случае, мы устанавливаем пользовательскую подпрограмму, когда загружается драйвер, которая находится по смещению **EBX** + **34H**. Теперь нажимая **T**, мы видим, что это поле **DRIVERUNLOAD**.

![](/files/-LjrT87XyThZpy_iZMRz)

Мы видим, что программа загрузки драйвера не только печатает с помощью **DBGPRINT** строку "**DRIVER UNLOADING**"

![](/files/-LjrT87bLH-phKPMg4M_)

Поскольку раньше мы создавали символическую ссылку с помощью функции **IOCREATESYMBOLICLINK**, когда мы выходим, мы должны удалить ее с помощью функции **IODELETESYMBOLICLINK**, а также, поскольку мы использовали для создания функцию **DEVICEOBJECT** с **IOCREATEDEVICE**, теперь устройство будет удаляться с помощью **IODELETEDEVICE**, иначе возникнут проблемы с его загрузкой.

Последней вещью во входной функции является поле **MAJORFUNCTION**, которое представляет собой массив указателей обратных вызовов (**DWORD**) на разные функций.

![](/files/-Ljtu2fmuozNGEzUYOjw)

![](/files/-LjrT87j-UWQMNYgYWwV)

**MAJORFUNCTION** \[**IRP\_MJ\_CREATE**] - это первая позиция в массиве, т.е. **MAJORFUNCION**\[**0x0**].

Поскольку у нас есть таблица.

![](/files/-Ljtu2gKZZ4yDCNh5-4A)

> **\[IRP\_MJ\_CREATE]** это \*\*0x0
>
> \[IRP\_MJ\_CLOSE] **это** 0x02
>
> \[IRP\_MJ\_DEVICE\_CONTROL] **это** 0x0E\*\*

Три поля инициализируются адресом функции **DRIVERDISPATCH**

![](/files/-LjrT885XcFPKpRzySce)

Значение записывается в положение **0x0**, так как \[**IRP\_MJ\_CLOSE**] равно **0x0** \* **4** = **0**

Затем

**\[IRP\_MJ\_CLOSE]** равно **0x2** \* **4** даёт **8**

И затем

**\[IRP\_MJ\_DEVICE\_CONTROL]** это **0x0E** \* **4** даёт **0x38**

Таким образом, все три инструкции пишут один и тот же указатель на одну и ту же функцию.

Каждый из этих обратных вызовов вызывается в разные моменты взаимодействия из программы в пользовательском режиме.

![](/files/-Ljtu2gqoL5w9bQBZLG1)

![](/files/-Ljtu2h-A9DWpHk2ddZr)

![](/files/-Ljtu2h9tJ93qBInhnka)

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

![](/files/-LjrT88Gz8fBRew_4EHc)

Функция получает два аргумента. Знаменитый указатель на **DEVICE\_OBJECT**, а второй - указатель на структуру **IRP**, которая является сложной структурой, и мы увидим её позже.

![](/files/-Ljtu2hT5ELDXYeI60qY)

![](/files/-LjrT88OTsgQy0EQTEMN)

Мы видим, что, как и в предыдущий раз при регистрации и запуске, драйвер печатает **DRIVERENTRY CALLED** и **SUCESS**, а также при выгрузке **Driver UNLOADING**, но теперь также из пользовательского приложения, которое я сделал при его запуске, хотя раньше нужно было нажимать **START SERVICE** для того, чтобы он начал печатать.

![](/files/-Ljtu2ho79QpWxaWq-Tz)

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

```c
#include "stdafx.h"
#include <windows.h>

#define FILE_DEVICE_HELLOWORLD 0x00008337
#define IOCTL_SAYHELLO (ULONG) CTL_CODE( FILE_DEVICE_HELLOWORLD, 0x00, METHOD_BUFFERED, FILE_ANY_ACCESS )

int main()
{
    HANDLE hDevice;
    DWORD nb;
    hDevice = CreateFile(TEXT(".HelloWorld"), GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

    DeviceIoControl(hDevice, IOCTL_SAYHELLO, NULL, 0, NULL, 0, &nb, NULL);

    CloseHandle(hDevice);
    return 0;
}
```

Т.е. когда я вызываю функцию **CREATEFILE,** чтобы иметь хэндл драйвера, драйвер переходит к обработчику через обратный вызов \[**IRP\_MJ\_CREATE**] и печатает следующее:

![](/files/-LjrT88ZYFSve9M-40um)

Затем, когда Вы вызываете с помощью **функции DEVICEIOCONTROL**, передавая его **IOCTL** код.

![](/files/-Ljtu2iCa6oHJHcx97uy)

Драйвер использует обратный вызов \[**IRP\_MJ\_DEVICE\_CONTROL**], а затем проверяет, является ли **IOCTL** код равным в этом случае **IOCTL\_SAYHELLO**

![](/files/-LjrT88etHeXhue5rV2V)

В этом случае драйвер печатает "**HELLO WORLD**"

![](/files/-LjrT88hrpSZaivHD-PA)

И последний код вызывается, когда я вызываю функцию **CLOSEHANDLE** и вызывается соответствующий \[**IRP\_MJ\_CLOSE**]

![](/files/-Ljtu2ifFKlsdcTUh4Jq)

Я синхронизирую структуру **IRP** через **LOCAL TYPES**.

![](/files/-Ljtu2iqyiFSZP31Vt2D)

И я вижу на вкладке **STRUCTURES** ту же самую структуру.

Мы видим, что когда я его отладку, и я поставлю **BP** в функцию обработки, после прибываем в это место

![](/files/-Ljtu2j4GYA5tKLI2w6V)

Драйвер читает из структуры **IRP** часть **TAIL,** которая не определена в **MSDN**, но здесь, после поиска по смещению **EDI+60** и передачи этого значения в регистр **EBX**, его содержимое переходит в регистр **EAX**, который впервые имеет значение \[**IRP\_MJ\_CREATE**], т.е. нуль. И в этом случае драйвер пойдет туда, чтобы напечатать сообщение о том, что произошло создание.

Если я снова нажму **RUN**, драйвер снова остановится со значением регистра **EAX** равным **0x0E** из \[**IRP\_MJ\_DEVICE\_CONTROL**].

![](/files/-Ljtu2jIkDSilO0WFsFd)

Поскольку регистр **EAX** отличается от нуля, драйвер идет сюда.

![](/files/-LjrT88tj3kVJ1Rd0DT5)

И в этом случае драйвер приходит в розовый блок, печатая, что добрался сюда через **IOCTL**.

![](/files/-LjrT88xp6o6awddLFZX)

```c
#define IOCTL_SAYHELLO (ULONG) CTL_CODE(FILE_DEVICE_HELLOWORLD, 0x00, METHOD_BUFFERED, FILE_ANY_ACCESS)
```

В коде **IOCTL** код, который получается из значения **0x8337 FILE\_DEVICE**, выполняется несколькими операциями в соответствии с типом **IOCTL** (в этом случае **METHOD BUFFERED** и т.д. и т.д), Который дает нам **IOCTL** код равным **83370000**.

Здесь драйвер сравнивает это и как есть. Он выходит и печатает сообщение "**HELLO WORLD**!".

![](/files/-Ljtu2jvFc7e8UOz-JqS)

Когда мы проходим через функцию **DEBUGPRINT**, драйвер показывает нам в панели **WINDBG** сообщение. Если бы было несколько **IOCTL** с разными кодами, здесь был бы переключатель.

![](/files/-Ljtu2k41QRLC777Js2n)

В третий раз, когда мы останавливаемся, мы исполняем функцию **CLOSEHANDLE** и регистр **EAX** равен **2**.

![](/files/-LjrT899OPOjS4SaS1Jg)

И происходит печать.

![](/files/-LjrT89BFE0yoVhhfZQc)

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

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

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

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

22.10.2018

[**Источник: ricardonarvaja.info**](http://ricardonarvaja.info/WEB/IDA%20DESDE%20CERO/CURSO%20DE%20IDA%20TUTES/52-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-52.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.
