R0 CREW

Malware Analysis Tutorial 29: Stealthy Library Loading II (Using Self-Modifying APC) (Перевод: Prosper-H)

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

Цели урока:

  1. Попрактиковаться в перехвате загрузки DLL-библиотек с помощью WinDbg.
  2. Используя IMM протрейсить и изменить поток исполнения программы.
  3. Разобраться с техниками, используемыми Max++, направленными на скрытие загрузки DLL-библиотеки.
  4. Разобраться с image loading notifier, асинхронными вызовами процедур, а также с нормальными и ядерными процедурами (kernel and normal routines).

1. Ввдение

В этом уроке будет произведен анализ вредоносного драйвера B48DADF8.sys. Мы предполагаем, что вы извлекли его из скрытого диска, следуя инструкциям из Урока 28.

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

Нам нужно два образа Windows: один для заметок, а другой для запуска руткита. Так же, на HostXP, нам будет нужен WinDbg.

Основная идея заключается в том, чтобы запустить два экземпляра IMM и использовать один для отладки другого. После запуска отладчиков, по адресу 0x004E6095, следует установить брэйкпойнт и пропустить инструкцию, когда будет недопустимая запись в память. Как только B48DADF8.sys загрузится, мы сможем определить точку входа (entry) драйвера. Для этого во втором экземпляре IMM (Рис. 1) нажмите «View => Executable Modules», после чего вы увидите, что точкой входа драйвера является адрес +1259. Перейдя на него, мы окажемся в функции driver entry.

Рис. 1. Определение точки входа в B48DADF8.sys

Следуя инструкциям из Урока 28, настройте WinDbg и образ виртуальной машины для отладки в режиме ядра. Нам нужно остановиться на точке входа в драйвер. Для этого нам нужно определить её адрес. В WinDbg этого можно добиться, определив начальный адрес модуля и прибавив к нему смещение 1259.

kd> g
nt!DebugService2+0x10:
80506d3e cc              int     3
kd> g
nt!DebugService2+0x10:
80506d3e cc              int     3
kd> lm
start    end        module name
804d7000 806ed680   nt         (pdb symbols)          c:\windows\symbols\ntoskrnl.pdb\47A5AC97343A4A7ABF14EFD9E99337722\ntoskrnl.pdb
[B][U]faf0c000[/U][/B] faf11000   B48DADF8   (deferred)             
faf54000 faf5c000   _          (deferred)

Из приведённого выше листинга видно, что модуль B48DADF8 запускается по адресу faf0c000. Чтобы получить адрес точки входа, нам нужно к полученному адресу прибавить её смещение 1259. После чего получим следующий адрес faf0d259. Ставим на него брэйкпойнт, так как показано ниже:

kd> [B][U]bp faf0d259[/U][/B]
*** ERROR: Module load completed but symbols could not be loaded for B48DADF8.sys
kd> g
Wed May 30 09:55:24.281 2012 (UTC - 4:00): Breakpoint 0 hit
B48DADF8+0x1259:
faf0d259 55              push    ebp
kd> u
B48DADF8+0x1259:
faf0d259 55              push    ebp
faf0d25a 8bec            mov     ebp,esp
faf0d25c 56              push    esi
faf0d25d 8b7508          mov     esi,dword ptr [ebp+8]
faf0d260 893518f0f0fa    mov     dword ptr [B48DADF8+0x3018 (faf0f018)],esi
faf0d266 33c0            xor     eax,eax
faf0d268 6882d0f0fa      push    offset B48DADF8+0x1082 (faf0d082)
faf0d26d c746384cd1f0fa  mov     dword ptr [esi+38h],offset B48DADF8+0x114c (faf0d14c)

Если у вас есть какие-то сомнения насчет правильности нашего адреса faf0d259, то вы можете сравнить его код с кодом из Рис. 1. Теперь, когда мы нашли требуемый адрес, мы можем начать наш анализ.

3. Подключение драйвера к новому устройству (Device) и установка Image Load Notifier

Рассмотрим первую часть кода, которая выполняется в начале загрузки драйвера. На Рис. 3 показан код с комментариями.

