R0 CREW

Malware Analysis Tutorial 27: Stealthy Loading of Malicious Driver (Перевод: Prosper-H)

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

Цели урока:

  1. Попрактиковаться в перехвате загрузки DLL-библиотек с помощью WinDbg.
  2. Использовать Python-скрипты IMM, чтобы зарегистрировать последовательность загрузки DLL-библиотек.
  3. Разобраться с техникой скрытой загрузки DLL, которая используется руткитом.

1. Введение

Приведу ниже свою любимую цитату от Rumsfield, касаемо анализа вредоносных программ, хотя, он и не является компьютерным профессионалом.

Оригинал:

There are known knowns; there are things we know we know. We also know there are known unknowns; that is to say we know there are some things we do not know. But there are also unknown unknowns – there are things we do not know we don’t know. (Donald Rumsfield)

Перевод:

Есть известные знания; вещи, которые мы знаем, что знаем их. Мы так же знаем, что есть известные незнания, т.е. мы знаем, что есть что-то, чего мы не знаем. Но есть так же неизвестные незнания – вещи, которые мы не знаем, что не знаем их. (Donald Rumsfield)

Этот урок обсуждает поведение скрытой (stealthy) загрузки DLL-библиотеки руткитом Max++. Это что-то, что мы знаем, что незнаем этого. Анализ начнем с адреса 0x3C230F.

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

Мы можем просто воспользоваться настройками из Урока 20. По сути, вам нужно две виртуальные машины с Windows XP, одна для того, чтобы делать заметки, а другая для запуска руткита. Так же вам нужен WinDbg, установленный на Host XP, для отладки виртуальной машины Win_DEBUG. В частности, в Win_DEBBUG, вам будет нужно установить брэйкпойнт на адрес 0x3C230F. Это место, которое находится сразу перед вызовом CALL WS2_32.WSASocketW (Рис. 1).

Рис. 1. Что-то чего мы знаем, что не знаем

Как показано на рисунке, по адресу 0x3C230F, код вызывает функцию WS2_32.WSASocketW, которая пытается установить TCP/IP соединение. Интересно, что если вы попытаетесь пройти по ней по F8, то сможете заметить, что ваш IMM зависнет, примерно на 10 секунд, а затем, в своей строке состояния (status bar) выведет: «отлаживаемый процесс пытается получить доступ к http://74.117.114.86\max++.x86.dll»

Это явно плохой признак, почему у стандартного вызова WSASocketW возникает потребность в вызове удаленной DLL? Это должно быть что-то, чего мы не знаем, что не знаем!

3. Скрипты отладчика IMM для регистрации стековых фреймов во время загрузки DLL-библиотек

В последующем анализе будем использовать Immunity Debugger. Если вы посмотрите в папку PyCommands, которая находится в директории IMM, то заметите, что там есть много интересных примеров для управления отладчиком Immunity Debugger с помощью Python-скриптов! В текущем исследовании руткита, нас интересуют следующие вопросы:

  1. Когда загружается DLL (в частности, когда и почему Max++ загружает .x86.dll)?
  2. Кто выдает запрос на загрузку DLL?

Чтобы ответить на эти вопросы, воспользуемся двумя возможностями Python-бибиотеки IMM: (1) функциями, которые позволяют отобразить содержимое стэка (благодаря им, мы сможем проследить за вызовами функций, до того места, где их вызывают); (2) «event hookers», которые позволяют писать в лог информацию, когда загружаются DLL. Основываясь на этой информации, мы могли бы продолжить исследование с помощью брэйкопйнтов.

И так, найдите исходный код «TraceDLLLoad.py». Вы можете положить его в корневую директорию IMM, запустить Python-окно в IMM (вторая кнопка на панели инструментов), ввести «from TraceDLLLoad import *», после чего ввести «hk = TraceDLLLoad();» и «hk.add(‘abc’);», чтобы установить перехват (hook).

from immlib import *;
from immutils import *;

HOOK_NAME = "testbplog"
imm = Debugger();

class TraceDLLLoad(LoadDLLHook):
    def __init__(self):
        LoadDLLHook.__init__(self)

    def run(self, regs):
        ev = imm.getEvent();
        if ev.isLoadDll(): imm.log(" it is LOADDLL!---");
        imm.log("!!!!!!!! DLL Loaded: %x" % ev.lpImageName);
        baseaddr = ev.lpBaseOfDll;
        imm.log("!!!!!!!! DLL image: %x" % ev.lpBaseOfDll);
      
        regs = imm.getRegs();
        call_stack = imm.callStack()
        for frame in call_stack:
            imm.log("Addr= %08x | Stack = %08x | Procedure %s | Frame %08x | Called from %08x" \
            %(frame.address, frame.stack, frame.procedure, frame.frame, frame.calledfrom))

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

Содержимое окна «Log data» показано на Рис. 1. «image base» каждого DLL-файла показана на Рис. 2 (нажмите на «View => Executable Modules» в IMM).

Рис. 1. DLL Load Log

Рис. 2. DLL Image Base

При просмотре Рис. 1, у вас могут возникнуть интересные наблюдения. Прежде всего, при загрузке WSASocket, система загружает много DLL-бибилиотек (скорее всего, она читает импорт DLL-библиотек и рекурсивно загружает их). В их список входят old32.dll, mswsocket.dll, etc. Однако, действительно интересное, заключается в том, что 74.117.114.86\max++.x86.dll загружается в конце (с wshtcpip.dll), но самым поразительным открытием, которое у нас есть до настоящего момента, является то, что система, на самом деле, загружает (и затем выгружает) еще один модуль: \.\C2CAD9724#4079…\L\max++.00.x86! Но так, как он выгружается, то на Рис. 2, его нет в списке видимых исполняемых модулей! (как будет показано позже, на самом деле, новая библиотека max++.x86.dll загружается при помощи \.\C2CAD…\max++.00.x86 и занимает под себя адресное пространство 0x35670000).

4. Первая попытка: брэйкпойнт на LdrLoadDll

