R0 CREW

Malware Analysis Tutorial 22: IRP Handler and Infected Disk Driver (Перевод: Prosper-H)

Перейти к содержанию

Цели урока:

  1. Использовать WinDbg для отладки на уровне ядра.
  2. Разобраться во внутренней работе драйвера диска (disk driver).
  3. Разобраться с созданием скрытого виртуального диска.
  4. Исследовать технику, используемую руткитом Max++ для инфицирования драйвера.

1. Введение

Этот урок продолжает анализ из Урока 20. Здесь мы покажем, как Max++ использует модифицированный драйвер диска, чтобы обработать запросы ввода/вывода направляемые диску, который он создал (его имя «\?\C2CAD…»). Напомним, что в разделе 4.2.3 мы показали, как Max++ создает новое устройство ввода/вывода и подключает к нему вредоносный драйвер, чтобы всякий раз, когда на этом устройстве возникает запрос ввода/вывода, он был перенаправлен к объекту драйвера «8112d550», как показано ниже. Обратите внимание на значение MajorFunction (0xfae36bde), это то место, где будут обработаны запросы ввода/вывода. Получив базовый адрес модуля, мы можем легко вычислить ее смещение: « _+2BDE ».

kd> dt _DRIVER_OBJECT 8112d550 
nt!_DRIVER_OBJECT
   +0x000 Type             : 0n4
  ...
   +0x02c DriverInit       : [B][U]0xfae4772b[/U][/B]     long  +0
   +0x030 DriverStartIo    : (null) 
   +0x034 DriverUnload     : (null) 
   +0x038 MajorFunction    : [28] [B][U]0xfae56bde[/U][/B]     long  +0

Чтобы повторить эксперименты этого урока, вам нужно следовать инструкциям Раздела 2 из Урока 20. В этом уроке, мы будем выполнять анализ кода raspppoe.sys начиная с адреса « _+2BDE ».

2. Подготовка

Мы будем использовать настройки Раздела 2 из Урока 20. Ниже мы напомним только несколько из наиболее важных шагов, которые требуются для настройки рабочей среды:

(1) Вам нужен отдельный образ виртуальной машины, с именем «Win_Notes», чтобы иметь возможность комментировать код. На этой виртуальной машине, мы не будет запускать исследуемую вредоносную программу, а будем просто использовать ее для записи всех ваших наблюдений с помощью .udd файла. Чтобы сделать это, необходимо изменить поток исполнения IMM таким образом, чтобы избежать сбоя (crash) при работе с .sys файлами. За подробностями обращайтесь к Разделу 2 из Урока 20. Перейдите на адрес 0x10002BDE, чтобы начать анализ.

(2) Второй образ виртуальной машины «Win_DEBUG» должен быть запущен в DEBUG режиме. При этом, на HostXP должен быть запущен WinDbg, который, с помощью COM-порта, присоединиться к «Win_DEBUG» – таким образом, мы сможем отлаживать данную ОС в режиме ядра.

(3) В WinDbg установите брэйкпойнт «bu _+2BDE», чтобы перехватить функцию driver entry.

3. Дополнительная информация: Разработка драйверов Windows

Opferman предоставляет прекрасное введение в разработку драйверов Windows в [1], там же есть и примеры кода. Ниже, мы подведем итог основных моментов.

  1. Каждый драйвер имеет функцию driver entry, ее прототип показан ниже:
NTSTATUS DriverEntry(PDRIVER_OBJECT pDrv, PUNICODE_STRING reg)

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

Как было показано раньше в Уроке 20, функция DriverEntry размещена по адресу «_+372b ».

  1. Каждый драйвер может иметь набор из 28 функций, чтобы обрабатывать различные типы запросов ввода/вывода (такие как: read, write, etc). Код IRP-функции можно найти в [2] (наиболее типичные из них IRP_MR_CREATE и IRP_MR_READ)

Вы можете задать вопрос, должны ли мы установить брэйкпонйты на все 28 функций? Ответ и ДА, и НЕТ. Посмотрите на следующий дамп (объединив его с дампом из Раздела 1).

