R0 CREW

Malware Analysis Tutorial 11: Starling Technique and Hijacking Kernel System Calls using Hardware Breakpoints (Перевод: coldfire)

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

Цели урока:

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

1. Введение

Скворец – это птица, которая крадет гнезда других птиц. В этом уроке, вы увидите как техника «Starling (скворца)» используется в Max++ для запуска своего вредоносного кода используя «тело» другого модуля, именуемого «lz32.dll».

2. Настройка рабочей среды

Вы можете продолжать работать с настройками из Урока 10, или следуйте инструкциям описанным ниже. Мы продолжим анализ с функции 0x4014F9.

Рис. 1. Функция 0x4014F9 для анализа

  1. Удалите все аппаратные точки останова.
  2. В окне кода, нажмите «right click => Go to => Expression => 0x4014F9».
  3. Нажмите «right click => Breakpoints => Hardware, on execution».
  4. Нажмите F9 для того, что бы перейти к 0x4014F9.
  5. Если вы видите много инструкций DB, то выберите их и нажмите «right click => Analysis => During next analysis, treat selection as => Command».
  6. Перезапустите Max++ и перейдите к 0x4014F9 снова. Выберите инструкции ниже адреса 0x4014F9 и нажмите «right click => Analysis => Analyze Code». Сейчас вы должны видеть, что все циклы определены отладчиком IMM.

3. Background

Перед тем как перейти к самому интересному, быстро пройдемся по коду, где Max++ устанавливает аппаратную точку останова (hardware breakpoint). Мы оставляем для вас анализ большинства деталей кода и подытоживание функциональности этих фрагментов кода.

3.1 Возвращение к ZwAllocateVirtualMemory

Напомним, что в Уроке 10, мы показали вам трюк, как Max++ может вызывать системные вызовы из ntdll без использования таблицы экспорта. По адресу 0x00401157 он вызвает 0x004136BF, которая реализует бинарный поиск по закодированной таблице экспорта и потом переходит на начальный адрес функции ZwAllocateVirtualMemory. Теперь интересный момент: Какие параметры принимает данная функция?

Рис. 2. Содержимое стэка

Согласно [1], функция zwAllocateVirtualMemory принимает 6 параметров, как показано ниже:

NTSTATUS ZwAllocateVirtualMemory(
  __in     HANDLE ProcessHandle,
  __inout  PVOID *BaseAddress,
  __in     ULONG_PTR ZeroBits,
  __inout  PSIZE_T RegionSize,
  __in     ULONG AllocationType,
  __in     ULONG Protect
);

Первый параметр является дескриптором процесса в адресном пространстве которого будет выделена часть памяти. В нашем случае он равен 0xFFFF (-1) (Рис. 2), что означает текущий процесс. Второй параметр (базовый адресс) указывает на память, в которой будет храниться базовый адрес вновь выделенной памяти (обратите внимание, что его тип — IN/OUT, т.е. параметр используется как для передачи значений в функцию, так и для возврата результатирующего значения из функции). Последний параметр, Protect, его значение равно 0x0040 (смотри 5й word на Рис. 2). Из документации [1], мы знаем, что константные значения для параметра «Protect» определены в ntfs.h, или просто можно найти в Google по запросу «#define PAGE_NOACCESS» приведет нас на прямую к заголовочному файлу (ПРИМЕЧАНИЕ: всегда используйте вирутральную машину для поддобных поисковых запросов. Вебсайты, котороые публикуют исходный код Windows часто загружают вредоносный код, такой как drive-by download!). Мы знаем, что 0x40 – это значения макроса «PAGE_EXECUTE_READWRITE». Чуствуете ли вы запах чего-то подозрительного??? Вновь выделенная память будет исполняемой!

Пока все хорошо, мы знаем начальный адресс новой VM (виртуальная память) будет храниться по адресу 0x0012D648. Правый клик на 0x0012D648 в стеке и выбрите «Follow in Dump», нажмите F8 (step over) для исполнения инструкции call 0x4136BF по адресу 0x00401157, вы заметите, что вновь выделенная память начинается по адресу 0x00003A00. (Это значение может меняться от запуска к запуску, даже если вы используете один и тот же образ/экземпляр Vbox)

3.2 Еще один уровень самораспаковки [Another Layer of Self-Extration]

Сразу за функцией ZwAllocateVirtualMemory идет большой двух уровненный вложенный цикл (Рис. 3), начиная с 0x00401567. Его цель извлечь (распаковать) данные с адресного пространства Max++ и скопировать их в выделенную память VM начиная с 0x00003A00 (адрес может отличаться во время выполнения). Мы оставляем детали анализа и следующие вопросы тебе:

  1. Откуда получены данные?
  2. Какой размер кода копировался в 0x00003A00?
  3. Как вы выходите с двух уровненного вложенного цикла?

