R0 CREW

Malware Analysis Tutorial 30: Self-Overwriting COM Loading for Remote Loading DLL (Перевод: Prosper-H)

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

Цели урока:

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

1. Введение

В этом уроке будет произведен анализ вредоносного DLL-файла «\.\C2CAD…\max++.00.x86». Здесь мы увидим много интересных техник применяемых для скрытия вредоносного поведения.

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

Первый шаг состоит в том, чтобы извлечь max++.00.x86 из скрытого диска. Для осуществления этой цели, вы можете следовать методам, которые были представлены в Уроке 28. Основная идея заключается в вызове ZwCopyFileA, сразу после того, как файл будет доступен в скрытом диске.

Затем, нам нужно попасть на точку входа max++.00.x86, которая размещена по смещению +2DFF. Эта информация будет доступна в IMM, когда вы загрузите max++.00.x86 в отладчик, в качестве DLL-библиотеки. Перейдя по View => Executable Modules, вы увидите, что точкой входа DLL является смещение +2DFF, а ее база равна 35670000.

Обратите внимание, что вы можете исполнять max++.00.x86 в пошаговом режиме (в IMM), однако, это не раскроет «корректного» поведения руткита, так как отладчик не может нормально функционировать в среде, где Max++ заранее соединил, как ему это было нужно, определенные записи драйвера и обработчики исключений. Вместо этого, мы должны использовать WinDbg, чтобы добраться до точки входа DLL, следуя «правильному» потоку исполнения Max++.

Ниже мы покажем, как попасть на точку входа max++.00.x86.

Настройка среды подобна настройкам WinDbg из Урока 20. В экземпляре win_debug, нужно установить брэйкпойнт на адрес 0x3C230F. Это место прямо перед вызовом CALL WS2_32.WSASocketW, как показано ниже на Рис. 1.

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

Это то место, где Max++ пытается получить доступ к http://74.117.114.86\max++.x86.dll. Сейчас, нажмите F8 в IMM, и вы дважды, при загрузке образов (loading images), остановитесь в WinDbg. Теперь введите следующую команду, чтобы остановиться на точке входа max++.00.x86:

> [B][U]bp 35672dff[/U][/B]

Почему-то WinDbg не может перехватить брэйкпойнт, вместо этого, INT3 перехватывается отладчиком IMM, как показано на Рис. 2:

Рис. 2. Точка входа max++.00.x86

Если вы посмотрите на инструкцию по адресу 35672DFF (Рис. 2), то заметите, что там расположена инструкция «INT 3», помещенная туда отладчиком WinDbg. Нам нужно восстановить её оригинальную инструкцию PUSH EBP (мы можем узнать эту информацию из предыдущей сессии отладки, в которой мы загружали, в отладчик IMM, файл max++.00.x86, в качестве DLL-библиотеки). Если мы не восстановим «PUSH EBP», то инструкция по адресу 0x35672E05 (MOV EAX, [EBP+C]) не будет корректно работать, потому что EPB будет неправильно установлен. Пожалуйста, следуйте инструкциям ниже (они предназначены для отладчика IMM):

  1. В панели кода, нажмите «right click» на инструкции INT 3 и выберите опцию «Assemble», после чего введите «PUSH EBP».
  2. Нажмите на 2-ю кнопку (Python) на панели инструментов, чтобы открыть окно Python, после чего введите в него и выполните следующую команду: imm.setReg(“EIP”, 0x35672DFF). Это приведет к переустановке регистра EIP. Начиная с этого момента, вы можете выполнять max++.00.x86 в пошаговом режиме.

3. Скрытие загрузки DLL

Теперь мы можем объяснить, почему загрузка удаленной DLL, раньше (в Уроке 29), не перехватывалась отладчиком IMM. Посмотрите на Рис. 3.

Рис. 3. Скрытая загрузка DLL

Первый вызов (показанный на Рис. 3), ZwTestAlert очищает очередь APC, а второй вызов DisableThreadLibraryCalls отсоединяет сигнал DLL_LOAD, таким образом, позже, событие загрузки DLL не будет перехвачено. Затем, программа совершает следующий трюк, она считывает, по одному, адресу функций, которые сохранены в промежутке с 356761E0 по 35676200, и поочередно вызывает каждую из них (см. выделенную область в дампе памяти, Рис. 3). Адресами этих функция являются 3567595c, 35675979, etc.

Задача 1. Определите адреса всех функций (и их прототипы), которые были вызваны выше.

Логика выглядит примерно следующим образом:

  1. Вначале Max++ создает AVL-дерево.
  2. Затем копирует адреса нескольких ntdll-функций, таких как NtWriteFile, в глобальную память. Это, по сути, нужно для подготовки секции данных (заданной жестко в коде) в глобальной памяти. Затем он вызывает 356752D7 (по адресу 0x35672E5E).
  3. Затем, сохраняет несколько dll имен, таких как «kernel32.dll», «kernel32base.dll» в стэк.