Рис. 3. Первая часть B48DADF8.sys

Первой интересной частью является то, что драйвер берет свой (DRIVER_OBJECT) и сохраняет его в глобальную переменную. Для этого он читает данные из EBP+8, которые соответствуют первому параметру драйвера (PDRIVER_OBJECT), см. первую выделенную часть на Рис. 3. Далее мы увидим, что это значение будет нужно руткиту позже.

Следуя командам, указанным ниже, мы можем убедиться в том, что значение ffb81268, действительно является объектом драйвера. Здесь очевидно, что объект драйвера еще не полностью настроен, т.е. DeviceObject равен null.

kd> dd esp
f7c88920  [B][U]faf4d121[/U][/B] [B][U]ffb81268[/U][/B] [B][U]00000000[/U][/B] 02002000
...
kd> [B][U]dd _DRIVER_OBJECT ffb81268[/U][/B]
Couldn't resolve error at '_DRIVER_OBJECT ffb81268'
kd> dt _DRIVER_OBJECT ffb81268
nt!_DRIVER_OBJECT
   +0x000 Type             : 0n4
   +0x002 Size             : 0n168
   +0x004 DeviceObject     : (null) 
   +0x008 Flags            : 4
   +0x00c DriverStart      : (null) 
   +0x010 DriverSize       : 0
   +0x014 DriverSection    : 0xffb8c8f0 Void
   +0x018 DriverExtension  : 0xffb81310 _DRIVER_EXTENSION
   +0x01c [B][U]DriverName       : _UNICODE_STRING "\driver\4157114776"[/U][/B]
   ...

Далее B48DADF8.sys пытается вызвать функцию PsSetLoadImageNotifyRoutine для адреса +1082. Это операция, которая пытается скрыть загрузку модулей. Мы еще не вникли в подробности, но мы можем установить брэйкпойн на адрес +1082. После чего, увидим, что брэйкпойнт сработает несколько раз, и что DebugService + 2bde больше ни разу не сработает.

Затем, B48DADF8.sys пытается создать устройство ввода/вывода и подключить себя в качестве драйвера для этого устройства. Согласно MSDN, IoCreateDevice() имеет 6 параметров: PDRIVER_OBJECT, DriverExtension, DeviceName, DeviceType, DeviceCharacteristics, Exclusive, PDEVICE_OBJECT.

Исходя из дампа WinDbg, указанного ниже, можно прийти к выводу, что именем нового устройства является «??\EBB02C33…#…0CFE», а тип устройства соответствует FILE_DEVICE_UNKNOWN.

kd> dd esp
f7c888fc  [B][U]ffb81268 00000000 faed6114[/U][/B] 00000022
...
kd> dt _UNICODE_STRING faed6114
nt!_UNICODE_STRING
 "\??\EBB02C33#910D#415d#BB61#FBD3CC1D0CFE"
   +0x000 Length           : 0x50
   +0x002 MaximumLength    : 0x52
   +0x004 Buffer           : 0xfaed60c0  "\??\EBB02C33#910D#415d#BB61#FBD3CC1D0CFE"

4. Скрытие модуля ядра

Теперь давайте обсудим действия B48DADF8.sys направленные на то, чтобы скрыть себя. Эта часть содержит не более 20-ти инструкций (Рис. 4).

Рис. 4. Скрытие драйвера B48DADF8.sys

В начале кода, ESI указывает на _DRIVER_OBJECT из B48DADF8. Затем, код извлекает слово по смещение 0x14 из _DRIVER_OBJECT, после чего, EDX теперь указывает на DriverSection (чей тип данных соответствует структуре _LDR_DATA_TABLE_ENTRY). Используя WinDbg, мы можем легко проверить её содержимое, как показано ниже. Вы можете видеть, что «full DLL name» равно «??.. C2CAD…B48DADF8.sys».

