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.relocsection, and 70 MB of0xCCpadding bolted onto the end purely to defeat AV/sandbox file-size limits and the published YARA rule’sfilesize < 5000KBcondition.
This binary is a textbook 2024-era LummaC2 delivery artifact. Four structural tricks combine to defeat naive defenses:
-
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 againstmulticommander.com, and ASUS-related strings all survive in the binary. A defender allowlisting on those signatures would be wide open. -
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…DCC518and7BC15AF2…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 §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 theBaseRelocationTabledata directory. Static analysers that trust the directory miss it; the runtime loader needs only the legitimate relocs and ignores the trailer. -
Massive
0xCCoverlay padding. 73,513,369 bytes ofINT3bytes 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 requiresfilesize < 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.
Sample identification
Delivered artifact (74 MB padded)
| File type | PE32 GUI executable, Intel i386, 5 sections, MFC-based |
| Size | 74,715,545 bytes (71.25 MiB) |
| MD5 | a3be0b0ebbf9428015cacc27cf5d51a7 |
| SHA-1 | 23f0f30e2bc4fb1308c01328e951b1681f439d46 |
| SHA-256 | eff51f995cd6463cd9b3a2ea4a14cc85e3cc5c1b5b71db6d90765b3df175abba |
| PE TimeDateStamp | 2024-12-23 13:44:40 UTC (forgeable) |
| ImageBase / EP RVA | 0x00400000 / 0x00046ffc |
| Digital signature | Broken — security data dir VA=0x473d7d8 Size=0x39c0 points beyond EOF |
| Linker | Microsoft VS 2022 Pro, MSVC 14.42 (ATL/MFC, statically linked libxml2) |
| PDB path | D:\Projects\MultiCommander\BuildOutput\Output\Win32\Release v143\MultiUpdate\MultiUpdate.pdb |
| Manifest | name=“Microsoft.Windows.AutoUpdate” description=“MultiUpdate” |
Stripped carrier (overlay removed)
| Created by | Truncating to first 1,202,176 bytes (sum of section raw sizes) |
| Size | 1,202,176 bytes (1.15 MiB) |
| SHA-256 | c466795354007a604fa1805b6d97b6f3e43179c85544594fe365f22ede8fe0a6 |
| Use | Substrate for radare2 / static analysis. Now under the 5000 KB threshold of the published YARA rule (still does not match — see §8) |
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.
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.
PE section table
| Name | VAddr | RawSize | Entropy | Note |
|---|---|---|---|---|
| .text | 0x00401000 | 0x00094a00 | 6.6849 | MFC application code (carrier) |
| .rdata | 0x00496000 | 0x00028800 | 5.1722 | Read-only data, RTTI, manifest, vftables |
| .data | 0x004bf000 | 0x00002e00 | 4.4563 | Initialised globals |
| .rsrc | 0x004c9000 | 0x00008400 | 4.7417 | MFC dialog/string resources |
| .reloc | 0x004d2000 | 0x0005d000 | 7.5579 | 33 KB legit relocs + 347 KB encrypted trailer |
Overlay characterisation
| Start (file offset) | 0x00125800 (= last raw section end) |
| End | 0x04747999 (EOF) |
| Length | 73,513,369 bytes (≈ 70.1 MiB) |
| Content | 0xCC 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 §3 Authenticode signature) |
| Entropy (first 1 MB) | 0.0775 |
| Entropy (last 1 MB) | 4.2181 |
| Function | Defeat 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:
| DLL | Function | Carrier use | Payload abuse |
|---|---|---|---|
| KERNEL32 | LoadLibraryA / LoadLibraryW | Updater plug-ins | Resolve payload API set at runtime |
| KERNEL32 | GetProcAddress | — | Dynamic API resolution (T1129) |
| KERNEL32 | WinExec / CreateProcessW | Auto-relaunch | Launch second-stage payload |
| KERNEL32 | OpenProcess | Updater process check | Process inspection / token theft |
| KERNEL32 | IsDebuggerPresent | (none) | Anti-debug (T1622) |
| ADVAPI32 | RegCreateKeyExW / RegSetValueExW | Updater config | Persistence via Run key (T1547.001) |
| SHELL32 | ShellExecuteW / ShellExecuteExW | Open release notes URL | Drop-and-execute child stages |
| WS2_32 | WSAStartup | Update download | C2 communication primitive |
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.
| # | Digest | Cert serial | Subject CN/OU | Validity | Issuer |
|---|---|---|---|---|---|
| 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?
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 §10 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:
- Entropy. 4 KB-block entropy across the 67.5 MB middle of the overlay sits at
0.0775bits/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 produces0.08. - Whole-file block scan.
rahash2 -B -b 4096 -a entropyover the full 75 MB file flags only 77 blocks at entropy ≥ 7.0. Every one of them lives inside the.reloctrailer (the known 347 KB Lumma payload, §4) 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. - Magic-byte hunt. Searches across the overlay for
MZ,\x7fELF,PK\x03\x04, gzip, 7-Zip, RAR, NSIS, Inno Setup, single-byte-XOR’dMZ, LZMA, PNG / JPEG / WebM — zero hits. A bound browser would leave at least the wrapper or inner-PE signature. - Imports.
VirtualAlloc,VirtualProtect, and theCrypt*family are not in the static IAT. OnlyVirtualQueryis. 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. - Structure. The overlay isn’t random fill, but it also isn’t data. A 15-byte template (
f8 64 6e 09· eight0xCC·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-0xCCbytes. Identical statistics across 68 megabytes is the signature of a custom padding tool, not data.
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 (§4). The 70 MB overlay
is inert padding produced by tooling. The 7-byte beacon
(f8 64 6e 09…90 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 |
| Length | 346,819 bytes (0x54ac3) |
| Entropy (just the blob) | 7.5097 |
| Padding before blob | 0x8224 → 0x8400 (476 bytes of zeros — alignment) |
| Padding after blob | 0x5cec3 → 0x5d000 (317 bytes of zeros — alignment) |
First 256 bytes of the encrypted payload
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.
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 6.1.4. 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 §7 MITRE ATT&CK coverage.
data_4 to Chrome cacheT1204.002user lands on staged carrierentry0 → __scrt_main → WinMainunpacker not at entry point; reached after MFC initialisation.reloc trailer — read 347 KB blob @ 0x4da400, decrypt to second-stage PET1027 / T1027.002LoadLibrary + GetProcAddress rebuilds the stealer’s IAT post-decryptionT1129HKCU\…\Run + scheduled taskT1547.001SELECT * FROM AntiVirusProduct; HWID, OS, GPU, localeT1082 / T1518.001GET steamcommunity.com/profiles/<id>, parse bio → resolved C2 domainT1102.001Delivery On-host execution Malicious post-exploitation Exfiltration
MITRE ATT&CK coverage
Techniques compiled from sandbox detonation in the engagement environment, cross-checked with the static analysis above. ✅ = directly observed in this sample’s behaviour or static structure; 🟡 = expected for LummaC2 generally, would need dynamic confirmation.
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
Persistence · TA0003
- 🟡 T1547.001 Run Key RegCreateKeyExW + RegSetValueExW imports present
- 🟡 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
- ✅ T1553.002 Code Signing Lifted PKCS#7 with leaked NVIDIA cert (Lapsus$ 2022)
- 🟡 T1070.006 Timestomp
- 🟡 T1112 Modify Registry
- 🟡 T1497 Virtualization / Sandbox Evasion
- 🟡 T1622 Debugger Evasion IsDebuggerPresent imported
Credential Access · TA0006
- 🟡 T1555.003 Credentials from Web Browsers Web Data, Login Data SQLite
- 🟡 T1539 Steal Web Session Cookie
- 🟡 T1056.001 Keylogging
Discovery · TA0007
- 🟡 T1012 Query Registry
- 🟡 T1016 System Network Configuration
- 🟡 T1018 Remote System Discovery (hosts file)
- 🟡 T1033 Owner / User Discovery
- 🟡 T1057 Process Discovery
- 🟡 T1082 System Information Discovery
- 🟡 T1083 File / Directory Discovery
- 🟡 T1518.001 Security Software Discovery
- 🟡 T1614 System Location Discovery
Command & Control · TA0011
- 🟡 T1071.001 Web Protocols HTTPS to .shop domains
- 🟡 T1102.001 Dead Drop Resolver Steam profile bio
- 🟡 T1573 Encrypted Channel
Collection · TA0009
- 🟡 T1005 Data from Local System Browser profiles, crypto wallets
- 🟡 T1115 Clipboard Data Crypto address swap
- 🟡 T1119 Automated Collection
- 🟡 T1056.001 Input Capture: Keylogging
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.
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.
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 variant | Filesize gate | $o1 / $o2 / $o3 | Verdict |
|---|---|---|---|
| Original 74 MB carrier | FAIL (74 MB > 5 MB) | 0 / 0 / 0 | No match |
| Stripped 1.2 MB carrier | PASS | 0 / 0 / 0 | No match |
| Memory-dumped unpacked payload | PASS | 3 / 3 / 3 expected | Match |
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
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:
- Strips the 0xCC overlay (drops 70 MB of padding).
- 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). - Extracts the 1,072-byte polymorphic prefix and the 345,747-byte encrypted body separately.
- Documents the cipher constants harvested from the prefix’s instruction stream.
- 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:
| Constant | Used in | Operation | Likely role |
|---|---|---|---|
| 0x5f50f517 | +0x001 / +0x053 | xor edx, K | EDX whitening (paired) |
| 0xef19cbd0 | +0x015 / +0x03f | sub eax / add eax | EAX offset (paired) |
| 0x8ef362b6 | +0x072 / +0x084 | sub ecx / add ecx | ECX offset (paired) |
| 0xda1ee056 | +0x067 / +0x08f | sub edx / add edx | EDX offset (paired) |
| 0x1c1addf8 | +0x09f | add edx, K | EDX increment |
| 0xac8b0af8 | +0x0bd | sub edx, K | EDX decrement |
| 0xe5a0b512 | +0x0c7 / +0x0eb | xor eax, K | EAX whitening (paired) |
| 0xdc8117ae | +0x0ac | xor eax, K | EAX whitening |
| 0x1f7eeabb | +0x193 | sub [ecx], K | Per-block decrypt step (memory write — needs valid [ecx]) |
| 0x7b5677b6 | +0x1a9 | add ebx, K | EBX offset |
| 0x547a4 | +0x05a | mov esi, K | Length: 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:
| Hypothesis | Test | Result |
|---|---|---|
| Single-byte XOR | All 256 keys, look for MZ at decrypted offset 0 | 0 keys produce MZ |
| 4-byte rolling XOR | Each prefix constant as the 4-byte key | 0 keys produce MZ; 36–55 % printable |
| RC4 | Each prefix constant + blob[0:16] as the key | 0 keys produce MZ; 33–44 % printable |
| Cyclic XOR with legit reloc table | Body bytes XOR with the 33 KB legitimate reloc data, cyclically | No MZ, no PK, no plaintext |
| zlib / raw-deflate / lzma | Decompress at offsets 0, 0x430, 0x800, 0x1000 | No valid stream at any offset |
| Embedded markers | Search for MZ, PE\0\0, PK\x03\x04, gzip magic | 5 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.
The carrier never executes the blob from anywhere in .text.
- No
.textreaches the blob. Confirmed via radare2 (axt @ 0x004da400= empty), value search for0xd2000/0x4d2000/0x8224/0x547a4as immediates anywhere in.text= zero hits, and pattern search forpush 0xd2000/mov reg, 0xd2000= zero hits. - The carrier itself never executes the blob. Even with full PE-image emulation, no execution path from
entry0throughWinMainever transfers control to address0x004da400. - The prefix is genuine cipher code, not noise. Length constants
0x547a4 + 0x31f = 0x54ac3exactly 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.
The blob is either cargo or it’s waiting on runtime state.
(1) Cargo for a separate loader stage. The blob
is data on disk. A different component in the campaign (a
JavaScript dropper, PowerShell stage, or a downloader) reads
the carrier file off disk, locates .reloc, sets up
a 0x547a4-byte buffer + a 0x31f-byte buffer, jumps into the
prefix code, and the cipher fills the buffers with the
unpacked Lumma payload. The carrier is just a delivery
wrapper.
(2) Cipher requires runtime state from the
carrier’s WinMain. A heap allocation, a global
initialised by MFC’s
CWinApp::InitInstance, or a value passed via TLS
— something the static analysis has not enumerated
— sets up [ecx] in the live process. The
unpacker only runs after a long initialisation
sequence and is reached via an indirect mechanism (vtable,
callback, exception handler, message map) not via a literal
address.
Recommended dynamic-analysis breakpoint
To extract the unpacked payload in REMnux/FlareVM with x32dbg / WinDbg:
- Open the sample in the debugger; let MFC
WinMaininitialise (run until idle). - Set a memory-write breakpoint at the page covering VA
0x004da593, OR a hardware execute breakpoint at0x004da593directly. - Continue execution. When the breakpoint fires, capture
ECX— that points to the destination buffer the cipher writes to. - Let the loop run to completion (the prefix ends at
0x004da830); dump the destination buffer of sizeESI(= 0x547a4 bytes) to disk. - 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
| Artefact | Type | Value |
|---|---|---|
| Delivered carrier (74 MB padded) | SHA-256 | eff51f995cd6463cd9b3a2ea4a14cc85e3cc5c1b5b71db6d90765b3df175abba |
| Delivered carrier | MD5 | a3be0b0ebbf9428015cacc27cf5d51a7 |
| Stripped carrier | SHA-256 | c466795354007a604fa1805b6d97b6f3e43179c85544594fe365f22ede8fe0a6 |
Code-signing — lifted leaked NVIDIA certificates (Lapsus$ March 2022)
| Sig digest | Cert serial | SHA-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
| Domain | Role | Confidence |
|---|---|---|
| abruptyopsn[.]shop | C2 candidate | Medium |
| cegu[.]shop | C2 candidate | Medium |
| cloudewahsj[.]shop | C2 candidate | Medium |
| framekgirus[.]shop | C2 candidate | Medium |
| klipgonuh[.]shop | C2 candidate | High — observed |
| nearycrepso[.]shop | C2 candidate | Medium |
| noisycuttej[.]shop | C2 candidate | Medium |
| rabidcowse[.]shop | C2 candidate | Medium |
| regularlavhis[.]click | C2 candidate | Medium |
| tirepublicerj[.]shop | C2 candidate | Medium |
| wholersorie[.]shop | C2 candidate | Medium |
| yuriy-gagarin[.]com | C2 candidate | High — observed |
| steamcommunity[.]com | Dead-drop resolver (legit, abused) | High |
| www[.]gstatic[.]cn | C2 candidate (typosquat of gstatic.com) | Medium |
Network — observed IPs
| IP | Port | Associated | Category |
|---|---|---|---|
| 104[.]21[.]82[.]94 | 443 | yuriy-gagarin[.]com | Suspicious (Cloudflare-fronted) |
| 172[.]67[.]199[.]224 | 443 | yuriy-gagarin[.]com | Suspicious (Cloudflare) |
| 172[.]67[.]162[.]153 | 443 | klipgonuh[.]shop | Suspicious (Cloudflare) |
| 185[.]160[.]247[.]17 | — | — | Lumma exfil egress observed in panel dump |
| 64[.]233[.]181[.]94 | 443 | — | Suspicious |
TLS fingerprints (Steam dead-drop call)
| SNI | steamcommunity.com |
| Version | TLS 1.2 |
| JA3 | a0e9f5d64349fb13191bc781f81f42e1 |
| JA3S | b677083c9768d0548331fca998152a10 |
| Cert thumbprint | e4fde2a81727d33dcbe228f20c59a9ee522fc470 (DigiCert SHA2 EV CA → Valve Corp) |
Host artefacts
| Type | Value |
|---|---|
| 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 |
| Inherited PDB path | D:\Projects\MultiCommander\BuildOutput\Output\Win32\Release v143\MultiUpdate\MultiUpdate.pdb |
| Manifest identity | name="Microsoft.Windows.AutoUpdate" / description="MultiUpdate" |
| LummaC2 panel banner (companion file) | "LummaC2, Build Oct 9 2023" / LID format BhgGkI--IB4 |
Recommendations
For incident responders working this engagement
- Validate the new YARA rule against the existing IOC pack and historical malware-bazaar pulls. Roll into EDR if matches are clean.
-
Expand the dead-drop hunt: any host with
Sysmon-recorded TLS to
steamcommunity.comfollowed by TLS to a*.shopdomain within ≤ 5 minutes is likely a Lumma victim regardless of which specific.shopdomain rotated in. - Re-issue affected user credentials — session-token revocation for any browser-stored OAuth tokens; the operator has the cookies.
-
Block the inherited carrier identity:
AppLocker or WDAC rule for any
MultiUpdate.exenot signed by the genuine MultiCommander publisher (Mathias Svensson), denied at execution. - 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
-
Static decryption of the
.reloctrailer. The encrypted blob is preserved at/tmp/lumma_encrypted_blob.bin. Walk back from the cross-reference to0x004da400to 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. - 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.
- 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 - 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)
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.