kd> dd 8112d550 
8112d550  00a80004 81210030 00000002 fae54000
8112d560  00008000 ffbd7d80 8112d5f8 001a001a
8112d570  e1389208 8068fa90 00000000 fae5772b
8112d580  00000000 00000000 [B][U]fae56bde fae56bde[/U][/B]
8112d590  fae56bde fae56bde fae56bde fae56bde
8112d5a0  fae56bde fae56bde fae56bde fae56bde
8112d5b0  fae56bde fae56bde fae56bde fae56bde
8112d5c0  fae56bde fae56bde fae56bde fae56bde

По смещению 0x38 от driver object (начало массива major function), все IRP-обработчики установлены на одну и туже функцию « _+2BDE »! Здесь автор руткита достаточно ленив, и эта его лень сохраняет много нашего времени. Поэтому, в конкретно нашей ситуации, мы можем просто сконцентрироваться на адресе « _+2BDE »!

Прежде чем двигаться дальше, нам нужно знать, что каждый IRP-обработчик имеет следующий прототип:

[B]NTSTATUS Handler(PDEVICE_OBJECT pDevice, PIRP pIRP)[/B]

Здесь, первый параметр это device object, а второй параметр представляет IRP-запрос для обработки.

Теперь, попадя в обработчик «_+2BDE », мы сможем легко узнать содержимое двух входящих параметров (device object находится по адресу 8112d550, а IRP по адресу 00070000), как показано ниже:

kd> dd esp
fafb73fc  81210030 [B]8112d550 00070000[/B] 81210030
fafb740c  fafb7460 804e37f7 81210030 ffbbe7e8
fafb741c  00000000 fb07c7a9 81210030 c000014f
fafb742c  00000000 00000000 c3a408e0 00000000
fafb743c  00000001 00000000 804e2490 fa047501
fafb744c  00000000 fafb7450 fafb7450 804fb1a9
fafb745c  00000000 fafb748c fb07ce80 81210030
fafb746c  fafb7484 ffb6fe10 81210030 ffb6fe10

kd> [B]dt _DEVICE_OBJECT 8112d550[/B] 
nt!_DEVICE_OBJECT
   +0x000 Type             : 0n4
   +0x002 Size             : 0xa8
   +0x004 ReferenceCount   : 0n-2128543696
   +0x008 DriverObject     : 0x00000002 _DRIVER_OBJECT
   +0x00c NextDevice       : 0xfae54000 _DEVICE_OBJECT
   ...

kd> [B]dt _IRP 00070000[/B] 
nt!_IRP
   +0x000 Type             : 0n193
   +0x002 Size             : 0
   +0x004 MdlAddress       : 0x00000100 _MDL
  ...

4. Анализ инфицированного драйвера диска

На Рис. 1 показана первая часть функции IRP-обработчика, по адресу «_+2BDE».

Рис. 1. Инфицированный Драйвер Диска

Как показано на Рис. 1, поток исполнения функции очень простой. Вначале, она извлекает указатель на PDEVICE_OBJECT из EBP+8 (1-й параметр) и сравнивает его с глобальной переменок, которая хранится по адресу 100061B0 (см. выделенную область). Очевидно, что глобальная переменная хранит ново-созданное инфицированное устройство (для ??\C2CAD…). Если это не запрос к «??\C2CAD», то поток исполнения переходит к адресу 10002BFD (вторая выделенная область), где вызывает PoCallDriver для передачи запроса низкоуровневому (реальному) драйверу, чтобы тот выполнил свою работу. Если же запрос адресован «??\C2CAD», то поток исполнения вызывает собственную, вредоносную функцию handleIRPForVirtualVolume, которая выполняет реальную операцию, чтобы имитировать работу виртуального диска.

Задача 1. Проанализируйте логику между адресами 10002BFD и 10002C25 (выделенная область на Рис. 1). В особенности, объясните инструкции по адресам 0x10002C16 и 0x10002C19.

5. Имитирование операций виртуального диска

Теперь давайте перейдем к анализу функции handleIRPForVirtualVolume. Она размещена по адресу « _+292A ». В данном случае, в WinDbg, вам нужно установить брэйкпойнт используя следующую команду «bp _+292A». На Рис. 2 показано тело major function. Обратите внимание, что вы можете легко сделать вывод, исходя из содержимого контекста (context), что EBX является входящим параметром функции, и ОН, прямо сейчас, указывает на IRP-запрос!

Рис. 2. Тело функции handleIRPForVirtualVolum