Теперь рассмотрим возможность использования брэйкпойнтов IMM, чтобы вникнуть в процесс загрузки DLL-библиотек. В качестве примера используем \.\C2CAD9724…\L\max++.00.x86.

Наша первая попытка будет заключаться в установке брэйкпойнта на ntdll.LdrLoadDLL. Это может быть достигнуто при помощи следующих шагов. В «IMM => View => Executable Modules», найдите запись ntdll.dll и нажмите «right click => View => Names». Все функции, экспортируемые из ntdll.dll, будут отображены в алфавитном порядке. Найдите функцию LdrLoadDll и установите на неё брэйкпойнт (F2). Вернитесь назад в CPU-панель и нажмите F9, после чего вы увидите, что LdrLoadDll вызывается несколько раз. На Рис. 3 показан один из стэковых фрэймов вызова:

Рис. 3. Параметры LdrLoadDll

Осуществляя простой поиск в Google, мы сможем легко найти определение функции ntdll.LdrLoadDll. У нее есть четыре параметра: (1) PathToFile, (2) Flags, (3) ModuleFileName, (4) handle. Нам интересен третий параметр ModuleName, типом которого является _Unicode_String. Просматривая содержимое стэка из Рис. 3, мы можем легко определить, что значением ModuleFileName является 0x0012CE8C. (где, первая выделенная область является папкой Max++, что соответствует первому параметру).

Нажмите «right click» по 0x112CE8C и выберите «follow in dump», в итоге, в дампе, отобразится содержимое памяти, на которое указывает значение из адреса 0x0012CE8C. Из документации по _Unicode_String, мы вскоре прейдем к выводу, что первое слово (0x3E0040) указывает на максимальную и действительную длину Unicode-строки, а второе слово 0x7FFDEC00 содержит начальный адрес самой строки (в wide char). Перейдя на адрес 0x7FFDEC00, получим содержимое показанное на Рис. 4: c:\windows\system32\mswsock.dll.

Рис. 4. Содержимое строки состоящей из имени DLL-библиотеки

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

[B]mswsock.dll, \\.\C2CAD....\L\max++.00.x86, hnet.cfg.dll, mswsock.dll (снова), wshtcpip.dll, SXS.dll[/B]

Обратите внимание, что система может генерировать случайное поведение, например, иногда, в выше указанной последовательности, библиотека SXS.dll заменяется библиотекой wstheme.dll. Это может быть вызвано случайным поведением Max++ (но мы не проверили этого в его коде).

Так же интересно знать, кто вызывает LdrLoadDll. На Рис. 5 в отличии от Рис. 4, показано больше данных из стэка, в момента, когда mswsock.dll впервые загружается. Тут не трудно заметить цепочку вызовов: LoadLibraryExA => LoadLibraryExW => LdrLoadDll, которые загружают mswsock.dll.

Рис. 5. Больше данных из стэка для определения цепочки вызовов, вызвавших LdrLoadDll

Если мы сравним эту последовательность с последовательностью показанной на Рис. 2, то сможем заметить, что есть две DLL-библиотеки, которые небыли перехвачены с помощью брэйкпойнта установленного на LdrLoadDll: (1) это old32.dll и (2) это max++.x86.dll! Очевидно, что этот подход не работает для max++.x86.dll. Но это должно позволить нам найти того, кто загружает \?\C2CAD…\L\max++.00.x86!

Задача 1. Используйте выше упомянутую технику для того, чтобы определить, кто загружает max++.00.x86.

5. Вторая попытка: брэйкпойнт на ntdll.KiFastSyscall

Наш второй подход будет состоять в том, чтобы прерваться (break) на вызове KiFastSyscall. К этому подталкивает trace log отладчика IMM из Рис. 2. Кажется, что IMM перехватывает вызов загрузки DLL-библиотек, останавливаясь на KiFastSyscall. По сути, KiFastSyscall является оберткой ядра операционной системы. Если вы посмотрите на её реализацию, то заметите, что она, через регистры, устанавливает код сервиса (service code) и параметры, а затем вызывает программное прерывание.

При установке брэйкпойнта на KiFastSyscall, мы надеемся получить лучшее представление о том, как загружаются DLL-библиотеки. На Рис. 6 показано содержимое стэка одного из перехваченных вызовов KiFastSyscalls. Смотря на содержимое стэка, вы можете сразу сделать вывод, что она выполняет вызов ZwQueryAttributesFile для mswsock.dll.

Рис. 6. Syscall для zwQueryAttributeFile

Для LdrLoadDll брэкпойнт будет срабатывать несколько раз. Последовательность вызовов показана ниже:

[B]ZwFsControlFile, ZwQueryAttributesFile, ZwOpenFile, ZwCreateSection, ZwClose, ZwMapViewOfSection, ZwClose.[/B]

Теперь мы можем иметь хорошее представление о загрузке DLL: системе нужно прочитать DLL-файл, затем создать секцию для отображения новой DLL в адресное пространство процесса.

6. Кто загружает max++.00.x86?

Сейчас, используем технику, показанную в Разделе 4, чтобы проверить, кто загружает max++.00.x86.

6.1 Подход с низу вверх

Одно из естественных решений, могло бы состоять в том, чтобы установить брэйкпойнт на LdrLoadDll, а затем проследить за содержимым стэка. Подготовка довольно проста: после достижения адреса 0x3C230F, установите брэйкпойнт на ntdll.LdrLoadDll, после чего нажмите F9. Затем, когда мы дважды попадем на LdrLoadDll, мы будем находиться на вызове для загрузки \.\C2CAD…\max++.00.x86. Используя технику из Раздела 4, вы можете исследовать дамп, как показано на Рис. 7.

Слева, на Рис. 7, строка \.\C2CAD…\max++.00.x86, а справа, вы можете видеть, что Unicode-строка начинается с адреса 0x0012C4AC (вторая выделенная область).

Рис. 7. Подход с низу вверх