kd> dt _LDR_DATA_TABLE_ENTRY ffbd61b0 -r1
nt!_LDR_DATA_TABLE_ENTRY
   +0x000 InLoadOrderLinks : _LIST_ENTRY [ 0x8055b1c0 - 0xffbacea8 ]
      +0x000 Flink            : 0x8055b1c0 _LIST_ENTRY [ 0x8131db20 - 0xffbd61b0 ]
      +0x004 Blink            : 0xffbacea8 _LIST_ENTRY [ 0xffbd61b0 - 0x811974c8 ]
   +0x008 InMemoryOrderLinks : _LIST_ENTRY [ 0x0 - 0x0 ]
      +0x000 Flink            : (null) 
      +0x004 Blink            : (null) 
   +0x010 InInitializationOrderLinks : _LIST_ENTRY [ 0x630069 - 0x0 ]
      +0x000 Flink            : 0x00630069 _LIST_ENTRY
      +0x004 Blink            : (null) 
   +0x018 DllBase          : 0xfaedc000 Void
   +0x01c EntryPoint       : 0xfaedd259 Void
   +0x020 SizeOfImage      : 0x5000
   +0x024 [B][U]FullDllName      : _UNICODE_STRING "\??\C2CAD972#4079#4fd3#A68D#AD34CC121074\B48DADF8.sys"[/U][/B]
      +0x000 Length           : 0x6a
      +0x002 MaximumLength    : 0x6a
      +0x004 Buffer           : 0xe1a678c8  "\??\C2CAD972#4079#4fd3#A68D#AD34CC121074\B48DADF8.sys"
   +0x02c BaseDllName      : _UNICODE_STRING "B48DADF8.sys"
      +0x000 Length           : 0x18
      +0x002 MaximumLength    : 0x18
      +0x004 Buffer           : 0xffbd61fc  "B48DADF8.sys"
   +0x034 Flags            : 0x1104000
   +0x038 LoadCount        : 1
   +0x03a TlsIndex         : 0x49
   +0x03c HashLinks        : _LIST_ENTRY [ 0xffffffff - 0x4f51 ]
      +0x000 Flink            : 0xffffffff _LIST_ENTRY
      +0x004 Blink            : 0x00004f51 _LIST_ENTRY
   +0x03c SectionPointer   : 0xffffffff Void
   +0x040 CheckSum         : 0x4f51
   +0x044 TimeDateStamp    : 0xfffffffe
   +0x044 LoadedImports    : 0xfffffffe Void
   +0x048 EntryPointActivationContext : (null) 
   +0x04c PatchInformation : 0x00340042 Void

Следующие несколько инструкций (с адреса 0x100012CE по 0x100012D2, Рис. 4) очищают FullDLLName. Если после адреса 0x100012D2, вы отобразите DriverSection снова, то вы увидите, что содержимое FullDllName исчезло, как показано ниже. Однако, содержимое BaseDllName не тронуто и всё еще присутствует, я думаю, что автор руткита забыл очистить его.

kd> dt _LDR_DATA_TABLE_ENTRY ffbd61b0
nt!_LDR_DATA_TABLE_ENTRY
   ...
   +0x024 FullDllName      : _UNICODE_STRING ""
   +0x02c BaseDllName      : _UNICODE_STRING "B48DADF8.sys"
   ...

Далее, B48DADF8.sys пытается удалить себя из списка модулей. Как показано на Рис. 4, по адресу 100012D6, EAX и ECX сейчас содержат FLINK и BLINK из списка первого модуля полученного с помощью InLoadOrderModule. Следующие четыре инструкции составляют типичную операцию REMOVE_NODE для двунаправленного связанного списка, которая удаляет из списка модуль B48DADF8.

Задача 2. Объясните логику работы кода с адреса 100012D6 по 100012E3 (Рис. 4)

4. Подключение к PCI Device

На следующем шаге (функция 0x100011D0) должна подключить PCI Device путем копирования из оригинального PCI драйвера. Это показано на Рис. 5.

Рис. 5. Копирование из PCI Driver

Как показано на Рис. 5, на первом шаге функция 0x100011D0 должна получить объект PCI Driver по имени «driver\PCI». Затем она копирует несколько атрибутов, таких как: driver_start, driver_init, driver size и driver name из PCI драйвера в текущий драйвер. Однако, major functions 0xfaed514c драйвера (массив, который содержит записи адресов IRP-обработчиков) не изменяется. Здесь все записи перенаправляются на адрес 0xfaed514, как показано ниже (ниже, дан дамп содержимого объекта драйвера B4DADF8.sys, перед и после вызова 0x100011D0)

