R0 CREW

Malware Analysis Tutorial 18: Infecting Driver Files (Part II: Simple Infection) (Перевод: Prosper-H)

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

Цели урока:

  1. Разобраться с часто используемыми трюками, которые применяет малварь для инфицирования драйверов.
  2. Понять роль реестра в ОС Windows.
  3. Поупражняться в анализе функций.
  4. Поупражняться в реверс инженеринге сложных системных структур данных.

1. Введение

В этом уроке мы продолжаем анализ из Урока 17. Из прошлого анализа мы знаем, что Max++ случайным образом подбирает системный модуль, который удовлетворяет двум критериям: (1) имеет таблицу экспорта и (2) размер файла > 0x4c10. Этот урок покажет, как Max++ инфицирует и выполняет случайно подобранный драйвер.

Начнем анализ с кода по адресу 0x3C1E73.

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

(0) Запустите Guest XP в DEBUGGED режиме. Теперь в Host XP, запустите окно командной строки и перейдите в «C:\Program Files\Debugging Tools for Windows (x86)» (туда где установлен WinDbg). Введите «windbg -b -k com:pipe,port=\.\pipe\com_12» (проверьте номер COM-порта, установленный у вашего экземпляра VBox). Когда WinDbg запустится, дважды введите «g» (go), чтобы позволить отлаживаемой операционной системы продолжить свое выполнение.

(1) Теперь на Guest XP запустите IMM и очистите в нем все аппаратные и программные брэйкпойнты (см. «View => Breakpoints» и «View => Hardware Breakpoints»)

(2) Перейдите на адрес 0x4012DC и установите там аппаратный брэйкпойнт (почему не программный BP? Потому что эта область будет перезаписана во время само-распаковки и программный BP будет потерян).

(3) Намжите F9 несколько раз, чтобы выполнить программу до адреса 0x4012DC. Вы столкнетесь с несколькими брэйкпойнтами прежде чем дойдете до 0x4012DC. Если вы обратите внимание, то заметите, что они в действительности вызваны трюками с INT 2D (объяснено в Уроках 3, 4 и 5). Просто игнорируйте их и продолжайте выполнение (используя F9) до тех пор, пока вы не достигните 0x4012DC.

На Рис. 1 показан код, который вы сейчас должны видеть. Как можно видеть, это прямо перед вызовом RtlAddVectoredException, где устанавливается аппаратный BP, который предназначен для того, чтобы прервать вызов LdrLoadDll (Урок 11).

Рис. 1. Код по адресу 0x4012DC

(4) Теперь прокрутите на две страницы вниз и установите SOFTWARE BREAKPOINT на 0x401417. Это сразу после вызова LdrLoadDll(«lz32.dll»), где Max++ завершает загрузку lz32.dll. Затем нажмите несколько раз SHIFT+F9 пока не достигните 0x401417 (вы попадете дважды на адрес 0x7C90D500, это где-то внутри функции ntdll.ZwMapViewSection, которая вызывается LdrLoadDll).

Рис. 2. Код по адресу 0x401407

(6) Теперь установим брэйкпойнт на 0x3C1E73. Перейдите на 0x3C1E73 и установите SOFTWARE BREAKPOINT. Нажмите SHIFT+F9, чтобы программа выполнилась до 0x3C1E73 (при установке брэйкпойнта вы можете видеть предупреждение, сообщающее, что его установка происходит вне кодового сегмента. Просто игнорируйте его).

На Рис. 3 показан код, который вы должны видеть по адресу 0x3C1E73. Это прямо после цикла, который случайным образом подбирает системный модуль для инфицирования.

Рис. 3. Код по адресу 0x3C1E73 (Двойная проверка системных функций)

Раздел 3. Разминка перед инфицированием

