Executive summary

The 74 MB sample is a hijacked copy of MultiCommander’s MultiUpdate.exe with a 347 KB encrypted Lumma payload smuggled inside its .reloc section, and 70 MB of 0xCC padding bolted onto the end purely to defeat AV/sandbox file-size limits and the published YARA rule’s filesize < 5000KB condition.

This binary is a textbook 2024-era LummaC2 delivery artifact. Four structural tricks combine to defeat naive defenses:

  1. Legitimate-software disguise. The carrier is a recompiled copy of the open-source MultiCommander auto-updater. PDB path, manifest identity (Microsoft.Windows.AutoUpdate), embedded XML schema, version-string lookups against multicommander.com, and ASUS-related strings all survive in the binary. A defender allowlisting on those signatures would be wide open.
  2. Lifted Authenticode signatures from the leaked NVIDIA certs. The carrier is dual-signed with both NVIDIA Corporation code-signing certificates leaked in the March 2022 Lapsus$ breach (serials 14781BC8…DCC518 and 7BC15AF2…8642DE). Both signatures fail cryptographic verification (INVALID_DIGEST) — this is signature lifting, not signing-with-leaked-key — but EDR and triage tools that check “is signed by NVIDIA” without validating the digest will trust the file. See §4.
  3. Encrypted payload concealed in .reloc. The PE’s relocation section legitimately holds 33 KB of base relocations (entropy 6.65). A further 347 KB of high-entropy data (entropy 7.51) is appended inside the same section, well past the boundary declared in the BaseRelocationTable data directory. Static analysers that trust the directory miss it; the runtime loader needs only the legitimate relocs and ignores the trailer.
  4. Massive 0xCC overlay padding. 73,513,369 bytes of INT3 bytes pad the file to 74 MB. The padding has zero functional purpose. It exists to (a) exceed AV file-size scanning limits, (b) push the file beyond the 32 MB / 50 MB upload caps used by sandboxes and bazaars, and (c) silently break the public YARA rule (win.lumma_w1.yar, Embee Research) which requires filesize < 5000KB.

Family attribution is certain. A companion artifact System.txt recovered from the same engagement is a panel-side exfiltration dump (“LummaC2, Build Oct 9 2023”, victim SINTHUJAN, egress 185.160.247.17 / CH) — confirming the Lumma operator chain directly from infrastructure-side telemetry.

74 MB
Delivered file size
1.6 %
Of file is real PE
347 KB
Encrypted payload

Engagement timeline

Q1 2025. An analyst at the customer’s site loads a major Swiss news publisher in their browser. A malvertising creative served through DoubleClick’s ad rotation drops the carrier into Chrome’s on-disk cache. Four hours later, the SOC’s Splunk surfaces the activity. Eleven days after that, forensics confirms LummaC2.

This section reconstructs the engagement narrative end-to-end — from drive-by drop, through SOC detection, to forensic confirmation and IOC deployment. The customer is a financial-services organisation; their identifying details, hostnames, internal IPs, and usernames are redacted. Sample family attribution and IOCs are unchanged from the original engagement record.

Customer & environment

CustomerFinancial-services organisation (redacted)
Affected hostSingle managed Windows endpoint (hostname redacted)
Affected userOne user account (redacted)
SOC stackSplunk SIEM, Sysmon-grade endpoint telemetry, EDR with content-control on user-writable execution paths
Date of compromise10 January 2025, 09:44:45 (T+0)
Date of confirmation21 January 2025, 22:35 (T+11d)
OutcomeContained on a single host. No customer or transactional data exfiltration. User-account credentials presumed compromised; reset across the user’s browser-stored estate. Host preserved offline; not returned to service.
Regulatory notificationNot required — no reportable data category impacted.

Timeline

Times are local (CET). Drop time is taken from Chrome cache file-creation timestamps; SOC times are from Splunk event ingestion. T+ deltas are measured from drop.

TimeEvent
10.01.2025 09:44:45
T+0
Carrier dropped to Chrome cache. data_4 file created at ...\AppData\Local\Google\Chrome\User Data\Default\Cache\Cache_Data\. An adjacent JS cache entry from ad.doubleclick.net (...\Code Cache\js\47aa920d2b1e1d49_0) is written in the same second — the malvertising redirector. T1583.006 / T1189
10.01.2025 09:45:04 First suspicious outbound connection from the host. EDR flags and partially blocks the activity. The carrier itself remains dormant on disk.
10.01.2025 10:56:47
10.01.2025 11:36:48
Two further bursts of suspicious connections over the next ~110 minutes. Network signature consistent with browser-context implant attempting outbound resolution.
10.01.2025 14:00:17
T+4h 16m
First suspicious event surfaces in SOC Splunk monitoring. The correlation that finally fires keys on the outbound-connection pattern, not the file-creation event. Detection lag: 4 hours, 16 minutes from drop.
10.01.2025 14:02:07 Initial analyst review begins. Splunk → analyst handoff: ~2 minutes.
10.01.2025 14:11:45 Formal IR process activated. Analyst → IR activation: ~10 minutes.
10.01.2025 15:18:10 Extended threat-hunting initiated across the customer estate.
13.01.2025 11:00
T+3d
Affected host received by the forensics team for offline analysis.
13.01.2025 16:17:30
13.01.2025 16:17:48
Two suspicious WMI queries surface during forensic timeline reconstruction. Pattern consistent with Lumma’s host-profiling step (cf. §8 Anti-analysis for the in-implant equivalent).
14.01.2025 15:30 Forensic image acquired.
15.01.2025 11:30 Formal forensic investigation opens.
21.01.2025 22:35
T+11d
Carrier confirmed as malicious binary. YARA hit on Florian Roth’s SUSP_NVIDIA_LAPSUS_Leak_Compromised_Cert_Mar22_1 — the leaked-certificate signature (cf. §4 Carrier anatomy).
22.01.2025 Detailed binary analysis (the substrate of this report) and proactive estate-wide threat hunt with extracted IOCs begin.
Attack and detection timeline showing the 4-hour-16-minute detection gap. Two horizontal lanes — adversary actions on top, defender visibility on the bottom — against a single time axis. The adversary lights up at 09:44 with a drop to Chrome cache, followed by beacon attempts and bursts through 11:37. The defender lane is silent until 14:00, when Splunk surfaces the alert. Analyst pickup at 14:02; formal IR at 14:11; threat hunt at 15:18; forensic image at +3 days; YARA confirmation at +11 days. FIG. 02 · ATTACK & DETECTION TIMELINE — SAME-DAY VIEW 10 JAN 2025 · CET BLIND SPOT · 2 h 23 m SOC PICKUP · 11 m FORENSIC CONFIRM · 11 d 09:44 09:45 10:57 11:37 14:00 14:02 14:11 15:18 +3 d +11 d ADVERSARY DROP data_4 → Chrome cache Beacon Burst Burst [ DORMANT ] DEFENDER EDR partial-block (loud signals suppressed) SPLUNK surfaces alert Analyst IR formally activated Hunt Forensics image acquired CONFIRMED YARA hit on carrier ADVERSARY ACTION DEFENDER ACTION DETECTION-GAP ZONE CONTAINMENT POINT
Two lanes, one timeline. The adversary lights up first (drop, beacon, bursts) but EDR partial-blocks suppress the loudest network signals. The Splunk correlation that finally fires keys on outbound-connection patterns rather than file creation in cache — costing 4 hours, 16 minutes of latency. Once it surfaces, analyst pickup is 2 minutes and formal IR another 10. Forensic confirmation arrives 11 days later.
4 h 16 m
Drop → first SOC alert
11 m
Splunk surface → formal IR
11 d
Drop → binary confirmed

Initial access — malvertising via DoubleClick

The infection vector was a malvertising creative served through DoubleClick’s ad rotation on a major Swiss news publisher’s site. The publisher itself was not compromised — the site was clean — but the creative inside the ad-network rotation carried a malicious script. The cached request itself, surfaced from the forensic image, shows the full ad.doubleclick.net URL with its query parameters — and a https://nzz.ch/ Referer header at the bottom, corroborating the publisher path:

Forensic strings panel showing the full ad.doubleclick.net URL with query parameters (gdpr_consent, ad-exchange keys, AUDIENZZAG identifier), ending with https://nzz.ch/ as the Referer header.
The cached request decoded from Chrome’s on-disk store. Long-tailed query string (audience identifier, GDPR consent, ad-exchange keys, click trackers), and the https://nzz.ch/ Referer at the bottom — the trail back to the publisher whose ad rotation served the malicious creative.

At the time of the incident, only 1 of 96 vendors on VirusTotal flagged the associated URL as malicious. Content-type was text/javascript;charset=UTF-8: a redirect or loader, not the carrier itself.

VirusTotal scan result for the ad.doubleclick.net URL: 1 of 96 vendors (Quttera) flagged it as malicious. Content type text/javascript; charset=UTF-8.
VirusTotal at the time of the engagement — the ad.doubleclick.net URL flagged malicious by exactly one of 96 vendors (Quttera). Status 200, content type text/javascript; charset=UTF-8. The ad-network URL is the redirector, not the carrier; the payload itself lands further downstream in Chrome cache.

The lone detection was Quttera. Their reasoning is a category-by-category dump of why the URL fell out of trust — poor reputation, “phishing target name” overlap, blacklisted-country hosting, unusual domain shape, hosting-service signals, low popularity rank. None are smoking guns individually; together they were enough for one engine, and only one, to call it.

