В этой статье я покажу, как с помощью ути­литы INetSim эму­лиро­вать вза­имо­дей­ствие мал­вари с управля­ющим сер­вером. Так­же рас­смот­рим работу заг­рузчи­ка, рас­паку­ем основную наг­рузку вре­доно­са и иссле­дуем схе­му работы сти­лера.

Мы будем раз­бирать задание UnPackMe Blue Team Lab с пло­щад­ки Cyber Defenders. Уро­вень задания — слож­ный. В лабора­тор­ной работе тебе пот­ребу­ется отве­тить на ряд воп­росов по ито­гам про­хож­дения, одна­ко при­водить отве­ты мы не будем — если ты пов­торишь про­хож­дение, то без тру­да отве­тишь на всё сам.

warning

Ана­лиз вре­донос­ных фай­лов необ­ходимо про­водить в изо­лиро­ван­ной сре­де. О том, как соб­рать лабора­торию для ана­лиза вре­доно­сов, под­робно рас­ска­зано в статье «Код под над­зором. Соз­даем вир­туаль­ную лабора­торию для ана­лиза мал­вари».

Используемые утилиты

  1. DIE — прог­рамма для опре­деле­ния типов фай­лов.
  2. PeStudio — прог­рамма для поис­ка арте­фак­тов исполня­емых фай­лов.
  3. IDA Pro — инте­рак­тивный дизас­сем­блер, исполь­зуемый для реверс‑инжи­нирин­га.
  4. Wireshark — инс­тру­мент для ана­лиза сетево­го тра­фика.
  5. Burp Suite — плат­форма, которая исполь­зует­ся в качес­тве проз­рачно­го прок­си‑сер­вера для ана­лиза вза­имо­дей­ствия вре­донос­ного фай­ла по про­токо­лу HTTPS.
  6. x64dbg — опен­сор­сный отладчик для Windows, пред­назна­чен­ный для ана­лиза вре­донос­ных прог­рамм.
  7. INetSim — эму­лятор работы с интерне­том.

Первичный анализ

По­лучим пер­вичную информа­цию об иссле­дуемом образце. Заг­рузим файл в DIE и выберем ана­лиза­тор Nauz File Detector.

Информация о файле
Ин­форма­ция о фай­ле

Ис­сле­дуемый обра­зец соб­ран для 32-раз­рядных сис­тем, раз­работан на язы­ке C/C++ и ском­пилиро­ван в Microsoft Visual Studio. Получим энтро­пию исполня­емо­го фай­ла по сек­циям. Перей­дем на вклад­ку Entropy и опре­делим его показа­тель.

Энтропия исполняемого файла
Эн­тро­пия исполня­емо­го фай­ла

Эн­тро­пия иссле­дуемо­го фай­ла — 7,389, в сек­ции .text — самое высокое зна­чение. Зна­чит, иссле­дуемый файл упа­кован.

Исследуем загрузчик

Заг­рузим файл в IDA и прис­тупим к ана­лизу. Точ­ка вхо­да ука­зыва­ет на фун­кцию WinMain, а основной поток выпол­нения реали­зован в фун­кции sub_468480.

Псевдокод функции WinMain
Псев­докод фун­кции WinMain

Пе­рей­дем в дан­ную фун­кцию двой­ным нажати­ем и деком­пилиру­ем, нажав F5.

В фун­кции WrapGlobalAlloc реали­зова­но получе­ние адре­са GlobalAlloc и выделе­ние в куче памяти, рав­ной дли­не шелл‑кода. Далее в фун­кции WrapVirtualProtect выделен­ному учас­тку памяти наз­нача­ются пра­ва на выпол­нение, чте­ние и запись (PAGE_EXECUTE_READWRITE) с помощью фун­кции VirtualProtect. Сле­дующим эта­пом начина­ется про­цесс рас­шифров­ки шелл‑кода.

Шиф­рование сос­тоит из пре­обра­зова­ния клю­ча и опе­рации XOR.

Функция преобразования ключа
Фун­кция пре­обра­зова­ния клю­ча
Функция расшифровки шелл-кода
Фун­кция рас­шифров­ки шелл‑кода