Теперь, если мы посмотрим на правую сторону Рис. 7 (содержимое стэка), то увидим много кандидатов для адреса возврата (return address) (обратите внимание, что Рис. 7 показывает не все адреса, вам нужно прокрутить вниз, в вашем IMM, чтобы увидеть все из них). Если объединить их в цепочку, то мы будем иметь цепочку гипотетических адресов возврата, как показано на Рис. 8. Обратите внимание, что мы называем их «гипотетическими», потому что это только предположение – нет никаких тому причин, чтобы полагать, что руткит будет следовать стандартному соглашению о вызовах языка С, и поэтому все адреса (в диапазоне кодовых сегментов различных модулей) рассматриваются как кандидаты для адресов возврата. На Рис. 8, области с жирным шрифтом являются проверенным адресами возврата. Проверка осуществлялась пошаговым выполнением инструкций в IMM. Те из областей, которые выделены оранжевым цветом, на самом деле не являются адресами возврата!

Давайте исследуем содержимое Рис. 8 (снизу), логика довольно ясна: lz32 загружает модуль ws_32 для network socket service, который вызывает загрузку библиотеки для загрузки mwsock32.dll Во время процесса загрузки, так или иначе, это приводит к вызову ZwMapViewOfSection, который загружает библиотеку \.\C2CAD…\max++.00.x86. Очевидно, что такое поведение связанно с каким-то трюкачеством руткита, иначе зачем ZwMapViewOfSection понадобилось бы загружать \.\C2CAD…\max++.00.x86? Продолжим дальнейшую отладку, чтобы выяснить это.

----------------------------------------------------------------------------------------------------------
----> [B][U]ntdll.7C801BBD[/U][/B]  (stored at 0x0012C484)
---> 0x0038005C   (stored at 0x0012C4B0)
---> [B][U]ntdll.7C80AEEC (part of LoadLibraryExW of \\.\C2CAD...\max++.00.x86)[/U][/B] (stored at 0x0012C4EC)
--> [COLOR="darkorange"]0x003800CE  (stored at 0x0012C4F0)[/COLOR]
--> [B][U]0x003800CE[/U][/B] (stored at 0x0012C500) 
 --> [COLOR="darkorange"]0x0038005C (stored at 0x0012C504 )[/COLOR]
---> [B][U]0x7C90E437[/U][/B] ([B][U]KiDispatchUserAPC[/U][/B], stored at 0x0012C528 )
---> 0x7C90E4F4 (ntdll.KiFastSyscallRet) (stored at 0x0012C5E0 )
---> [B][U]0x7C90E4F4[/U][/B] (ntdll.KiFastSyscallRet) (stored at 0x0012C5F0 )
---> [B][U]ntdll.0x7C90D50C[/U][/B] (stored at 0x0012C804 )
---> [B][U]ntdll.0x7C91BD03[/U][/B] (zwMapViewOfSection)(stored at 0x0012C808 )
---> [B][U]ntdll.0x7C91624A[/U][/B] (stored at 0x0012C8FC )
 ---> [B][U]ntdll.0x7C801BBD[/U][/B] (part of ntdll.LoadLdrDll of mwsock32.dll) (stored at 0x0012CE64)
--->
... a lot of ntdll calls
--> [B][U]ws2_32.71AB78F1[/U][/B] [this is where ws2_32 calls load library) (stored at 0x0012CEFC)
--> ...
---> [B][U]lz32.0x003C2315[/U][/B] (this is the instruction right after 0x003C230F) (stored at 0x0012D314).

[CENTER][B]Рис. 8.[/B] Гипотетические адреса возврата в стеке[/CENTER]
---------------------------------------------------------------------------------------------------- 

Давайте сначала проверим действительные адреса в стэке. На этот раз, мы используем пошаговое выполнение (по F8) вместо выполнения до возврата (execute until return) Ctrl+F9 (потому что выполнение до возврата не надежное средство). Ниже приводится последовательность цепочки вызовов, которую мы проверили до настоящего времени. Вы можете убедиться, что некоторые «гипотетические» адреса возврата с Рис. 8 являются поддельными. Еще одно интересное наблюдение заключается в том, что: когда мы входим в KiDispatchUserAPC (7c90E437), то мы не можем продолжить выполнение, потому что следующей инструкцией является CALL ZwContinue (Рис. 10), которая переключает потоки.

----------------------------------------------------------------------------------------------
LdrLoadDrll
---->  [U]ntdll.7C801BBD[/U]  (stored at 0x0012C484)
----> [U]ntdll.7C80AEEC[/U] (LoadLibraryExW of max++.00.x86)  (stored at 0x0012CE4C)
-----> [U]0x003800CE[/U] (stored at 0x0012C500)
 -----> [U]ntdll.7C90E437[/U] ([U][B]KiDispatchUserAPC[/B][/U]) (stored at 0x0012C528)

[CENTER][B]Рис. 9.[/B] Проверка стэкового фрейма[/CENTER]
---------------------------------------------------------------------------------------------------

Рис. 10. Код из KiDispatchUserAPC

Согласно MSDN, KiDispatchUserAPC должна обработать пользовательскую процедуру APC (асинхронный вызов процедуры), которая, как правило, является трудоёмким запросом ввода/вывода. Когда она заканчивает выполнение своей задачи, она вызывает ZwContinue, чтобы возобновить выполнение оригинального потока (т.е. она выполняет переключение контекста). Очевидно, что нажатие на F8, здесь, работать не будет, потому что после вызова KiDispatchUserAPC мы больше не достигнем следующей за ней инструкции. Вместо этого, нам нужно определить, с какой следующей инструкции ОС возобновит работу. Смотря на документацию по ZwContinue (или NtContinue), вы обнаружите, что она принимает два параметра: pContenxt, который указывает на структуру контекста потока, и bRaiseAlert. Смотря на содержимое стэка, мы можем прийти к выводу, что значение pContext равно 0x0012C538. Исходя из этого, мы можем легко отобразить все её содержимое, использую для этого WinDbg (просто запустите WinDbg на пользовательском уровне (Ring 3) и присоедините его к процессу Max++ в режиме non-invasive).

