Распаковка исполняемых файлов на примере банковского трояна GootKit

Распаковка исполняемых файлов

Распаковка исполняемых файлов — одна из задач, возникающих при реверсинге приложений. И если наш сегодняшний экспериментальный объект— это вредонос, то зачастую приходится сталкиваться с кастомными упаковщиками. Сегодня погоровим о распаковке исполняемых файлов и на примере банковского трояна GootKit будем бороться с защитными механизмами банкера, который постоянно развивается и обновляется, к тому же использует разные методы защиты от отладки.

Из инструментария мы будем использовать отладчик x64dbg (его 32-битную версию x32dbg), интерактивный дизассемблер IDA, шестнадцатеричный редактор HxD и детектор пакеров и протекторов DiE. Мы будем противостоять антиотладке при помощи мьютексов и разберемся с нестандартными параметрами функции CreateFileA.

Все описанные в статье действия выполнялись внутри виртуальной машины, которая была изолирована от сети. Повторение действий на основном компьютере может привести к заражению банкером GootKit, способным похитить ваши данные.

Для начала давайте посмотрим на GootKit через программу Detect it Easy.

распаковка exe файла
Нераспакованный образец GootKit

Детектор не определяет никакой навесной защиты, зато энтропия файла зашкаливает.

распаковка exe файла
Энтропия GootKit

Загружаем файл в дизассемблер для более интимного знакомства. Видим, что при входе у нас сразу идет вызов подпрограммы. Прыгаем в него и наблюдаем достаточно интересный код.

распаковка exe файла

На стек помещаются значения, инициализируются переменные, далее идут вызовы. Разумеется, это похоже на динамический вызов функций.

Чтобы самостоятельно убедиться в этом, можете скомпилировать и посмотреть в IDA простую программу, которая использует эту технику вызовов. Конечно, код будет более «чистый» и понятный, но общая суть очевидна.

Давайте переключимся в псевдокод, нажав кнопку F5, так будет еще очевиднее.

распаковка exe файла
Псевдокод

Инициализация и вызов функций выглядит таким образом:

И тем не менее код кажется странным, хоть и очевидно, как он работает и что делает. Посмотрим перекрестные ссылки на буферах, которые передаются в функции. Вот один из буферов.
Распаковка исполняемых файлов

Очевидно, применяется шифрование строк при помощи XOR по ключу 89798798798g79er$. Видим шифротекст byte_41F000[edi].
Распаковка исполняемых файлов

Псевдокод этого алгоритма такой:

Зная ключ, попробуем расшифровать содержимое буфера средствами Python, который встроен в IDA. Набираем

Пишем байты по адресу 0x41F0EC в количестве 0x0B в переменную encrypt. Проверим, просто введя имя переменной и нажав Enter:

Все верно: это именно то, что мы видели в IDA. Теперь присвоим переменной y известный нам пароль и зададим переменную decrypt для выходных данных:

Теперь приступим к циклу дешифровки.

Мы смогли расшифровать строку, и у нас получилось Psapi.dll. Таким образом можно расшифровать все зашифрованные строки и соотнести их с переменными в псевдокоде. Листинг преобразился и стал намного более понятным. Теперь нам ясно, какие WinAPI получаются динамически. Скроллим псевдокод ниже и видим:

Здесь заметна работа с мьютексами и запуск еще одной копии собственного процесса через ShellExecuteA с параметром -l. Вообще, когда есть подобный код, неплохо запустить наш вирус в какой-нибудь анализирующей песочнице, чтобы видеть, какие процессы в каком порядке создаются. Давайте это и сделаем.

Есть несколько онлайновых песочниц, которые подходят для этих целей, многие из них платные. Но есть и бесплатные, например hybrid-analysis.com. Закидываем семпл в песочницу и смотрим на результат.

Распаковка исполняемых файлов
Результат гибридного анализа

В этом дереве процессов нас интересуют три первых: сначала запускается основной семпл, потом он же с параметром командной строки -l, а третий процесс, вероятно, наш целевой распакованный код. Возвращаемся к исследованию защиты.

Обратите внимание на строки создания мьютексов и на этот код:

После создания процесса родительский процесс спит две секунды, а потом завершается. Запомним этот момент и идем дальше. Найдем код создания процесса, который создается копией процесса с параметром -l. Для этого в таблице импорта просматриваем вызовы и находим CreateProcessA. Идем по перекрестной ссылке и находим функцию sub_403A56(CHAR *a1, void *a2) и вот такой код в ней (я расшифровал имена вызовов WinAPI и переименовал одну переменную для удобочитаемости кода).