kd> dt _DRIVER_OBJECT ffb80b90
nt!_DRIVER_OBJECT
   +0x000 Type             : 0n0
   +0x002 Size             : 0n168
   +0x004 DeviceObject     : 0xffbb4af8 _DEVICE_OBJECT
   +0x008 Flags            : 4
   +0x00c DriverStart      : [B][U](null) [/U][/B]
   +0x010 DriverSize       : [B][U]0[/U][/B]
   +0x014 DriverSection    : [B][U]0xffb8c8f0[/U][/B] Void
   +0x018 DriverExtension  : [COLOR="darkorange"]0xffb80c38[/COLOR] _DRIVER_EXTENSION
   +0x01c DriverName       : [B][U]_UNICODE_STRING "\driver\4157114776"[/U][/B]
   +0x024 HardwareDatabase : (null) 
   +0x028 FastIoDispatch   : (null) 
   +0x02c DriverInit       : [B][U]0xfaf572c5[/U][/B]     long  +0
   +0x030 DriverStartIo    : (null) 
   +0x034 DriverUnload     : (null) 
   +0x038 MajorFunction    : [28] [COLOR="darkorange"]0xfaed514c[/COLOR]     long  +0
kd> p
...
kd> dt _DRIVER_OBJECT ffb80b90
nt!_DRIVER_OBJECT
   +0x000 Type             : 0n0
   +0x002 Size             : 0n168
   +0x004 DeviceObject     : 0xffbb4af8 _DEVICE_OBJECT
   +0x008 Flags            : 4
   +0x00c DriverStart      : [B][U]0xfaafc000[/U][/B] Void
   +0x010 DriverSize       : [B][U]0x10a80[/U][/B]
   +0x014 DriverSection    : [B][U]0x8131d8a0[/U][/B] Void
   +0x018 DriverExtension  : [COLOR="darkorange"]0xffb80c38[/COLOR] _DRIVER_EXTENSION
   +0x01c DriverName       : [B][U]_UNICODE_STRING "\Driver\PCI"[/U][/B]
   +0x024 HardwareDatabase : (null) 
   +0x028 FastIoDispatch   : (null) 
   +0x02c DriverInit       : [B][U]0xfab0a004[/U][/B]     long  +fffffffffab0a004
   +0x030 DriverStartIo    : (null) 
   +0x034 DriverUnload     : (null) 
   +0x038 MajorFunction    : [28] [COLOR="darkorange"]0xfaed514c[/COLOR]     long  +0

Задача 3. Обратите внимание, что в этот момент, PCI device еще не было полностью подключено к новому драйверу. Найдите способ выяснить, где, в конечном счете, новый драйвер устанавливается как драйвер обработки для PCI device.

5. Загрузка образа (image)

Теперь вернемся обратно к изучению вызова загрузки образа (image loading call) по адресу +1082, который обсуждался ранее в Разделе 3. Max++ устанавливает адрес +1082 в качестве функции обратного вызова, который будет срабатывать всякий раз, когда будет вызвана функция NtImageLoad. Конечно, это нарушает мониторинг загрузки образа (image) в WinDbg. Но код и сам по себе делает много вредоносных вещей. Давайте установим брэйкпойнт на адрес +1082 и понаблюдаем за его поведением. Как установить брэйкпойнт показано ниже:

kd> lm
start    end        module name
804d7000 806ed680   nt         (pdb symbols)          c:\windows\symbols\ntoskrnl.pdb\47A5AC97343A4A7ABF14EFD9E99337722\ntoskrnl.pdb
[B][U]faee4000[/U][/B] faee9000   B48DADF8   (deferred)             
faf64000 faf6c000   _          (deferred)             