Как показано на Рис. 3, сразу после цикла (по адресу 0x3C1E73), EDI указывает на имя системного модуля для инфицирования (в нашем случае, это имя «mouclass.sys» – оно будет отличается каждый раз, когда вы запускаете Max++). Начиная с адреса 0x3C1E73 по 0x3C1F83, Max++ выполняет некоторые разминочные действия, чтобы убедиться в том, что все важные системные функции работают правильно. Подробности отображены на Рис. 3. Руткит проверяет следующие функции:

  1. ZwCreateSection – эта функция позволяет создать секцию (объект совместно используемой памяти между процессами), она также может быть использована to map между файлом и регионом памяти.
  2. ZwMapViewOfSection – отображает (map) объект секции в адресное пространство процесса.
  3. ZwReadFile – читает содержимое файла в область памяти.

Если все проверки прошли хорошо, то руткит продолжит заражение.

Задача 1. Проанализируйте вызов sprintf по адресу 0x3C1E84 (Рис. 3). Каковы его параметры? Что он возвращает?
Задача 2. Проанализируйте вызов ZwCreateSection по адресу 0x3C1EEE (Рис. 3). Каковы его параметры? Что он возвращает?
Задача 3. Проанализируйте вызов ZwMapViewOfSection по адресу 0x3C1F20 (Рис. 3). Каковы его параметры? Что он возвращает?
Задача 4. Проанализируйте вызов ZwReadFile по адресу 0x3C1F73 (Рис. 3). Каковы его параметры? Что он возвращает?

Раздел 4. Различия в ключах реестра

По адресу 0x3C1FAD, Max++ вызывает функцию 0x3C196A. Это та функция, которая выполняет вредоносную операцию заражения.

Первая серия действий, выполняемая руткитом Max++, предназначена для того, чтобы создать запись в реестре и установить для неё значение (Рис. 4). Предположим, что инфицируется файл mouclass.sys, в этом случае, Max++ создает ключ реестра с именем «\registry\MACHINE\SYSTEM\CurrentControlSet\Services.mouclass». Затем он создает несколько пар значений, таких как «start» и «ImagePath». Обратите внимание, что в «CurrentControlSet\Services» есть оригинальная запись реестра с именем «mouclass» (только без «точки»). Оригинальная и новая версия реестра показаны при помощи regedit, рисунки 5 (а) и 5 (b) соответственно.

Рис. 4. Ключи реестра

Посмотрите на Рис. 5 и сравните значения для «Start» и «ImagePath». Здесь есть интересные моменты: В старой записи Mouclass, «Start» равен «0» (т.е. драйвер загружается во время загрузки системы), в новой записи «.Mouclass», «Start» равен «3» (т.е. загружается по требованию). ImagePath в старой записи Mouclass является абсолютным адресом (path) к драйверу, в то время как ImagePath новой .Moulcass равен «*». Значения этих параметров могут быть найдены в MSDN [1]. Насколько нам известно, «*» не является легальным значением для ImagePath. Тут должен быть какой-то специальный обработчик для этого, который должен появиться сразу после того, как Max++ инфицирует file/disk driver. Значение «*» пока остается открытым вопросом для нас.

Рис. 5. Ключи реестра «mouclass» (старый) и «.moclass» (новый)

Раздел 5. Отключение File Protection Service (Функция 0x003C15AB)

Далее, Max++ отключает сервис защиты файлов (file protection service) называемый sfc_os.dll. Тут sfc означает «Microsoft Windows File Protection Service», он следит за операциями над системными файлами (включая драйверы). Max++ нужно отключить sfc_os.dll, чтобы перезаписать системный драйвер, т.е. инфицировать его. Чтобы сделать это, Max++ вызывает функцию 0x003C15AB по адресу 0x003C1A16 (внизу на Рис. 4). Функция 0x003C15AB один за другим убивает kernel process/thread связанные с sfc_os.dll, для достижения своей цели. Сейчас мы углубимся в детали функции 0x003C15AB.

Рис. 6. Поиск «Winlogon.exe»

На Рис. 6 видно нашу старую подругу ZwQuerySystemInformation. В этот раз, Max++ использует ее для получения информации о существующих процессах в системе (в последний раз в Уроках 16 и 17, она использовалась для получения системных модулей/драйверов). Затем руткит перечисляет информацию о каждом процессе и сравнивает ее с именем «Winlogon.exe», пока не находит процесс Winlogon.exe.

