R0 CREW

Malware Analysis Tutorial 9: Encoded Export Table (Перевод: Prosper-H)

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

Цели урока:

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

1. Введение

Этот урок ответит на вопросы из Урока 8. Мы объясним действия, выполняемые руткитом Max++, которые связанны с таблицей экспорта. В этом уроке мы будем практиковать анализ функций, которые не следуют соглашению передачи параметров функции языка C; исследуем функцию подсчета контрольной суммы и функцию кодирования (encoding functions).

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

Прежде чем начать урок, нужно вернуться к месту последней остановки. Сделать это можно следующим образом:

  1. В панели кода нажмите «right click => Go to => Expression => 0x40105c».
  2. Установите аппаратный брэйкпойнт на адрес 0x40105c, «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.

3. Анализ кода с адреса 0x40108C по 0x4010C4

Теперь продолжим анализ из Урока 8, который остановился на адресе 0x401087.

Рис. 1. Код с адреса 0x40108C по 0x4010C4

Напомним, что EAX в данный момент указывает на начало базы DLL. По рис. 1 из Урока 8, мы можем сделать вывод, что смещение 0x78 (120 байт) – это адрес начала EXPORT TABLE, а смещение 0x7C (124 байта) – это размер EXPORT TABLE. Таким образом инструкция CMP DWORD DS:[EAX+7C], 0 производит сравнение размера таблицы экспорта с нулем. Очевидно, что инструкции JE по адресу 0x401091 не будет выполнена и поток управления продолжится с 0x40109F.

Давайте понаблюдаем за инструкцией MOV EDX, DWORD PTR DS:[ESI+18]. Обратите внимание, что регистр ESI был установлен инструкцией по адресу 0x40108A и сейчас указывает на таблицу экспорта. Содержимое это регистра равно значению 0X7C903400 (которое является начальным адресом таблицы экспорта). В разделе 3 из Урока 8, мы привели определение структуры таблицы экспорта, из которого, мы можем сделать вывод, что смещение 0x18 соответствует количеству имен функций. Таким образом, после выполнения данной инструкции, EDX будет содержать количество имен функций (0x523) экспортируемых из ntdll.dll.

Используя тот же метод, мы можем заключить, что последовательность инструкций (с адреса 0x4010AD по 0x4010C4) присваивают значения регистрам от EAX до EDI. В итоге мы имеем:

  • EAX <= смещение (относительно 0x7C90000, начала базы ntdll) начального адреса массива, который хранит адреса функций.
  • EBX <= смещение начального адреса массива, который хранит имена функций.
  • EDI <= смещения начального адреса массива, который хранит ординалы (как мы упоминали ранее, чтобы найти адрес функции, нужно найти его индекс, для имеющегося имени функции, в массиве имен функций, а затем использовать полученный индекс, чтобы найти ординал, и используя его – определить адрес функции, в массиве адресов функций).

Мы можем проверить выше упомянутое утверждение. Возьмите EBX, в качестве одного из примеров, его значение равно 0x7C9048B4. Ниже дан дамп памяти, начинающийся с этого адреса. Обратите внимание, что каждый элемент является «смещением имени» (offset of the name). Таким образом, первый элементом является 0x00006790 (в действительности, это означает, что строка размещена по адресу 0x7C906790), и аналогично, вторая строка размещается по адресу 0x7C9067A9. На рис. 3 отображает содержимое памяти с адреса 0x7C906790 (вы можете видеть, что первым именем фукнции является строка CsrAllocateCaptureBuffer)

Рис. 2. Массив имен функций (Array of Function Names)

Рис. 3. Строки

4. Анализ функции 0x004138A8

Продолжим с инструкции по адресу 0x004010C6 (Рис. 1). Она вызывает функцию, расположенную по адресу 0x004138A8. На рис. 4. показана часть этой функции:

Рис. 4. Функция 0x004138A8

Обратите внимание, что авторы руткита не будут следовать соглашению о вызовах языка С (т.е. передавать параметры через стэк). Вместо этого, они могут на прямую использовать регистры для передачи информации между вызовами функций. Чтобы проанализировать возможности и предназначение функции, нам нужно выяснить: (1) какими входящими и исходящими параметрами она оперирует? (2) и какая у нее логика работы.

