История одного вскрытия: как мы ревёрсили Hancitor +29




Для тех, кто уже наигрался с задачками crackme, мы подвезли свежего троянца. В дикой природе загрузчик Hancitor еще встречается в своей естественной среде обитания — спам-рассылках. В настоящее время он активно используется для прогрузки банковского трояна Panda, который является модификацией небезызвестного Zeus.

В один холодный летний вечер мы встретились с ним лицом к лицу, просматривая почтовый спам. Если любите смотреть, что там у вредоносов «под капотом», почитайте наш новый реверс-разбор.

Наш вредоносный документ выглядит так:



По умолчанию запуск макросов блокируется, поэтому система предупреждает: «Макросы были отключены». Однако содержимое письма уверяет, что макросы следует включить. Что ж, давайте сделаем это. После нажатия на кнопку Enable Content («Включить») происходит инфицирование компьютера. А теперь посмотрим, что за макрос выполняется и как именно происходит заражение. Это можно сделать сразу в ворде, перейдя на вкладку View->Vacros\View Macros.



Можно поступить более профессионально – заюзать olevba из пакета oletools. Установить пакет можно там же по ссылке. Далее набираем olevba doc_file –c –decode >source.txt и получаем исходник макроса.



По коду сразу хочется сказать, что троян относится к классу даунлоадеров. Скрипт просто выкачивает откуда-то малвару. Чтобы это доказать, давайте раскодируем base64 строки. Нам удобнее делать это сразу через hiew, чтобы сто раз не копипастить. Для этого мы используем специальный плагин, который описан тут. Вот что получилось:



Это вредоносный скрипт, разбитый на две части и сохраненный, как 1.hta. В принципе нам особо не интересно, как он работает. Тут всё довольно банально. Единственный важный момент – определить URL, по которому происходит скачивание вредоносного файла. Давайте его попробуем найти, так как эта информация может быть полезна безопасникам. Они могут добавить сетевые правила на блокировку запросов по таким URL, что может спасти компанию от заражения.

Ха! Ссылок никаких нет. Но откуда тогда берётся файл 6.exe, который запускается 1.hta? Пришло время ещё раз взглянуть на наш доковский файл в hiew, но уже внимательнее:



Да, в доковском документе встроен exe-шник. Да ещё и сильно упакованный. Поясним, что происходит. Макрос дропает вредоносный скрипт 1.hta в папку Temp и запускает его, а 1.hta запускает в свою очередь 6.exe из своей директории. Ясное дело, что 6.exe – это тот самый запакованный PE-шник, который мы видим на скрине. Но нам сейчас интересно, как 6.exe дропается в %temp%. А происходит это из-за интересной особенности в пакете Microsoft Office. Дело в том, что в любой OLE — документ может быть встроен любой другой файл в формате Ole10Native.

Если это так, то MS-Office при запуске сам дропает встроенный таким образом файл в папку %temp% под именем, указанным в заголовке структуры Ole10Native. Давайте глянем на этот объект. Нам помог плагин к FAR-manager – OLE2Viewer. Открываем в плагине наш вредоносный документ, переходим в каталог ObjectPool\ _1593522492 и видим вот что:



Скопируем этот файлик (Ole10Native) и откроем в hiew.



Тут мы видим под каким именем дропнется в папку %temp% наш OLE-объект – 5c.pif. А теперь вернёмся к нашему макросу. Его задача – запустить дропнутый exe-шник.



На самом деле, если быть точным, то макрос запускает дропнутый файл не всегда через 1.hta, а ещё и так: Shell «cmd.exe /c  ping localhost -n 100 && » & Environ(«Temp») & "\6.pif", vbHide



Способ запуска, как мы видим, зависит от присутствия следующих процессов в системе: bdagent.exe и PSUAMain.exe. А зачем тогда нужен ping localhost -n 100? А это древнегреческий трюк для создания искусственной задержки, на всякий случай. Обычно его вариации используется для самоудаления.

На данном этапе мы разобрали схему работы вредоносного документа. Нам стало ясно, что сам документ не относится к ВПО типа Trojan-Downloader, как казалось на первый взгляд, а подходит к типу Trojan-Dropper. Вот что получилось при запуске:



Сейчас осталось разобрать сам payload. Мы уже поняли, что наш троян упакован, поэтому мы должны его сначала распаковать. Обычно этот процесс делится на две стадии:
1. Снятие распакованного дампа + восстановление таблицы импорта.
2. Анализ и чистка глобальных переменных.

Второй этап нужен нам потому, что существует множество API-функций, которые в результате своей работы нам возвращают неповторяющиеся значения. К примеру, CreatHeap вернёт нам дескриптор кучи, который далее будет использоваться для работы. Очень часто в коде есть проверки типа: если дескриптор кучи == 0, то получить дескриптор кучи, иначе использовать уже инициализированный. Но на момент снятия дампа переменная, содержащая данный дескриптор, была уже им инициализирована и в тот момент дескриптор был валиден. Когда мы попробуем запустить снятый дамп, переменная с нашим дескриптором будет содержать старое значение, т.е не равное 0, а значит пройдёт проверку.

После этого, как только программа попытается использовать этот дескриптор, ОС бросит исключение, и программа вывалится с ошибкой. Так вот, чтобы такого не было, нужно эти переменные занулить. Обычно они находятся в секции данных, доступной на запись. Наверное вы предложите открыть в hex-редакторе данную секцию и всё затереть нулями? Вы отчасти правы, но не стоит совершать необдуманных действий. Есть такие трояны, которые производят проверку переменных не на 0, а на рандомный DWORD. И в зависимости от того, проходит проверка или нет, предпринимаются различные действия. За примерами далеко ходить не надо. Достаточно взглянуть на Cridex (в последнее время куда-то исчез с радаров, видимо, полностью проапгрейдился до EMOTETA).

Итак, давайте запустим наш семпл (на виртуальной машине, естественно). Ещё желательно, чтобы трафик, который будет выходить из виртуальной машины, шел через VPN.

Мы запускаем наш троянчик, и он «просто» висит в процессе. Замечательно! Обычно трояны «по-быстрому» делают свои дела, а затем сразу завершаются и самоудаляются. Поэтому приходится искать места в коде, отвечающее за эти действия, ставить на них бряк, а потом дампить. В нашем случае мы можем сделать это просто так.

Для дампа используем замечательную утилиту – Process Dump, которую можно взять тут. Эта утилита не только находит все скрытые исполняемые модули и дампит их, но ещё и сама восстанавливает таблицу импорта. Утилиту нужно запускать от имени администратора таким образом: pd /pid xxxx, где xxxx – id троянского процесса. После чего, утилита сдампит все модули процесса. Мы удалили лишние и вот что осталось:



Имя исполняемого файла троянского процесса – 1.exe. Оказывается, по адресу 0x2C0000 находился распакованный троян. Откроем его в hiew:



Просто радуется глаз! Теперь файл распакован, это отчётливо видно. Таблица импортов также распозналась. Давайте откроем его в IDA-PRO.



Некоторые функции мы уже переименовали, пока разбирали сэмпл. Первое, с чего начинает работу троян, — определяет адрес загрузки своего модуля. И действительно: откуда ему знать по какому адресу его stub распаковал? Делается это старой проверенной техникой – отлистыванием страницы памяти назад по 0x1000 относительно текущего адреса до тех пор, пока не наткнёмся на байты «MZ». Это работает, потому что исполняемые модули всегда загружаются ОС по адресу, кратному 0x1000. Проверьте сами. В нашем случае установлен лимит на 100 отлистываний.



Если кто не понял, где тут отлистывание назад, то вот где:
result += 0xFFFFF000 эквивалентно result -= 0x1000.

После получения адреса загрузки своего модуля распакованный троян получает адреса нужных для своей работы функций. Для начала ищутся адреса двух функций – LoadLibraryA и GetProcAddress. Зная адреса этих функций, можно с помощью них получить все остальные. Эти функции находятся в библиотеке kernel32. Её адрес получается с помощью чтения первого (нулевой — ntdll, первый kernelbase и тд) элемента кольцевого списка, описывающего все загруженные в порядке инициализации модули структурой _LDR_DATA_TABLE_ENTRY. Указатель на список вытаскивается из PEBа.



Получив адрес kernel32.dll (начиная с  Windows 7 – kernelBase.dll), троян может вручную распарсить её таблицу экспорта и найти нужные две функции, что предсказуемо выполняется в подпрограмме sub_EF1E60.