Рис. 3. Снова самораспаковка

3.3. First section of 0x4012AB

Если вы успешно вышли с 2-х уровненного цикла описанного в части 3.2, вы скоро увидите функцию 0x4012AB. Это важная функция. На рис.4 показана первая часть. Сейчас мы находимся на первом интересном вызове функции по адресу 0x4012C9 (Рис. 4). Он взывает функцию 0x004137D1. Трассируя эту функция, вы видите, что она вызывает ntdll функцию RtlNtImageNtHeader, которая принимает адрес модуля как параметр и возвращает указатель на структуру IMAGE_NT_HEADERS. Прочитайте Урок 8 (PE header) для детального анализа данной структуры.

Что за параметр принимает функция RtlNtImageHeader? Это 0x003A0000 ( вновь выделенная память). Возвращаемое значение (указатель на IMAGE_NT_HEADER) также - 0x003A0000. Вы можете убедиться, что он действительно является указателем на IMAGE_NT_HEADER

Рис. 4. RtImageNtHeader

В панели memory dump перейдите по адресу 0x003A0000, далее сделайте правый клик и выберите Special->PE header. (Рис. 5). Есть несколько интересных моментов на которое вы должны обратить внимание. Например, точка входа – смещение 0x24FB. Кроме того, вы можете узнать точную точку входа модуля ( 0X003A24FB ).

Рис. 5. PE-заголовок недавно извлеченного двоичного файла

Задание 1: Посмотрите на 2 инструкции, начинающиеся по адресу 0x4012CE ( команда сравнения следует за ними). Объясните логику.

4. The Starlings Trick: Захват адресного пространства другого модуля