Unloaded modules:
...
kd> [B][U]bp faee4000 + 1082[/U][/B]
*** ERROR: Module load completed but symbols could not be loaded for B48DADF8.sys
kd> g
Mon Jun  4 08:34:46.187 2012 (UTC - 4:00): Breakpoint 0 hit
B48DADF8+0x1082:
faee5082 55              push    ebp

На Рис. 6 показана major function из адреса +1082 (также пишется как 0x10001082).

Рис. 6. Тело функции 0x10001082

Как показано на Рис. 6, основная часть кода из адреса +1082 должна создать и поместить APC-объект в очередь (APC означает «Asynchronous Procedure Call»). APC часто используется в операциях ввода/вывода, это означает, что объект будет выполнен позже, через какое-то время.

Посмотрите на выделенную область (Рис. 6), поток управления довольно очевиден: вначале Max++ пытается вызвать ExAllocatePool, чтобы зарезервировать 30 байт в памяти ядра для APC-объекта. Затем он вызывает KeInitializeAPC и KeInsertQueue, чтобы поместить APC-вызов в очередь. Нам нужно более подробно рассмотреть вызов KeInitializeAPC. Согласно документации ReactOS, прототип функции KeInitializeAPC имеет следующий вид:

VOID NAPI KeInitializeAPC(
        IN PKAPC pApc,
        IN PKTHREAD thread, 
        IN KAPC_ENVIRONMENT env,
        IN PKKERNEL_ROUTINE kernelRoutine,
        IN PKROUNDOWN_ROUTINE rundownRoutine,
        IN PKNORMAL_ROUTINE normalRoutine,
        IN KPROCESSOR_MODE mode,
        IN VOID context
)

Дам из WinDbg можно посмотреть следующим образом:

kd> dd esp
f7c88bb4 ffbb2e48 81176320 00000000 faee530c
f7c88bc4 faee52f0 71a50000 00000001 00000000

Здесь, kernelRoutine равен faee530c (+130c), rundownRoutine равен faee52f0 (+12f0), normalRoutine равен 71a50000 (ваша задача состоит в том, чтобы выяснить какому модулю это принадлежит) и mode равен 1. Согласно MSDN, если mode равен 1 и normalRoutine не равен 0, то это user mode APC, которая позже вызовет normalRoutine. Однако, для надежности, установим брэйкпойнты на все процедуры +130c, +12f0, 71a50000.

Обратите особое внимание, в данные момент, normal routine соответствует адресу 71a50000!

Интересно, что первым сработает брэйкпойнт, который был установлен на процедуру ядра 130c, Ниже приведен стэк вызовов:

kd> kv
ChildEBP RetAddr  Args to Child              
WARNING: Stack unwind information not available. Following frames may be wrong.
f7c88d4c 804de855 00000001 00000000 f7c88d64 B48DADF8+0x130c
f7c88d4c 7c90e4f4 00000001 00000000 f7c88d64 nt!KiServiceExit+0x58 (FPO: [0,0] TrapFrame @ f7c88d64)
0012c8f8 7c91624a 0017c0e0 0012c984 0012ceac 0x7c90e4f4
0012cbb8 7c9164b3 00000000 0017c0e0 0012ceac 0x7c91624a
0012ce60 7c801bbd 0017c0e0 0012ceac 0012ce8c 0x7c9164b3
0012cec8 7c801d72 7ffdfc00 00000000 00000000 0x7c801bbd
0012cedc 7c801da8 0012d15c 00000000 00000000 0x7c801d72
0012cef8 71ab78f1 0012d15c 00155218 0017f410 0x7c801da8
0012d27c 71ab496d 0017f694 0017f420 00000000 0x71ab78f1
0012d29c 71ab49cc 0017f410 c000010e 00000000 0x71ab496d
0012d2b8 71ab40a3 00000002 00000001 00000006 0x71ab49cc
0012d310 003c2315 00000002 00000001 00000006 0x71ab40a3
0012d604 003c24ef 0012d634 7c906786 7c903400 0x3c2315
0012d648 003c2507 00401166 003c0000 fffffffe 0x3c24ef
0012ffd0 8054b6b8 0012ffc8 81176320 ffffffff 0x3c2507
00413a40 ec81ec8b 0000030c d98b5653 f4b58d57 nt!ExFreePoolWithTag+0x676 (FPO: [Non-Fpo])
00413a4c f4b58d57 8bfffffd f45589c3 0000c0e8 0xec81ec8b
00413a50 8bfffffd f45589c3 0000c0e8 10c38300 0xf4b58d57
00413a54 f45589c3 0000c0e8 10c38300 0cf85d89 0x8bfffffd
00413a58 00000000 10c38300 0cf85d89 f3b58dff 0xf45589c3