Пытливый читатель сразу поймет, что это стандартная инжекция кода в процесс, который здесь же и создается. Да, это тот процесс, который порождается экземпляром приложения, запущенного с параметром -l. Мы видим, как данные записываются в память процесса, устанавливается контекст потока, затем поток возобновляется.

РЕКОМЕНДУЕМ:
Взлом приложений для Андроид с помощью отладчика

Естественно, мы можем увидеть в вызове NtWriteVirtualMemory, какие именно данные записываются. Давайте проследим, откуда вызывается функция sub_403A56(CHAR *a1, void *a2). Кроме того, я рекомендую переименовать эту функцию в inject, чтобы было удобнее и понятнее в дальнейшем.

В этой небольшой функции в памяти создается буфер и в него дешифруется код, а затем происходит инжект. Посмотрим перекрестную ссылку на эту функцию, и… мы возвращаемся в код с мьютексами. А функция, из которой мы вернулись, называется sub_401E47(). Получается, основная распаковка выполняется именно в этом месте приложения. Давайте попробуем разобраться, зачем тут мьютексы. Я сократил код, оставив самую суть, и переименовал известную нам функцию.

Антиотладочный трюк состоит в том, что родительский процесс создает мьютекс и проверяет, не создан ли он ранее. Далее порождается дочерний процесс, и он тоже проверяет мьютекс. Дело в том, что родительский процесс умирает спустя две секунды, и если бы мы отлаживали этот код, то в тот момент, когда бы мы приступили к отладке потомка, родительский процесс был бы уже мертв и созданного им мьютекса не было бы. Получается простой, но интересный способ проверить целостность своих процессов и отследить факт отладки.

Итак, теперь мы знаем, как устроена антиотладка. Давайте попробуем заполучить распакованный образец банкера. Для этого мы загружаем семпл в отладчик и останавливаемся на точке входа. Теперь нам нужно установить точку останова в тот момент, когда создается новый процесс (помните, что процессов порождается несколько?). Установим точку останова на функцию CreateProcessInternalW.

Мы установили точку останова на функцию CreateProcessInternalW потому, что все функции, порождающие процессы ( CreateProcess, CreateProcessAsUser, CreateProcessWithTokenW и CreateProcessWithLogonW), в итоге вызывают именно эту недокументированную функцию.

Отладчик остановился, и мы можем видеть, что создано два процесса и мы стоим на точке распаковки малвари в память. Нужно понимать, что сейчас мьютексы созданы и мы не даем приложению завершиться.

Распаковка исполняемых файлов
Стек вызовов на точке останова

Нам необходимо свернуть отладчик и запустить еще один его экземпляр, загрузить туда наш банкер и поставить точку останова на функцию CreateFileA. Мы остановились на моменте создания файла, теперь давайте посмотрим параметры функции CreateFileA. Я выделил интересующее нас поле красным.

как распаковать исполняемый файл exe
Стек вызовов на точке останова

Вот прототип функции CreateFileA.

Третий параметр, dwShareMode, установлен в ноль, что задает «режим совместного доступа». Нулевое значение запрещает всяческий доступ извне или повторный доступ из самого приложения, пока дескриптор файла не будет закрыт. Изменим на 7 — это разрешит все доступы. После этого даем доработать функции и останавливаемся сразу после возврата из нее.

Теперь запускаем приложение и не забываем о точке останова на CreateProcessInternalW. После останова в стеке — параметры создания процесса с флагом CREATE_SUSPENDED. Значит, это именно наша инъекция из буфера в процесс. Перейдем к карте памяти и поищем M.Z. — ведь в данный момент распакованный файл находится в памяти. Точнее, даже два файла — вы ведь еще не забыли дерево процессов? Но интересует нас только один.

Первое вхождение — по адресу 0017BBB6. Ищем в карте памяти по базовому адресу 700xxx нужный образ. Осталось только снять дамп, в котором будет два образа PE-файла, которые мы разделяем при помощи hex-редактора HxD. Один из образов нам уже знаком, а вот второй и есть наш искомый распакованный банкер.

как распаковать файл exe

как распаковать файл exe
Распакованный GootKit

В IDA видно, что код отличается от исходного семпла, а также просматривается таблица импорта. Бинго! Вот мы и научились распаковывать исполняемый файл.

Понравилась статья? Поделиться с друзьями:
Добавить комментарий