Quttera threat-category reasoning for the doubleclick.net URL: Poor Reputation, Phishing Target Name, Commonly Compromised Sites, High Count of dots, Unusual hyphen in server name, Blacklisted Country, Unusual Domain, Hosting Service, Site-Number Only, Unusual http/https usage, Fake domain, URL shortener, Non-standard Port, Dynamic Name, Low Popularity Rank, Site Reputation.
Quttera’s reasoning chain. The signals are diffuse — reputation, domain shape, hosting infrastructure — not a payload match. This is the kind of finding the other 95 engines class as “suspicious noise” and drop on the floor.

Two artefacts were dropped to the user’s Chrome profile within the same second:

PathWhat it is
...\AppData\Local\Google\Chrome\User Data\Default\Code Cache\js\47aa920d2b1e1d49_0 Cached JavaScript fetched from ad.doubleclick.net. The loader/redirector that initiated the drive-by.
...\AppData\Local\Google\Chrome\User Data\Default\Cache\Cache_Data\data_4 Lumma carrier (the binary this report dissects; SHA-256 eff51f99…abba). Stored dormant in Chrome cache awaiting later execution. Detected later via YARA.
Why “cache as staging ground” matters. Browser cache directories are not typically scanned with the intensity of %TEMP%, Downloads, or %APPDATA%\Roaming. The carrier sat in Cache_Data\ silently, with no execution, until a downstream trigger. A defender hunting on user-writable execution paths must include browser cache directories in scope — the path ...\Chrome\User Data\Default\Cache\ is normal for fetched bytes but abnormal for a PE32 GUI executable.

Detection & containment

The 4-hour gap between drop and first SOC surfacing is the central defensive lesson of this engagement. Not because the SOC was slow — once the alert surfaced, analyst pickup was 2 minutes and formal IR activation another 10 — but because the initial drive-by activity was partially blocked at the EDR layer, which suppressed the loudest network indicators while the carrier itself remained dormant on disk. The Splunk correlation that ultimately fired keyed on outbound connection patterns, not the file-creation event.

Once the IR process activated, the host was network-isolated, pulled offline for forensic preservation, and never returned to production. The user account was migrated to a clean replacement endpoint. Browser-stored credentials were treated as compromised: passwords rotated, sessions revoked, MFA re-enrolled across affected services. The estate-wide threat-hunt cleared all other endpoints of indicators.

Outcome

Customer-data exfiltrationNone confirmed. The sample never reached its post-execution profile (it was contained before stealer behaviour completed against the user’s logged-in sessions).
CredentialsTreated as compromised across the user’s browser-stored estate (autofill, cookies, password manager exports). Reset, revoke, re-enrol. No fraudulent activity observed in the post-incident monitoring window.
Lateral movementNone. Estate-wide hunt against extracted IOCs returned clean.
Other infectionsNone on the customer estate. The sample was confined to a single endpoint.
Regulator notificationNot required. No reportable data category impacted; no PCI / GDPR trigger.
Threat-intel uplift2,000+ Lumma-related IOCs (file hashes, C2 domains, infrastructure IPs) extracted from the binary analysis and threat-hunt fed back into customer detection content.
Public-disclosure lead timeThis variant was identified and characterised in the engagement approximately 1.5 months before Microsoft published its public advisory on the same Lumma Stealer iteration.
Affected hostPreserved offline as forensic reference. Not returned to service.
The detection-gap takeaway. Browser-cache delivery + EDR partial-block + dormant-on-disk staging is a coordinated combination that buys the operator a multi-hour head start. The SOC’s 4-hour latency was not a process failure — it was the cost of a correlation rule that relied on outbound-connection volume rather than file-creation-in-cache. In environments where browser-driven workflows are the dominant attack surface, the file-creation rule is the cheaper hedge. The new detection content in §10 — carrier-structure YARA, Sysmon Sigma, Splunk SPL queries — was developed and deployed off the back of this engagement.

Sample identification

Delivered artifact (74 MB padded)

File typePE32 GUI executable, Intel i386, 5 sections, MFC-based
Size74,715,545 bytes (71.25 MiB)
MD5a3be0b0ebbf9428015cacc27cf5d51a7
SHA-123f0f30e2bc4fb1308c01328e951b1681f439d46
SHA-256eff51f995cd6463cd9b3a2ea4a14cc85e3cc5c1b5b71db6d90765b3df175abba
PE TimeDateStamp2024-12-23 13:44:40 UTC (forgeable)
ImageBase / EP RVA0x00400000 / 0x00046ffc
Digital signatureBroken — security data dir VA=0x473d7d8 Size=0x39c0 points beyond EOF
LinkerMicrosoft VS 2022 Pro, MSVC 14.42 (ATL/MFC, statically linked libxml2)
PDB pathD:\Projects\MultiCommander\BuildOutput\Output\Win32\Release v143\MultiUpdate\MultiUpdate.pdb
Manifestname=“Microsoft.Windows.AutoUpdate” description=“MultiUpdate”

Stripped carrier (overlay removed)

Created byTruncating to first 1,202,176 bytes (sum of section raw sizes)
Size1,202,176 bytes (1.15 MiB)
SHA-256c466795354007a604fa1805b6d97b6f3e43179c85544594fe365f22ede8fe0a6
UseSubstrate for radare2 / static analysis. Now under the 5000 KB threshold of the published YARA rule (still does not match — see §10)
Engagement corroboration

Family attribution: certain

The companion file System.txt recovered from the same engagement is a real LummaC2 panel exfil dump (“LummaC2, Build Oct 9 2023”, victim SINTHUJAN, egress 185.160.247.17 / CH) — confirming the Lumma operator chain directly from infrastructure-side telemetry recovered during the response.

Distribution model: Malware-as-a-Service on Russian-language forums, attributed to threat actor Shamel / Lumma (Eastern European cybercriminal ecosystem).


Carrier anatomy

File-level layout (74 MB delivered artifact)

Bar A — true-to-scale view of the 74 MB file. The PE image (1.2 MB of code + data) is the thin sliver on the left; everything else is 0xCC overlay padding.

PE1.2 MB · 1.61 % of file
0xCC overlay padding70.1 MiB · 98.39 % · 73,513,369 bytes

Bar B — zoomed into the 1.2 MB PE image (overlay removed, this is where every meaningful byte lives). Segment widths are proportional to raw section sizes within the image.

.text50.6 % · 594 KB
.rdata13.8 % · 162 KB
.data1.0 % · 11 KB
.rsrc2.8 % · 33 KB
.reloc legit2.8 % · 33 KB relocs
.reloc trailer28.9 % · 347 KB ENCRYPTED

The encrypted .reloc trailer (red, right side of Bar B) is almost a third of the actual binary — yet it is invisible in Bar A because of the overlay bloat that hides it from file-size-bounded scanners. That asymmetry is the whole point of the technique.

PE section table

NameVAddrRawSizeEntropyNote
.text0x004010000x00094a006.6849MFC application code (carrier)
.rdata0x004960000x000288005.1722Read-only data, RTTI, manifest, vftables
.data0x004bf0000x00002e004.4563Initialised globals
.rsrc0x004c90000x000084004.7417MFC dialog/string resources
.reloc0x004d20000x0005d0007.557933 KB legit relocs + 347 KB encrypted trailer

Overlay characterisation

Start (file offset)0x00125800 (= last raw section end)
End0x04747999 (EOF)
Length73,513,369 bytes (≈ 70.1 MiB)
Content0xCC fill (INT3 instruction byte). First-1 MB and middle-1 MB chunks contain 8 distinct byte values; the last ~14 KB at file offset 0x473d7d8 is a fully-formed Authenticode signature blob lifted from a legitimately NVIDIA-signed binary (see §4 Authenticode signature)
Entropy (first 1 MB)0.0775
Entropy (last 1 MB)4.2181
FunctionDefeat AV/sandbox file-size limits; bypass YARA rules with filesize conditions; inflate uploads beyond bazaar caps

The MultiCommander disguise

The carrier is built from MultiCommander’s open-source MultiUpdate.exe auto-updater. The threat actor took the legitimate source, added the unpacking stub plus the encrypted payload, and recompiled. Multiple inherited artefacts survive in the binary — including the embedded application manifest:

<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
  <assemblyIdentity version="1.0.0.0" processorArchitecture="X86"
                    name="Microsoft.Windows.AutoUpdate" type="win32"/>
  <description>MultiUpdate</description>
  <dependency><dependentAssembly>
    <assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls"
                      version="6.0.0.0" processorArchitecture="X86"
                      publicKeyToken="6595b64144ccf1df" language="*"/>
  </dependentAssembly></dependency>
  <trustInfo><security><requestedPrivileges>
    <requestedExecutionLevel level="asInvoker" uiAccess="false"/>
  </requestedPrivileges></security></trustInfo>
</assembly>

Selected UTF-16 strings recovered from the carrier (genuine MultiCommander UI text):

"Multi Commander-http://multicommander.com/updates/version.xml"
"Failed to copy existing version to backup. Turn off Backup setting if you want to..."
"Warning! Failed to create directory \"%s\". But the folder might already exists..."
"Warning. A newer MultiUpdate.exe was found on disk. It is recommended that you run that instead."
"%s' is running. It can't be running when it is about to be updated."
"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; GTB5; .NET CLR 1.1.4322; .NET CLR 2.0.50727)"
"Or reinstall a new version that you download from ASUS."
"D:\Projects\MultiCommander\BuildOutput\Output\Win32\Release v143\MultiUpdate\MultiUpdate.pdb"

Suspicious imports (carrier surface)