Задача 1. Откуда вы знаете, что вызов функции по адресу 0x3C1612 (RtlEqualUnicodeString) предназначен для сравнения с Winlogin.exe? Покажите параметры требуемые для вызова RtlEqualUnicodeString. (Подсказка: используйте WinDbg для отображения UNICODE_STRING).

Рис. 7. Запрос отладочной информации

Затем, Max++ делает запрос на получение отладочной информации из процесса Winlogon.exe (Рис. 7). Мы оставляем подробный анализ этого кода за вами. Вы можете обратиться к [2] за получением подробной информации о RtlqueryProcessDebugInfo. Потенциально, Max++ мог бы использовать эту информацию, чтобы определить находится ли система под отладкой или нет, но он не выполняет здесь никаких анти-отладочных действий и просто переходит к функции 0x003C14D7 (по адресу 0x003c1651, см. Рис. 7). Функция 0x003C14D7 отключает сервис защиты файлов. Пока мы просто будем иметь в виду, что _DEBUG_BUFFER расположен по адресу 0x00900000, и это может быть полезно позже. Обратите внимание, что _DEBUG_BUFFER по 0x00900000 хранит информацию о WinLogon.exe!

Задача 2. Проследите за вызовом RtlQueryProcessDebugInfo и покажите содержимое _DEBUG_MODULE_INFO.

5.1 Отключение File Protection Service

Теперь давайте протрассируем функцию 0x003C14D7. На Рис. 8 показано тело функции. Как обычно, первое, что мы должны сделать – это найти входящие параметры функции. Исследуя первую часть инструкций, вскоре вы должны заметить, что функция читает из двух регистров.

  1. EAX – содержит значение 0x00900000 (примечание: это _DEBUG_BUFFER для winlogon.exe).
  2. ECX – содержит _SYSTEM_MODULE_INFORMION winlogon.exe.

We leave the details of inferring the semantics of EAX, ECX parameters to you:

Задача 2. Как определить содержащийся тип данных в регистрах EAX, ECX (входящие параметры) для функции 0x003C14D7?

Рис. 8. Тело функции 0x3C14D7 (Часть 1)

Первая часть функции является циклом (c адреса 0x3C14EC по 0x3C150E, см. Рис. 8). Max++, один за другим, читает _DEBUG_MODULE_INFORMATION, и сравнивает полученное имя с «sfc_os.dll» (см. вызов функции по адресу 0x3C14FB, и содержимое стэка на Рис. 8). Руткит сразу выходит из цикла, как только находит _DEBUG_MODULE_INFORMATION для «sfc_os.dll».

Сложность анализа этой части кода состоит в том, что отсутствует документация для таких структур ядра как _DEBUG_BUFFER и _DEBUG_MODULE_INFORMATION.

Задача 3. ***** Понаблюдайте за инструкцией по адресу 0x003C14E3, как бы вы определили, что EDI сейчас содержит количество модулей, предположим, что EAX сейчас содержит PVOID ModuleInformation из _DEBUG_BUFFER? Попытка, просто найти какую-либо информацию по строке «typedef _DEBUG_BUFFER», даст вам не больше, чем тип PVOID. Вам нужно просмотреть исходный код RtlQueryProcessDebugInformation [3] и LdrQueryProcessModuleInformationEx [4], чтобы определить интересующую информацию.

Рис. 9. Уничтожение потоков (threads) относящихся к sfc_os.dll

Теперь, когда Max++ определил DEBUG_MODULE_INFORMATION sfc_os.dll, он может узнать ImageBase адрес sfc_os.dll. Сразу после этого, идет цикл, который ищет все потоки (threads) winlogon.exe и убивает те из них, которые связанны с sfc_os.dll. Подробности отображены на Рис. 9.

The basic control flow is:
  for each thread in winlogon.exe
        open thread
        get the base address of the thread
        if [B][U]base address of the thread - imageAddress of sfc_os  <= imageSize of sfc_os[/U][/B]
                  suspend the thread

Таким образом: если базовый адрес потока находится внутри диапазона sfc_os, то он будет убит.

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

Задача 4. Понаблюдайте за инструкцией MOV EAX, [ESI+8] по адресу 0x3C1519 (Рис. 9). Как бы вы определили, что EAX будет содержать базу (base) из sfc_os.dll?