0:000> dt _CONTEXT 0x0012C538
ntdll!_CONTEXT
   +0x000 ContextFlags     : 0x10017
   +0x004 [B][U]Dr0              : 0x4012dc[/U][/B]
   +0x008 Dr1              : 0
   +0x00c Dr2              : 0
   +0x010 Dr3              : 0
   +0x014 Dr6              : 0xffff0ff0
   +0x018 Dr7              : 0x401
   +0x01c FloatSave        : _FLOATING_SAVE_AREA
   +0x08c SegGs            : 0
   +0x090 SegFs            : 0x3b
   +0x094 SegEs            : 0x23
   +0x098 SegDs            : 0x23
   +0x09c Edi              : 0
   +0x0a0 Esi              : 0
   +0x0a4 Ebx              : 0
   +0x0a8 Edx              : 0x7c90e4f4
   +0x0ac Ecx              : 0x900000
   +0x0b0 Eax              : 0
   +0x0b4 Ebp              : 0x12c8f8
   +0x0b8 [B][U]Eip              : 0x7c90e4f4[/U][/B]
   +0x0bc SegCs            : 0x1b
   +0x0c0 EFlags           : 0x296
   +0x0c4 Esp              : 0x12c804
   +0x0c8 SegSs            : 0x23
   +0x0cc ExtendedRegisters : [512]  "???"

Вы можете видеть, что значение EIP равно 0x7C90E4F4, и (вы так же можете видеть Dr0: 0x4012DC, который является аппаратным брэйкпойнтом, который был установлен нами ранее). Теперь мы можем установить брэйкпойнт на адрес 0x7C90E4F4 и продолжить выполнение анализа в пошаговом режиме. Можно убедиться, что посещаются следующие адреса возврата из Рис. 8:

---> [B][U]0x7C90E4F4[/U][/B] (ntdll.KiFastSyscallRet) (stored at 0x0012C5F0 )
---> [B][U]ntdll.0x7C90D50C[/U][/B] (stored at 0x0012C804 )
---> [B][U]ntdll.0x7C91BD03[/U][/B] (zwMapViewOfSection)(stored at 0x0012C808 )
---> [B][U]ntdll.0x7C91624A[/U][/B] (stored at 0x0012C8FC )
 ---> [B][U]ntdll.0x7C801BBD[/U][/B] (part of [B][I]ntdll.LoadLdrDll of ws2help.dll[/I][/B]) (stored at 0x0012CE64)

До настоящего времени, мы проверили все адреса возврата, давайте повторно посетим их на Рис. 8. Ничего подозрительного не находите? Посмотрите на те из адресов которые выделены красным цветом.

----------------------------------------------------------------------------------------------------------- 
---> [B][U]ntdll.[COLOR="red"]7C80AEEC[/COLOR] (part of LoadLibraryExW of \\.\C2CAD...\max++.00.x86)[/U][/B] (stored at 0x0012C4EC)
--> 0x003800CE  (stored at 0x0012C4F0)
--> [B][U][COLOR="red"]0x003800CE[/COLOR][/U][/B] (stored at 0x0012C500) 
 --> 0x0038005C (stored at 0x0012C504 )
---> [B][U]0x7C90E437[/U][/B] ([B][U]KiDispatchUserAPC[/U][/B], stored at 0x0012C528 )
---> 0x7C90E4F4 (ntdll.KiFastSyscallRet) (stored at 0x0012C5E0 )
---> [B][U]0x7C90E4F4[/U][/B] (ntdll.KiFastSyscallRet) (stored at 0x0012C5F0 )
---> [B][U]ntdll.0x7C90D50C[/U][/B] (stored at 0x0012C804 )
---> [B][U]ntdll.0x7C91BD03[/U][/B] ([COLOR="red"]zwMapViewOfSection[/COLOR])(stored at 0x0012C808 )
---------------------------------------------------------------------------------------------------------- 

Первый вопрос: почему ZwMapViewOfSection вызывает LoadLibraryExW?
Второй вопрос: какому диапазону адресов принадлежит адрес 0x003800CE? Это похоже на диапазон адресов из ядра. Если это модуль ядра, то каким образом мы можем протрейсить (trace into) его?

Два вопроса, очевидно, связаны между собой. Мы подозреваем, что по адресу 0x003800CE расположен некоторый вредоносный модуль, загруженный Max++. Далее мы рассмотрим эту проблему.

6.2 Скрытый сегмент памяти по адресу 0x00380000

На Рис. 11 показан список сегментов памяти системы, в момент, когда мы попадаем на брэйкпойнт, установленный на LdrLoadDll для \.\C2CAD…\max++.00.x86.

Рис. 11. Скрытый сегмент памяти по адресу 0x00380000

Обратите внимание, что сегмент памяти очень сркытный (stealthy)! Когда мы в первый раз попадаем в LdrLoadDll (для загрузки mwsock32.dll), его там нет. Позже, когда мы возвращаемся к завершению ZwMapViewOfSection (по цепочке вызовов, Рис. 8), он исчезает! Исходя из карты памяти (memory map), мы не можем сказать, кто является владельцем сегмента 0x00380000!

Если мы запустим WinDbg в пользовательском режиме и присоединим его к процессу Max++, то мы сможем выполнить указанную ниже команду для того, чтобы найти информацию об адресе 0x00380000. Но, как можно видеть, она не может предоставить больше информации, чем IMM.

0:000> !address 0x00380000                                     

Failed to map Heaps (error 80004005)
Usage:                  [B][U]<unclassified>[/U][/B]
Allocation Base:        00380000
Base Address:           00380000
End Address:            00381000
Region Size:            00001000
Type:                   00020000    MEM_PRIVATE
State:                  00001000    MEM_COMMIT
Protect:                00000040    PAGE_EXECUTE_READWRITE

