Первый драйвер для WINDOWS. IDA Pro «с нуля» ч.52

Обсудить статью на форуме


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

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

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

IDA Pro взлом и реверсинг программ

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

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

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

IDA Pro взлом и реверсинг программ

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

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

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

---------------------------------------------------------------------------------------------------------------------------
IDA Pro взлом и реверсинг программ

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 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 structure. TheDEVICE_OBJECT structure is allocated from nonpaged pool.

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

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

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

IDA Pro взлом и реверсинг программ

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

IDA Pro взлом и реверсинг программ

Функция начинается с тех же двух указателей на структуры типа _DRIVER_OBJECT и _UNICODE_STRING.

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

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

IDA Pro взлом и реверсинг программ

IDA Pro взлом и реверсинг программ

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

IDA Pro взлом и реверсинг программ

Python>hex(0x19*2)
0x32

IDA Pro взлом и реверсинг программ

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

IDA Pro взлом и реверсинг программ

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

IDA Pro взлом и реверсинг программ

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

IDA Pro взлом и реверсинг программ

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

IDA Pro взлом и реверсинг программ

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

IDA Pro взлом и реверсинг программ

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

IDA Pro взлом и реверсинг программ

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

IDA Pro взлом и реверсинг программ

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

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

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

IDA Pro взлом и реверсинг программ

IDA Pro взлом и реверсинг программ

IDA Pro взлом и реверсинг программ

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

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

IDA Pro взлом и реверсинг программ

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

IDA Pro взлом и реверсинг программ

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

IDA Pro взлом и реверсинг программ

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

#define FILE_DEVICE_HELLOWORLD 0x00008337

Затем появляется указатель на структуру с _UNICODE_STRING с DEVICENAME

IDA Pro взлом и реверсинг программ

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

IDA Pro взлом и реверсинг программ

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

IDA Pro взлом и реверсинг программ

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

IDA Pro взлом и реверсинг программ

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

Затем программа будет делать то же самое с другой строкой UNICODE при преобразовании ее из буфера со строкой UNICODE в структурную форму _UNICODE_STRING, как и раньше, с помощью API RTLINITUNICODESTRING.

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

IDA Pro взлом и реверсинг программ

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

Регистр EBX имеет указатель на структуру DRIVER_OBJECT

IDA Pro взлом и реверсинг программ

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

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

IDA Pro взлом и реверсинг программ

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

IDA Pro взлом и реверсинг программ

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

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

IDA Pro взлом и реверсинг программ

IDA Pro взлом и реверсинг программ

MAJORFUNCTION
[IRP_MJ_CREATE] - это первая позиция в массиве, т.е. MAJORFUNCION[0x0].

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

IDA Pro взлом и реверсинг программ

[IRP_MJ_CREATE] это 0x0
[IRP_MJ_CLOSE]
это 0x02
[IRP_MJ_DEVICE_CONTROL]
это 0x0E

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

IDA Pro взлом и реверсинг программ

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

Затем

[IRP_MJ_CLOSE] равно 0x2 * 4 даёт 8

И затем

[IRP_MJ_DEVICE_CONTROL] это 0x0E * 4 даёт 0x38

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

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

IDA Pro взлом и реверсинг программ

IDA Pro взлом и реверсинг программ

IDA Pro взлом и реверсинг программ

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

IDA Pro взлом и реверсинг программ

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

IDA Pro взлом и реверсинг программ

IDA Pro взлом и реверсинг программ

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

IDA Pro взлом и реверсинг программ

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

#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] и печатает следующее:

IDA Pro взлом и реверсинг программ

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

IDA Pro взлом и реверсинг программ

Драйвер использует обратный вызов [IRP_MJ_DEVICE_CONTROL], а затем проверяет, является ли IOCTL код равным в этом случае IOCTL_SAYHELLO

IDA Pro взлом и реверсинг программ

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

IDA Pro взлом и реверсинг программ

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

IDA Pro взлом и реверсинг программ

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

IDA Pro взлом и реверсинг программ

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

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

IDA Pro взлом и реверсинг программ

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

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

IDA Pro взлом и реверсинг программ

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

IDA Pro взлом и реверсинг программ

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

IDA Pro взлом и реверсинг программ

#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!”.

IDA Pro взлом и реверсинг программ

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

IDA Pro взлом и реверсинг программ

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

IDA Pro взлом и реверсинг программ

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

IDA Pro взлом и реверсинг программ

Я думаю, что с этим туториалом мы хорошо познакомились с этой темой. Мы продолжим в следующей части и будем углубляться больше.
=======================================================
Автор текста: Рикардо Нарваха - Ricardo Narvaja (@ricnar456)
Перевод на русский с испанского: Яша_Добрый_Хакер(Ростовский фанат Нарвахи).
22.10.2018
Версия 1.0


Обсуждение статьи: Первый драйвер для WINDOWS. IDA Pro «с нуля» ч.52 >>>


При перепечатке ссылка на https://exelab.ru обязательна.



Видеокурс ВЗЛОМ