Задача 5. Понаблюдайте за инструкцией MOV EAX, [EBP-8] по адресу 0x3C1562 (Рис. 9). Как бы вы определили, что EAX содержит базовый адрес (base address) потока?

К этому моменту, модуль sfc_os.dll должен быть полностью отключен и OS не должна выполнять проверку безопасности для драйверов.

Раздел 6. Инфицирование

Теперь перейдем к обсуждению заражения. Часть этого кода начинается с адреса 0x3C1A1B (сразу после того, как управление вернется из вызова 0x3C15AB отключающего sfc_os.dll).

На Рис. 10 показаны первые действия принимаемые руткитом Max++. Он проверяет, что случайно подобранный драйвер (например, mouclass.sys – заметьте, что имя меняется каждый раз, когда вы запускаете руткит) не сжато (compressed). Это достигается вызовом ZwFsControlFile.

Задача 6. Понаблюдайте за инструкцией по адресу 0x003C1A63 (вызов ZwFsControlFile). Проанализируйте ее входящие параметры.

Рис. 10. Change File Property

На Рис. 11 показаны следующие действия выполняемые Max++: Он создает section object и затем view. Section object – используется Windows для совместно использования памяти между процессами, так же он может использоваться для того, чтобы отобразить (map) файл в память. Max++ использует второй вариант: он создает section object для того, чтобы отобразить содержимое драйвера, предназначенного для заражения (пусть это будет mouclass.sys), в память, а затем создает view для section object. Вам нужно проанализировать выделенные вызовы функций, чтобы получить больше информации, например, каков начальный адрес области памяти у отображенного файла и какой у нее размер?

Рис. 11. Создание отображения файла в память

Рис. 12. Новый Section Object

Задача 7. Проанализируйте выделенные функции на рис. 11 и ответьте на следующие вопросы: (1) куда отображена память для mouclass.sys (файл для заражения), т.е. где она начинается и где заканчивается? (2) откуда вы знаете, что вызов ZwCreateSection не предназначен для создания совместно используемой памяти между двумя процессами, а предназначен для отображения файла? (подсказка: вы можете нажать «View => View Memory», и вы увидите, что начальный адрес ново-созданного section object, см. Рис. 12)

Рис. 13. Декодирование и копирование данных в отображенный файл

Далее, Max++ устанавливает декодированную таблицу (Рис. 13), и копирует некоторое содержимое из стэка (диапазон 0x12XXXX) в 0x00380000 (отображенный регион для файла mouclass.sys). Обратите внимание, что значение 0x00380000 может варироваться. После нескольких циклов декодирования, мы наконец-то получаем содержимое, с начальным адресом 0x00380000 (см. Рис. 13, панель дампа). Там вы заметите последовательность 0x4D5A, которая является магическим словом для «DOS-заголовка».

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

Конечное действие Max++ заключается в установке контрольной суммы для PE-заголовка и записи содержимого из выделенного региона памяти в файл (с помощью ZwFlushVirtualMemory, см. Рис. 14).

Задача 8. Проанализируйте 2-ю выделенную область из рис. 14, объясните? почему инструкция устанавливает контрольную сумму (checksum) для PE-файла?

Рис. 14. Установка контрольной суммы и запись содержимого обратно в файл

Как показано на Рис. 14 (4-я выделенная область), Max++ сразу вызывает ZwLoadDriver(), чтобы загрузить обновленный файл. Если мы бы захотели вникнуть в логику работы нового драйвера, то мы бы не смогли этого сделать, потому что драйвер будет запущен в привилегированном режиме, в то время как IMM является Ring 3 отладчиком. В этом случае, нам нужен ядерный отладчик WinDbg. Мы перейдем к анализу инфицированного драйвера в следующем уроке.

© Translated by Prosper-H from r0 Crew

Ссылки

[1] Microsoft, “CurrentControlSet\Services subentry keys”
[2] Unknown, “RtlQueryProcessDebugInformation as Anti-Dbg Trick”
[3] Unofficial source code of RtlQueryProcessDebugInformation
[4] Unofficial source code of LdrQueryProcessModuleInformationEx