Чтобы определить входящие параметры функции, нам нужно посмотреть на те регистры, которые READ перед присваиванием. Смотря на инструкции, начинающиеся с адреса 0x0004138A8, мы вскоре определим, что EAX входящий параметр, в данный момент его значение равно 0x00002924. Напомним, что в Разделе 3, он содержит смещение начала массива, который содержит адреса функций, т.е. адрес 0x7C902924 является началом массива, содержащего адреса функций.

Затем, начиная с 0x004138A9, следующие несколько арифметических инструкций, кажется, округляют значение ECX основываясь на значении EAX. Сейчас, на рис. 5 показана вторая половина функции. Есть две интересные инструкции, первая по адресу 0x0041389A (должна уменьшать значение EAX на 0x1000, внутри цикла), а затем вторая инструкция по адресу 0x413893, должна поменять местами значения в EAX и ESP.

Таким образом, возможным исходящим параметром является регистр ESP, который был несколько раз уменьшен на 0x1000 байт (в нашем случае на 0x2000 байт) по сравнению со своим первоначальным значением.

Если кратко, то функция по адресу 0x004138A8 расширяет фрэйм стэка (напомним, что стэк растет с высших адресов к низшим адресам) на 0x2000 байт. Почему? Это нужно для того, чтобы сохранить новую таблицу экспорта, которая является закодированной (encoded)!

Первый вопрос дня: где находится начало новой таблицы экспорта?

Рис. 5. Вторая половина функции 0x004138A8

5. Расположение функции кодирования (Rest of Encoding Function)

Теперь перейдем к анализу кода между адресами 0x4010CB и 0x40113B. Обратите внимание, что расширенный стэк, сейчас, включает в себя около 0x2000 байт, начиная с адреса 0x0012D66C. Здесь руткит собирается хранить новую закодированную (encoded) таблицу экспорта. Эта закодированная таблица (ее формат) определена авторами руткита. Каждая запись состоит из двух элементов, а каждый элемент равен 4 байта. Первый элемент является контрольной суммой имени функции, а второй является адресом функции. Позже, Max++, сможет вызывать системные функции из ntdll.dll без разрешения их адресов, при помощи таблицы экспорта из ntdll.dll, но с использованием совей собственной закодированной таблицы.

Рис. 6. Код с адреса 0x4010CB по 0x40113B

Большая часть программной логики довольно ясна. Инструкции из промежутка 0x4010D0 – 0x4010DF сохраняют некоторую важную информацию в стэк. Сейчас оба [EBP-C] и [EBP-14] равны значению 0x0012D66C (которое является значением ESP+C). Это значение будет началом закодированной таблицы, которая будет продемонстрирована позже.

Затем идет 2-х уровневый, вложенный цикл по адресам 0x004010F9 – 0x00401136. Давайте вначале посмотрим на внутренний цикл, с адреса 0x401103 по 0x401110. Очевидно, EAX является входящим параметром для этого внутреннего цикла и заметьте, что EAX указывает на массив символов, который представляет собой имя функции (см. инструкцию по 0x004010FB). По адресу 0x401110, в каждой итерации цикла, EAX увеличивается на единицу. Ясно, что внутренний цикл осуществляет некий подсчет контрольной суммы для имени функции. В этом цикле контрольной суммы, есть два регистра, в которые будет записан код: EDX и ECX. Если вы внимательно читаете код, то вы заметите, что значение EDX полностью перезаписывается на каждой итерации (обратите внимание на инструкцию 0x401103). Только предыдущее значение ECX влияет на его последующее значение. Таким образом, ECX должен быть исходящим параметров цикла и он хранит контрольную сумму!

Затем инструкции с адреса 0x401117 по 0x401133 должны установить запись для функции. Инструкция MOV DS:[EAX], ECX (0x40111D) должна сохранить контрольную сумму функции в первом элементе, а инструкции по адресу 0x0040112B должна сохранить адрес функции, в качестве второго элемента.

Второй вопрос дня: Объясните логику выражения «EDX+ECX*4» из инструкции по адресу 0x00401122. Подсказка: изучите использование ординалов в таблице экспорта.

5. Заключение

Наше заключение: Max++ читает таблицу экспорта из ntdll.dll и создает собственную закодированную таблицу экспорта. Позже мы увидим ее использование.

6. Задача дня

По адресу 0x0040113E, Max++ вызывает функцию 0x0040165E. Проанализируйте ее функциональность.

© Translated by Prosper-H from r0 Crew