4. Манипуляции с системными DLL-библиотеками

Продолжим анализ с функции 0x356762D7. На Рис. 4 показано её тело.

Рис. 4. Обработка модулей ядра

Функция 0x356752D7 по очереди обрабатывает связанные DLL-библиотеки ядра, в цикле. Набор названий DLL-библиотек предварительно установлены Max++ (см. пояснения «логики», после Задачи 1). Функция пытается получить дескриптор для каждой DLL, после чего вызывает функцию 0x3567525C для обработки информации заголовка из каждой DLL (обработка заголовка происходит только в том случае, когда был получен дескриптор, т.е. DLL в данный момент уже присутствует в системном пространстве).

Теперь рассмотрим содержимое функции 0x3657525C (Рис. 5).

Рис. 5. Функция 0x3567525c

Главной частью функции является вызов RtlImageDirectoryEntryToData. Это недокументированный системный вызов (если вы поищите на ReactOS, то сможете найти его исходный код), который принимает четыре параметра: (1) image base DLL файла, (2) флаг, который указывает, является ли файл образом (image), (3) ID записи для получения и (4) размер записи. В нашем случае, ID равен «1», читая документацию MSDN по IMAGE_DIRECTORY PE-заголовка, мы можем обнаружить, что ID «1» означает import table address и size. Мы оставляем для вас задачу, которая заключает в том, чтобы выяснить, кто использует эту информацию, т.е. что с ней планирует делать Max++?

Задача 2. Выясните, кто использует информацию заголовков (NT header information), полученную с помощью кода по адресу 0x35675277 (Подсказка: используйте брэйкпойнты на данные, чтобы узнать, где и когда используется информация).

5. Переустановка обработчика исключений (SEH)

Давайте посмотрим на интересное поведение max++.00.x86, которое переустанавливает SEH-обработчик. Перед осуществлением этого трюка, текущая SEH-цепочка выглядит следующим образом (View => SEH chain в IMM):

Рис. 6. SEH-цепочка перед переустановкой SEH-обработчика

При возникновении исключения, оно вначале передается в ntdll.7C90E900 (3 раза), а затем kernel32.7C839AC0 (если 7C90E900 не правильно обработает его). SEH-цепочка это очень простая, односвязная структура. Каждая её запись состоит из двух элементов:

  struct SEH_RECORD{
      SEH_RECORD *next;
      void*   handler_entry
 };

Например, первая SEH-запись, размещенная по адресу 0x0012BF1C, выглядит следующим образом (Рис. 7).

Рис. 7. Дамп памяти первой SEH-записи

Как видно из рисунка (выделенная часть), следующий указатель (next pointer) указывает на адрес 0x0012C1C8 (сравните его с Рис. 6), а обработчик равен значению 0x7C90E900.

Теперь, на Рис. 8, мы представим вредоносный код из max++.00.x86, который переустанавливает SEH. Основная идея заключается в том, чтобы создать новую SEH-запись и поместить её в стэк, после чего переустановить значение FS;[0] (которое является частью данных ядра, которые указывают на SEH-цепочку). Важные инструкции выделены подчеркиванием.

Рис. 8. Манипулирование SEH-цепочкой

Задача 3. Объясните в деталях (исходя из инструкций указанных на Рис. 8, начиная с адреса 0x356754BC): почему адрес 35675510 является новым обработчиком исключений?

Задача 4. Объясните логику работы обработчика расположенного по адресу 35675510 и выясните, когда он вызывается.

5. Загрузка удаленной DLL

Следующая интересная часть расположена по адресу 0x35672E6D. На Рис. 9 показан вызов ZwCreateThread и его параметры. Стартовой процедурой потока (т.е. функцией, которая будет выполнена, когда будет создан поток) является адрес 0x356718D2.

Рис. 9. Создание нового потока, который вызывает 0x356718D2

Давайте установим программный брэйкпойнт функцию 0x356718D2 и проследим за ней. На Рис. 10, показано её тело. Оно содержит две главные функции: 0x35671797 и ole32.CoInitialize.

Рис. 10. Стартовая процедура потока

На Рис. 11 изображена первая часть тела функции 0x35671797.

Рис. 11. Первая часть функции 0x0035671797

Как показано на Рис. 11, первым важным вызовом в 0x35671797 является LdrFindEntryForAddress(0x0035670000, 0x009FFFA8). Ища документацию по LdrFindEntryForAddress, мы вскоре узнаем, что функция ищет module entry (LDR_DATA_TABLE_ENTRY) для адреса 0x0035670000, и сохраняет указатель на LDR_DATA_TABLE_ENTRY, по адресу 0x009FFFA8 (который, после вызова, содержит значение 0x00252A50). Дамп данных структуры, осуществленный при помощи WinDbg, представлен ниже (запустите WinDbg внутри WIN_DEBUG и присоединитесь к процессу max++ в режиме noninvasively). Обратите внимание, что типом FullDllName является строка _UNICODE, в то время как реальный массив символов хранится по адресу 0x002529c0.

