Исследование одного вредоноса

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.
imageПопался мне недавно вредоносный doc файл, который рассылали с фишинговыми письмами. Я решил, что это неплохой повод поупражняться в реверс-инжиниринге и написать что-то новое на Хабр. Под катом — пример разбора вредоносного макроса-дроппера и не менее вредоносных dll.

Макрос


Sha256 от файла — abb052d9b0660f90bcf5fc394db0e16d6edd29f41224f8053ed90a4b8ef1b519. В самом doc файле на первой странице находится картинка, сообщающая что этот файл защищён и объясняющая как включить макросы, ещё в файле есть две большие таблицы с числами. Числа записаны в десятичной форме, самые длинные — десятизначные, есть и положительные, и отрицательные.
Когда жертва разрешает исполнение макросов (есть те, у кого оно по умолчанию разрешено?), запускается цепочка действий, которая в конце концов выполняет функцию updatedb_network, которая изменяет текущую директорию на временную и создаёт в ней файл «icutils.dll», в который подряд записывает числа из первой таблицы как 32 битные integer со знаком. В резльтате получается корректная dll. Из этой dll импортируется функция clone:
Declare PtrSafe Function clone Lib "icutils.dll" _
(ByVal Saved As String, ByVal Time As Integer) As Boolean

И запускается с двумя параметрами:
R1 = Module1.clone("Cream", 0)

Если вызов clone возвращает False, то файл «icutils.dll» перезаписывается данными из второй таблицы и снова вызывается clone с такими же параметрами.
Забегая вперёд скажу, что первая dll 64 битная и не будет выполняться на 32 битных системах. Таким образом макрос подбирает правильную архитектуру бинарного кода.

Что интересно, в функции updatedb_network есть такой кусок кода, который никакого функционального назначения не имеет:
Sub updatedb_network()
...
Dim query_to_change As Variant
    Set query_to_change = CurrentDb.QueryDefs("query_name")
    
    query_to_change.SQL = "SELECT * FROM Table ORDER BY ID Asc"
    query_to_change.SQL = "SELECT Field1, Field2 FROM Table ORDER BY ID Asc"
    query_to_change.SQL = "SELECT Field1, Field2 FROM Table WHERE Field LIKE Fashion"
    query_to_change.SQL = "SELECT Field1, Field2 FROM Table WHERE Field LIKE '" & something & "'"
...
End Sub

Возможно он тут для придания видимости полезной работы для тех, кто быстро пролистает код, увидит какие-то строки на SQL и подумает, что всё ОК? Не знаю. Так же большинство функций и переменных имеют случайные или не относящиеся к реальному назначению имена (как, например, updatedb_network, которая ни с БД, ни с сетью не взаимодействует). Хотя есть, например функция dump_payload, которая сохраняет 4 байта в icutil.dll. Но в любом случае, сразу должно насторожить наличие функции Document_Open, её произвольно переименовать авторы ВПО не могут (правда вместо неё могут использовать другую автоматически запускаемую функцию).

Итак, функционал макроса более-менее понятен, пора выгружать dll и переходить к их анализу.

Первая dll


Первая dll (sha256 7427cc4b6b659b89552bf727aed332043f4551ca2ee2126cca75fbe1ab8bf114) 64 битная.
В списке импортируемых функций есть функции CreateProcessW (запуск программы), CreateRemoteThread (создание потока в другом процессе), VirtualAllocEx (выделение блока памяти в другом процессе), WriteProcessMemory (запись в память другого процесса), что сразу наводит на мысли об инъекции кода в другой процесс. Теперь посмотрим что именно она делает с помощью IDA Free и Ghidra.
Основная точка входа просто возвращает 1, ничего больше не делает. Вторая экспортируемая функция — clone, именно она вызывается макросом и содержит вредоносный код.
Параметры, с которыми она вызывается, вроде ни на что не влияют. Приложение расшифровывает два блока данных. Первый блок данных длиной 0x78 со следующим содержимым (уже расшифрованный):
https://pastebin.com/raw/Jyujxy7z\x00\x00\x00\x00\x00\x00\x00\xf2i\xe0\x1d\x95h\xbc\x03\xe4#\xe0\x1d<\x04\xe0\x1d\xe6\x00\xde\x01\xa4\x17\xbc\x03x\x01\xe0\x1d\xe2\x16x\x07Qy\xbc\x03@Fx\x07Df\xbc\x03\x89a\xde\x01q\x11\xe0\x1d|Ix\x07D@\xbc\x03\x8a\x01\xde\x01^9\xde\x01\xf2i\xe0\x1d\x95h\xbc\x03\xe4#\xe0\x1d\xab