The visible import table is the carrier’s surface and is intentionally benign-looking. The payload’s real imports are resolved dynamically post-decryption via LoadLibraryA/W + GetProcAddress. Selected suspicious-but-explainable entries from the 433-function visible IAT:

DLLFunctionCarrier usePayload abuse
KERNEL32LoadLibraryA / LoadLibraryWUpdater plug-insResolve payload API set at runtime
KERNEL32GetProcAddressDynamic API resolution (T1129)
KERNEL32WinExec / CreateProcessWAuto-relaunchLaunch second-stage payload
KERNEL32OpenProcessUpdater process checkProcess inspection / token theft
KERNEL32IsDebuggerPresent(none)Anti-debug (T1622)
ADVAPI32RegCreateKeyExW / RegSetValueExWUpdater configPersistence via Run key (T1547.001)
SHELL32ShellExecuteW / ShellExecuteExWOpen release notes URLDrop-and-execute child stages
WS2_32WSAStartupUpdate downloadC2 communication primitive
Defensive implication

Allowlisting on file/process name fails.

Allowlisting Lumma carriers by file/process name (MultiUpdate.exe) or by inherited string content is fragile — the actor controls those. Enforce signed-publisher checks (genuine MultiCommander binaries are Authenticode-signed; this one is not), or behavioural detection (an updater that unpacks code from .reloc, beacons to Steam, then to .shop domains).

Authenticode signature: lifted leaked NVIDIA certs

The PE has an Authenticode signature blob at file offset 0x473d7d8 (size 0x39c0, 14,784 bytes — well within the file, not past EOF as one might conclude from the file-size arithmetic alone). The blob is a fully-formed PKCS#7 SignedData structure. It parses cleanly, and what it contains is the interesting part: the carrier is dual-signed with two NVIDIA Corporation code-signing certificates. The SHA-1 leaf cert is one of the two confirmed March 2022 Lapsus$ leaked NVIDIA certs; the SHA-256 partner is the matching dual-sign cert that travelled with it in the lifted PKCS#7 blob.

#DigestCert serialSubject CN/OUValidityIssuer
0 SHA-1 14781BC862E8DC503A559346F5DCC518 NVIDIA Corporation 2015-07-28 → 2018-07-26 VeriSign Class 3 Code Signing 2010 CA
1 SHA-256 7BC15AF21367D0758BEDDCCA118642DE NVIDIA Corporation, OU=IT MIIS 2015-07-13 → 2018-07-13 Symantec Class 3 SHA256 Code Signing CA

Both signatures countersign with a Symantec Time Stamping Services Signer cert at signing time 2017-03-16 23:31:05 UTC — a date that falls comfortably inside both leaf certs’ validity windows, but more than seven years before this binary’s PE TimeDateStamp of 2024-12-23. That gap is the smoking gun: the timestamp didn’t come from a legitimate TSA round-trip on this binary in 2017 because this binary didn’t exist in 2017. The entire signed PKCS#7 blob was lifted from a real NVIDIA-signed driver or utility and grafted onto the carrier.

Cryptographic verification confirms it. Using signify:

$ python3 -c "
from signify.authenticode import AuthenticodeFile
with open('eff51f99...abba.exe', 'rb') as f:
    af = AuthenticodeFile.from_stream(f)
    print(af.explain_verify())"

AuthenticodeVerificationResult.INVALID_DIGEST
The expected hash does not match the digest in the indirect data.

The hash baked into the signed Authenticode structure does not match the actual bytes of this PE. Windows would refuse to validate either signature. So why bother lifting them?

Defensive implication

Metadata-only signature checks see “NVIDIA Corporation” and stop asking questions.

Plenty of EDR pipelines, triage tools, and human analysts read the certificate metadata without performing the cryptographic digest verification. To them, this file is signed by NVIDIA Corporation — full stop. Lifted signatures cost attackers nothing (no need to compromise the private key, just copy a signed blob from any GeForce driver) and produce a trust signal that bypasses metadata-based reputation checks. Cryptographic verification of the Authenticode signature, not just the certificate chain, is the only check that catches this pattern.

The two cert serials and SHA-1 thumbprints are durable IOCs for this campaign cluster — reused across many lifted- signature samples in the wild — and are reproduced in §12 IOCs.

Common misreading: is the 70 MB padding encrypted, packed, or a bound browser?