На­чаль­ное зна­чение клю­ча — 0xD5AF2F45. Для рас­шифров­ки шелл‑кода мы раз­работа­ем скрипт для IDA, на вход которо­го переда­дим адрес зашиф­рован­ного шелл‑кода, его раз­мер, началь­ное зна­чение клю­ча и имя фай­ла для сох­ранения дан­ных.

import struct
import idaapi
HIWORD = lambda x: x >> 16
def GenerateKey(key):
key = (key * 0x343fd) & 0xffffffff
key = (key + 0x269ec3) & 0xffffffff
return key
def DecryptShellcode(data,key):
result = bytearray()
for i in data:
key = GenerateKey(key)
k = HIWORD(key) & 0xFF
result.append(i ^ k)
return result
def main(start,size,key,filename):
w = open(filename,'wb')
b = bytearray()
for i in range(size):
x = idaapi.get_byte(start + i)
b.append(x)
d = DecryptStage(b,key)
w.write(d)
w.close()

Рас­шифро­вывать будем в режиме отладки. Пос­тавим точ­ку оста­нова на фун­кции WrapDecryptShellcode (горячая кла­виша F2) и запус­тим отладку (F9). Двой­ным нажати­ем на перемен­ную shellcode получим адрес исполня­емо­го кода, а его раз­мер узна­ем из перемен­ной size_shellcode.

Заг­рузим раз­работан­ный скрипт, для это­го зай­дем на вклад­ку File → Script File. В коман­дной стро­ке Python вызовем фун­кцию main.

main(start=0x7B96E8,size=0x4e9d3,key=0xD5AF2F45,filename='stage01')

Мы получи­ли зна­чение рас­шифро­ван­ного исполня­емо­го кода. Заг­рузим его в IDA и про­ведем ста­тичес­кий ана­лиз. Получен­ный шелл‑код сос­тоит из фун­кций получе­ния адре­сов исполь­зуемых WinAPI и рас­шифров­ки основной наг­рузки.

Функция получения адресов Windows API
Фун­кция получе­ния адре­сов Windows API

Пре­обра­зуем деком­пилиру­емый код в чита­емый вид. Для это­го выберем перемен­ную a1, клик­нем пра­вой кноп­кой мыши и выберем пункт Create Struct. IDA сфор­миру­ет струк­туру. Далее пере­име­новы­ваем каж­дое зна­чение поля (горячая кла­виша N).

Для получе­ния фун­кций WinAPI LoadLibraryA и GetProcAddress из биб­лиоте­ки kernel32.dll исполь­зует­ся хеширо­вание. Из поля InMemoryOrderModuleList струк­туры PEB про­цес­са счи­тыва­ются име­на динами­чес­ких биб­лиотек. Из каж­дого име­ни вычис­ляет­ся зна­чение хеша и срав­нива­ется со зна­чени­ем 0xd4e88. Такая же про­цеду­ра про­изво­дит­ся с име­нами фун­кций экспор­та най­ден­ной динами­чес­кой биб­лиоте­ки.

Ал­горитм хеширо­вания пред­став­лен ниже.

def ApiHashing(name_func,hashing):
res = 0
for i in name_func:
v5 = (ord(i) | 0x60) & 0xff
res = (res + v5) * 2
if res == hashing:
return True
return False

Зна­чение 0xd4e88 соот­ветс­тву­ет kernel32.dll.

Функция расшифровки основной нагрузки
Фун­кция рас­шифров­ки основной наг­рузки

Про­цесс получе­ния основной наг­рузки сос­тоит из эта­пов рас­шифров­ки и разар­хивиро­вания.

Процесс расшифровки и разархивирования
Про­цесс рас­шифров­ки и разар­хивиро­вания

Ал­горитм рас­шифров­ки тот же, а вот алго­ритм деком­прес­сии силь­но обфусци­рован и содер­жит мно­жес­тво перехо­дов (goto). Рас­паку­ем нашу наг­рузку. Для это­го в режиме отладки перехо­дим к выпол­нению шелл‑кода и находим фун­кцию DecryptUnpackShellcode. Далее спус­каем­ся к выпол­нению фун­кции Unpack, перехо­дим на вклад­ку Hex View (кла­виша G) и вво­дим адрес, который хра­нит­ся в перемен­ной data. Раз­мер получа­ем из зна­чения перемен­ной unpack_size.

