
Author: Danyal Rana
SHA256: 0bb0df4532ac09f270020c14b6aa3c430af126d4e017f76119e180f06e80ab42
MD5: 577e05630d004293f6bbf580f5d880fb
Analysis status: Ongoing
Target geography: Brazil
Assessment: High-confidence banking credential and session theft capability
A malicious Windows Installer package was analyzed and found to implement a multi-stage delivery framework ultimately focused on browser-based banking theft.
The initial installer masquerades as a legitimate Brazilian digital-certificate / electronic-invoice component. Its MSI database contains a malicious PowerShell dropper and a VBScript custom action that reconstructs and launches the PowerShell component with execution-policy bypass. The installer also displays a fake “successful invoice registration” notification to the victim while the malicious downloader continues independently in the background.
The PowerShell stage contacts remote infrastructure to obtain a large phantom_bundle.zip payload. The bundle contains an encrypted PowerShell installer, Chromium and Firefox browser-extension builds, a Node.js/Puppeteer environment, a headless browser agent, watchdog components, and PowerShell-based autostart mechanisms.
Static analysis of the browser component uncovered bank-specific targeting for Brazilian financial institutions, including Banco do Brasil PJ and Bradesco Net Empresa. The extension requests broad browser permissions covering cookies, history, tabs, storage, web traffic, and arbitrary URL access. The code contains explicit handling for usernames, passwords, tokens, session/local storage, XHR interception, WebSocket communications, and dedicated banking capture queues.
The evidence supports classifying the malware as a multi-stage browser banking trojan / browser-injection framework, with capabilities extending beyond simple credential harvesting into session and application-state collection.
The malware is distributed as an MSI masquerading as a legitimate Brazilian financial/certificate utility.
The MSI installs:
LocalApp/CertDigital/dropper.ps1The corresponding MSI component is named DropperComp, with dropper.ps1 as its key path.
A second component creates:
HKCU\Software\Microsoft\CertDigital\Deploywith a Token value.
The analyzed specimen contains:
TOKENPLACEHOLDERrather than a real 16-digit victim token, indicating that the sample is a generic/template build rather than a victim-specific patched installer. Searches for 16-digit numeric tokens across the MSI produced no results.
The MSI does not simply install a PowerShell script and stop.
A malicious VBScript custom action named RunDropper is executed as part of the installation sequence. The custom action reconstructs the PowerShell dropper from an embedded Base64 representation and writes it to disk.
The resulting PowerShell process is launched with:
-NoProfile
-ExecutionPolicy Bypass
-WindowStyle HiddenThe VBS launch is asynchronous, allowing the malicious PowerShell process to continue after the MSI process has exited.
This behavior is particularly notable because the embedded source comments explicitly document implementation problems and fixes. The author states that executing the second-stage ScriptBlock inside the original PowerShell process could terminate the entire dropper when exit was invoked. The author therefore moved execution into a child PowerShell process so crashes or exit calls would terminate only the child while the parent remained available to record the exit code.
This provides unusual insight into the malware’s development process and demonstrates that the framework has been iteratively engineered rather than assembled as a simple one-shot script.
The MSI includes a fake success notification after the malicious process is launched.
The VBScript creates a temporary VBS file displaying a message styled as a Brazilian electronic-invoice / government transaction confirmation.
The result is effectively:
Victim launches MSI
↓
Malicious PowerShell starts silently
↓
Victim receives "transaction successful" message
↓
MSI closes
↓
Malicious activity continues in backgroundThis provides a straightforward method of hiding malicious activity behind an apparently successful installation.
The PowerShell dropper contacts:
https://notaemitida.dynv6.netand requests:
/download-zip.php?serve=phantom_bundle&t=<token>The loader infrastructure was reachable during analysis.
Observed infrastructure:
IP: 185.223.252.219
Server: Apache/2.4.58 (Ubuntu)The server returned:
Content-Disposition: attachment; filename="phantom_bundle.zip"
Content-Type: application/zipwith a reported size of:
39,314,460 bytesThe recovered ZIP was:
6d9ee16e493ba6204fb1eaf9d568469cc1e607e4a5ff8729c44d4a1af393f326The same infrastructure also served a separate dependency archive:
/headless-deps.zipwhich was approximately 36.5 MiB.
The first-stage downloader implements two apparent identity mechanisms.
The first is a 16-digit token obtained from:
HKCU\Software\Microsoft\CertDigital\Deploy\TokenInvalid or missing tokens are replaced with:
0000000000000000The script then extracts a clientHash from:
dist/sentinel.idand can generate a random ten-character client identifier when necessary.
The generated identifier is also written to:
dist/sentinel.id
dist/hash.seedThe author comments reference server-side handling involving first_seen state and a 4009 duplicate condition, suggesting that the backend maintains installation/client state.
A hard-coded fallback value:
6QJMBCE2V4is also referenced by the downloader comments.
The exact backend semantics remain partially unresolved because the analyzed MSI does not contain a real deployment token.
The downloaded bundle contains:
phantom_inject_agent.ps1.encThe file is approximately 72 KB.
The first-stage PowerShell contains the complete decryption routine.
The encrypted format is:
16-byte IV
+
ciphertext
+
32-byte HMACThe key material is derived from an embedded secret using SHA-256.
Two keys are derived:
SHA256(secret + "|inst-enc|")
SHA256(secret + "|inst-mac|")The payload is then verified using HMAC-SHA256 and decrypted with:
AES-CBC
PKCS7 paddingThe decrypted PowerShell is deliberately not written back to disk. Instead, it is divided into approximately 30,000-character chunks and passed to a child PowerShell process through environment variables.
The child reconstructs the plaintext and executes it through:
[ScriptBlock]::Create(...)This provides a form of fileless second-stage execution.
The recovered phantom_bundle.zip provides the clearest picture of the malware’s purpose.
Relevant files include:
dist/background.js
dist/content.js
dist/engine.js
dist/fingerprint.js
dist/inject.js
dist/page-capture.js
dist/ws-crypto.js
dist/manifest.json
dist-firefox/background.js
dist-firefox/content.js
dist-firefox/engine.js
dist-firefox/fingerprint.js
dist-firefox/inject.js
...
headless/headless-agent.js
headless/install.ps1
headless/install-autostart.ps1
headless/launch-autostart.ps1
headless/run-agent-watchdog.ps1
headless/start-agent-when-ready.ps1The presence of both dist/ and dist-firefox/ demonstrates that the framework contains separate Chromium-oriented and Firefox-oriented implementations.
The Chromium extension identifies itself as:
Drive Chromeand requests the following permissions:
activeTab
background
cookies
history
storage
tabs
windows
webNavigation
scripting
alarms
webRequest
declarativeNetRequest
declarativeNetRequestWithHostAccessIts host permissions cover:
http://*/*
https://*/*
<all_urls>The content scripts execute:
document_start
all_framesand selected scripts run in the page’s:
MAINexecution world.
This grants the framework broad access to browser state, page execution, and web traffic.
The manifest therefore demonstrates capability for arbitrary-site browser interaction rather than access restricted to a single application.
The most significant discovery is that the browser code contains explicit financial-sector targeting logic.
The code contains dedicated handling for Banco do Brasil PJ authentication flows.
It checks URL paths including:
/login/token
/token/validar
/validar-token
/login/validar
/validar-dispositivoThe presence of a dedicated _isBbPjDevice2Path() function and a saveBbCapture() / flushBbCaptureQueue() workflow indicates that Banco do Brasil traffic is handled separately from generic browser activity.
The code explicitly detects:
bradesconetempresaand looks for paths associated with:
login
acesso
autentic
auth
signonCaptured objects contain fields corresponding to:
usuario
senhaand related credential material.
This establishes bank-specific credential capture logic, rather than merely generic password-field detection.
The browser injection code contains logic associated with:
password
token
loginand inspects page and application state.
The code also enumerates:
sessionStorage
localStoragerather than accessing only a single known key.
This effectively allows the framework to collect arbitrary storage state from targeted sessions.
Such state can potentially contain authentication tokens, application identifiers, session state, or other sensitive values. The presence of collection logic is confirmed; the exact contents captured vary by banking application.
The injection code hooks the browser’s XMLHttpRequest implementation.
The code references the original XHR methods and replaces them with wrapped handlers.
The surrounding logic filters traffic associated with authentication/session URLs, including patterns such as:
/login
autentic
signin
session
tokenThis means the malware can inspect application/API traffic occurring within the victim’s browser session.
This is significantly more capable than simple DOM scraping because it can observe data exchanged between the web application and its backend.
The malware contains an explicit:
saveBbCapture()function and corresponding:
flushBbCaptureQueue()routine.
Capture data is stored locally in a queue and later flushed once the control channel is available.
This is a particularly strong indicator that browser-derived banking data is being treated as a structured collection intended for transmission to the operator.
The browser framework establishes a WebSocket connection and maintains:
clientHash
BROWSER_ID
BROWSER_TYPEas part of the client state.
The code maintains reconnect logic, endpoint selection, ping intervals, and connection state.
More importantly, the WebSocket session initializes a custom cryptographic context using:
WSCrypto
g_aesKey
g_wsSeq
g_wsLastSeqbefore normal communications begin.
Thus, the browser component communicates with the operator over an application-level encrypted WebSocket channel rather than relying solely on the underlying TLS connection.
The code also contains functionality resembling:
fetchRemoteModule()
injectRemoteModule()with hostname-based checks before the module is fetched and injected.
This indicates that the framework may support remote retrieval and injection of additional browser functionality.
The exact scope and conditions of this mechanism require further reversing.
The malware also ships its own browser automation environment.
The separate dependency bundle contains:
Node.js
Puppeteer Core
@puppeteer/browsers
Chromium BiDi
DevTools ProtocolThe identified runtime versions include:
puppeteer-core 23.11.1
@puppeteer/browsers 2.6.1
chromium-bidi 0.11.0
devtools-protocol 0.0.1367902The main bundle additionally contains:
headless-agent.jsat approximately 536 KB.
This is accompanied by installation, autostart, watchdog, and startup helper scripts.
The result is a self-contained browser automation stack that does not need the victim to already have the required Node tooling installed.
Observed infrastructure includes:
notaemitida.dynv6.net
185.223.252.219
Apache/2.4.58 (Ubuntu)The domain presented a valid Let’s Encrypt certificate during analysis.
vamosestourar.sbs:443The host also presented a valid Let’s Encrypt certificate during analysis.
The browser payload’s WebSocket communications are associated with this second infrastructure role, while the notaemitida.dynv6.net host serves payloads and dependencies.
The loader additionally returned:
Access-Control-Allow-Origin: https://nfe-emissao.orgwhich identifies another domain involved in the loader’s CORS configuration. This alone is not sufficient to establish that the domain itself is malicious infrastructure, but it is a relevant infrastructure pivot.
The observed architecture can be summarized as:
Malicious MSI
│
▼
VBScript CustomAction
│
├── reconstruct PowerShell
├── ExecutionPolicy → Bypass
├── launch hidden PowerShell
└── display fake transaction success
│
▼
PowerShell dropper
│
├── token/client identification
├── download phantom_bundle.zip
└── decrypt second-stage PS1
│
▼
browser framework
│
┌─────────────┴─────────────┐
│ │
▼ ▼
Browser extensions Headless agent
Chromium + Firefox Node + Puppeteer
│ │
└─────────────┬─────────────┘
▼
Banking/session targeting
│
┌─────────────┴─────────────┐
│ │
Credential capture Session/state capture
│ │
└─────────────┬─────────────┘
▼
Encrypted WebSocket C2The analyzed malware should be considered a high-severity browser banking trojan.
Its capabilities extend well beyond a conventional credential stealer. The framework combines:
The most significant finding is the application-specific banking logic, particularly the dedicated handling of Banco do Brasil PJ and Bradesco Net Empresa authentication flows. The malware appears designed not merely to steal a static password, but to operate inside a victim’s authenticated browser environment and capture credentials, session material, and application traffic.
Zeebo.msi
phantom_bundle.zip
headless-deps.zip
phantom_inject_agent.ps1.enc
dist/background.js
dist/content.js
dist/engine.js
dist/fingerprint.js
dist/inject.js
dist/page-capture.js
dist/ws-crypto.js
headless/headless-agent.jsphantom_bundle.zip
6d9ee16e493ba6204fb1eaf9d568469cc1e607e4a5ff8729c44d4a1af393f326
headless-deps.zip
a38b8baf52c6fe8fce0b629a143907a11746db15bf8c333cf98c0b5c645962f4
dropper.ps1
51d7e1735a45c44d903749831d829be41cf661269d7958df1a448e6f9f710734notaemitida.dynv6.net
vamosestourar.sbs185.223.252.219%LOCALAPPDATA%\CertDigital
%LOCALAPPDATA%\Microsoft\Windows\Caches\EdgeUpdate\phantomHKCU\Software\Microsoft\CertDigital\DeployDrive ChromeWhat initially appeared to be a malicious MSI is actually the front end of a considerably larger browser-focused malware framework.
The initial installer exists primarily to establish execution and retrieve the real payload. The substantive malicious functionality resides in the browser injection and headless-browser ecosystem that follows.
The strongest evidence currently points to a campaign focused on Brazilian online banking, with specific handling for Banco do Brasil and Bradesco enterprise authentication workflows, combined with browser-state collection and an encrypted operator-controlled WebSocket channel.
Further reversing of saveBbCapture(), the WebSocket command dispatcher, ws-crypto.js, and the encrypted PowerShell stage should allow the remaining C2 protocol, exact stolen-data schema, persistence mechanism, and operator capabilities to be reconstructed in greater detail.
Further analysis of headless-agent.js shows that the headless component functions as an interactive browser-control agent rather than merely a Puppeteer runtime. Its command interface supports tab creation and management, navigation, page inspection, screenshots, DOM interaction, form manipulation, and arbitrary JavaScript execution through script.execute, which ultimately invokes page.evaluate() against the active browser page.
This extends the previously documented collection and injection capabilities: the operator can not only obtain browser data and manipulate injected content, but can also actively control the victim’s browser session.
The headless agent contains dedicated Bradesco commands including dom.bradescoChave, dom.bradescoField, dom.bradescoLogin, and dom.bradescoSubmit. These routines incorporate bank-specific selectors, field handling, authentication-state detection, and submission logic rather than relying exclusively on generic browser automation.
This confirms that the Brazilian banking targeting identified earlier is implemented directly within the operator-controlled automation layer, allowing targeted workflows to be handled through purpose-built routines.
The agent supports screenshot, screencast.start, and screencast.stop, with browser frames transmitted through its WebSocket infrastructure. It also creates a local /frames relay and uses WebRTC signaling to support browser-frame transport.
This adds a visual layer to the DOM, storage, and network interception already documented, giving the operator the ability to observe the controlled browser session while interacting with it remotely.
The agent maintains multiple panel hosts, tracks endpoint health, and temporarily suppresses failed endpoints before attempting them again. It also persists the last known-good host, providing basic C2 failover beyond the primary infrastructure identified earlier.
A shared clientHash is resolved from sentinel.id, hash.seed, configuration, and fallback locations, allowing the browser and headless components to maintain a consistent installation identity. The WebSocket layer additionally rejects duplicate instances using close code 4009, after which the agent searches for another running headless-agent.js process before exiting.
Further reversing uncovered a dedicated Bradesco authentication subsystem capable of handling separate AGN, CTA, and DIG values. The agent uses multiple fallback mechanisms to populate these fields, including bank-specific native filling, tab-based interaction, DOM methods, and direct CDP-based input.
The login routine can also submit the authentication form automatically. Its dom.bradescoChave handler contains additional logic for identifying CAPTCHA-related elements, traversing frames, and locating short numeric authentication fields. A separate stage-detection routine determines the current state of the Bradesco authentication workflow.
The headless agent contains authenticated update mechanisms for both the browser extension and the agent itself. Updates include version, download URL, SHA-256, and HMAC metadata, with integrity/authenticity checks derived from the configured AgentKey. The payloads are decrypted using AES-GCM before the extension or agent is replaced.
The agent update routine additionally validates the announced version, checks the replacement JavaScript with node --check, replaces the running agent, schedules a restart, and launches the updated process. This gives the operator a mechanism for maintaining, replacing, or extending the deployed framework after initial infection.
The additional reversing substantially expands the original assessment. The earlier analysis established browser injection, banking credential/session collection, encrypted C2, persistence, and browser automation. The headless-agent analysis now shows that these capabilities are tied together through an interactive remote browser-control layer with dedicated Brazilian banking workflows, visual session access, authenticated component updates, and C2 failover.
The Bradesco-specific routines are particularly significant: the framework can identify authentication stages, populate targeted banking fields, handle site-specific UI conditions, and submit the login workflow.