Нашим последним доносчиком может быть WinDbg в режиме ядра. Настройка рабочей среды похожа на настройки из Раздела 2, за исключением того, что виртуальная машина загружается в режиме DEBUGGED и к ней присоединяется из HostXP ядерный отладчик WinDbg. Затем, выполнившись до адреса 0x003C203F, мы начнем наш анализ в режиме ядерной отладки, с помощью WinDbg (из HostXP).

Нажмите Ctrl+Break в WinDbg, чтобы войти в командный режим. Наш первый шаг будет заключаться в том, чтобы найти и присоединиться к процессу при помощи WinDbg работающего в режиме ядра.

kd> !process 0 0
**** NT ACTIVE PROCESS DUMP ****
PROCESS 812ed020  SessionId: none  Cid: 0004    Peb: 00000000  ParentCid: 0000
    DirBase: 00039000  ObjectTable: e1000d10  HandleCount: 239.
    Image: System

...

PROCESS [B][U]ff9d9020[/U][/B]  SessionId: 0  Cid: 0130    Peb: 7ffde000  ParentCid: 07f8
    DirBase: 0999d000  ObjectTable: e1aa7bc0  HandleCount:  40.
    Image: [B][U]Max++ downloader install_2010.exe[/U][/B]

kd> .process
80559c20 ...

Мы обнаружили, что идентификатором процесса Max++ является ff9d9020. Текущим процессом (ядра) является 80559c20. Присоединяемся к нему:

kd> .process ff9d9020
Implicit process is now ff9d9020
WARNING: .cache forcedecodeuser is not enabled
kd> dd 00380000
00380000  ???????? ???????? ???????? ????????
00380010  ???????? ???????? ???????? ????????
00380020  ???????? ???????? ???????? ????????
00380030  ???????? ???????? ???????? ????????
00380040  ???????? ???????? ???????? ????????
00380050  ???????? ???????? ???????? ????????
00380060  ???????? ???????? ???????? ????????
00380070  ???????? ???????? ???????? ????????

Как показано выше, сегмент 0x00380000 еще не был создан. Но мы можем установить memory breakpoint на запись, и посмотреть, что мы получим.

kd> ba w1 0x003800CE
kd> bl
 0 e 003800ce w 1 0001 (0001)

Обратите внимание, что 0x003800CE является адресом возврата, как показано на Рис. 8. Мы заинтересованы в обнаружении того, кто устанавливает код по этому адресу. Здесь «ba w1 0x003800CE» означает установку memory breakpoint на адрес 0x003800CE, который будет срабатывать, всякий раз, когда будет перезаписан байт. Теперь введите «g» (означает продолжение) в GDB и нажмите F9 в IMM, после чего сработает брэйкпойнт.

[B]B48DADF8+0x105f:
faea505f f3a5            rep movs dword ptr es:[edi],dword ptr [esi]kd> r edi
edi=003800d0
kd> r esi
esi=faea5408[/B]

Очевидно, что код пытается скопировать данные с адреса 0xFAF3D408 в 0x003800D0. Далее идет интересная часть: кому принадлежит код, размещенный по адресу 0xFAF3D05F (REP перемещает инструкции)? Ввод команды !address 0x003800CE ничего не дает. Смотря на подсказку «B48DADF8+0x105f», мы знаем, что код принадлежит модулю с именем B48DADF8. Это может быть подтверждено с помощью:

kd> lm
start    end        module name
804d7000 806ed680   nt         (pdb symbols)          c:\windows\symbols\ntoskrnl.pdb\47A5AC97343A4A7ABF14EFD9E99337722\ntoskrnl.pdb
f7612000 f761b000   HIDCLASS   (deferred)             
f7682000 f76ac180   kmixer     (deferred)             
f76b9000 f76bb880   hidusb     (deferred)             
f76bd000 f76bff80   mouhid     (deferred)             
[B][U]faea4000 faea9000   B48DADF8   (no symbols)[/U][/B]           
faf24000 faf2c000   _          (deferred)             

Unloaded modules:
fad9c000 fada5000   HIDCLASS.SYS
fafec000 fafef000   hidusb.sys
...

Очевидно, что B48DADF8 является ядерным модулем, который был раньше загружен Max++. Но когда он загружается? Мы оставим поиск ответа на этот вопрос для вас.

Задача 1. Определите, когда загружается модуль B48DADF8. (Подсказка: включите event handler в WinDbg для того, чтобы следить за загрузкой модулей и объедините это с анализом стэка).

6.3 Заключение о Max++.00.x86

lz32.dll загружает B48DADF8 как модуль ядра и модифицирует таблицу системных сервисов (system service table) для обработки ZwMapViewSection. Затем B4DADF8 создает в Max++ сегмент памяти по адресу 0x00380000 и переустанавливает таблицу обработки ядра (kernel handling table) для ZwMapViewSection. Когда ZwMapViewSection пытается выполнить инструкцию SYSENTER, он переходит на адрес 0x003800CE, где происходит вызов LoadLibraryW(“max++.00.x86”). Вот таким образом max++.00.x86 и загружается. Обратите внимание, что это достигается с помощью удаленной загрузки модуля ядра и с помощью перенаправления системного вызова – что позволяет руткиту избежать обнаружения антивирусами.

Однако, этот подход имеет уязвимость. Обратите внимание, SYSENTER предполагает ввод кода ядра. Автор руткита, здесь очень ленив и напрямую вызывает пользовательские функции ntdll.LoadLibrary и ntdll.LdrLoadDll, которые фактически позволяют нам проследить с их помощью до созданного неизвестного/нового региона памяти по адресу 0x380000, что в итоге приводит к раскрытию модуля B48DADF8. Если бы автор руткита встроил весь код, отвечающий за загрузку библиотеки, в ядро, то его было бы намного сложнее найти.

7. Кто загружает удаленную библиотеку http://74.117.114.86\max++.x86.dll?

Теперь попытаемся проанализировать, кто загружает http://74.117.114.86/max++.x86.dll. Как показано на Рис. 2, LoadDllHook не может перехватить загрузку удаленной DLL-библиотеки, однако IMM видит её загрузку. Нам нужно найти другой выход. Для этого, мы снова обратимся к WinDbg в режиме ядра.

