KREMLIN Loader and Malicious Browser Extension Forgery
An adversary is using multi-stage JavaScript loaders to install a persistent Node.js task that sideloads malware via SentinelOne to forge browser integrity checks and install malicious extensions.
Based on research by Elastic Security Labs 2026-09-20 13 steps · 6 queries T1047 T1053.005 T1059.001 T1176
Brief
Why hunt for KREMLIN?
Elastic Security Labs recently detailed the KREMLIN campaign, a multi-stage operation targeting financial sessions. The adversary bypasses browser security by forging integrity checks rather than just tricking users. This allows them to install extensions that capture banking credentials silently. Because the campaign uses legitimate signed binaries for sideloading and modifies existing browser configuration files, simple signature-based detections may not catch the full chain of activity.
How the hunt flows
The hunt begins with the hb_software_inventory surface. This first step identifies every host running Chrome or Edge to focus the subsequent queries on vulnerable targets.
In the next phase, the hunt runs two queries in parallel. One query monitors hb_file_activity for the creation of deceptive JavaScript loaders in user directories. The second query examines hb_scheduled_job for a specific task named MicrosoftNodeRuntimeUpdater, which provides the adversary with persistent execution.
Once an agent triages these leads, the hunt pivots to deeper technical payloads. It uses the hb_module_activity surface to find a signed SentinelOne binary loading an unsigned or missing-signature DLL. At the same time, it checks hb_dns_activity for resolutions to known command-and-control domains or payload delivery paths on archive.org.
Finally, the hunt uses stack-counting on the hb_file_activity surface. It looks for rare processes modifying the Secure Preferences files in Chrome or Edge. By excluding legitimate browser executables, the hunt identifies the specific moment the malware forges the HMAC integrity checks required to sideload the extension.
What the hunt cannot see
This hunt has two primary blind spots. First, it cannot confirm the DLL sideloading stage if the endpoint does not provide module load telemetry, such as Sysmon Event ID 7 or equivalent EDR logs. Second, the initial JavaScript loaders are ephemeral. If the adversary deletes these files before the hunt runs or if the file activity logging window is too short, the earliest indicator of infection will be lost.
Steps
-
Scope to hosts with Chromium browsers
Query · scopingFocus the hunt on hosts with Chrome or Edge installed as they are the targets for the KREMLIN extension forgery.
reads hb_software_inventorysqlSELECT DISTINCT device_hostname FROM hb_software_inventory WHERE (LOWER(package_name) LIKE '%chrome%' OR LOWER(package_name) LIKE '%edge%')What a hit looks like. A list of hostnames with targeted browsers. Silence indicates no inventory, meaning the hunt proceeds unscoped.
-
Deceptive JavaScript loader creation
Query · enrichmentDetect the temporary scripts created to lure users into execution.
reads hb_file_activitysqlSELECT device_hostname, file_name, file_path, process_name, time FROM hb_file_activity WHERE LOWER(file_name) LIKE 'popup_%.js' AND ('{{scope_hosts}}' = '' OR instr(',' || '{{scope_hosts}}' || ',', ',' || device_hostname || ',') > 0) AND time >= datetime('now', '-{{lookback_days}} days')What a hit looks like. Creation of scripts with the popup prefix. Silence means no such files were logged, but they are often deleted quickly.
-
Persistent Node.js updater task
Query · detection candidateIdentify the specific scheduled task used by KREMLIN to maintain execution.
reads hb_scheduled_jobsqlSELECT device_hostname, job_name, job_cmd_line, job_user_name, time FROM hb_scheduled_job WHERE LOWER(job_name) = LOWER('{{malicious_task_name}}') AND ('{{scope_hosts}}' = '' OR instr(',' || '{{scope_hosts}}' || ',', ',' || device_hostname || ',') > 0) AND time >= datetime('now', '-{{lookback_days}} days')What a hit looks like. The presence of the MicrosoftNodeRuntimeUpdater task. This is a high-confidence indicator of persistence.
-
Triage early indicators
Agent triageAssess whether the initial loader or persistence stages have completed.
-
SentinelOne DLL sideloading
Query · enrichmentDetect the use of SentinelMemoryScanner.exe to sideload an unsigned or missing-signature malware DLL.
reads hb_module_activitysqlSELECT device_hostname, process_name, module_name, module_signed, time FROM hb_module_activity WHERE LOWER(process_name) LIKE '%sentinelmemoryscanner.exe' AND LOWER(module_name) = 'sentinelagentcore.dll' AND (module_signed = 'False' OR module_signed IS NULL) AND ('{{scope_hosts}}' = '' OR instr(',' || '{{scope_hosts}}' || ',', ',' || device_hostname || ',') > 0) AND time >= datetime('now', '-{{lookback_days}} days')What a hit looks like. A row showing a security tool loading an unsigned DLL with a specific name. This confirms the defense evasion stage.
-
Rare browser preference modifications
Query · baselineStack-count processes modifying Secure Preferences to find rare forgery events.
reads hb_file_activitysqlSELECT process_name, COUNT(DISTINCT device_hostname) AS hosts, COUNT(*) AS touches, MIN(time) AS first_seen FROM hb_file_activity WHERE LOWER(file_path) LIKE '%\\secure preferences' AND NOT (instr(',' || '{{known_browsers}}' || ',', ',' || LOWER(process_name) || ',') > 0) AND ('{{scope_hosts}}' = '' OR instr(',' || '{{scope_hosts}}' || ',', ',' || device_hostname || ',') > 0) AND time >= datetime('now', '-{{lookback_days}} days') GROUP BY process_name HAVING hosts <= 3 ORDER BY hosts ASCWhat a hit looks like. Any rare process modifying browser preferences is a high-confidence indicator of forgery. Silence means no such tampering was caught.
-
C2 and payload domain resolution
Query · enrichmentCorroborate endpoint activity with network resolutions to campaign infrastructure.
reads hb_dns_activitysqlSELECT device_hostname, query_hostname, process_name, time FROM hb_dns_activity WHERE (instr(',' || '{{c2_domains}}' || ',', ',' || LOWER(query_hostname) || ',') > 0) AND ('{{scope_hosts}}' = '' OR instr(',' || '{{scope_hosts}}' || ',', ',' || device_hostname || ',') > 0) AND time >= datetime('now', '-{{lookback_days}} days')What a hit looks like. DNS resolutions to known C2 domains. Silence is expected if domains have rotated.
-
Synthesize the intrusion story
Agent triageCombine evidence from all stages to confirm a full KREMLIN infection.
-
Route on final verdict
DecisionDirect containment for confirmed malicious infections.
-
Contain and Remediate Infection
Response actionPrevent further session theft and remove malware persistence.
-
Analyst review and validation
Analyst taskVerify the extent of the extension forgery and identify any secondary payloads.
-
Close out hunt
Analyst taskFinal documentation and post-incident review.
Coverage
Scenario coverage
| Stage | Covered | How, or why not |
|---|---|---|
| JavaScript Loader and Sandbox Evasion T1059.001 · T1047 |
Yes | js-loader-files |
| Node.js Runtime Persistence T1053.005 |
Yes | persistence-task |
| Blockchain Config and Payload Retrieval | Yes | c2-dns-resolution |
| DLL Sideloading via SentinelOne | Yes | sentinel-sideloading |
| Malicious Extension Integrity Bypassing T1176 |
Yes | browser-forgery-prevalence |
Blind spots
- Needs hb_module_activity load events. Without module load events, the primary defense evasion mechanism is invisible, forcing reliance on file forgery outcomes. It would answer whether the unsigned SentinelAgentCore.dll was loaded. Remediation: Enable Sysmon Event ID 7 or ensure EDR library load events are captured.
- Needs hb_file_activity create events. The malware deletes its temporary loader scripts immediately; if file creation logs have short retention, the lead is lost. It would answer whether the popup script was deleted before collection. Remediation: Configure file activity logging for user profile directories.
Parameters & data
Parameters
| Parameter | Type | Default | What it is |
|---|---|---|---|
c2_domains | list[domain] | connection.upgradeonline.site, granderevolucao.store, archive.org, ia601808.us.archive.org | C2 and payload delivery domains observed in the campaign. |
known_browsers | list[string] | chrome.exe, msedge.exe, explorer.exe, systemsettings.exe | Legitimate processes that typically modify browser preference files. |
lookback_days | number | 14 | Days of history to examine. |
malicious_task_name | string | MicrosoftNodeRuntimeUpdater | The name of the scheduled task created for persistence. |
scope_hosts | list[host] | — | Optional list of hostnames to focus the hunt on. |
Telemetry
| Source | Category | Telemetry |
|---|---|---|
| Endpoint telemetry (hb_ surfaces) | endpoint | endpoint |
Source
---
analysis: This hunt pivots across file activity, scheduled jobs, module loads, and
DNS resolutions. It uses stack-counting to identify rare browser preference tampering
that a single rule would miss without fleet-wide context.
blind_spots:
- id: no-module-telemetry
owner: Endpoint Engineering
question: whether the unsigned SentinelAgentCore.dll was loaded
remediation: Enable Sysmon Event ID 7 or ensure EDR library load events are captured.
requires: hb_module_activity load events
risk: Without module load events, the primary defense evasion mechanism is invisible,
forcing reliance on file forgery outcomes.
stage: dll-sideloading-sentinelone
- id: ephemeral-loader-files
owner: Detection Engineering
question: whether the popup script was deleted before collection
remediation: Configure file activity logging for user profile directories.
requires: hb_file_activity create events
risk: The malware deletes its temporary loader scripts immediately; if file creation
logs have short retention, the lead is lost.
stage: initial-javascript-loader-execution
coverage:
- stage: initial-javascript-loader-execution
status: covered
steps:
- js-loader-files
- stage: scheduled-task-persistence
status: covered
steps:
- persistence-task
- stage: ethereum-dead-drop-resolution
status: covered
steps:
- c2-dns-resolution
- stage: dll-sideloading-sentinelone
status: covered
steps:
- sentinel-sideloading
- stage: browser-extension-forgery
status: covered
steps:
- browser-forgery-prevalence
guardrails:
claims: no_unsupported
evidence: citation_required
missing_data: not_benign
telemetry: untrusted
hunt:
applicability: campaign-specific
handoff: promote-to-detection
justification: KREMLIN is an active 15-month campaign targeting financial institutions
via sophisticated browser forgery; detecting it protects banking sessions and
identifies persistent compromises.
methodology: model-assisted
trigger: intel-report
hypothesis: An adversary is using multi-stage JavaScript loaders to install a persistent
Node.js task that sideloads malware via SentinelOne to forge browser integrity checks
and install malicious extensions.
labels:
- hunt
- attack.t1047
- attack.t1053.005
- attack.t1059.001
- attack.t1176
name: KREMLIN Loader and Malicious Browser Extension Forgery
parameters:
c2_domains:
default:
- connection.upgradeonline.site
- granderevolucao.store
- archive.org
- ia601808.us.archive.org
description: C2 and payload delivery domains observed in the campaign.
from:
kind: article
observed: '2026-09-14'
ref: https://www.elastic.co/security-labs/threat-command/malicious-browser-extension-kremlin-banking-malware
type: list[domain]
known_browsers:
default:
- chrome.exe
- msedge.exe
- explorer.exe
- systemsettings.exe
description: Legitimate processes that typically modify browser preference files.
type: list[string]
lookback_days:
default: '14'
description: Days of history to examine.
type: number
malicious_task_name:
default: MicrosoftNodeRuntimeUpdater
description: The name of the scheduled task created for persistence.
from:
kind: article
observed: '2026-09-14'
ref: https://www.elastic.co/security-labs/threat-command/malicious-browser-extension-kremlin-banking-malware
type: string
scope_hosts:
default: []
description: Optional list of hostnames to focus the hunt on.
type: list[host]
provenance:
authors:
- name: Huntbase hunt generation
org: huntbase.io
generated:
by: huntbase-hunt-generation
from: https://www.elastic.co/security-labs/threat-command/malicious-browser-extension-kremlin-banking-malware
gates:
- dry-run
- lint
- critic
model: hb_google/gemini-3-flash-preview
rationale: Start with Windows hosts having Chrome or Edge installed. If inventory
is missing, prioritize hosts with DNS lookups to archive.org or attacker domains.
references:
- name: "Elastic Security Labs \u2014 The extension you never installed: KREMLIN forges\
\ Chrome's own integrity checks to steal banking sessions"
url: https://www.elastic.co/security-labs/threat-command/malicious-browser-extension-kremlin-banking-malware
related:
- hunt: browser-extension-sideloading-generic
reason: This hunt is specifically tuned to the KREMLIN infection chain and its unique
SentinelOne sideloading technique.
relation: out-of-scope-alternative
scenario:
stages:
- name: JavaScript Loader and Sandbox Evasion
observables:
- popup_*.js
- shell.Popup
- wmic process get
- certutil -decode
- connection.upgradeonline.site/api/log_loader
slug: initial-javascript-loader-execution
tactic: execution
techniques:
- T1059.001
- T1047
- name: Node.js Runtime Persistence
observables:
- MicrosoftNodeRuntimeUpdater
- conhost.exe --headless node.exe
- items.json
slug: scheduled-task-persistence
tactic: persistence
techniques:
- T1053.005
- name: Blockchain Config and Payload Retrieval
observables:
- '0xCD7360A83E5cdbBbbbcEB0e78748babA6740d07b'
- archive.org/download/hotelmoskva/hotelmoskva.jpg
- granderevolucao.store/5c92d3b8734b4f498752f735a1ca0987
- ia601808.us.archive.org
slug: ethereum-dead-drop-resolution
tactic: command-and-control
- name: DLL Sideloading via SentinelOne
observables:
- SentinelMemoryScanner.exe
- SentinelAgentCore.dll
- LdrpLoaderLock
- LdrpWorkInProgress
slug: dll-sideloading-sentinelone
tactic: defense-evasion
- name: Malicious Extension Integrity Bypassing
observables:
- Secure Preferences
- App-Bound encrypted hashes
- HMAC regeneration
- Kr3mlin4rt1st
slug: browser-extension-forgery
tactic: persistence
techniques:
- T1176
summary: KREMLIN is a Brazilian banking malware operation that uses multi-stage
JavaScript loaders to establish persistence via scheduled tasks and DLL sideloading.
The malware leverages Ethereum smart contracts as dead-drop resolvers to fetch
payload URLs, ultimately installing malicious browser extensions that forge Chromium's
Secure Preferences to steal session tokens.
severity: high
targets:
analyst:
name: Tier-2 analyst
role: analyst
endpoint:
category: endpoint
name: Endpoint telemetry (hb_ surfaces)
telemetry:
- endpoint
hunter:
agent: true
name: Hunt agent
tlp: clear
type: investigation
---
# KREMLIN Loader and Malicious Browser Extension Forgery
This hunt follows the KREMLIN infection chain across two phases. First, it identifies early beachhead markers: the temporary JavaScript loader files and the specific scheduled task used for Node.js persistence. After an agent triages these leads, the hunt pivots to technical payloads, looking for a signed SentinelOne binary sideloading an unsigned DLL. Finally, it uses stack-counting to identify rare processes modifying Chrome or Edge Secure Preferences to install malicious extensions and steal banking sessions.
## scope-browser-hosts
<!-- Scope to hosts with Chromium browsers -->
Focus the hunt on hosts with Chrome or Edge installed as they are the targets for the KREMLIN extension forgery.
```sqlite target=endpoint role=scoping
~~~yaml
expected: A list of hostnames with targeted browsers. Silence indicates no inventory,
meaning the hunt proceeds unscoped.
reads:
- device_hostname
- package_name
silence: not_evidence_of_absence
source: hb_software_inventory
verified: dry-run
verified_at: '2026-09-20'
~~~
SELECT DISTINCT device_hostname FROM hb_software_inventory WHERE (LOWER(package_name) LIKE '%chrome%' OR LOWER(package_name) LIKE '%edge%')
```
## early-indicators-parallel
<!-- Hunt early beachhead markers -->
parallel:
- → js-loader-files
- → persistence-task
join: → early-triage
## js-loader-files
<!-- Deceptive JavaScript loader creation -->
Detect the temporary scripts created to lure users into execution.
```sqlite target=endpoint role=enrichment params=(lookback_days=lookback_days, scope_hosts=scope_hosts)
~~~yaml
expected: Creation of scripts with the popup prefix. Silence means no such files were
logged, but they are often deleted quickly.
reads:
- device_hostname
- file_name
- file_path
- process_name
- time
silence: not_evidence_of_absence
source: hb_file_activity
verified: dry-run
verified_at: '2026-09-20'
~~~
SELECT device_hostname, file_name, file_path, process_name, time FROM hb_file_activity WHERE LOWER(file_name) LIKE 'popup_%.js' AND ('{{scope_hosts}}' = '' OR instr(',' || '{{scope_hosts}}' || ',', ',' || device_hostname || ',') > 0) AND time >= datetime('now', '-{{lookback_days}} days')
```
## persistence-task
<!-- Persistent Node.js updater task -->
Identify the specific scheduled task used by KREMLIN to maintain execution.
```sqlite target=endpoint role=detection-candidate params=(lookback_days=lookback_days, scope_hosts=scope_hosts, malicious_task_name=malicious_task_name)
~~~yaml
expected: The presence of the MicrosoftNodeRuntimeUpdater task. This is a high-confidence
indicator of persistence.
reads:
- device_hostname
- job_name
- job_cmd_line
- job_user_name
- time
silence: not_evidence_of_absence
source: hb_scheduled_job
verified: dry-run
verified_at: '2026-09-20'
~~~
SELECT device_hostname, job_name, job_cmd_line, job_user_name, time FROM hb_scheduled_job WHERE LOWER(job_name) = LOWER('{{malicious_task_name}}') AND ('{{scope_hosts}}' = '' OR instr(',' || '{{scope_hosts}}' || ',', ',' || device_hostname || ',') > 0) AND time >= datetime('now', '-{{lookback_days}} days')
```
## early-triage
<!-- Triage early indicators -->
```agent target=hunter
cite: required
context:
- js-loader-files
- persistence-task
max_iterations: 3
objective: Determine if hosts show evidence of the KREMLIN loader scripts or the specific
Node.js persistence task.
success_criteria: A per-host verdict with citations for artifacts.
tools:
- endpoint
```
## follow-on-indicators-parallel
<!-- Hunt follow-on technical payloads -->
parallel:
- → sentinel-sideloading
- → browser-forgery-prevalence
- → c2-dns-resolution
join: → final-triage
## sentinel-sideloading
<!-- SentinelOne DLL sideloading -->
Detect the use of SentinelMemoryScanner.exe to sideload an unsigned or missing-signature malware DLL.
```sqlite target=endpoint role=enrichment params=(lookback_days=lookback_days, scope_hosts=scope_hosts)
~~~yaml
expected: A row showing a security tool loading an unsigned DLL with a specific name.
This confirms the defense evasion stage.
reads:
- device_hostname
- process_name
- module_name
- module_signed
- time
silence: not_evidence_of_absence
source: hb_module_activity
verified: dry-run
verified_at: '2026-09-20'
~~~
SELECT device_hostname, process_name, module_name, module_signed, time FROM hb_module_activity WHERE LOWER(process_name) LIKE '%sentinelmemoryscanner.exe' AND LOWER(module_name) = 'sentinelagentcore.dll' AND (module_signed = 'False' OR module_signed IS NULL) AND ('{{scope_hosts}}' = '' OR instr(',' || '{{scope_hosts}}' || ',', ',' || device_hostname || ',') > 0) AND time >= datetime('now', '-{{lookback_days}} days')
```
## browser-forgery-prevalence
<!-- Rare browser preference modifications -->
Stack-count processes modifying Secure Preferences to find rare forgery events.
```sqlite target=endpoint role=baseline params=(lookback_days=lookback_days, scope_hosts=scope_hosts, known_browsers=known_browsers)
~~~yaml
baseline:
compare: first_seen
window: '{{lookback_days}}d'
expected: Any rare process modifying browser preferences is a high-confidence indicator
of forgery. Silence means no such tampering was caught.
prevalence:
by: device_hostname
key:
- process_name
rare_below: 3
reads:
- process_name
- device_hostname
- file_path
- time
silence: not_evidence_of_absence
source: hb_file_activity
verified: dry-run
verified_at: '2026-09-20'
~~~
SELECT process_name, COUNT(DISTINCT device_hostname) AS hosts, COUNT(*) AS touches, MIN(time) AS first_seen FROM hb_file_activity WHERE LOWER(file_path) LIKE '%\\secure preferences' AND NOT (instr(',' || '{{known_browsers}}' || ',', ',' || LOWER(process_name) || ',') > 0) AND ('{{scope_hosts}}' = '' OR instr(',' || '{{scope_hosts}}' || ',', ',' || device_hostname || ',') > 0) AND time >= datetime('now', '-{{lookback_days}} days') GROUP BY process_name HAVING hosts <= 3 ORDER BY hosts ASC
```
## c2-dns-resolution
<!-- C2 and payload domain resolution -->
Corroborate endpoint activity with network resolutions to campaign infrastructure.
```sqlite target=endpoint role=enrichment params=(lookback_days=lookback_days, scope_hosts=scope_hosts, c2_domains=c2_domains)
~~~yaml
expected: DNS resolutions to known C2 domains. Silence is expected if domains have
rotated.
reads:
- device_hostname
- query_hostname
- process_name
- time
silence: not_evidence_of_absence
source: hb_dns_activity
verified: dry-run
verified_at: '2026-09-20'
~~~
SELECT device_hostname, query_hostname, process_name, time FROM hb_dns_activity WHERE (instr(',' || '{{c2_domains}}' || ',', ',' || LOWER(query_hostname) || ',') > 0) AND ('{{scope_hosts}}' = '' OR instr(',' || '{{scope_hosts}}' || ',', ',' || device_hostname || ',') > 0) AND time >= datetime('now', '-{{lookback_days}} days')
```
## final-triage
<!-- Synthesize the intrusion story -->
```agent target=hunter
cite: required
context:
- early-triage
- sentinel-sideloading
- browser-forgery-prevalence
- c2-dns-resolution
max_iterations: 5
objective: Combine early-stage verdicts with sideloading, C2 activity, and browser
tampering results to confirm a KREMLIN intrusion.
success_criteria: A final verdict of malicious for hosts showing multiple stages of
the attack chain.
tools:
- endpoint
```
## route-on-verdict
<!-- Route on final verdict -->
if~: "the final-triage verdict identifies multiple stages of the KREMLIN infection on the same host" (confidence: high, judge=hunter)
then: → contain-and-remediate
indeterminate: → analyst-review
unavailable: → analyst-review (blind_spot: no-module-telemetry)
else: → close-out
## contain-and-remediate
<!-- Contain and Remediate Infection -->
```action target=endpoint
~~~yaml
approval: required
~~~
Isolate the endpoint to prevent banking session theft. Delete the 'MicrosoftNodeRuntimeUpdater' scheduled task and stop the SentinelMemoryScanner.exe process if it is still running.
```
→ analyst-review
## analyst-review
<!-- Analyst review and validation -->
```manual target=analyst
Review the browser extension directories for the author string 'Kr3mlin4rt1st'. Verify the Secure Preferences file for regenerated HMACs or unexpected extension IDs. Confirm whether any banking sessions were successfully exfiltrated in proxy logs.
```
→ close-out
## close-out
<!-- Close out hunt -->
```manual target=analyst
Document the infection chain artifacts. Update the C2 domain list if any new resolvers were discovered during the hunt.
```
→ end
Run it
Take this hunt into your environment.
Open it in Huntbase to run every step against your own connections, with Scout weighing the evidence and your analysts in command. Or take the open hunt.md file anywhere that reads the format.
Machine-drafted by huntbase-hunt-generation using hb_google/gemini-3-flash-preview, gated by dry-run, lint, critic, then reviewed by a person.