:000> dt _LDR_DATA_TABLE_ENTRY 0x00252a50 -r1
ntdll!_LDR_DATA_TABLE_ENTRY
   +0x000 InLoadOrderLinks : _LIST_ENTRY [ 0x252b10 - 0x252cd0 ]
      +0x000 Flink            : 0x00252b10 _LIST_ENTRY [ 0x252bd8 - 0x252a50 ]
      +0x004 Blink            : 0x00252cd0 _LIST_ENTRY [ 0x252a50 - 0x252900 ]
   +0x008 InMemoryOrderLinks : _LIST_ENTRY [ 0x252b18 - 0x252cd8 ]
      +0x000 Flink            : 0x00252b18 _LIST_ENTRY [ 0x252be0 - 0x252a58 ]
      +0x004 Blink            : 0x00252cd8 _LIST_ENTRY [ 0x252a58 - 0x252908 ]
   +0x010 InInitializationOrderLinks : _LIST_ENTRY [ 0x252d48 - 0x252be8 ]
      +0x000 Flink            : 0x00252d48 _LIST_ENTRY [ 0x252e08 - 0x252a60 ]
      +0x004 Blink            : 0x00252be8 _LIST_ENTRY [ 0x252a60 - 0x252b20 ]
   +0x018 DllBase          : 0x35670000 Void
   +0x01c EntryPoint       : 0x35672dff Void
   +0x020 SizeOfImage      : 0xc000
   +0x024 FullDllName      : _UNICODE_STRING [B][U]"\\.\C2CAD972#4079#4fd3#A68D#AD34CC121074\L\max++.00.x86"[/U][/B]
      +0x000 Length           : 0x6e
      +0x002 MaximumLength    : 0x72
      +0x004 Buffer           : [B][U]0x002529c0[/U][/B]  "\\.\C2CAD972#4079#4fd3#A68D#AD34CC121074\L\max++.00.x86"
   ...

Следующим вызовом является OLE32.LoadTypeLibrary("\.\C2CAD…max++.00.x86", REGKIND_NONE, 0x009FFFAC), где указатель на ITypeLib хранится по адресу 0x009FFFAC. Если мы сдампим содержимое памяти, то узнаем, что указатель на ITypeLib равен 0x00180F10.

Далее идет интересная часть. В ней происходит чтение ECX = [EAX] (Рис. 11), затем происходит вызов [ECX+18], с передачей трёх параметров: 0x00180F10 (это указатель на «this» для интерфейса ITypeLib), 0x003567696C и 0x00356782A0 (и может быть еще какие-то параметры, т.е. не обязательно три). Ни 0x003567696c, ни 0x00356792a0 не похожи на внутренний регион памяти. Но какую функцию, вызовет вызов [ECX+18]?

Для ответа на этот вопрос, нужно внимательно прочитать документацию MSDN о ITypeLib. Согласно MSDN, ITypeLib поддерживает несколько функций, таких как FindName, GetDocumentation, etc. Если мы исследуем содержимое памяти по адресу 0x00356782A0, до и после вызова [ECX+18], то сможем заметить, что оно изменилось. Таким образом, проверив список функций, доступных в ITypeLib, мы найдем только одну функцию, у которой второй параметр является исходящим (output). Такой функцией является ITypeLib::getTypeInfoOfGUID(int GUID, ITypeLib **).

Задача 5. Проверьте, действительно ли выше указанная гипотеза, касательно getTypeInfoOfGUID, является корректной.

Теперь давайте изучим последнюю часть функции 0x35671797 (Рис. 12).

Рис. 12. Перезапись информации о модуле

Как видно из рисунка, это просто функция memcpy, которая копирует строку «\74.117.114.86\max++.x86.dll» в адрес 0x002529C0. Еще раз, прочитайте обсуждение, данное после Рис. 11, адрес 0x002529C0 хранит имя текущего модуля в списке модулей для загрузки (in-order-load module list)!

Теперь, когда поток исполнения возвращается из 0x35671797 (Рис. 10), он вызывает Ole32.CoInitializ. Поскольку текущий модуль зарегистрирован в качестве type library, то модули Ole32, будут пытаться, так или иначе, загрузить его снова, что заставит систему загрузить удаленную DLL \74.117.114.86\max++.x86.dll.

Так как, файл \74.117.114.86\max++.x86.dll больше не доступен в Интернете, то вызов CoInitialize завершится неудачей. Посему, нам нужно найти способ, который позволит исследовать оставшуюся логику. Мы продолжим наше исследование в следующем уроке.

© Translated by Prosper-H from r0 Crew