Прежде, чем мы продолжим, имейте в виду, что \.\C2CAD…\max++.00.x86 загружается в диапазон 0x35670000. Это хорошо видно в «IMM => View => Executable Modules»

7.1. Подготовка рабочей среды

Установите настройки рабочей среды, следуя инструкциям из Раздела 6.2 (WinDbg в режиме ядра), затем выполняйте код в IMM до тех пор, пока вы не увидите, что IMM показывает, в своем «Status Bar», что он пытается загрузить http://74.117.114.86/max++.x86.dll (как показано в красном прямоугольнике, Рис. 12). Убедитесь, что Интернет для виртуальной машины GuestXP отключен (чтобы попытка загрузить 74.117.114.86/max++.x86.dll привела к неудаче). Теперь нажмите Ctrl + Break в WinDbg (в режиме ядра), чтобы остановить выполнение.

Рис. 12. Перехват загрузки удаленной DLL

7.2 Анализ

Сейчас мы заинтересованы в том, чтобы выяснит состояние всех связанных процессов в системе. Введите !process 0 0. Это даст нам список всех существующих процессов. Теперь у нас есть «адрес» процесса (рассматривайте его как process ID) Max++. В нашем случае он равен 81180228. Мы можем отобразить всю информацию о нем, как показано ниже. Её много, но её легко интерпретировать. Найдите минутку, чтобы просмотреть имеющуюся информацию и отгадать: есть два потока, какой из них загружает 74.117.1147.86/max++.x86.dll?

kd> [B][U]!process 81180228 7[/U][/B]
PROCESS 81180228  SessionId: 0  Cid: 0214    Peb: 7ffd4000  ParentCid: 01ac
    DirBase: 01652000  ObjectTable: e1070918  HandleCount:  51.
    Image: Max++ downloader install_2010.exe
    ...
        [B][U]THREAD 811807f0[/U][/B]  Cid 0214.01e4  Teb: 7ffdf000 Win32Thread: e10c6118 WAIT: (Suspended) KernelMode Non-Alertable
SuspendCount 1
            8118098c  Semaphore Limit 0x2
        ...
        Stack Init f7d10000 Current f7d0fb68 Base f7d10000 Limit f7d0c000 Call 0
        Priority 10 BasePriority 8 PriorityDecrement 0 DecrementCount 16
        ChildEBP RetAddr  Args to Child              
        f7d0fb80 804dc0f7 81180860 811807f0 804dc143 nt!KiSwapContext+0x2e (FPO: [Uses EBP] [0,0,4])
        f7d0fb8c 804dc143 8118095c 811807f0 81180824 nt!KiSwapThread+0x46 (FPO: [0,0,0])
        f7d0fbb4 804f8ca9 00000000 00000005 00000000 nt!KeWaitForSingleObject+0x1c2 (FPO: [Non-Fpo])
        f7d0fbcc 804f1718 00000000 00000000 00000000 nt!KiSuspendThread+0x18 (FPO: [3,0,0])
        f7d0fc14 804ecae9 00000000 00000000 00000000 nt!KiDeliverApc+0x124 (FPO: [Non-Fpo])
        f7d0fc2c 804dc143 00000000 811807f0 e1767f58 nt!KiSwapThread+0x64 (FPO: [0,0,0])
        f7d0fc54 8058f9db 00000001 00000000 00000001 nt!KeWaitForSingleObject+0x1c2 (FPO: [Non-Fpo])
        f7d0fd10 80587a0f 00185f18 0012c608 0012c610 nt!NtSecureConnectPort+0x662 (FPO: [Non-Fpo])
        f7d0fd3c 804de7ec 00185f18 0012c608 0012c610 [B][U]nt!NtConnectPort[/U][/B]+0x24 (FPO: [Non-Fpo])
        f7d0fd3c 7c90e4f4 00185f18 0012c608 0012c610 nt!KiFastCallEntry+0xf8 (FPO: [0,0] TrapFrame @ f7d0fd64)
WARNING: Frame IP not in any known module. Following frames may be wrong.
        0012c6f8 00000000 00000000 00000000 00000000 0x7c90e4f4

        [B][U]THREAD 81228da8[/U][/B]  Cid 0214.07c0  Teb: 7ffde000 Win32Thread: e171a6f0 WAIT: (Suspended) KernelMode Non-Alertable
SuspendCount 1
           ...
        Win32 Start Address 0x35671d82
        Start Address 0x7c8106e9
        Stack Init f76e4640 Current f76e40f0 Base f76e5000 Limit f76e1000 Call f76e464c
        Priority 10 BasePriority 8 PriorityDecrement 0 DecrementCount 0
        ChildEBP RetAddr  Args to Child              
        f76e4108 804dc0f7 81228e18 81228da8 804dc143 nt!KiSwapContext+0x2e (FPO: [Uses EBP] [0,0,4])
        f76e4114 804dc143 81228f14 81228da8 81228ddc nt!KiSwapThread+0x46 (FPO: [0,0,0])
        f76e413c 804f8ca9 00000000 00000005 00000000 nt!KeWaitForSingleObject+0x1c2 (FPO: [Non-Fpo])
        f76e4154 804f1718 00000000 00000000 00000000 nt!KiSuspendThread+0x18 (FPO: [3,0,0])
        f76e419c 806f4c0e 00000000 00000000 f76e41b4 nt!KiDeliverApc+0x124 (FPO: [Non-Fpo])