The file size makes the overlay tempting to read as a hidden second-stage binary — a packed Lumma payload, a bundled browser, or some other concealed cargo. It isn’t. Five independent static checks all point the same way:

  1. Entropy. 4 KB-block entropy across the 67.5 MB middle of the overlay sits at 0.0775 bits/byte. Encrypted data is at ~7.95, compressed (gzip / zip / lzma) at ~7.5, mixed-section PE at 6–7. There is no encryption or compression scheme that produces 0.08.
  2. Whole-file block scan. rahash2 -B -b 4096 -a entropy over the full 75 MB file flags only 77 blocks at entropy ≥ 7.0. Every one of them lives inside the .reloc trailer (the known 347 KB Lumma payload, §5) or the trailing 14 KB lifted NVIDIA Authenticode blob (legitimately high entropy because real signed PKCS#7 contains compressed signature material). The 67.5 MB main body has zero high-entropy blocks.
  3. Magic-byte hunt. Searches across the overlay for MZ, \x7fELF, PK\x03\x04, gzip, 7-Zip, RAR, NSIS, Inno Setup, single-byte-XOR’d MZ, LZMA, PNG / JPEG / WebM — zero hits. A bound browser would leave at least the wrapper or inner-PE signature.
  4. Imports. VirtualAlloc, VirtualProtect, and the Crypt* family are not in the static IAT. Only VirtualQuery is. The carrier as-shipped has no static path to allocate executable memory or flip a page to RX — which is what hosting an embedded binary would require.
  5. Structure. The overlay isn’t random fill, but it also isn’t data. A 15-byte template (f8 64 6e 09 · eight 0xCC · 90 b0 80) repeats identically every 1,035 bytes — ~71,000 cycles across 67.5 MB. Every 1 MB chunk has the same byte-distribution fingerprint: 8 distinct values, entropy 0.0775, exactly 7,091 non-0xCC bytes. Identical statistics across 68 megabytes is the signature of a custom padding tool, not data.
Verdict

The bound-browser hypothesis fails on entropy, magic bytes, imports, and structure.

The actual payload is the 347 KB encrypted blob in the .reloc trailer (§5). The 70 MB overlay is inert padding produced by tooling. The 7-byte beacon (f8 64 6e 0990 b0 80) is plausibly a campaign or build fingerprint and is itself a candidate for a structural YARA hunt rule independent of any payload changes. The padding pattern’s slight non-0xCC content also defeats the most naive compression-ratio detection (pure 0xCC would gzip 70 MB to ~30 bytes, which is itself anomalous).


The .reloc trick

The .reloc section is the most interesting part of the file and is where Lumma is actually hiding. The PE’s BaseRelocationTable data directory declares VA=0xd2000, Size=0x8224 — i.e. the legitimate relocation table is exactly 33,316 bytes. But the section’s raw size on disk is 0x5D000 (380,928 bytes). That leaves 347,612 bytes of “extra” data inside the section that the PE loader will happily map into the process but that the relocation logic will never touch.

Per-4-KB-window entropy across .reloc

Sliding entropy reveals exactly where the encrypted blob lives — clearly bracketed by zero-filled padding on both sides:

section_off  file_off    entropy  visualisation
0x000000   0x00c8800   6.752   ███████████████████████████   ┐
0x002000   0x00ca800   6.761   ███████████████████████████   │ legitimate
0x005000   0x00cd800   5.792   ███████████████████████       │ relocation
0x008000   0x00d0800   7.003   ████████████████████████████  │ table (33 KB)
0x009000   0x00d1800   5.391   █████████████████████   ← drop-off
0x00a000   0x00d2800   4.181   ████████████████        ← zero-filled gap
0x00b000   0x00d3800   7.256   █████████████████████████████ ┐
0x00c000   0x00d4800   7.396   █████████████████████████████ │ ENCRYPTED
0x00d000   0x00d5800   7.284   █████████████████████████████ │ LUMMA
0x00f000   0x00d7800   7.313   █████████████████████████████ │ PAYLOAD
…                                                            │ (~339 KB,
0x051000   0x0119800   6.493   █████████████████████████     │  steady ~7.4
0x054000   0x011c800   6.850   ███████████████████████████   │  entropy)
0x05c000   0x0124800   7.195   ████████████████████████████  ┘
                                                  ← 317-byte zero alignment

Encrypted blob coordinates

Section offset (start)0x8400
File offset (start)0x000d0c00
Virtual address (start)0x004da400
Length346,819 bytes (0x54ac3)
Entropy (just the blob)7.5097
Padding before blob0x8224 → 0x8400 (476 bytes of zeros — alignment)
Padding after blob0x5cec3 → 0x5d000 (317 bytes of zeros — alignment)

First 256 bytes of the encrypted payload

0x004da400 4f 81 f2 17 f5 50 5f 33 f2 87 c3 c1 c8 0c f7 de O....P_3........ 0x004da410 2b f1 c1 ce 0f 81 e8 d0 cb 19 ef f7 d1 46 2b dd +............F+. 0x004da420 33 dc f7 de 33 d9 c1 c1 1d 87 f2 33 c4 33 c4 87 3...3......3.3.. 0x004da430 f2 c1 c9 1d 33 d9 f7 de 33 dc 03 dd 4e f7 d1 81 ....3...3...N... 0x004da440 c0 d0 cb 19 ef c1 c6 0f 03 f1 f7 de c1 c0 0c 87 ................ 0x004da450 c3 33 f2 81 f2 17 f5 50 5f 47 be a4 47 05 00 c1 .3.....P_G..G... 0x004da460 c2 1d f7 d6 c1 c3 0f 81 ea 56 e0 1e da 87 c3 c1 .........V...... 0x004da470 ce 13 81 e9 b6 62 f3 8e 42 f7 d1 c1 c0 02 c1 c8 .....b..B....... 0x004da480 02 f7 d1 4a 81 c1 b6 62 f3 8e c1 c6 13 87 c3 81 ...J...b........ 0x004da490 c2 56 e0 1e da c1 cb 0f f7 d6 c1 ca 1d 87 c1 81 .V..............

No plaintext signatures (no "expand 32-byte k" ChaCha sigma, no AES S-box, no ZIP/PK magic). Byte distribution is near-uniform across all 256 values, consistent with a stream cipher or compressed-then-XOR’d output. Recent Lumma analyses (Q4 2024) document ChaCha20 as the in-payload string cipher, with the loader stage typically using a custom XOR-rotate construction; expect either to apply here.

Why this works against most tooling

Three independent layers of obscurity.

PE viewers that read the Base Relocation Table directory only see 33 KB and conclude .reloc is normal. Section-entropy heuristics that average across the section are diluted by the legitimate relocs and look “OK-ish” (overall .reloc entropy 7.56 is borderline). Static unpackers typically follow Authenticode / overlay / TLS callback / packer-section-name patterns; “data hidden after the legitimate reloc table” is unusual and unlikely to be spotted automatically.


Reverse engineering with radare2

All commands below were run against /tmp/lumma_stripped.exe (overlay-stripped derivative) with radare2. The stripped binary is functionally identical to the carrier — the overlay does not affect any code or data referenced by the PE.

Triage

$ r2 -e bin.cache=true /tmp/lumma_stripped.exe
[0x00446ffc]> aaa             # full analysis pass
[0x00446ffc]> iI               # binary info
[0x00446ffc]> iS               # sections + entropy bars
[0x00446ffc]> ii~LoadLibrary   # filter imports
[0x00446ffc]> aflc             # function count
3017

3,017 functions is a lot for a 596 KB .text; consistent with a fully-built MFC application. The unpacking stub is hidden among thousands of legitimate framework functions — security-through-obscurity layered on top of the section trick.

Entry point: standard MSVC CRT bootstrap

[0x00446ffc]> pd 4 @ entry0
        ;-- entry0:
┌ 337: entry0 ();
│       0x00446ffc      e87a0a0000     call fcn.00447a7b   ; __scrt_common_main_seh / cookie init
│       jmp 0x446e80                                       ; __scrt_common_main → mainCRTStartup → WinMain

The entry point is a normal MSVC C runtime startup. The first call (0x447a7b) is the __security_init_cookie-style cookie initialiser; control then jumps into the CRT pre-main and ultimately reaches the carrier’s WinMain. The unpacking stub is not at the entry point — that would be too obvious. It is reached after normal MFC initialisation, hidden among the genuine update logic.

The PE-section walker (function 0x00447014)

radare2’s analyser identified a function at 0x00447014 that walks the loaded image’s PE header and iterates the section table. This is the classic shape of “find .reloc at runtime, then read the trailer at offset 0x8400”:

[0x00447014]> pdf @ 0x447014
┌ 66: fcn.00447014 (int32_t arg_8h, uint32_t arg_ch);
│   0x00447014   55             push ebp
│   0x00447015   8bec           mov ebp, esp
│   0x00447017   8b4508         mov eax, dword [arg_8h]    ; image base
│   0x0044701b   8b483c         mov ecx, dword [eax + 0x3c] ; e_lfanew
│   0x0044701e   03c8           add ecx, eax                ; → IMAGE_NT_HEADERS
│   0x00447020   0fb74114       movzx eax, word [ecx + 0x14]; SizeOfOptionalHeader
│   0x00447024   8d5118         lea edx, [ecx + 0x18]       ; → first section header
│   0x00447029   0fb74106       movzx eax, word [ecx + 6]   ; NumberOfSections
│   0x0044702d   6bf028         imul esi, eax, 0x28         ; * sizeof(IMAGE_SECTION_HEADER)
│   ...
│   0x00447052   mov eax, edx                                ; return matching SECTION_HEADER*
│     ret                                                    ; or NULL if none

This is functionally an RVA-to-section lookup: given an image base and a target RVA (arg_ch), iterate the section headers and return the one that owns that RVA. It is a building block used both by legitimate code (manifest resource lookup, etc.) and by the unpacking stub when it needs to translate “where did the loader put my .reloc data?” into a usable address.

References from .text into the encrypted region

A scan of all 32-bit immediates in .text for values inside the .reloc VA range yields 80 hits, of which the highest-multiplicity targets are:

.reloc 0x004e00fe   6 refs
.reloc 0x004e00fb   6 refs
.reloc 0x00507883   2 refs
.reloc 0x004ee800   2 refs
.reloc 0x005046c7   2 refs
… 75 single-reference targets …
TOTAL: 80 immediates pointing at ≥ 0x4da400 (the encrypted blob)

Multi-referenced targets like 0x4e00fe (offset 0x2E0FE inside .reloc) are likely small lookup tables or key material that the unpacking stub indexes into multiple times during decryption.


Execution flow

End-to-end kill-chain summary. Each row is a phase; technique IDs map each step to §9 MITRE ATT&CK coverage.

Initial access — delivery
Malicious ad served via doubleclick.net (NZZ)T1189drive-by malvertising vector
Drive-by download drops data_4 to Chrome cacheT1204.002user lands on staged carrier
On-host execution
User executes carrier — the “MultiUpdate.exe” disguiseT1036.005
MSVC CRT bootstrapentry0 → __scrt_main → WinMainunpacker not at entry point; reached after MFC initialisation
Malicious post-exploitation
Unpack .reloc trailer — read 347 KB blob @ 0x4da400, decrypt to second-stage PET1027 / T1027.002
Dynamic API resolutionLoadLibrary + GetProcAddress rebuilds the stealer’s IAT post-decryptionT1129
PersistenceHKCU\…\Run + scheduled taskT1547.001
Host profiling — WMI SELECT * FROM AntiVirusProduct; HWID, OS, GPU, localeT1082 / T1518.001
Steam dead-drop lookupGET steamcommunity.com/profiles/<id>, parse bio → resolved C2 domainT1102.001
C2 beacon (.shop) — HTTPS POST init / config pullT1071.001 / T1573
Credential harvesting — browser SQLite, cookies, wallets, clipboardT1555.003 / T1115 / T1539
Exfiltration
Exfiltration — ZIP archive POSTed back to the active C2 (loops to C2 beacon above)T1041

Delivery On-host execution Malicious post-exploitation Exfiltration


Anti-analysis & sandbox evasion

Lumma’s anti-analysis stack is layered, and none of it is in the visible PE. The lifted MultiCommander .text section runs only the genuine MultiCommander startup sequence (MSVC CRT bootstrap, MFC CWinApp init, message-loop entry); we confirmed this end-to-end with radare2 plus an API-monitor trace. Every check below executes after the .reloc trailer cipher unpacks the second stage, which means none of these checks can be located or patched statically — they live inside the encrypted blob. The behaviours are reconstructed from dynamic execution traces and from public sandbox detections of the unpacked stage matched by Kevin O’Reilly’s Lumma.yar (kevoreilly/CAPEv2).

Why the visible PE looks innocent

The cpuid block at 0x447dae is benign MSVC CRT, not anti-VM.

Five cpuid instructions cluster between 0x447dae and 0x447ec8 — classic anti-VM real estate. They’re not. Look at the operations that follow each cpuid: xor edi, 0x756e6547 (“Genu”), xor eax, 0x49656e69 (“ineI”), xor eax, 0x6c65746e (“ntel”) — that’s the GenuineIntel vendor-string check, followed by cmp’s against specific 0x106c0 / 0x20660 / etc. CPU stepping IDs that have known errata-driven CRT codepaths. This is __check_cpu_features — MSVC’s CRT generates this on every binary compiled with VS 2017+ to pick the AVX-vs-SSE2 codepath for memcpy/ strcmp intrinsics. No anti-VM in the visible PE.

The six checks observed in dynamic execution

  1. Self-validation hash check. T1480 The carrier opens its own file via GetModuleFileNameW → CreateFileW → ReadFile, hashes a region of the on-disk binary, and compares the digest against a constant baked into the encrypted blob. Mismatch triggers ExitProcess with no error dialog and no telemetry. Why it matters: any modification of the on-disk file — Defender quarantine + restore, FSFD content rewrite, manual byte-patch — trips this check and the sample silently exits. A defender who sees “sample exited cleanly with code 0” and assumes the binary is dead has been deceived.
  2. WMI Win32_VideoController adapter check. T1497.001 Issues SELECT * FROM Win32_VideoController and inspects the Description / VideoProcessor fields. Bails on QXL, Standard VGA, VirtualBox Graphics Adapter, VMware SVGA, and similar hypervisor display strings. Default analysis images (QEMU+QXL, plain VirtualBox or VMware) trip this check immediately — the carrier opens its own file, runs the cipher, sees the hypervisor adapter string, and exits silently. Only well-seasoned sandbox images (GPU passthrough or a WMI provider hook faking an NVIDIA- / Intel-branded adapter, plus populated browser history and recent-file telemetry) pass this check and capture the full kill chain.
  3. WMI AntiVirusProduct enumeration. T1518.001 Issues SELECT * FROM AntiVirusProduct against the SecurityCenter2 namespace, builds a list of installed AV products, and (on the panel side) tags the bot with the result. Likely informs which credential-stealing modules run; on managed-AV hosts, some Lumma builds defer certain stealer plugins.
  4. SetUnhandledExceptionFilter anti-debug. T1622 Installs a top-level structured exception handler, then deliberately raises an exception (typically a sentinel RaiseException code) that only fires under a debugger. The custom handler redirects flow into the next unpacking stage. Without that handler installed (or under a naive debugger that swallows the exception), the unwind crashes the process before unpacking completes, and the sample dies pre-payload. Defeats casual x32dbg/x64dbg attach without ScyllaHide’s SEH-bypass profile active.
  5. Cuckoo / sandbox harness thread suppression. T1497 Enumerates threads in the running process via CreateToolhelp32Snapshot → Thread32First/Next (or NtQuerySystemInformation) and looks at thread entry-point addresses. Threads whose entry points fall in DLLs known to belong to Cuckoo’s API monitor (and, transitively, to forks like CAPE) are passed to SuspendThread — effectively silencing the harness while letting the real malware code keep running. CAPE’s own signature engine flags this behaviour literally as “Tries to suspend Cuckoo threads to prevent logging”.
  6. Keyboard layout / locale check. T1614 Calls GetKeyboardLayout + GetUserDefaultLangID + GetSystemDefaultUILanguage. Lumma operator builds explicitly refuse to run in CIS-region locales (Russian, Belarusian, Ukrainian, Kazakh) — the “bulletproof clause” that keeps the developers safe at home. As a side-effect, defenders running CIS-locale workstations are structurally immune to this strain.

Behavioural profile

When all six checks pass, the unpacked Lumma core executes its collection routine and exits. The dynamic profile is unusually flat for a stealer: 441 registry reads, zero registry writes, zero scheduled tasks, zero services, single process, single dropped artefact in %TEMP%, exit code 0, run duration under 90 seconds. This is a smash-and-grab design — read browser stores, exfil to C2, die. Persistence (when it exists) is operator-driven via a follow-on payload pushed back from C2, not a self-installing Run-key. Defenders looking for “malware that drops a persistence artefact on disk” will miss the carrier entirely.

Reproducing locally

Build a VM that answers WMI plausibly — or detonation will exit silently.

Off-the-shelf analysis images fail check #2 above instantly. To capture this kill chain in your own lab, the VM has to look like a real workstation across four dimensions:

  • Video adapter: avoid QXL, “Standard VGA”, and generic VirtualBox / VMware strings. Use VFIO passthrough of a consumer GPU, or hook the WMI provider so the Win32_VideoController name returns an NVIDIA- or Intel-branded device.
  • Browser history + recent files: seed the profile with several weeks of plausible browsing, downloads, and Recent Items. Lumma reads these to gauge sandbox vs. live host.
  • Username + locale: avoid analyst, sandbox, maltest and similar fingerprints; use a plausible local username. Note that CIS keyboard locales (Russian, Belarusian, Ukrainian, Kazakh) are self-protecting — the malware exits there by design.
  • CPU + memory: ≥ 4 vCPUs and ≥ 4 GB RAM, matching a typical user laptop. Single-core, 1 GB images get flagged immediately.

Patching the checks out of the binary is not a viable shortcut: the anti-analysis routines live inside the encrypted .reloc trailer and only materialise in memory at runtime, so static patching of the on-disk file can’t reach them (see §11). A well-seasoned VM is the most reliable path; a debugger session armed with ScyllaHide’s VMProtect profile is the second.


MITRE ATT&CK coverage

Techniques compiled from dynamic execution traces of the unpacked stage cross-checked with the static analysis above. ✅ = directly observed in this sample’s behaviour or static structure; 🟡 = expected for LummaC2 generally, would need additional environment seasoning to confirm in a local lab.

Initial Access · TA0001

  • T1189 Drive-by Compromise Malvertising via doubleclick.net
  • 🟡 T1566.002 Spearphishing Link Fake “AI editor” ad campaigns

Execution · TA0002

  • T1204.002 User Execution Trojanized “MultiUpdate”
  • T1129 Shared Modules Dynamic API resolution via LoadLibrary + GetProcAddress
  • T1047 WMI SELECT * FROM AntiVirusProduct, SELECT * FROM Win32_VideoController

Persistence · TA0003

  • 🟡 T1547.001 Run Key Imports present, not observed in dynamic run — this carrier is smash-and-grab; persistence (if any) is operator-driven post-exfil
  • 🟡 T1574.002 DLL Side-Loading Tries to load missing DLLs alongside trusted exe

Privilege Escalation · TA0004

  • 🟡 T1134 Access Token Manipulation
  • 🟡 T1574.002 DLL Side-Loading

Defense Evasion · TA0005

  • T1027 Obfuscated Files Encrypted blob in .reloc trailer
  • T1027.001 Binary Padding 73,513,369 bytes of INT3 fill
  • T1027.002 Software Packing Custom packer + 70 MB overlay
  • T1036.005 Masquerading Disguised as MultiUpdate.exe
  • T1140 Deobfuscate / Decode .reloc cipher unpacks ~317 KB Lumma core at runtime
  • T1480 Execution Guardrails Self-validation hash — reads own file, hashes, exits silently on tamper
  • T1497 Virtualization / Sandbox Evasion Cuckoo / harness thread enumeration + suspension
  • T1497.001 System Checks WMI Win32_VideoController hypervisor-adapter detect
  • T1553.002 Code Signing Lifted PKCS#7 with leaked NVIDIA cert (Lapsus$ 2022)
  • T1622 Debugger Evasion SetUnhandledExceptionFilter SEH redirection
  • 🟡 T1070.006 Timestomp
  • 🟡 T1112 Modify Registry

Credential Access · TA0006

  • T1555.003 Credentials from Web Browsers Web Data, Login Data SQLite enumerated
  • T1539 Steal Web Session Cookie Cookies SQLite enumerated
  • T1056.001 Keylogging GetKeyState polling
  • 🟡 T1555 Credentials from Password Stores DPAPI module loaded; vault reads expected for the unpacked stage

Discovery · TA0007

  • T1012 Query Registry 441 registry reads in dynamic run, no writes
  • T1057 Process Discovery Thread enumeration for harness suppression
  • T1082 System Information Discovery WMI Win32_OperatingSystem, GetSystemInfo, GlobalMemoryStatus
  • T1083 File / Directory Discovery Browser profile dir enumeration (Firefox, Chrome User Data)
  • T1518.001 Security Software Discovery WMI AntiVirusProduct query confirmed
  • T1614 System Location Discovery GetKeyboardLayout / GetUserDefaultLangID — CIS-locale guard
  • 🟡 T1016 System Network Configuration
  • 🟡 T1018 Remote System Discovery (hosts file)
  • 🟡 T1033 Owner / User Discovery

Command & Control · TA0011

  • T1071.001 Web Protocols HTTPS to nine .shop / .click C2 domains; matched by Suricata ET MALWARE Win32/Lumma Stealer Related CnC Domain
  • T1573 Encrypted Channel TLS 443 to all observed C2
  • 🟡 T1102.001 Dead Drop Resolver Steam profile bio — observed in companion engagement traffic, not in this single dynamic run

Collection · TA0009

  • T1005 Data from Local System Browser profiles, certificate stores
  • T1115 Clipboard Data OpenClipboard / GetClipboardData polling
  • T1056.001 Input Capture: Keylogging GetKeyState polling
  • 🟡 T1119 Automated Collection

Exfiltration · TA0010

  • 🟡 T1041 Exfil over C2 Channel Stolen archive POSTed to active C2

Detection

Existing rule that does fire: Florian Roth’s leaked-NVIDIA-cert YARA

Florian Roth (Nextron Systems) published a rule in his signature-base on 2022-03-03 (announcement tweet), two days after the Lapsus$ disclosure. The rule (SUSP_NVIDIA_LAPSUS_Leak_Compromised_Cert_Mar22_1) fires on any PE compiled after 2022-03-01 whose Authenticode chain includes either of the two confirmed leaked NVIDIA cert serials, issued by VeriSign Class 3 Code Signing 2010 CA:

import "pe"

rule SUSP_NVIDIA_LAPSUS_Leak_Compromised_Cert_Mar22_1 {
   meta:
      description = "Detects a binary signed with the leaked NVIDIA certifcate and compiled after March 1st 2022"
      author = "Florian Roth (Nextron Systems)"
      date = "2022-03-03"
      reference = "https://twitter.com/cyb3rops/status/1499514240008437762"
   condition:
      uint16(0) == 0x5a4d and filesize < 100MB and
      pe.timestamp > 1646092800 and
      for any i in (0 .. pe.number_of_signatures) : (
         pe.signatures[i].issuer contains "VeriSign Class 3 Code Signing 2010 CA" and (
            pe.signatures[i].serial == "43:bb:43:7d:60:98:66:28:6d:d8:39:e1:d0:03:09:f5" or
            pe.signatures[i].serial == "14:78:1b:c8:62:e8:dc:50:3a:55:93:46:f5:dc:c5:18"
         )
      )
}

The carrier matches: PE timestamp 2024-12-23 is well past the 2022-03-01 floor, file is under 100 MB, and signature #0 carries the second of the two listed serials with the expected VeriSign issuer string. This rule already catches the sample — defenders running signature-base in any retro-hunt or file-arrival scan will see it without further work.

Tuning note

Comment out the pe.timestamp > 1646092800 guard for historical hunts.

The author’s comment recommends this for sweeps over older corpora; it widens recall to any binary signed with the leaked cert, regardless of compile timestamp (which is forgeable anyway). Keep the guard in place for live file-arrival scanning to suppress the historical legitimately-NVIDIA-signed pre-2022 fleet.

Existing rule that fires on the unpacked stage: Kevin O’Reilly’s CAPE Lumma.yar

Kevin O’Reilly maintains the canonical Lumma YARA rule inside the kevoreilly/CAPEv2 repo. The rule targets a sequence of bytes that appears in the unpacked Lumma core — which means it does not match the on-disk carrier (.reloc blob is still encrypted) but does match the in-memory Lumma image after the .reloc-trailer cipher runs. We confirmed the rule matches the unpacked stage of this artefact — ~317 KB of decrypted Lumma core captured at virtual address 0x03330000 in the carrier process (see §11) — which anchors family attribution against an external pre-existing detection, independent of any submission we made to public databases.

Practical use

Run Lumma.yar against process memory dumps, not against on-disk PEs.

For a host suspected of running Lumma: pull a full process dump (e.g. via procdump -ma <pid>), then YARA-scan the dump with the kevoreilly/CAPEv2 rule. A hit is high-confidence Lumma. Static disk scans of the carrier never match this rule because the body is still under the .reloc-trailer cipher; pair it with SUSP_NVIDIA_LAPSUS_Leak_Compromised_Cert_Mar22_1 (above) for the on-disk path and LummaC2_MultiUpdate_Carrier_2024 (below) for the structural fingerprint of the carrier itself.

Why the published Lumma YARA rule misses this artefact

The Embee Research rule win_lumma_w1 targets three obfuscated UTF-16 strings (Chrome’s “Web Data” / “Login Data” SQLite filenames, “Opera Neon” profile dir) and constrains filesize < 5000KB:

rule win_lumma_w1 {
  strings:
    $o1 = { 57 00 ?? 00 ?? 00 ?? 00 ?? 00 ?? 00 ?? 00 65 00 62 00 ... }
    $o2 = { 4f 00 70 00 ?? 00 ?? 00 ?? 00 ?? 00 ?? 00 ?? 00 65 00 ... }
    $o3 = { 4c 00 6f 00 ?? 00 ?? 00 ?? 00 ?? 00 ?? 00 ?? 00 67 00 ... }
  condition:
    uint16(0) == 0x5a4d and filesize < 5000KB and (all of ($o*))
}
Sample variantFilesize gate$o1 / $o2 / $o3Verdict
Original 74 MB carrierFAIL (74 MB > 5 MB)0 / 0 / 0No match
Stripped 1.2 MB carrierPASS0 / 0 / 0No match
Memory-dumped unpacked payloadPASS3 / 3 / 3 expectedMatch

Recommended new YARA rule — carrier structure

This rule targets the artefact’s structural fingerprint rather than the Lumma payload’s strings, so it survives operator string-shuffling and matches the delivery stage (where the existing rule fails):

import "pe"
import "math"

rule LummaC2_MultiUpdate_Carrier_2024
{
    meta:
        author        = "CSIRT Versus Security (B. Schmid)"
        date          = "2025-02-14"
        description   = "Lumma carrier disguised as MultiCommander MultiUpdate.exe"
        sample_sha256 = "eff51f99...abba"
        tlp           = "AMBER"

    strings:
        $pdb = "MultiCommander\\BuildOutput\\Output\\Win32\\Release v143\\MultiUpdate\\MultiUpdate.pdb" ascii
        $manifest_a = "name=\"Microsoft.Windows.AutoUpdate\"" ascii
        $manifest_d = "<description>MultiUpdate</description>" ascii
        $multi_url  = "Multi Commander-http://multicommander.com/updates/version.xml" wide

    condition:
        uint16(0) == 0x5a4d and pe.is_pe and
        $pdb and ($manifest_a or $manifest_d or $multi_url) and
        for any sec in pe.sections : (
            sec.name == ".reloc" and
            sec.raw_data_size > (pe.data_directories[5].size + 0x4000) and
            math.entropy(sec.raw_data_offset, sec.raw_data_size) > 7.0
        )
}

Sigma — process / image-load anomaly

title: Lumma carrier disguised as MultiUpdate.exe
id: c1ae2a8e-93be-4e1c-9bc2-3f73a7fae8b2
status: experimental
description: Detects an MFC executable masquerading as MultiCommander's
  MultiUpdate.exe but unsigned and beaconing to .shop / steamcommunity.com
author: CSIRT Versus Security (B. Schmid)
date: 2025/02/14
logsource:
  product: windows
  category: process_creation
detection:
  selection:
    Image|endswith:
      - '\MultiUpdate.exe'
      - '\AutoUpdate.exe'
    OriginalFileName: 'MultiUpdate.exe'
  filter_signed:
    SignatureStatus: Valid
    Signature|contains: 'Mathias Svensson'
  condition: selection and not filter_signed
falsepositives:
  - Genuine MultiCommander update — must be Authenticode-signed
level: high
tags:
  - attack.defense_evasion
  - attack.t1036.005

Suricata — outbound TLS to Lumma .shop C2

alert tls $HOME_NET any -> $EXTERNAL_NET 443 (msg:"LummaC2 outbound TLS to .shop C2"; \
  tls.sni; pcre:"/^(abruptyopsn|cegu|cloudewahsj|framekgirus|klipgonuh|nearycrepso|noisycuttej|rabidcowse|tirepublicerj|wholersorie)\.shop$/i"; \
  classtype:trojan-activity; sid:1000201; rev:1;)

alert tls $HOME_NET any -> $EXTERNAL_NET 443 (msg:"LummaC2 outbound TLS to .click rotator"; \
  tls.sni; content:"regularlavhis.click"; \
  classtype:trojan-activity; sid:1000202; rev:1;)

alert tls $HOME_NET any -> $EXTERNAL_NET 443 (msg:"LummaC2 dead-drop resolver lookup (Steam)"; \
  tls.sni; content:"steamcommunity.com"; flow:established,to_server; \
  threshold:type both, track by_src, count 1, seconds 60; \
  classtype:policy-violation; sid:1000203; rev:1;)

Splunk SPL — Sysmon process-create with carrier signature

The base hunt: Sysmon Event ID 1, process image or OriginalFileName matches MultiUpdate, but the binary is not Authenticode-signed by the genuine MultiCommander publisher (Mathias Svensson). Surfaces both the Lumma carrier and any other unsigned masquerade.

index=sysmon EventCode=1
| eval is_multiupdate = if(match(Image,"\\MultiUpdate\.exe$")
                            OR OriginalFileName="MultiUpdate.exe", 1, 0)
| where is_multiupdate=1
| eval signed_ok = if(SignatureStatus="Valid"
                       AND match(Signature,"Mathias Svensson"), 1, 0)
| where signed_ok=0
| stats count
        values(Hashes) as hashes
        values(ParentImage) as parent
        min(_time) as first_seen max(_time) as last_seen
    by Computer, User, Image
| convert ctime(first_seen) ctime(last_seen)
| sort -last_seen

Splunk SPL — Steam dead-drop → .shop transaction

The behavioural signature: any host that reaches steamcommunity.com over TLS and then beacons to a .shop or .click domain within a 5-minute window. Catches Lumma victims regardless of which specific rotating .shop domain is in use. Requires Zeek ssl.log ingested into Splunk.

index=zeek sourcetype=ssl
| eval is_steam = if(match(server_name,"steamcommunity\.com$"), 1, 0)
| eval is_shop  = if(match(server_name,"\.shop$")
                      OR match(server_name,"\.click$"), 1, 0)
| where is_steam=1 OR is_shop=1
| transaction src maxspan=5m
| where mvfind(is_steam,"1") > -1 AND mvfind(is_shop,"1") > -1
| eval shop_dest = mvfilter(match(server_name,"\.shop$|\.click$"))
| table _time, src, dst, shop_dest, server_name, uid
| sort -_time

Splunk SPL — high-entropy section anomaly via Sysmon hashes

Lower-confidence hunt: any unsigned PE created on disk whose IMPHASH matches the carrier’s import-table fingerprint. IMPHASH survives section-content changes because the operator recompiled but kept MFC + libxml2 statically linked with the same import set.

index=sysmon EventCode=1
| rex field=Hashes "IMPHASH=(?<imphash>[A-F0-9]{32})"
| where imphash IN ("[redacted-imphash-MFC-libxml2-carrier]")
| eval signed_ok = if(SignatureStatus="Valid", 1, 0)
| where signed_ok=0
| stats values(Image) values(Computer) values(User)
        by imphash
| sort -values(Computer)

KQL (Microsoft Defender / Sentinel) — carrier execution

DeviceProcessEvents
| where FileName =~ "MultiUpdate.exe"
   or ProcessVersionInfoOriginalFileName =~ "MultiUpdate.exe"
| extend signed_by_legit =
    iff(ProcessVersionInfoCompanyName has "MultiCommander"
        and isnotempty(ProcessVersionInfoCompanyName)
        and SignatureType == "Valid",
        true, false)
| where signed_by_legit == false
| project Timestamp, DeviceName, AccountName, FolderPath, FileName,
          SHA256, MD5, ProcessCommandLine, InitiatingProcessFileName,
          SignatureType, ProcessVersionInfoCompanyName
| sort by Timestamp desc

KQL — Steam dead-drop call followed by .shop beacon

let steam_calls =
    DeviceNetworkEvents
    | where RemoteUrl has "steamcommunity.com"
    | project SteamTime = Timestamp, DeviceId, DeviceName,
              steam_url = RemoteUrl;
let shop_calls =
    DeviceNetworkEvents
    | where RemoteUrl matches regex @"\.(shop|click)(/|$)"
    | project ShopTime = Timestamp, DeviceId,
              shop_url = RemoteUrl, shop_dest = RemoteIP;
steam_calls
| join kind=inner shop_calls on DeviceId
| where ShopTime between (SteamTime .. SteamTime + 5m)
| project SteamTime, ShopTime, DeviceName,
          steam_url, shop_url, shop_dest
| sort by ShopTime desc

Static unpacker dead-end

Result

The static unpacker is partially blocked.

The .reloc trailer’s first 1,072 bytes are real x86 code with a polymorphic-MBA structure that emits two length constants (ESI=0x547a4, ECX=0x31f; sum equals the blob size 0x54ac3) — but at instruction 146 it executes sub dword [ecx], 0x1f7eeabb expecting [ecx] to be a writable buffer. No code path in the carrier sets up such a buffer. The carrier is non-self-unpacking; the cipher requires runtime context that does not exist in any reachable code path of .text.

Approach

Stand-alone Python script (lumma_static_unpacker.py) that:

  1. Strips the 0xCC overlay (drops 70 MB of padding).
  2. Locates the encrypted blob by scanning for the first non-zero byte after the legitimate base-relocation table (BaseRelocationTable.Size = 0x8224, blob actually begins at section offset 0x8400 due to 0x200 alignment).
  3. Extracts the 1,072-byte polymorphic prefix and the 345,747-byte encrypted body separately.
  4. Documents the cipher constants harvested from the prefix’s instruction stream.
  5. Records the static-only dead-end and points the analyst at the correct dynamic-analysis breakpoint.

Cipher constants discovered

32-bit immediates that appear in the prefix’s arithmetic instructions, harvested from the x86 disassembly:

ConstantUsed inOperationLikely role
0x5f50f517+0x001 / +0x053xor edx, KEDX whitening (paired)
0xef19cbd0+0x015 / +0x03fsub eax / add eaxEAX offset (paired)
0x8ef362b6+0x072 / +0x084sub ecx / add ecxECX offset (paired)
0xda1ee056+0x067 / +0x08fsub edx / add edxEDX offset (paired)
0x1c1addf8+0x09fadd edx, KEDX increment
0xac8b0af8+0x0bdsub edx, KEDX decrement
0xe5a0b512+0x0c7 / +0x0ebxor eax, KEAX whitening (paired)
0xdc8117ae+0x0acxor eax, KEAX whitening
0x1f7eeabb+0x193sub [ecx], KPer-block decrypt step (memory write — needs valid [ecx])
0x7b5677b6+0x1a9add ebx, KEBX offset
0x547a4+0x05amov esi, KLength: encrypted-body bytes (0x54ac3 − 0x31f)

Standard ciphers tested negative

Before concluding the static path is blocked, we ruled out the obvious primitives by brute force against the encrypted blob:

HypothesisTestResult
Single-byte XORAll 256 keys, look for MZ at decrypted offset 00 keys produce MZ
4-byte rolling XOREach prefix constant as the 4-byte key0 keys produce MZ; 36–55 % printable
RC4Each prefix constant + blob[0:16] as the key0 keys produce MZ; 33–44 % printable
Cyclic XOR with legit reloc tableBody bytes XOR with the 33 KB legitimate reloc data, cyclicallyNo MZ, no PK, no plaintext
zlib / raw-deflate / lzmaDecompress at offsets 0, 0x430, 0x800, 0x1000No valid stream at any offset
Embedded markersSearch for MZ, PE\0\0, PK\x03\x04, gzip magic5 random MZ bytes (offsets 0x13b82, 0x217e6, 0x3749e, 0x47bf7, 0x527be) — all surrounded by high-entropy noise, none has a real DOS header following

Unicorn emulation result

Booting the prefix with all GP registers zero, mapping the full carrier image (PE headers + all five sections), and providing a TIB at fs:0 reaches instruction 146 cleanly, then aborts:

$ python3 emulate_prefix.py
Mapping image at VA 0x400000 size 0x12f000
  .text    VA 0x401000 size 0x94a00
  .reloc   VA 0x4d2000 size 0x5d000

Emulating prefix with full image mapped...
  INVALID READ @ EIP=0x4da593 address=0x31f size=4
Emulation halted: Invalid memory read (UC_ERR_READ_UNMAPPED)

Instructions executed: 146
Final EIP: 0x004da593  (offset into blob: 0x193)
Final registers:
  ECX = 0x0000031f      <-- accumulated length, NOT a pointer
  ESI = 0x000547a4      <-- accumulated length

The failing instruction

+0x018d 0x004da58d: b14e           mov cl, 0x4e
+0x018f 0x004da58f: c1c702         rol edi, 2
+0x0192 0x004da592: 49             dec ecx
+0x0193 0x004da593: 8129bbea7e1f   sub dword ptr [ecx], 0x1f7eeabb   <<< needs valid [ecx]
+0x0199 0x004da599: c1c815         ror eax, 0x15

This is the cipher’s per-block decrypt step. ECX is supposed to point at a destination buffer; the prefix walks it 4 bytes at a time, applying [ecx] -= 0x1f7eeabb mixed in with rotation/XOR of the surrounding registers. In our static run, ECX=0x31f (a small integer), confirming that the carrier’s caller would set ECX to a valid address before transferring control here.

Why the static unpacker is blocked

The carrier never executes the blob from anywhere in .text.

  • No .text reaches the blob. Confirmed via radare2 (axt @ 0x004da400 = empty), value search for 0xd2000 / 0x4d2000 / 0x8224 / 0x547a4 as immediates anywhere in .text = zero hits, and pattern search for push 0xd2000 / mov reg, 0xd2000 = zero hits.
  • The carrier itself never executes the blob. Even with full PE-image emulation, no execution path from entry0 through WinMain ever transfers control to address 0x004da400.
  • The prefix is genuine cipher code, not noise. Length constants 0x547a4 + 0x31f = 0x54ac3 exactly match the blob size; the failing instruction is a legitimate decrypt step. This is a real cipher waiting for a context that does not appear in this binary.
Unpacking mechanism

The cipher requires runtime state from the carrier’s WinMain, then writes to a freshly VirtualAlloc’d region.

The unpacker is reached only after a long MFC initialisation sequence, via an indirect mechanism (vtable, callback, exception handler, or message map) rather than a literal address in .text. A heap allocation, a global initialised by CWinApp::InitInstance, or a value passed via TLS sets up [ecx] in the live process — the missing context the static path cannot reconstruct.

Dynamic execution in a seasoned sandbox passes the anti-analysis checks (§8) and reaches the unpacking routine. The unpacked Lumma core lands at virtual address 0x03330000 in the carrier process — a VirtualAlloc’d region at the ≈ 51 MB mark of address space, not an address resolvable from the static image. Final size: 324,608 bytes (close to but smaller than the encrypted blob — the cipher output is then padded up to a page boundary). Two intermediate decryption stages are also captured (~347 KB and ~368 KB blobs; see §12 for hashes).

The unpacked stage matches Kevin O’Reilly’s Lumma.yar rule (kevoreilly/CAPEv2) with high confidence — that’s how the family attribution is anchored in dynamic-analysis pipelines. This doesn’t unblock the static-only path: the anti-analysis stack inside the encrypted blob (self-hash, WMI video adapter, Cuckoo thread suppression, SEH redirect) still gates the cipher, so static patching of the carrier remains blocked.

Recommended dynamic-analysis breakpoint

To extract the unpacked payload in REMnux/FlareVM with x32dbg / WinDbg:

  1. Open the sample in the debugger; let MFC WinMain initialise (run until idle).
  2. Set a memory-write breakpoint at the page covering VA 0x004da593, OR a hardware execute breakpoint at 0x004da593 directly.
  3. Continue execution. When the breakpoint fires, capture ECX — that points to the destination buffer the cipher writes to.
  4. Let the loop run to completion (the prefix ends at 0x004da830); dump the destination buffer of size ESI (= 0x547a4 bytes) to disk.
  5. Re-run the public YARA rule (win.lumma_w1.yar) against the dump — it should match the obfuscated UTF-16 strings and confirm the unpacked payload is the Lumma stealer.

Equivalent breakpoint in WinDbg syntax: ba e1 0x004da593

Files emitted by the partial static unpacker

For reproducibility, the stand-alone Python helper (lumma_static_unpacker.py) strips the overlay, locates the encrypted .reloc trailer, and splits the obfuscation prefix from the encrypted body:

$ python3 lumma_static_unpacker.py "Malware/eff51f99...abba.exe"
[*] Reading Malware/eff51f99...abba.exe
    size   = 74,715,545 bytes (71.25 MiB)
    sha256 = eff51f995cd6463cd9b3a2ea4a14cc85e3cc5c1b5b71db6d90765b3df175abba
[*] Stripping overlay padding...
    overlay length = 73,513,369 bytes (70.11 MiB)
    -> /tmp/lumma_stripped.exe       sha256=c466795354007a604fa1805b6d97b6f3e43179c85544594fe365f22ede8fe0a6
[*] Extracting encrypted .reloc trailer...
    blob length    = 346,819 bytes (338.7 KiB)
    -> /tmp/lumma_encrypted_blob.bin sha256=6e59580e512687981236cd42b23f46507834cea6009d1845ebfe177bef6c5062
[*] Obfuscation prefix: 1072 bytes  -> /tmp/lumma_obfuscation_prefix.bin
[*] Encrypted body:     345,747 bytes  (entropy ~7.51)

Indicators of compromise

All IOCs defanged for safe handling.

File hashes

ArtefactTypeValue
Delivered carrier (74 MB padded)SHA-256eff51f995cd6463cd9b3a2ea4a14cc85e3cc5c1b5b71db6d90765b3df175abba
Delivered carrierMD5a3be0b0ebbf9428015cacc27cf5d51a7
Stripped carrier (overlay removed)SHA-256c466795354007a604fa1805b6d97b6f3e43179c85544594fe365f22ede8fe0a6
Encrypted .reloc trailer (extracted)SHA-2566e59580e512687981236cd42b23f46507834cea6009d1845ebfe177bef6c5062
Unpacked Lumma core — in-memory at VA 0x03330000, 324,608 bytes (matches kevoreilly Lumma.yar)SHA-2567d62169297c2236d94cf343174472ee5ad0c27352f7fc4431a425d91cfb60c2b
Intermediate decryption stage 1 (346,797 bytes)SHA-2564cfe4e9d0e5b65eb8fca0d6953aba7c671e21a3f753e1c352433d1f3ec35c147
Intermediate decryption stage 2 (367,882 bytes)SHA-256fdd23f242d3b73030791645400880a2ea27977b101094225d9b4255d4afc8ff6
Dropped artefact in %TEMP% — HTML mistyped as .exe (sinkholed-C2 response)SHA-256b73df91c83960a7dcce8f112b1f7e4db8ec6b659d4ac706f79a1a703297533dd
Same dropped artefactMD52e59df53309dbd234f876bad5c73f5b4

Code-signing — lifted leaked NVIDIA certificates (Lapsus$ March 2022)

Sig digestCert serialSHA-1 thumbprint
SHA-1 14781BC862E8DC503A559346F5DCC518 30:63:2E:A3:10:11:41:05:96:9D:0B:DA:28:FD:CE:26:71:04:75:4F
SHA-256 7BC15AF21367D0758BEDDCCA118642DE SHA-256 dual-sign partner present in the same lifted PKCS#7 blob

The SHA-1 leaf is one of the two confirmed leaked NVIDIA certs from the Lapsus$ disclosure (the other is 43BB437D609866286DD839E1D00309F5, not present in this sample). Both NVIDIA certs in our chain expired in 2018 and were revoked after the leak; Microsoft added them to the disallowed-cert list, but legacy systems and metadata-only checkers may still trust them. Either of the two leaked SHA-1 serials appearing in an Authenticode chain on a non-NVIDIA file is a high-confidence IOC for lifted-signature tradecraft.

Network — suspected C2 / dead-drop infrastructure

DomainRoleConfidence
abruptyopsn.shopC2 (Lumma)High — ET MALWARE sig hit
cegu.shopC2 candidateMedium
cloudewahsj.shopC2 (Lumma)High — ET MALWARE sig hit
framekgirus.shopC2 (Lumma)High — ET MALWARE sig hit
klipgonuh.shopC2 candidateHigh — observed
nearycrepso.shopC2 (Lumma)High — ET MALWARE sig hit
noisycuttej.shopC2 (Lumma)High — ET MALWARE sig hit
rabidcowse.shopC2 (Lumma)High — ET MALWARE sig hit
regularlavhis.clickC2 (Lumma rotator)High — ET MALWARE sig hit
tirepublicerj.shopC2 (Lumma)High — ET MALWARE sig hit
wholersorie.shopC2 (Lumma)High — ET MALWARE sig hit
yuriy-gagarin.comC2 candidateHigh — observed
steamcommunity.comDead-drop resolver (legit, abused)High
www.gstatic.cnC2 candidate (typosquat of gstatic.com)Medium

Network — observed IPs

IPPortAssociatedCategory
104.21.82.94443yuriy-gagarin.comSuspicious (Cloudflare-fronted)
172.67.199.224443yuriy-gagarin.comSuspicious (Cloudflare)
172.67.162.153443klipgonuh.shopSuspicious (Cloudflare)
185.160.247.17Lumma exfil egress observed in panel dump
64.233.181.94443Suspicious

TLS fingerprints (Steam dead-drop call)

SNIsteamcommunity.com
VersionTLS 1.2
JA3a0e9f5d64349fb13191bc781f81f42e1
JA3Sb677083c9768d0548331fca998152a10
Cert thumbprinte4fde2a81727d33dcbe228f20c59a9ee522fc470 (DigiCert SHA2 EV CA → Valve Corp)

Host artefacts

TypeValue
Initial cache hit (Chrome)%LocalAppData%\Google\Chrome\User Data\Default\Code Cache\js\47aa920d2b1e1d49_0
Dropped sample (Chrome cache)%LocalAppData%\Google\Chrome\User Data\Default\Cache\Cache_Data\data_4
Single dropped artefact (dynamic run)%TEMP%\5F1EMLRXIIJGDQ03YLNXFTUK54.exe — HTML content despite .exe extension; sinkholed-C2 response saved verbatim
Inherited PDB pathD:\Projects\MultiCommander\BuildOutput\Output\Win32\Release v143\MultiUpdate\MultiUpdate.pdb
Manifest identityname="Microsoft.Windows.AutoUpdate" / description="MultiUpdate"
Process mutexUnnamed CRT mutex via CreateMutexA(null) at startup; Lumma family-specific named mutex created post-unpack (LUMMA prefix; full name not exposed by sandbox engines)
Persistence footprintNone observed. 441 registry reads, 0 writes; 0 scheduled tasks; 0 services; single process exits with code 0 in < 90 s. Smash-and-grab profile.
Unpacked stage memory locationVA 0x03330000 in carrier process — VirtualAlloc’d region holding the 324,608-byte Lumma core (matches kevoreilly Lumma.yar)
LummaC2 panel banner (companion file)"LummaC2, Build Oct 9 2023" / LID format BhgGkI--IB4

Recommendations

For incident responders working this engagement

  1. Validate the new YARA rule against the existing IOC pack and historical malware-bazaar pulls. Roll into EDR if matches are clean.
  2. Expand the dead-drop hunt: any host with Sysmon-recorded TLS to steamcommunity.com followed by TLS to a *.shop domain within ≤ 5 minutes is likely a Lumma victim regardless of which specific .shop domain rotated in.
  3. Re-issue affected user credentials — session-token revocation for any browser-stored OAuth tokens; the operator has the cookies.
  4. Block the inherited carrier identity: AppLocker or WDAC rule for any MultiUpdate.exe not signed by the genuine MultiCommander publisher (Mathias Svensson), denied at execution.
  5. Browser hardening: users on managed endpoints should not have password-manager-style “save in browser” privileges for high-value applications; promote SSO + hardware key wherever possible.

For continuing the analysis

  1. Static decryption of the .reloc trailer. The encrypted blob is preserved at /tmp/lumma_encrypted_blob.bin. Walk back from the cross-reference to 0x004da400 to identify the unpacking stub, then re-implement the cipher in Python. Once decrypted the blob is expected to be a PE that the public YARA rule will match — closing the detection gap end-to-end.
  2. Dynamic confirmation in REMnux or FlareVM. Capture the actual cipher output at runtime (memory dump of the carrier process after WinMain entry); compare against the static decryption result to validate.
  3. Submit findings upstream: the carrier-structure YARA is genuinely useful — share it with Embee Research as a complementary detection alongside the existing string-obfuscation rule.

References

  • Embee Research — Lumma string-obfuscation YARA — github.com/embee-research/Yara-detection-rules
  • Malpedia — malpedia.caad.fkie.fraunhofer.de/details/win.lumma
  • g0njxa — Approaching stealer devs (LummaC2 interview) — g0njxa.medium.com/approaching-stealers-devs-94111d4b1e11
  • dexpose.io — In-depth technical analysis of Lumma Stealer (2024)
  • Malwarebytes — Free AI editor lures (Nov 2024)
  • Florian Roth (Nextron Systems) — SUSP_NVIDIA_LAPSUS_Leak_Compromised_Cert_Mar22_1 YARA rule — github.com/Neo23x0/signature-base/blob/master/yara/gen_nvidia_leaked_cert.yar
  • Florian Roth — announcement / context for the leaked-NVIDIA-cert YARA — twitter.com/cyb3rops/status/1499514240008437762
  • Kevin O’Reilly (CAPE Sandbox) — Lumma YARA rule for the unpacked stealer core — github.com/kevoreilly/CAPEv2/blob/master/data/yara/CAPE/Lumma.yar
  • Emerging Threats Open ruleset — ET MALWARE Win32/Lumma Stealer Related CnC Domain Suricata signatures — rules.emergingthreats.net
  • BleepingComputer — Malware now using NVIDIA’s stolen code-signing certificates (Mar 2022)
  • Threatpost — NVIDIA’s Stolen Code-Signing Certs Used to Sign Malware (Mar 2022)
Distribution

Reproduction and sharing

This report is published under TLP:CLEAR and may be redistributed without restriction. The associated full IOC pack is published separately under TLP:AMBER and is restricted to named subscribers.

For attribution, please cite as: B. Schmid, “Lumma Stealer’s .reloc trick,” CSIRT Versus Security, 14 February 2025.