Второй блок данных имеет длину 0x1D4C и содержит исполняемый код.
Кроме того в структуру длиной 0x90 байт записывается указатель на модуль kernel32, результат выполнения функции GetTickCount() и адрес функции ZwDelayExecution() из kernel32.
После чего создаётся процесс (CreateProcessW) “cmd.exe”. С помощью VirtualAllocEx в нём выделяются два буфера: с разрешениями RW длиной 0x108 и с разрешениями RWX длиной 0x1D4C. В RW буфер копируется приведённый выше блок с данными и вышеупомянутая структура длиной 0x90. В структуру так же записывается указатель на расшифрованный блок данных (в адресном пространстве дочернего процесса (cmd.exe)). В RWX буфер копируется (WriteProcessMemory) расшифрованный блок данных с кодом.
Потом в процессе cmd.exe создаётся поток (CreateRemoteThread) с точкой входа в начале RWX буфера, в качестве аргумента передаётся указатель на RW буфер. На этом функция clone завершается, действие продолжается в процессе cmd.exe.
Интересно, что в функции clone есть вроде как недостижимый кусок кода, который импортирует (LoadLibraryW) библиотеку "WorkPolyhistor".

Инжектированный в cmd.exe код


Он выполняет следующие действия:
  • находит адреса нужных функций из kernel32.dll (её адрес получается от родительского процесса)
  • загружает библиотеки ntdll, Ole32, User32
  • находит адреса нужных функций в этих библиотеках.

Интересно, что для бОльшей части функций в коде нет имени функции, а только CRC32 от имени (перебираются все имена функций из загруженной библиотеки, пока не найдётся функция с нужным CRC32 от имени). Возможно это защита от получения списка импортируемых функций утилитой strings, правда странно, что код, который хранится в зашифрованном виде имеет такую защиту, в то время как сама dll импортирует функции просто по именам. Всего обнаруживаются следующие функции:

kernel32:
  • GetProcAddress
  • LoadLibrary
  • GlobalAlloc
  • GetTempPath
  • GetFileAttributesW
  • CreateProcessW
  • GlobalFree
  • GlobalRealloc
  • WriteFile
  • CreateFileW (находится по имени)
  • WriteFile
  • CloseHandle
  • GetTickCount
  • ReadFile
  • GetFileSize

ntdll:
  • RtlCreateUnicodeStringFromAsciiz
  • ZwDelayExecution
  • ZwTerminateProcess
  • swprintf

ole32:
  • CoInitialize (находится по имени)
  • CoCreateInstance (находится по имени)

msvcrt:
  • rand
  • srand

Далее процесс с помощью GetTempPath получает путь к временной директории, создаёт в ней файл с названием вида 26342235.dat, где имя файла — десятичная запись TickCount, полученного от родительского процесса и итерируется на каждой новой попытке (т.е. если не удалось скачать пэйлоад с первой попытки, то на вторую попытку будет создан файл с именем 26342236.dat). После этого загружается библиотека wininet и в ней находятся указатели на следующие функции:
  • InternetCloseHandle (crc32: 0xe5191d24)
  • InternetGetConnectedState (crc32: 0xf2e5fc0c)
  • InternetOpenA (crc32: 0xda16a83d)
  • InternetOpenUrlA (crc32: 0x16505e0)
  • InternetReadFile (crc32: 0x6cc098f5)