WARNING: Frame IP not in any known module. Following frames may be wrong.
        f76e41c0 804dcce5 badb0d00 f76e4238 00000000 0x806f4c0e
        f76e4230 8065b4d0 ffffff00 f76e424c 8065b4da nt!ZwFlushInstructionCache+0x11 (FPO: [3,0,0])
        f76e423c 8065b4da 00000000 00000000 f76e42fc nt!DbgkpSendApiMessage+0x50 (FPO: [Non-Fpo])
        f76e424c 80610d25 f76e4268 00000001 81180228 nt!DbgkpSendApiMessage+0x5a (FPO: [Non-Fpo])
        f76e42fc 8057d8f2 e1d43d10 5ad70000 00000000 nt!DbgkMapViewOfSection+0xba (FPO: [Non-Fpo])
        f76e4374 804de7ec 000000c8 ffffffff 009ff318 [B][U]nt!NtMapViewOfSection[/U][/B]+0x31a (FPO: [Non-Fpo])
        f76e4374 7c90e4f4 000000c8 ffffffff 009ff318 nt!KiFastCallEntry+0xf8 (FPO: [0,0] TrapFrame @ f76e43a4)
        009ff334 00000000 00000000 00000000 00000000 0x7c90e4f4

Хотя первый поток осуществляет какую-то работу с сетью, второй поток (81228da8) на самом деле является тем, кто загружает удаленную DLL. Давайте более подробно рассмотрим его в WinDbg. Сначала, нам нужно правильно установить контекст (указать текущий процесс (81180228) и поток (81228da8), которые будут исследоваться, таким образом, когда мы говорим о виртуальных адресах, то предполагается, что они касаются исследуемого процесса). Затем мы отображаем содержимое стэка для потока 81228da8, используя команду «kv», как показано ниже:

kd> [B].process 81180228[/B]
Implicit process is now 81180228
WARNING: .cache forcedecodeuser is not enabled
kd> [B].thread 81228da8[/B]
Implicit thread is now 81228da8
kd> [B][U]kv[/U][/B]
  *** Stack trace for last set context - .thread/.cxr resets it
ChildEBP RetAddr  Args to Child             
f76e4108 804dc0f7 81228e18 81228da8 804dc143 nt!KiSwapContext+0x2e (FPO: [Uses EBP] [0,0,4])
f76e4114 804dc143 81228f14 81228da8 81228ddc nt!KiSwapThread+0x46 (FPO: [0,0,0])
f76e413c 804f8ca9 00000000 00000005 00000000 nt!KeWaitForSingleObject+0x1c2 (FPO: [Non-Fpo])
f76e4154 804f1718 00000000 00000000 00000000 nt!KiSuspendThread+0x18 (FPO: [3,0,0])
f76e419c 806f4c0e 00000000 00000000 f76e41b4 nt!KiDeliverApc+0x124 (FPO: [Non-Fpo])
WARNING: Frame IP not in any known module. Following frames may be wrong.
f76e41c0 804dcce5 badb0d00 f76e4238 00000000 0x806f4c0e
f76e4230 8065b4d0 ffffff00 f76e424c 8065b4da nt!ZwFlushInstructionCache+0x11 (FPO: [3,0,0])
f76e423c 8065b4da 00000000 00000000 f76e42fc nt!DbgkpSendApiMessage+0x50 (FPO: [Non-Fpo])
f76e424c 80610d25 f76e4268 00000001 81180228 [B][U]nt!DbgkpSendApiMessage[/U][/B]+0x5a (FPO: [Non-Fpo])
f76e42fc 8057d8f2 e161b338 5ad70000 00000000 [B][U]nt!DbgkMapViewOfSection[/U][/B]+0xba (FPO: [Non-Fpo])
f76e4374 804de7ec 000000c8 ffffffff 009ff318 [B][U]nt!NtMapViewOfSection[/U][/B]+0x31a (FPO: [Non-Fpo])
f76e4374 7c90e4f4 000000c8 ffffffff 009ff318 nt!KiFastCallEntry+0xf8 (FPO: [0,0] TrapFrame @ f76e43a4)
009ff334 7c91624a 00189ee0 009ff3c0 009ff8e8 0x7c90e4f4
009ff5f4 7c9164b3 00000000 00189ee0 009ff8e8 0x7c91624a
009ff89c 7c801bbd 00189ee0 009ff8e8 009ff8c8 0x7c9164b3
009ff904 7e428055 009ff968 00000000 00000008 0x7c801bbd
009ff930 7c90e453 009ff940 00000068 00000068 0x7e428055
009ffe48 7e42e442 00000000 0000c038 009ffee0 0x7c90e453
009ffef4 7e42d0d6 00000000 0000c038 009ffee0 0x7e42e442
009fff30 77512f04 00000000 0000c038 774ed2a4 0x7e42d0d6
009fff7c 774ff041 00000000 00000002 7c97b2b0 0x77512f04
009fff9c 77502a62 00181358 00000002 009fffec 0x774ff041
009fffac 35671d93 00000000 7c80b713 00000000 0x77502a62
009fffec 00000000 35671d82 00000000 00000000 0x[COLOR="red"][SIZE="3"]35671d93[/SIZE][/COLOR]

Теперь, у нас есть поток вызовов функцию:

[COLOR="darkorange"]35671D93 --> 77502A62 ---> ... 7C91624A ---> NtMapViewOfSection ---> DbgkMapViewOfSection ---> DbgkpSendApiMessage.[/COLOR]

Обратите внимание, что первый адресом является 35671D93 (это часть из max++.00.x86!). Снова, посмотрите назад на Рис. 12, 35671D8D в действительности является вызывающим (caller), а 35671D93 является адресом возврата. По адресу 35671D8D, код вызывает ole32.CoInitialize, который инициализирует среду окружения для объектов OLE32 (что реорганизует адресное пространство и загружает новую DLL с именем «uwtheme.dll»). Однако, во время процесса загрузки, удаленная DLL загружается. Что случилось?

Давайте попытаемся глубже углубиться в выше приведенную последовательность вызовов. Довольно ясно, что CoInitialize загружает DLL (вызвав LdrLoadDll для «uwtheme.dll», и затем отображает её в адресное пространство процесса, используя NtMapViewOfSection). Вместо этого, NtMapViewOfSection вызывает DbgkMapViewOfSection, которая является «отладочной версией» (debugged verision) NtMapViewOfSection, потому что она считает, что существует присоединенный отладчик и на самом деле дает отладчику загрузить DLL (таким образом, IMM должен иметь возможность контролировать загрузку всех DLL-библиотек!). Миссия завершается отправкой debug API command через debug port, с помощью DbgkpSendApiMessage. Затем генерируется внутреннее программное прерывание, отладчик IMM должен быть ответственен за то, чтобы обработать эту ApiMessage (т.е., загрузить DLL).