Тут мы подходим к интересной части. Посмотрите на Рис. 2, на адрес 0x1000293C, EAX сейчас равен «MajorFunction» из _IO_STACK_LOCATION (это значение является одним из типов IRP_MJ_xxx). Далее идет большой switch case (см. выделенную область, Рис. 2), который перенаправляет поток исполнения, чтобы обработать каждый из IRP-запросов направляемых устройству, например, таких как READ, WRITE, etc.

Задача 2. Как было заявлено выше, по адресу «0x1000293C», EAX содержит «MajorFunction» (значение которой равно одному из типов IRP_MJ_xxx). Вам может понадобиться найти определения значений IRP_MJ_xyz.

В качестве примера того, как Max++ имитирует дисковые операции, мы покажем то, как он обрабатывает запрос IRP_MJ_READ. На Рис. 3 показан код обработчика.

Рис. 3. Имитирование дисковых операций над файлом

Вначале, давайте посмотрим на определение структуры _IO_STACK_LOCATION, которая представляет собой задачу операции ввода/вывода. Обратите внимание, что в этот момент, ESI указывает на текущую структуру _IO_STACK_LOCATION, ниже представлено ее содержимое. Вы можете легко сделать вывод, что текущим Device Object является ??\C2CAD…

kd> dt _IO_STACK_LOCATION ff9c7fd8
nt!_IO_STACK_LOCATION
   +0x000 MajorFunction    : 0x3 ''
   +0x001 MinorFunction    : 0 ''
   +0x002 Flags            : 0x2 ''
   +0x003 Control          : 0 ''
   +0x004 Parameters       : __unnamed
   +0x014 DeviceObject     : 0xffb746d8 _DEVICE_OBJECT
   +[B][U]0x018 FileObject[/U][/B]       : (null) 
   +0x01c [B][U]CompletionRoutine[/U][/B] : (null) 
   +0x020 Context          : (null) 

Теперь давайте посмотрим на первую инструкцию LEA EAX, [ESI-24] (Рис. 3). Ее цель заключает в том, чтобы переместить указатель, находящийся в ESI, на 0x24 байта (обратите внимание на направление стэка). Размер _IO_STACK_LOCATION равен 0x24. Так образом, EAX сейчас указывает на новый экземпляр _IO_STACK_LOCATION. Следующие несколько инструкций копируют первые 9 слов из существующей структуры _IO_STACK_LOCATION в новую.

Затем по адресу 0x10002B10 (см. выделенная область, Рис. 3), новой структуре _IO_STACK_LOCATION, по смещению 0x18, присваивается значение из регистра ECX (глобальной переменной DS:[1000614C]). Обратите внимание, что 0x18 это атрибут FileObject (см. выше дамп из _IO_STACK_LOCATION!). Ниже представлен дамп File Object на который указывает ECX:

kd> dt _FILE_OBJECT 811b25d0
nt!_FILE_OBJECT
   +0x000 Type             : 0n5
   +0x002 Size             : 0n112
   ...
   +0x02c Flags            : 0x40040
   +0x030 FileName         : _UNICODE_STRING [B]"\WINDOWS\system32\config\yknueenf.sav"[/B]
   +0x038 CurrentByteOffset : _LARGE_INTEGER 0x0
   ...

Теперь совершенно ясно, что операция READ над диском, в дествительности осуществялется путем СОЗДАНИЯ НОВОЙ задачи _IO_STACK_LOCATION для файла «.sav», созданного ранее руткитом Max++!Последний интересный момент находится по адресу 0x10002B17: Max++ подключает функцию для CompleteRoutine (offset 0x1c of _IO_STACK_LOCATION), намерения этого действия вполне себе очевидны: данные, которые хранятся в «.sav» - закодированы, и Max++ декодирует их, когда производит чтение.

Мы закончили очень сложный и интересный анализ части кода связанного с инфицированным драйвером диска. Оставшуюся часть придется закончить вам:

Задача 3. Что произойдет, когда над виртуальным диском будет выполняется операция FormatEx?

Задача 4. Проанализируйте оставшиеся IRP_MJ_ операции, которые поддерживает инфицированный драйвер диска (подсказка: это может потребовать значительных усилий).

© Translated by Prosper-H from r0 Crew

Ссылки

[1] T. Opferman, “Driver Development Introduction Part I”
[2] MSDN, “IRP Function Code”