Задача 4. Проанализируйте, кто вызывает +130c (Подсказка: диапазон 0x71 принадлежит mwsock.dll, а диапазон 0x7c принадлежит ntdll.dll).

Теперь давайте изучим то, что делает функция +130c (0x1000130c в IMM). На Рис. 7 показано её тело:

Рис. 7. Тело функции +1307

Первая часть кода из +130c довольно интересна. Это набор функций обмена (exchange), которые, по сути, вращают (rotates) 6 слов на вершине стэка. Ниже показано содержимое стэка до вращения.

kd> [B]dd esp[/B]
f7c88d00  [B][U]804e60f1[/U][/B] [B][U]ffb9b638[/U][/B] [B][U]f7c88d48[/U][/B] [B][U]f7c88d3c[/U][/B]
f7c88d10  [B][U]f7c88d40[/U][/B] [B][U]f7c88d44[/U][/B] f7c88d64 0012c834 
...

Содержимое стэка после вращения показано ниже. Вы можете обнаружить, что 0x804e60f1 сдвинуто вправо (сейчас это 6-ое слово в стэке).

kd> [B]dd esp[/B]
f7c88d00  [B][U]ffb9b638[/U][/B] [B][U]f7c88d48[/U][/B] [B][U]f7c88d3c[/U][/B] [B][U]f7c88d40[/U][/B]
f7c88d10  [B][U]f7c88d44[/U][/B] [B][U]804e60f1[/U][/B] f7c88d64 0012c834

Зачем Max++ это делает? Причина состоит в том, что функция copyMaliciousCodeToNewVM (вызов размещен по адресу 10001327 на Рис. 7) фактически использует 5 дополнительных слов в стэке. Ниже отображено содержимое стэка после завершения вызова copyMaliciousCodeToNewVM.

kd> dd esp
f7c88d14  [B][U]804e60f1[/U][/B] f7c88d64 0012c834 f7c88d64
f7c88d24  ffffffff 804e2490 804f2001 7c90e4f4

Вы можете заметить, что 804e60f1 сейчас находится на вершине стэка. В данный момент, поток управления (по адресу 0x10001332) собирается перейти на функцию ntoskrnl.ObfDereferenceObject, которая, когда завершится, перейдет на адрес 0x804e60f1 (который, является первоначальным адресом возврата из +130c). Управляя стэком, подобным образом, Max++ может успешно запутать анализ потока исполнения, выполняемый статическими инструментами.

Теперь давайте изучим логику работы функции copyMaliciousCodeToNewVM, которая размещена по адресу +100f. На Рис. 8 показано её тело.

Рис. 8. Тело функции +100f

Логика работы функции copyMalicoiusCodeToNewVM (+100f) очень проста. Сначала она понижает уровень IRQ, а затем выделяет небольшой часть из памяти и копирует содержимое из адреса +1338 в целевой адрес (0x00380000). Напомним, «адрес 0x00380000 это скрытый сегмент памяти» из Урока 27, и сейчас ваша работа состоит в том, чтобы выяснить, что копируется в этот регион памяти.

Задача 5. Выясните, что копируется с помощью функции copyMaliciousCodeToNewVM.

Затем переход на ntoskrnl.obfDerefrenceObject разыменовывает новый объект драйвера и возвращается к системному вызову, который вызвал ядерную функцию +130c.

На Рис. 9 показано содержимое функции copyMalicoiusCodeToNewVM (+100f). Если вы посмотрите на ее логику, то увидите, что она, по сути, соответствует описанию выше. Однако, есть одна вещь, на которую мы бы хотели обратить ваше особое внимание:

Задача 6. Посмотрите на Рис. 9, где хранится новый VM-адрес (выделенный ZwAllocatevirtualMemory, т.е. 0x00380000)?

Рис. 9. Тело функции copyMalicousCodeToNewVM (0x1000100f)

Если вы посмотрите на две выделенные инструкции (Рис. 9), то можно заметить, что третий параметр из адреса 0x1000100F (copyMaliciousCodeToNewVM) используется для хранения 0x00380000. Но почему?

Задача 7. Выясните, что является мотивацией для хранения 0x00380000 в 3-м параметре из адреса 0x1000100F.

Чтобы решить указанную выше задачу, нам нужно вернуться назад к Рис. 8, и обратить внимание на то, что вызов 0x1000100F (copyMaliciousCodeToNewVM) происходит по адресу f7c88d3c.

Смотря на информацию ReactOS о процедуре ядра (ищите по строке PKKERNEL_ROUTINE на ReactOS), вы увидите, что процедура ядра (130c) принимает 5-ть параметров: APC, pNormal_Routine, Normal_Context, System_Arg, System_Arg2. Таким образом, f7c88d3c на самом деле является параметром NORMAL_CONTEXT из KERNEL_ROUTINE! Аналогичным образом, вы определите, что f7c88d48 (второй параметр) является NORMAL_ROUTINE.

Теперь, посмотрите на выделенную часть, на Рис. 9 (два желтых подчеркивания и три жирных), вы увидите, что copyMalicoiusCodeToNewVM записывает значение 0x00380000 в оба места, которые удерживаются для NORMAL_CONTEXT и NORMAL_ROUTINE!

[B]Настройка среды окружения для анализа кода по адресу 0x00380000:[/B]
Очевидно, что на следующем шаге нам нужно проследить за кодом, распложенным по адресу 0x00380000 (который был первоначально скопирован из адреса 0x10001338). Интересно, если вы установите BP в WinDbg, отладчик никогда не остановится на адресе 0x00380000. Мы подозреваем, что кто-то в какой-то момент очищает аппаратный BP. В нашем случае работают только программные брэйкпойнты («bp 0x00380000»), и в некоторых случаях вы обнаружите, что ваш IMM фактически получает INT3 (программный BP) прерывание, и вы должны сейчас отладить его в IMM. На Рис. 10, показан скриншот из IMM, когда был перехвачен программный BP. Вы можете видеть, что «инструкцией» по адресу 0x00380000 является «INT3».

Рис. 10. Интересное поведение во время отладки WinDbg/IMM

Открытая задача: Определите, почему программный брэйкпойнт установленный с помощью WinDbg перехватывается отладчиком Immunity Debugger (Предположение: отладочный порт операционной системы сбрасывается Max++).

Рис. 11. Код по адресу 0x00380000

На Рис. 11 отображена логика работы кода расположенного по адресу 0x00380000 (первоначально скопированного из 0x10001338). В основном, она состоит из трех шагов: (1) поиск модуля «kernel32.dll», (2) поиск функции LoadLibraryW в PE-заголовке, (3) вызов функции +1404 (мы назвали её LoadMax++00x86).

Основная часть функции LoadMax++00x86 (расположена по адресу +1404) показана на Рис. 12.

Рис. 12. Load Max++.00.x86

Основной частью функции +1404 является вызов LoadLibraryW(".\C2CAD…max++.00.x86").

Задача 8. Докажите, что выше приведенное утверждение истинно (в особенности, почему параметром функции является «.\C2CAD…max++.00.x86»?)

Итог:

К данному моменту, в сочетании с Уроком 27, мы показали вам полную картину скрытой техники удалённой загрузки DLL-библиотеки. Max++ производит загрузку max++.x86.dll в три шага: (1) загружает B48DADF8.sys; (2) загружает max++.00.x86; и (3) загружает max++.x86.dll. Во время каждого шага, используется различные способы, направленные на то, чтобы скрыть свои следы, например, путем изменения данных структур ядра. Существуют также техники, которые нам не полностью понятны (см. «открытую задачу», выше в этом уроке).

© Translated by Prosper-H from r0 Crew