R0 CREW

Malware Analysis Tutorial 10: Tricks for Confusing Static Analysis Tools (Перевод: Prosper-H)

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

Цели урока:

  1. Исследовать использование стэка для поддержки вызовов функций.
  2. Продолжить практиковать навыки реверс инженеринга.

1. Введение

В этом уроке рассматриваются несколько трюков, используемых Max++ для запутывания инструментов статического анализа. Эти трюки эффективно предотвращают работу инструментов, предназначенных для статического анализа программ, которые графически отображают граф вызовов (call graph) и извлекают, из вредоносной программы, информацию о системных вызовах. Под «статический» мы подразумеваем то, что инструмент в действительности не выполняет/запускает вредоносную программу. Самые «умные» вирусные сканеры являются инструментами статического анализа. Многие из них используют эвристику, чтобы определить, является ли исполняемый файл вредоносным или нет, исследуя набор системных вызовов из двоичного файла. Например, если двоичный файл вызывает слишком много операций, связанных реестром, то должен быть взведен флаг тревоги. Если такой анализ может быть заблокирован, то вредоносная программа может значительно улучшить свой уровень выживания. Обратите внимание, однако, что такие трюки не блокируют инструменты «динамического» анализа, которые в действительности выполняют вредоносное программное обеспечение (типичные инструменты включают CWSandBox и Anubis).

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

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

Рис. 1. Анализ функции 0x4014F9

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

3. Two-Layer Function Return

Теперь проанализируем первый трюк из two-layer function return, который разрушает построение графа вызовов функций. Позже мы проанализируем функцию 0x00401838. Проследите за инструкциями между адресами 0x401502 – 0x401505 (Рис. 2). Нашим первым впечатлением будет то, что функцию 0x401038 принимает три параметра: указатель на строку «ntdll.dll», 0x7C903400 и 0x7C905D40. Однако, позже мы обнаружим, что это не так: функция 0x00401838, используется просто для того, чтобы запутать инструменты статического анализа.

Рис. 2. Вызов Two Layer Function 0x401505

На рис. 3 изображено тело функции 0x401838. Оно начинается с вызова функции 0x00413650, за которым следует куча других инструкций (позже, вы заметите, что эти инструкции не будут никогда выполнены).

Рис. 3. Тело функции 0x401838

Обратите внимание, что по адресу 0x0040183B, происходит вызов функции. Тело этой функции отображено на рис. 4. Оно состоит всего лишь из двух инструкций: POP EAX и RETN.

Рис. 4. Тело функции 0x413650

Сейчас мы приблизились к интересной части. Посмотрите на содержимое стэка (Рис. 5). Он начинается с трех компьютерных слов, которые были помещены в стэк немного раньше (они указывают на «ntdll.dll», 0x7C903400 и 0x7C905D40). Затем вы можете заметить, что двумя первыми словами являются RETURN ADDRESSES, которые были помещены туда инструкциями CALL.

Каждая инструкция CALL по сути состоит из двух шагов: (1) поместить адрес следующей инструкции в стэк (так, что она может вернуться из функции, когда та, завершит свою работу) и (2) перейти на указанный адрес функции. Таким образом, не сложно сделать вывод, что 0x00401840 помещается с помощью инструкции CALL 0x4013650 по адресу 0x0040183B (Рис. 3), а 0x0040150A помещается инструкцией CALL 0x00401838 по адресу 0x00401505 (Рис. 2). Так что, инструкция POP EAX снимет со стэка адрес 0x00401840 и сохранит его в EAX. Когда инструкция RETN выполнится, она перейдет на адрес 0x40150A (т.е. она прыгнет на два уровня назад «two layers back»)! Очевидно, что инструкции, которые начинаются с адреса 0x00401840, никогда не будут выполнены, а прыжок на два уровня назад может сбить с толку множество инструментов статического анализа, когда они попытаются нарисовать граф вызовов функций.

Рис. 5. Содержимое стэка стоя на адресе 0x00413650

4. Вызов системных вызовов NTDLL с помощью собственной закодированной таблицы

Далее, мы покажем интересную технику вызова функций ntdll.dll без использования таблицы экспорта. Мы проанализируем инструкцию по адресу 0x401557 (Рис. 6), которая вызывает функцию 0x4136BF. Позже, мы обнаружим, что функция 0x4136BF не явно вызывает ZwAllocateVirtualMemory и не использует для этого таблицу экспорта.

Мы оставляем анализ логики работы руткита между адресами 0x40150A и 0x401557 (Рис. 6) для читателей. Основная цель этого кода, состоит в том, чтобы разместить в стэке собственную таблицу трансляции адресов функций, начиная с адреса 0x0012D538 по 0x0012D638.

Рис. 6. Код между адресами 0x40150A и 0x401557

Посмотрите на тело функции по адресу 0x004136BF (Рис. 7). Первой инструкцией является CALL 0x004136DC. Вы можете заметить, что между 0x004136BF и 0x004136DC, находится какой-то бредовый код. Если вы посмотрите на него более внимательно, то вы обнаружите, что он в действительности содержит строку «ZwAllocateVirtualMemory», где байт по адресу 0x004136C4 является символом «Z».

Подумайте о инструкции CALL 0x004136BF снова. По сути, она состоит из двух шагов:

  1. PUSH 0x004136C4 # заметьте, адрес 0x004136C4 является началом строки «ZwAllocateVirtualMemory» (Рис. 8)
  2. JUMP 0X004136DC

Рис. 7. Тело функции 0x4136BF

Можно довольно легко предположить то, что код пытается вызвать функцию ZwAllocateVitualMemory! Но как это сделать? Давайте еще больше углубимся в код. По адресу 0x004136DC происходит вызов функции 0x00401172, после чего она возвращает свое управление на адрес 0x004136E1, где размещена инструкция JMP EAX. Можете ли вы догадаться о назначении функции 0x00401172?

Рис. 8. Содержимое стэка находясь по адресу 0x4136DC перед вызовом функции 0x00401172

Ниже мы перечислим некоторые подсказки (Рис. 9):

  1. Цикл между адресами 0x0040118E и 0x004011A0 должен вычислить контрольную сумму для имени функции.
  2. Цикл между адресами 0x4011A2 и 0x004011BA осуществляет бинарный поиск. Поиск производится по собственной таблице экспорта (как обсуждалось в Уроке 9). Каждая запись состоит из двух элементов: (1) контрольная сума имени функции и (2) адрес функции.

Вы можете легко прийти к тому выводу, что функция 0x00401172, получает имя функции, а возвращает ее адрес. Когда функция завершает свою работу, она возвратит адрес запрошенной функции в регистре EAX. После чего инструкция JMP EAX по адресу 0x004136E1 вызовет ее.

5. Вопросы дня

  1. Чему равна контрольная сумма для функции ZwAllocateVirtualMemory?
  2. Какие параметры у функции ZwAllocateVirtualMemory? И почему Max++ вызывает эту функцию?
  3. Посмотрите на адрес 0x40117B, что хранится в локальной памяти потока (EAX+2C)? На чем основан ваш вывод?

© Translated by Prosper-H from r0 Crew