Дам­пим получен­ный пос­ле разар­хивиро­вания шелл‑код с помощью IDA API. Для это­го перехо­дим в коман­дную обо­лоч­ку Python и вво­дим сле­дующую коман­ду:

idc.savefile('path_filename', 0, address_shellcode, unpack_size)

Мы получи­ли основную наг­рузку, она пред­став­ляет собой шелл‑код, задача которо­го — заг­рузить методом Process Hollowing исполня­емый файл и начать выпол­нение с его точ­ки вхо­да. Заг­рузим получен­ный файл в Hex-редак­тор, ско­пиру­ем дан­ные, начиная с сиг­натуры MZ, и прис­тупим к его ана­лизу.

Содержимое основной нагрузки
Со­дер­жимое основной наг­рузки

На этом эта­пе мы с тобой рас­пакова­ли основную наг­рузку, которая пред­став­ляет собой исполня­емый файл. Работа заг­рузчи­ка раз­делена на нес­коль­ко эта­пов: сна­чала рас­шифро­выва­ется исполня­емый код, основная задача которо­го — рас­шифро­вать и рас­паковать шелл‑код сле­дующе­го эта­па. Шелл‑код сле­дующе­го эта­па заг­ружа­ет в память исполня­емый файл и про­дол­жает выпол­нение с его точ­ки вхо­да.

Схема работы загрузчика
Схе­ма работы заг­рузчи­ка

Исследуем основную нагрузку

По­лучим информа­цию об исполня­емом фай­ле, для это­го заг­рузим его в ути­литу DIE.

Информация об основной нагрузке
Ин­форма­ция об основной наг­рузке

Ос­новная наг­рузка раз­работа­на на C/C++, ском­пилиро­вана в Microsoft Visual Studio и соб­рана для 32-раз­рядных сис­тем. Заг­рузим файл в IDA и прис­тупим к ста­тичес­кому ана­лизу. Код вре­доно­са силь­но обфусци­рован, и граф потока выпол­нения очень боль­шой.

Поток выполнения функции main
По­ток выпол­нения фун­кции main

Все стро­ки, исполь­зуемые в про­цес­се работы модуля, зашиф­рованы при помощи XOR с одно­бай­товым клю­чом. Давай наб­роса­ем скрипт для IDA, который будет рас­шифро­вывать стро­ки.

def decrypt_str(data):
result = ''
key = data & 0xff
data = data >> 8
while data > 0:
result += chr(~((data & 0xff) ^ key)&0xff)
data = data >> 8
return result

На пер­вом эта­пе модуль про­веря­ет, запущен ли в сис­теме экзем­пляр, с помощью фун­кции OpenMutexA.

Функция проверки мьютекса
Фун­кция про­вер­ки мьютек­са

В коман­дной стро­ке Python запус­тим фун­кцию рас­шифров­ки наз­вания мьютек­са:

decrypt_str(0xBABCABAFACA4B832)

Имя мьютек­са фор­миру­ется из кон­стантно­го зна­чения uiabfqwfu, сло­жен­ного с име­нем поль­зовате­ля. Если мьютекс с таким наз­вани­ем сущес­тву­ет, модуль прек­раща­ет свою работу.

Да­лее модуль с помощью фун­кции GetLocaleInfoA узна­ёт, какие в сис­теме уста­нов­лены язы­ки. Если при­сутс­тву­ет язык из опре­делен­ного спис­ка (в него вхо­дит рус­ский), то модуль прек­раща­ет работу.

Сравнение установленного языкового стандарта
Срав­нение уста­нов­ленно­го язы­ково­го стан­дарта

Сле­дом начина­ется про­цесс вза­имо­дей­ствия с управля­ющим сер­вером. Адрес C2-сер­вера зашиф­рован алго­рит­мом RC4 и закоди­рован в Base64.

Зашифрованное значение RC4-ключа
За­шиф­рован­ное зна­чение RC4-клю­ча

Рас­шифру­ем ключ, который хра­нит­ся в перемен­ной RC4_KEY.

decrypt_str(0x9498D7AB8CAEB2808B9A8E9DDCB4CA11)

Ключ шиф­рования — $Z2s'ten\@bE9vzR для алго­рит­ма RC4.

Рас­шифру­ем адрес управля­юще­го сер­вера, для это­го вос­поль­зуем­ся ути­литой CyberChef.