Скворцы известны тем, что воруют гнезда других птиц. Ниже мы увидим искусную технику реализованную в Max++, которая позволяет симулировать “скворцов/ starlings” для загрузки нового исполняемого кода, который начинается по адресу 0x003A0000 используя адресное пространство другого модуля. Наш путь будет следующим:

  1. Добавить обработчик исключений по адресу 0x413657 (все прерывания, включая аппаратные точки останова, вызовут код, находящийся по адресу 0x413657.
  2. Установить аппаратное исключение по адрессу 0x7C90D500.
  3. Загрузить DLL библиотеку с именем «lz32.dll».
  4. В момент когда, ядро загружает «lz32.dll», срабатывает аппаратная точка останова, которая запускает код 0x413657. Этот код завершает работу по размещению модуля, по диапазону адресов, начиная с 0x003A0000, и тем самым избегает загрузки реального кода из файла «lz32.dll» в адресное пространство модуля.

Теперь давайте рассмотрим каждый из этих шагов один за одним.

4.1 Добавление обработчика векторных прерываний

По адресу 0x4012E4 (Рис.6 ), вредоносная программа вызывает 0x413776 (и снова, это трюк, который позволяет избежать использования таблицы экспорта для вызова функции ntdll) для вызова RtlAddVectoredExceptionHandler. Он устанавливает 0x00413657 как первый обработчик исключений для всех исключений, в том числе аппаратных точек останова, любое прерывание вызовет код на 0x00413657 в первую очередь.

Рис. 6. Установка обработчика векторных исключений

Задание 2: (1) покажите параметры функции RtlAddVectoredException; (2) Если мы запустим Max++ на другом компьютере, то получит ли обработчик исключений другой адрес отличный от 0x00413657?

Задание 3: (1) по адресу 0x401314, Max++ вызывает 0x4136E4. Проанализируйте какая системная функция вызывается на самом деле. (2) По адресу 0x401346 (call 0x413811), на самом деле вызывается функция RtlPushFrame. Поясните зачем. (3) A по адресу 0x00401360 (call 0x413701), на самом деле вызывается функция ZwMapViewOfSection. Поясните зачем. (4) Объедения (1), (2) и (3) объясните, что малварь пытается сделать?

4.2 Установка аппаратной точки останова 0x7C90D500

Перечислим некоторые детали второго шага. Ключевой на данном шаге является функция 0x413736, которая показана на Рис. 7. Она вызывает ZwSetContextThread. Эта функция официально не описана в документации Micrisoft поэтому немного поискав в сети мы находим, что она принимает 2 параметра:

  • IN HANDLE processHandle ( в нашем случаи = 0xFFFF как показано на панели со Стеком на рис. 7)
  • INOUT PCONTEXT pContext (в нашем случаи = 0x0012D028 как показано на рис. 7)

Рис. 7. ZwSetContextThread

0x0012D028 сохранит структуру Context. Его структура данных показана ниже (см. [2]). Начиная со 2го слова (word) структуре CONTEXT идут значения регистров аппаратных точек останова (Dr0 – Dr7). Если вы посмотрите на рис. 8 (текущий Contents находится по адресу 0x0012D028), вы заметите, что значение ContextFlags равно 0x00010010 и значение регистра Dr3 (4го регистра аппаратных прерываний) равно 0x7C90D500!!!

//for x86
typedef struct _CONTEXT {

DWORD ContextFlags;
...
DWORD Dr0;
DWORD Dr1;
DWORD Dr2;
DWORD Dr3;
DWORD Dr6;
DWORD Dr7;
...
FLOATING_SAVE_AREA FloatSave;
/...
} CONTEXT; 

Рис. 8. Содержимое структуры CONTEXT расположенной по адресу 0x0012D028

Теперь мы почти рассмотрели всю трюки, которые относятся к этому шагу. Вот несколько заданий для вас:

Задание 3: (1) Поясните значение 0x00010010, которое находится в ContextFlags, что оно значит? (2) Найдите 0x7C90D500, это начальный адрес (или где-то внутри) системной функции?

4.3 Загрузка lz32.dll

Следующий шаг – загрузить библиотеку lz32.dll. На рис. 9 показаны детали (смотри комментарии). Они в основном состоят из двух этапов: (1) построить юникод строку «lz32.dll» и потом (2) загрузить «lz32.dll» как модуль. Ответьте на следующие вопросы:

Задание 4: Объясните логику работы функции 0x004137EF. (2) Объясните логику работы 0x41385D.

Рис. 9. Загрузка lz32.dll

4.4 Прерывание процедуры LdrLoadDll

Отметим, что «lz32.dll" не является вредоносной, она является жертвой! Когда dll загружена, процедура загрузки будет прервана Max++'ом. Напомним, что Мах++ уже установил обработчик прерывания по адресу 0x00413657.

Сейчас установим программную точку остановка на адрес 0x00413657 и также поставим точку останова на 0x0040140E (сразу после загрузки lz32.dll). Сейчас вы узнаете про самую сложную часть анализа:

Задание 5: Объясните почему 0x00413657 никогда не прерывается? Найдите способ отснавиться по адрессу 0x00413657.

Коректный способ отсановки по адрессу [B]0x00413657:[/B]
1. [B]Перейдите по адрессу 0x00413657 и установите программную точку остановку на нем (нажав F2)

  1. Нажмите SHIFT+F9 (подумайте про то что значит SHIFT+F9).
  2. Вы должны остановиться на адрессе 0x7C90D5000 (подумайте почему вы остановитесь в IMM? – потому что аппаратаная точка останова установлена Max++. IMM остнавливается на ней, хотя это не аппаратаная точка останова внутри IMM).
  3. Нажмите еще раз SHIFT+F9 и вы остановитесь на 0x00413657. Теперь можно приступить к анализу обработчика прерываний.
    [/B]
    Затем будет происходить некоторый анализ. Ниже даны некоторые подсказки:
  • Код по адресу 0x00413657 является обработчиком прерывания. Проверьте [3] соглашение о вызовах (как параметры передаются) для обработчиков прерываний. Вы заметите, что код читает структуру исключения и говорит представляет ли она какой-то интерес. Если она не является аппаратной точкой останова (конкретнее single_step_trap), то обработчик просто передает дальше прерывание по цепочке обработчиков.
  • Если прерывание было определено как аппаратная точка останова, то обработчик что-то записывает в память. Например, он записывает значение «0x003A0000» в какую-то структуру данных в ядре (касающееся модуля). Подумайте почему?
  • Обработчик устанавливает поток Context вместо установки регистра EIP, в следствии чего функция 0х7Схх (ntdll) не будет вызвана. Что мы получим? Получим, что актуальный код lz32.dll не будет загружен. Таким образом, код , расположенный по адресу 0х003А0000, остается там и выполнится, когда модуль «lz32.dll» запущен.

Задание 6: проанализируйте каждую строчку начиная с адреса 0x00413657 и проверь, что вышеуказанные утверждения правильны.

© Translated by coldfire from r0 Crew

Ссылки

[1] Microsoft, “ZwAllocateVirtualMemory Routine”
[2] Microsoft, “Context Structure”
[3] M. Pietrek, “A Crash Course on the Depth of Win32Tm Structured Exception Handling,” Microsoft System Journal, 1997/01