Теперь взглянем на функцию, которую мы назвали getHeap.



Тут мы наблюдаем как раз ту ситуацию, которую описывали выше. На момент дампа переменная hHeap содержала значение 600000h. Поэтому GetProcessHeap не вызовется. Вместо этого программа перейдёт на метку loc_EF11DD, где вызовется HeapAlloc с невалидным дескриптором, что даст нам ошибку. Поэтому, берём hex редактор и зануляем это число. Подобных мест мы насчитали шесть.

Далее у нас начинается самое интересное. Троян генерирует уникальный ID «клиента», на основании серийного номера жёсткого диска и MAC адреса. Информация получена здесь:



Также получаем следующее: IP адрес, версия ОС, сетевое имя и имя пользователя. На основании всего этого формируется HTTP-запрос к админке, адрес которого мы пока не знаем. В строках его нет (даже в распакованном виде). А нет его там, потому, что он зашифрован в конфиге. Его адрес можно вытащить из кода:



Конфиг весит 0x2008 байт и имеет такой формат: первые 8 байт – RC4 ключ, 0x2000 байт – зашифрованные данные.



То, что используется алгоритм шифрования RC4, становится понятно из следующего листинга:



Обратите внимание, что первые 8 байт сами по себе не являются ключом к RC4. Ключом является хеш SHA1 от этих байт. Также нужно обратить внимание на флаг 0x280011 к функции CryptDeriveKey. В MSDN есть оговорка по поводу данного флага:



Из неё становится понятно, что старшие 16 бит этого флага задают размер ключа в битах. Т.е в байтах размер ключа такой: (0x280011 >> 16) / 8 = 5. Поэтому ключом будут являться первые пять байт от хеша от первых восьми байт конфига. Сдампим конфиг и напишем скрипт на python, который расшифрует нам его. Скрипт выглядит так:



Результатом его работы стал файл config.rc4. Откроем его в hiew:



Мы видим расшифрованный список админок. Первое слово – «19nep07» — номер билда. Под него отведено 16 байт. Далее идёт список URL админок, разделённых "|".

Итак, первое обращение к админке будет иметь такой формат:
GUID=3068075364164635648&BUILD=19nep07&INFO=WIN-56G04BL06SL @ WIN -56G04BL06SL\Reverse&IP=35.0.127.52&TYPE=1&WIN=6.1(x32



Потом сформированный запрос отправляется первой в списке админке.



Далее считывается ответ от админки, конечно, если она еще живая. Ответ должен быть в кодировке base64. Если это не так, то берётся следующая админка из списка. Иногда живые админки возвращают странный ответ:



Знакомо, не правда ли? Да это же те самые числа! На самом деле одному автору известно, почему админка их начинает возвращать! В нормальном виде возвращается команда. К сожалению, с полной уверенностью нельзя сказать, какой будет формат ответа, так как трафика нет, все админки дохлые. Раскодированный файл дополнительно расксоривается на 0x7A:



Ответ должен содержать команду. Если её нет, происходит обращение к следующей админке. Код команды кодируется как «x:», где x – буква кодирующая определённую комманду. Всего их 7: ‘r’,’l’,’e’,’b’,’d’,’c’,’n’. Рассмотрим команды «b» и «r».

В обработчиках этих команд есть одна и та же функция. Мы ее назвали, как GetExe. Вот как она выглядит:



Думаю тут всё ясно. Троян совершает http-запрос и ему возвращается в ответе исполняемый файл в сжатом виде, который потом расжимается. После чего возможны вариации. В случае с командой «b», происходят следующие три действия:

1. Создание процесса svchost в замороженном виде



2. Инжект в адресное пространство процесса скачанного модуля



3. Передача управления на заинжекченный модуль



В случае с командой «r», происходят следующие три действия:
1. Скачивание исполняемого файла вызовом функции GetExe.
2. Сохранение скачанного файла в темповскую папку под случайным именем.
3. Запуск дропнутого файла.



Done.

P.S. В следующий раз мы можем сделать разбор либо «Панды», либо какого-нибудь шифровальщика. Напишите в комментариях, какие вредоносы вас больше всего интересуют (в исследовательских целях, конечно же).




К сожалению, не доступен сервер mySQL