Расшифрование управляющего сервера
Рас­шифро­вание управля­юще­го сер­вера

Мы получи­ли адрес управля­юще­го сер­вера:

https://tttttt.me/ch0koalpengold

Взаимодействие с управляющим сервером

При обра­щении к управля­юще­му сер­веру https://tttttt.me/ch0koalpengold иссле­дуемый обра­зец получа­ет адрес основно­го сер­вера C2. Адрес рас­положен в бло­ке div, содер­жащем сле­дующие атри­буты.

Значение атрибута
Зна­чение атри­бута

Ос­новной адрес управля­юще­го сер­вера зашиф­рован алго­рит­мом RC4. Ключ:

6af7fae138b9752d1d76736dcb534c9d
Шифрование основного адреса управляющего сервера
Шиф­рование основно­го адре­са управля­юще­го сер­вера

При обра­щении к основно­му адре­су управля­юще­го сер­вера иссле­дуемый обра­зец получа­ет кон­фиг, зашиф­рован­ный алго­рит­мом RC4 с клю­чом $Z2s'ten\@bE9vzR.

Прис­тупим к нас­трой­ке INetSim. Откры­ваем файл sample.html вот по такому пути:

/var/lib/inetsim/http/fakefiles/sample.html

И добав­ляем блок div с перечис­ленны­ми выше атри­бута­ми. Зна­чение gzWH3jR7snsUFO5aZbvXyda3vfp8cjiu зашиф­ровано алго­рит­мом RC4 с клю­чом 6af7fae138b9752d1d76736dcb534c9d. В зашиф­рован­ную стро­ку добав­лены сим­волы, которые уда­ляют­ся в про­цес­се рас­шифров­ки.

Содержимое файла sample.html
Со­дер­жимое фай­ла sample.html

В катало­ге /var/lib/inetsim/http/fakefiles соз­дадим файл config.json, содер­жащий зашиф­рован­ные нас­трой­ки.

Зашифрованные настройки
За­шиф­рован­ные нас­трой­ки
{
"_id":"123",
"au":"files/",
"ip":"10.10.10.1",
"location":{"country":"qwe","country_code":"qwe","state":null,"state_code":null,"city":null,"zip":null},
"c":{"m":null,"lu":null},
"lu":null,
"rm":1,
"is_screen_enabled":1,
"is_history_enabled":0,
"depth":3
}

Этот кон­фиг получен в резуль­тате ста­тичес­кого ана­лиза кода и нужен для работы образца. Параметр au содер­жит URL для заг­рузки биб­лиоте­ки sqlite3.dll, парамет­ры ip и location — информа­цию о сетевом адре­се заражен­ной машины, параметр is_screen_enabled необ­ходим для получе­ния сним­ка рабоче­го сто­ла, а параметр rm нужен на слу­чай, если пот­ребу­ется уда­лить себя пос­ле окон­чания сбо­ра информа­ции.

Так­же в каталог /var/lib/inetsim/http/fakefiles заг­рузим файл sqlite3.dll.

В фай­ле /etc/inetsim/inetsim.conf в бло­ке https_static_fakefile добавим ста­тич­ные URL.

Конфигурация INetSim
Кон­фигура­ция INetSim

Каж­дый раз, ког­да мал­варь обра­щает­ся к адре­сам с подс­тро­кой /config/, заг­ружа­ется файл config.json (кон­фигура­ция модуля), а при откры­тии /config/files/ — биб­лиоте­ка sqlite3.dll.

Пе­реза­пус­тим ути­литу INetSim.

sudo inetsim

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

По­лучив основной адрес управля­юще­го сер­вера, вре­донос­ный файл начина­ет уста­нав­ливать свое пер­вое соеди­нение.

Первичное соединение с управляющим сервером
Пер­вичное соеди­нение с управля­ющим сер­вером

Зна­чения в POST-зап­росе сфор­мирова­ны сле­дующим обра­зом:

b=<MachineGuid>_<User>&c=c021300d0074689fde86c87568e215c582272721&f=json

Па­раметр b опи­сыва­ет иден­тифика­тор заражен­ной машины, который фор­миру­ется из име­ни поль­зовате­ля сис­темы и зна­чения MachineGuid клю­ча реес­тра:

HKLM:\SOFTWARE\Microsoft\Cryptography\MachineGuid

В парамет­ре c ука­зано кон­стантное зна­чение. Сфор­мирован­ная стро­ка шиф­рует­ся по алго­рит­му RC4 с клю­чом $Z2s'ten\@bE9vzR. В ответ от сер­вера управле­ния при­ходит кон­фигура­ция модуля.

Для работы фун­кции сбо­ра дан­ных из бра­узе­ров вре­доно­су необ­ходима динами­чес­кая биб­лиоте­ка sqlite3.dll. Параметр au в кон­фиге ука­зыва­ет на URL, по которо­му мал­варь может получить эту биб­лиоте­ку.

Загрузка sqlite3.dll
Заг­рузка sqlite3.dll

Сбор информации о системе

Ра­бочий каталог вре­донос­ного фай­ла:

C:\User\<User>\AppData\LocalLow

В этот каталог заг­ружа­ются допол­нитель­ные модули, а так­же соб­ранные фай­лы из сис­темы.

Фун­кция сбо­ра находит­ся по сме­щению 454A (в моем слу­чае фун­кция sub_56454A), для перехо­да к коду необ­ходимо на вклад­ке Function ввес­ти зна­чение сме­щения. На эта­пе сбо­ра дан­ных о сис­теме иссле­дуемый обра­зец фор­миру­ет файл System Info.txt в рабочей дирек­тории.

Содержимое файла System Info.txt
Со­дер­жимое фай­ла System Info.txt

Со­дер­жимое фай­ла System Info.txt:

Ес­ли в кон­фиге, получен­ном с управля­юще­го сер­вера, есть зна­чение is_screen_enabled:1, дела­ется сни­мок рабоче­го сто­ла поль­зовате­ля. Фун­кция соз­дания скрин­шота рас­положе­на по сме­щению 951B.

Функция создания снимка рабочего стола пользователя
Фун­кция соз­дания сним­ка рабоче­го сто­ла поль­зовате­ля

Пос­ле зах­вата экра­на рас­тро­вое изоб­ражение сох­раня­ется в рабочую дирек­торию с име­нем screen.jpeg.

Пос­ле сбо­ра всей информа­ции с хос­та в рабочей дирек­тории соз­дает­ся ZIP-архив. Имя фай­ла берет­ся из парамет­ра _id в кон­фиге, получен­ном с C2.

Отправка собранных данных
От­прав­ка соб­ранных дан­ных
Содержимое архива данных
Со­дер­жимое архи­ва дан­ных

Ес­ли зна­чение парамет­ра rm — «исти­на», то вре­донос­ный файл начина­ет про­цесс само­уда­ления. Фун­кция уда­ления рас­положе­на по сме­щению 5D47.

Функция самоудаления
Фун­кция само­уда­ления

В перемен­ной cmd фор­миру­ется коман­да для уда­ления сле­дов при­сутс­твия мал­вари в сис­теме. Давай рас­шифру­ем зна­чение.

decrypt_str(0x8FD1DE98D1DE929BBADED8DE928BB0DEC0DEB5BFBBACBCB1B0D1DECECFDEAAD1DE8A8B919B93978ADEBDD1DE9B869BD09A939D01)

Вот что получи­лось:

cmd.exe /C timeout /T 10 /NOBREAK > Nul & Del /f /q

Так­же при пер­вичном ана­лизе исполня­емо­го фай­ла я нашел пол­ный путь к катало­гу сбор­ки на компь­юте­ре зло­умыш­ленни­ка:

A:\_Work\rc-build-v1-exe\

Выводы

Эта лабора­тория помог­ла нам научить­ся эму­лиро­вать работу управля­юще­го сер­вера с помощью ути­литы INetSim. Мы разоб­рали механизм работы сти­лера, задачей которо­го была кра­жа крип­токошель­ков, фай­лов cookie, паролей от бра­узе­ров и поч­товых сер­веров и мно­гого дру­гого. Мы изу­чили работу заг­рузчи­ка и рас­пакова­ли основную наг­рузку, а так­же написа­ли скрип­ты для IDA и пот­рениро­вались в про­веде­нии ста­тичес­кого и динами­чес­кого ана­лиза вре­донос­ных фай­лов.