Следующий естественный шаг состоит в том, чтобы проверить состояние отладчика IMM. Аналогично, используя команду «!process 0 0» мы можем найти process id (address) соответствующий IMM и исследовать его потоки и дамп стэка, как показано ниже.

kd> [B][U]!process 81181da0 7[/U][/B]
PROCESS 81181da0  SessionId: 0  Cid: 01ac    Peb: 7ffdf000  ParentCid: 05f8
    DirBase: 08b8e000  ObjectTable: e1c38850  HandleCount: 139.
    Image: ImmunityDebugger.exe
    ...

        [B][U]THREAD 8117e748[/U][/B]  Cid 01ac.075c  Teb: 7ffde000 Win32Thread: e1023598 WAIT: (UserRequest) KernelMode Non-Alertable
            8119f7a4  SynchronizationEvent
        IRP List:
            ffbdff68: (0006,0094) Flags: 00000884  Mdl: 00000000
        Not impersonating
        ...
        ChildEBP RetAddr  Args to Child              
        f7d5f868 804dc0f7 8117e7b8 8117e748 804dc143 nt!KiSwapContext+0x2e (FPO: [Uses EBP] [0,0,4])
        f7d5f874 804dc143 00000000 8119f778 8119f7a4 nt!KiSwapThread+0x46 (FPO: [0,0,0])
        f7d5f89c fa96392e 00000000 00000006 00000000 nt!KeWaitForSingleObject+0x1c2 (FPO: [Non-Fpo])
WARNING: Frame IP not in any known module. Following frames may be wrong.
        f7d5f998 80564b0d 00000080 c00000be f7d5fa04 0xfa96392e
        f7d5fa04 804e37f7 812c4b28 ffbdff68 ffbdff68 nt!SepPrivilegeCheck+0x8d (FPO: [Non-Fpo])
        f7d5faf4 80563fec 812c4b28 00000000 ffb776a8 [B][U]nt!IopfCallDriver[/U][/B]+0x31 (FPO: [0,0,0])
        f7d5fb7c 805684da 00000000 f7d5fbbc 00000040 nt!ObpLookupObjectName+0x56a (FPO: [Non-Fpo])
        f7d5fbd0 805745a3 00000000 00000000 bf823d01 nt!ObOpenObjectByName+0xeb (FPO: [Non-Fpo])
        f7d5fd54 804de7ec 0022e278 0022e250 0022e2a4 [B][U]nt!NtQueryAttributesFile[/U][/B]+0xf1 (FPO: [Non-Fpo])
        f7d5fd54 7c90e4f4 0022e278 0022e250 0022e2a4 nt!KiFastCallEntry+0xf8 (FPO: [0,0] TrapFrame @ f7d5fd64)
        0022e2a4 7c916f13 0022e2b4 00000001 003c003a 0x7c90e4f4
        0022e2bc 7c9172f3 7c97bec8 00000001 0022e5cc 0x7c916f13
        0022e2dc 7c916023 002d5330 7c97bec8 00000000 0x7c9172f3
        0022e5a4 7c916866 002d5330 0022e5cc 0022e600 0x7c916023
        0022e620 7c916698 00000001 002d5330 00000000 0x7c916866
        0022e63c 7c801d23 002d5330 00000000 0022e668 0x7c916698
        0022e6a4 77c013bd 002e4758 00000000 00000002 0x7c801d23
        0022e6f4 77c01a28 002e4758 0022e854 015374e0 0x77c013bd
        0022e718 0049715a 015374e0 0022e854 01536960 0x77c01a28
        0022e878 004977a3 015374e0 01537894 00000014 0x49715a
        0022f548 00453e01 0022fe44 00000000 00000000 0x4977a3
        0022ffa8 80581c9f 7c90dc9c 7c817064 fffffffe 0x453e01
        00230018 00000000 00000014 00000001 00000006 nt!CcPfBeginAppLaunch+0x19f (FPO: [Non-Fpo])

Поток с id равным 8117e748,как показано выше, является потоком, отвечающим за обработку DbgApiMsg (и таким образом, он должен загрузить удаленную DLL). Мы можем убедиться в этом, посмотрев на параметры вызова функции в стэке вызовов. Как показано в MSDN, прототип функции NtQueryAttributesFile имеет следующий вид:

NTSTATUS NtQueryAttributesFile(
  __in   POBJECT_ATTRIBUTES ObjectAttributes,
  __out  PFILE_BASIC_INFORMATION FileInformation
);

Мы заинтересованы в определении ее первого параметра, как показано ниже:

kd> dt _OBJECT_ATTRIBUTES 0022e278 
nt!_OBJECT_ATTRIBUTES
   +0x000 Length           : 0x18
   +0x004 RootDirectory    : (null) 
   +0x008 ObjectName       : 0x0022e29c _UNICODE_STRING [B][U]"\??\UNC\74.117.114.86\max++.x86.dll"[/U][/B]
   +0x00c Attributes       : 0x40
   +0x010 SecurityDescriptor : (null) 
   +0x014 SecurityQualityOfService : (null)

Бинго! Сейчас IMM, обманным образом, загружает удаленную DLL находящуюся по адресу 74.117.114.86. Но вопрос в следующем:

Задача 2. Когда ole32.CoInitialize, первоначально, намеревается загрузить wstheme.dll, происходит загрузка 74.117.114.86\max.x86.dll. Почему? (Подсказка: поищите строку «max++.x86.dll» в «\.\C2CAD…\max++.00.x86» и установите на неё брэйкпойнт на данные и определите, кто её использует)

© Translated by Prosper-H from r0 Crew