MSI, PowerShell & Puppeteer: Anatomy of a Browser Banking Trojan

Author: Danyal Rana

SHA256: 0bb0df4532ac09f270020c14b6aa3c430af126d4e017f76119e180f06e80ab42

MD5: 577e05630d004293f6bbf580f5d880fb

Analysis status: Ongoing
Target geography: Brazil
Assessment: High-confidence banking credential and session theft capability

Executive Summary

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.

1. Initial Delivery

The malware is distributed as an MSI masquerading as a legitimate Brazilian financial/certificate utility.

The MSI installs:

 
LocalApp/CertDigital/dropper.ps1
 

The corresponding MSI component is named DropperComp, with dropper.ps1 as its key path.

A second component creates:

 
HKCU\Software\Microsoft\CertDigital\Deploy
 

with a Token value.

The analyzed specimen contains:

 
TOKENPLACEHOLDER
 

rather 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.

2. MSI Custom Action Execution

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 Hidden
 

The 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.

3. Social Engineering Component

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 background
 

This provides a straightforward method of hiding malicious activity behind an apparently successful installation.

4. Remote Payload Acquisition

The PowerShell dropper contacts:

 
https://notaemitida.dynv6.net
 

and 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/zip
 

with a reported size of:

 
39,314,460 bytes
 

The recovered ZIP was:

 
6d9ee16e493ba6204fb1eaf9d568469cc1e607e4a5ff8729c44d4a1af393f326
 

The same infrastructure also served a separate dependency archive:

 
/headless-deps.zip
 

which was approximately 36.5 MiB.

5. Token and Client Identification

The first-stage downloader implements two apparent identity mechanisms.

The first is a 16-digit token obtained from:

 
HKCU\Software\Microsoft\CertDigital\Deploy\Token
 

Invalid or missing tokens are replaced with:

 
0000000000000000
 

The script then extracts a clientHash from:

 
dist/sentinel.id
 

and can generate a random ten-character client identifier when necessary.

The generated identifier is also written to:

 
dist/sentinel.id
dist/hash.seed
 

The 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:

 
6QJMBCE2V4
 

is also referenced by the downloader comments.

The exact backend semantics remain partially unresolved because the analyzed MSI does not contain a real deployment token.

6. Encrypted Second Stage

The downloaded bundle contains:

 
phantom_inject_agent.ps1.enc
 

The file is approximately 72 KB.

The first-stage PowerShell contains the complete decryption routine.

The encrypted format is:

 
16-byte IV
+
ciphertext
+
32-byte HMAC
 

The 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 padding
 

The 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.

7. Browser-Focused Payload

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.ps1
 

The presence of both dist/ and dist-firefox/ demonstrates that the framework contains separate Chromium-oriented and Firefox-oriented implementations.

8. Browser Extension Capabilities

The Chromium extension identifies itself as:

 
Drive Chrome
 

and requests the following permissions:

 
activeTab
background
cookies
history
storage
tabs
windows
webNavigation
scripting
alarms
webRequest
declarativeNetRequest
declarativeNetRequestWithHostAccess
 

Its host permissions cover:

 
http://*/*
https://*/*
<all_urls>
 

The content scripts execute:

 
document_start
all_frames
 

and selected scripts run in the page’s:

 
MAIN
 

execution 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.

9. Brazilian Banking Targeting

The most significant discovery is that the browser code contains explicit financial-sector targeting logic.

Banco do Brasil

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-dispositivo
 

The presence of a dedicated _isBbPjDevice2Path() function and a saveBbCapture() / flushBbCaptureQueue() workflow indicates that Banco do Brasil traffic is handled separately from generic browser activity.

Bradesco

The code explicitly detects:

 
bradesconetempresa
 

and looks for paths associated with:

 
login
acesso
autentic
auth
signon
 

Captured objects contain fields corresponding to:

 
usuario
senha
 

and related credential material.

This establishes bank-specific credential capture logic, rather than merely generic password-field detection.

10. Credential and Session Collection

The browser injection code contains logic associated with:

 
password
token
login
 

and inspects page and application state.

The code also enumerates:

 
sessionStorage
localStorage
 

rather 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.

11. Network/API Interception

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
token
 

This 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.

12. Dedicated Banking Capture Queue

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.

13. WebSocket Command and Control

The browser framework establishes a WebSocket connection and maintains:

 
clientHash
BROWSER_ID
BROWSER_TYPE
 

as 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_wsLastSeq
 

before 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.

14. Remote Module Loading

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.

15. Headless Browser Infrastructure

The malware also ships its own browser automation environment.

The separate dependency bundle contains:

 
Node.js
Puppeteer Core
@puppeteer/browsers
Chromium BiDi
DevTools Protocol
 

The identified runtime versions include:

 
puppeteer-core 23.11.1
@puppeteer/browsers 2.6.1
chromium-bidi 0.11.0
devtools-protocol 0.0.1367902
 

The main bundle additionally contains:

 
headless-agent.js
 

at 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.

16. Infrastructure

Observed infrastructure includes:

Loader

 
notaemitida.dynv6.net
185.223.252.219
Apache/2.4.58 (Ubuntu)
 

The domain presented a valid Let’s Encrypt certificate during analysis.

Agent / C2

 
vamosestourar.sbs:443
 

The 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.org
 

which 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.

17. Overall Infection Chain

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 C2
 

18. Assessment

The analyzed malware should be considered a high-severity browser banking trojan.

Its capabilities extend well beyond a conventional credential stealer. The framework combines:

  • malicious MSI delivery
  • deceptive installer behavior
  • PowerShell execution-policy bypass
  • detached child-process execution
  • encrypted second-stage loading
  • fileless PowerShell execution
  • browser-extension deployment
  • Chromium and Firefox support
  • cookie and history access
  • local/session storage harvesting
  • XHR/API interception
  • page-level JavaScript injection
  • bank-specific targeting
  • structured banking credential capture
  • dedicated capture queues
  • encrypted WebSocket communications
  • remote module loading
  • portable Node/Puppeteer browser automation
  • watchdog and autostart components

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.

IOC’s

Files

 
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.js
 

SHA-256

 
phantom_bundle.zip
6d9ee16e493ba6204fb1eaf9d568469cc1e607e4a5ff8729c44d4a1af393f326

headless-deps.zip
a38b8baf52c6fe8fce0b629a143907a11746db15bf8c333cf98c0b5c645962f4

dropper.ps1
51d7e1735a45c44d903749831d829be41cf661269d7958df1a448e6f9f710734
 

Domains

 
notaemitida.dynv6.net
vamosestourar.sbs
 

IP

 
185.223.252.219
 

Local paths

 
%LOCALAPPDATA%\CertDigital
%LOCALAPPDATA%\Microsoft\Windows\Caches\EdgeUpdate\phantom
 

Registry

 
HKCU\Software\Microsoft\CertDigital\Deploy
 

Browser extension

 
Drive Chrome
 

Conclusion

What 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.

Updated Notes

Remote Browser Control

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.

Banking-Specific Automation

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.

Visual Session Access

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.

C2 Resilience and Session Management

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.

Bradesco Authentication Automation

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.

Remote Component Updating

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.

Updated Assessment

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.