С помощью InternetGetConnectedState проверяется есть ли сеть, если нет — приложение вызывает функцию по несуществующему адресу и падает (такая защита от определения адреса откуда получается пэйлоад с помощью изолированной от сети машины. Это единственный случай, когда приложение завершается нештатно, в остальных — делаются 3 попытки, после чего cmd.exe завершается с помощью ZwTerminateProcess). Если сеть есть, то с помощью найденный функций пэйлоад скачивается с переданного из родительского процесса URL (https://pastebin.com/raw/Jyujxy7z) и сохраняется в созданный ранее файл с расширением .dat.
Далее пэйлоад считывается из .dat файла, декодируется (base64), расшифровывается с помощью XOR с CRC32 от URL, проверятеся, что первые 2 байта расшифрованных данных — 'MZ', если да — результат сохраняется в файл с таким же именем, но расширением .exe
Код для расшифрования на Python
from binascii import crc32
from base64 import b64decode


def decrypt_payload(payload_b64: bytes, url: str):
    payload_bin = b64decode(payload_b64.decode())
    key = str(crc32(url.encode())).encode()
    decrypted = bytearray()
    for i, b in enumerate(payload_bin):
        decrypted.append(b ^ key[i % len(key)])
    return bytes(decrypted)


С помощью функции CreateProcessW сохранённый файл запускается. Если всё удачно — процесс cmd.exe завершается с помощью ZwTerminateProcess. Если что-то пошло не так (кроме отсутствия сети), то всё повторяется заново, максимум делаются 3 попытки, имена dat и exe файлов каждый раз увеличиваются на 1.

Вторая dll


Вторая dll (sha256 006200fcd7cd1e71be6c2ee029c767e3a28f480613e077bf15fadb60a88fdfca) 32 битная.
В ней основной вредоносный функционал реализован в функции clone. Она тоже расшифровывает 2 буфера. Первый буфер имеет размер 0x78 (120) байт, в него расшифровываются и записываются такие данные (в расшифрованном виде):
https://pastebin.com/raw/Jyujxy7z\x00\x00\x00\x00\x00\x00\x00\x1e(\xf0\x0e\xc5r\xc0;\x12)\xc0;Jr\xc0;Y4\xbc\x03/Mx\x07\x038\xde\x01\x9e\x05\xe0\x1d#\x08\xbc\x03\xeeU\xf0\x0e\x18{x\x078\x1a\xf0\x0e\xccg\xf0\x0eze\xde\x01\x89&\xe0\x1d\xf6\x1f\xe0\x1d
Видно, что в начале находится такой же URL, как и в x64 версии.
Второй буфер размером 0x4678 байт выделяется с RWX разрешениями. В него расшифровывается код, после чего из него вызывается функция со смещением 0x4639 от начала буфера.
Подробно разбирать этот код я не стал. Он так же находит функции по CRC32, запускает notepad.exe, инжектирует туда код, который скачивает пэйлоад с того же URL на pastebin.

Пэйлоад с pastebin


Расшифрованный пэйлоад с pastebin — это 32битный exe файл (sha256 9509111de52db3d9a6c06aa2068e14e0128b31e9e45ada7a8936a35ddfaf155f) Подробно разбирать его я пока не стал в силу нехватки времени.

Заключение


В целом вредонос на меня произвёл впечатление довольно плохо написанного, с большим количеством ошибок (не называю их здесь намеренно). Как будто писали на скорую руку.
dll находит в памяти функции, которые потом нигде не используются. Наверно этот код, так же как макрос, регулярно переписывается злоумышленниками, каждый раз когда он начинает детектироваться антивирусами, в результате чего в коде остаются такие артефакты.
Так же интересно, что «роняемой» на диск x64 версии dll имена «опасных» импортируемых функций ничем не замаскированы (можно хоть strings их увидеть), а в коде, который расшифровывается в памяти и на диск не ложится, они находятся по CRC32 от имени, а не просто по именам.
Пэйлоад с pastebin через несколько дней был удалён.

P.S. КДПВ взята отсюда twitter.com/BroadAnalysis/status/897254224475631616
Источник: https://habr.com/ru/post/489020/


Интересные статьи

Интересные статьи

Привет Хабр! Я хочу рассказать тебе историю о давних временах. Был 1891 год. Малоизвестный тогда сербско-американский ученый по имени Никола Тесла разработал устройство, генерирующее и пе...
Есть несколько способов добавить водяной знак в Битрикс. Рассмотрим два способа.
Всем привет. Когда я искал информацию о журналировании (аудите событий) в Bitrix, на Хабре не было ни чего, в остальном рунете кое что было, но кто же там найдёт? Для пополнения базы знаний...
Данный материал создан ввиду прошедшей защиты выпускной квалификационной работы бакалавра, учитывающей некоторые замечания по объекту управления. Материал создаётся в качестве первоначального зад...
2013 год. Я узнаю об альфе нового проекта под названием GNOME Calendar. Интересно. Я люблю календари. «Круто, буду следить за ним», — сказал я по молодости. В ветке ui-rework шла бурная раз...