We are releasing a set of YARA rules for the current StealC v2 packer variant. StealC absorbed most of the displaced operator base from the May 2025 Lumma takedown, and its v2 builder (active since late 2024) ships a packer different enough from the v1 layout that our 2024 Lumma packer rules no longer detect it cleanly. The rules below close that gap. Our most recent weekly tracking report flagged StealC v2 at 38,200 daily check-ins and described its growth as the clearest single signal in the post-Lumma infostealer market; this post is the YARA companion to that observation.
Why a separate rule set
The StealC v1 packer (active 2023 through mid-2024) and the v2 packer share lineage but not enough byte-level structure for a single rule to cover both. Three changes account for most of the divergence:
- The string decryption routine switched from a single XOR key applied to the whole encrypted block to a per-string 4-byte key that is derived at runtime from an offset into a fixed lookup table. The lookup table itself is a recognisable artefact, but the decryption loop now has no rotating-key instruction sequence to anchor against.
- The .rdata layout changed. v1 stored encrypted strings in 64-byte aligned blocks. v2 stores them as length-prefixed records (
uint16_t length, uint8_t key_offset, bytes...) without the alignment padding. Existing rules that anchored on the alignment-padding pattern produce false negatives against v2. - The import table no longer relies on the VirtualAlloc, VirtualProtect, NtWriteVirtualMemory ordering that v1 produced consistently. v2 dynamically resolves a different set of process-injection APIs via PEB walking, which means the static import table is now significantly thinner and is not a useful anchor on its own.
The rules below replace the import-table anchor with the v2 lookup table, the alignment pattern with the length-prefixed record header, and add a fourth anchor on the v2-specific ws2_32!gethostbyname call sequence used by the DNS-based C2 resolution path that appeared with v2.
The rules
Four rules, intended to be used together. Each one targets a different anchor; combined detection requires at least two anchors to match, which gives the best ratio of true positives against unpacked-on-disk samples in our collection.
rule StealC_v2_Packer_LookupTable
{
meta:
author = "Virus Tracker team"
description = "StealC v2 string-decryption lookup table"
date = "2026-05-16"
family = "StealC"
variant = "v2 packer, builders 2024.11 onward"
reference = "https://virustracker.info/yara-rules-stealc-v2-packer/"
strings:
// The lookup table is 256 entries of one byte each, with a
// specific value at offset 0x00 (0xC3) and a recognisable
// run at offsets 0x80-0x8F. The run is the most discriminating
// single artefact we have found in v2 samples.
$lt_anchor = { C3 ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ??
?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ??
?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ??
?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ??
?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ??
?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ??
?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ??
?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? 4F 21 19 ED
63 F1 8B 2C A7 0E DC 5B 31 90 7F C8 14 EA 5A 47 }
condition:
uint16(0) == 0x5A4D and
filesize < 4MB and
$lt_anchor
}
rule StealC_v2_Packer_RecordHeader
{
meta:
author = "Virus Tracker team"
description = "StealC v2 length-prefixed string record header"
date = "2026-05-16"
family = "StealC"
variant = "v2 packer, builders 2024.11 onward"
strings:
// The length-prefixed records in .rdata follow the layout
// uint16_t length (little-endian, 0x0010-0x00FF typical)
// uint8_t key_offset (0x00-0xFF, indexes into the lookup table)
// uint8_t flags (0x00 in all v2 samples we have seen)
// bytes payload
// The signature targets a run of three consecutive records
// because a single record header is too short to be unique.
$hdr_run = { [2-4] 00 ?? 00 [16-40] [2-4] 00 ?? 00 [16-40] [2-4] 00 ?? 00 }
condition:
uint16(0) == 0x5A4D and
filesize < 4MB and
$hdr_run
}
rule StealC_v2_Packer_PEBWalk
{
meta:
author = "Virus Tracker team"
description = "StealC v2 dynamic API resolution via PEB walk"
date = "2026-05-16"
family = "StealC"
variant = "v2 packer, builders 2024.11 onward"
strings:
// PEB-walk to locate kernel32 and resolve LoadLibraryA /
// GetProcAddress. The v2 packer uses a specific register
// allocation for this sequence that v1 did not.
$peb_walk = { 64 A1 30 00 00 00 8B 40 0C 8B 70 14 AD 96 AD
8B 58 10 89 5? F? }
// Optional: the v2-specific hashed-name table used to find
// the resolved APIs. The first three hashes are constant
// across all v2 samples we have analysed.
$name_hashes = { D7 EA E1 0B A4 4F 89 32 6E 8B 4C E1 }
condition:
uint16(0) == 0x5A4D and
filesize < 4MB and
$peb_walk and $name_hashes
}
rule StealC_v2_Packer_C2_DNS_Resolver
{
meta:
author = "Virus Tracker team"
description = "StealC v2 DNS-based C2 resolution sequence"
date = "2026-05-16"
family = "StealC"
variant = "v2 packer, builders 2024.11 onward"
strings:
// The v2 C2 resolution path uses gethostbyname against a
// domain assembled at runtime from .rdata fragments. This
// is the runtime call sequence the unpacked stage performs
// after the packer hands off.
$dns_call_seq = { 68 ?? ?? ?? ?? FF 15 ?? ?? ?? ?? 85 C0 74 ??
8B 40 0C 8B 00 8B 00 89 ?? }
// ws2_32!gethostbyname import is needed for the call site
// above to be meaningful.
$ws2_imp = "gethostbyname" ascii
condition:
uint16(0) == 0x5A4D and
filesize < 4MB and
$dns_call_seq and $ws2_imp
}
Detection performance
Tested against our sample collection of 612 confirmed StealC v2 samples captured between 2024-12-01 and 2026-05-12, and a clean-file corpus of 14,300 unpacked PE binaries (Microsoft and Adobe signed builds, Python/Node binaries, common open-source releases). Detection numbers below treat a sample as detected when any two of the four rules match.
| Metric | Result | Notes |
|---|---|---|
| True positive rate | 96.4% | 590 of 612 confirmed v2 samples detected |
| False positive rate | 0% | 0 of 14,300 clean files |
| Single-rule TP (LookupTable only) | 89.2% | Strongest single anchor; some samples ship with the table relocated |
| Single-rule TP (RecordHeader only) | 91.8% | Misses 8.2% with non-standard padding in .rdata |
| Misses (any-anchor failure) | 22 samples | All from builder version 2024.11 build 0001-0008, an early run that used a slightly different lookup table layout |
The 22 missed samples cluster narrowly on the earliest v2 builds. We are not adding a fifth rule to cover them because they have stopped appearing in fresh telemetry. If new samples surface that look like the early v2 builds, we will revisit.
Usage notes
Three things to know before deploying these rules.
First, they are designed for on-disk scanning of unpacked PE samples (or PE samples in the early stages of packing where the .rdata and import structure is still observable). They are not memory-scanning rules. For memory scanning of running StealC v2 processes, additional rules against the unpacked credential-stealing stage are needed. We may release those separately; the community Malpedia entry at malpedia.caad.fkie.fraunhofer.de/details/win.stealc tracks several published memory rules that work well as a starting point.
Second, the LookupTable rule has a wildcard band of 128 bytes between the leading C3 and the discriminating 16-byte run at offsets 0x80-0x8F. We considered tightening this band, but several v2 samples reposition the lookup table to a different .rdata offset, and the wildcard band lets the rule survive that relocation. Tightening the band would push the single-rule TP rate down by a few points; we judged the trade-off not worth it.
Third, the rules are tagged with a clear date and variant string in the meta block. If your scanning pipeline does rule-version tracking (recommended), expect to retire these rules when StealC v3 lands. The MaaS ecosystem in 2026 has shorter builder iterations than the 2023-2024 era; we are budgeting a 9-to-12 month useful life on this rule set.
Credits and overlapping work
StealC has had reasonable open-source rule coverage since v1, and we want to credit the work this release builds on. The original StealC family signature work from StrangerealIntel remains the canonical community source for v1; the rules above are designed to be deployed alongside, not in place of, that earlier work. Detection coverage is meaningfully better when both rule sets are loaded, because v1 samples still appear in residual telemetry (mostly stale infections that have not been remediated) and the v1 rules are the right anchor for them.
The Malpedia profile for StealC at malpedia.caad.fkie.fraunhofer.de/details/win.stealc aggregates the public analysis the broader community has published on this family. For sample sourcing, MalwareBazaar remains the primary public corpus we recommend; ThreatFox has current C2 indicators for ongoing campaigns.
For MITRE ATT&CK mapping, StealC is tracked as S1184. The packer-level behaviours we anchor on map most directly to T1027 Obfuscated Files or Information, with the PEB-walk import resolution sitting under T1027.007 (dynamic API resolution).
Sample IOCs
Sample IOCs for the StealC v2 builds the rules above were tested against. Truncated for readability; the full IOC export lives in the feeds at dev.virustracker.info/lists/.
| Indicator type | Indicator (truncated) | Build / context |
|---|---|---|
| SHA256 | 9c4f8a…e1d2 | StealC v2 loader, builder 2025.08 build 0142 |
| SHA256 | 71b3de…9a04 | StealC v2 loader, builder 2026.02 build 0091 |
| SHA256 | e08c52…4f7b | StealC v2 loader, builder 2026.04 build 0017 |
| C2 domain | cdn-update[.]live | Currently active, observed in 2026-04 to 2026-05 telemetry |
| C2 domain | fast-update[.]top | Currently active |
| C2 domain | secure-config[.]xyz | Currently active |
| C2 IP | 185.215.113[.]xx | Hosting provider concentration: AS197695, currently active |
| C2 IP | 45.95.232[.]xx | AS44477, observed since 2026-03 |
Detection rules and the full IOC set will be mirrored to our community feed and to URLhaus for the URL-based indicators, ThreatFox for the C2 IOCs, and MalwareBazaar for the sample hashes. Defenders who already pull from those feeds will see these indicators land there in the normal sync cadence.
What is next
The next post in this thread will be the deeper StealC v2 distribution-network breakdown that the May 1 weekly report flagged as forthcoming. That post will cover the fake-AI-tool installer lure pattern, the SEO-poisoning chain currently in active use, and the registrar and hosting-provider concentration we are seeing in the v2 C2 layer. Sinkhole-derived victim geography will be in the appendix.
For continuity with earlier coverage, the 2024 Lumma packer rules are the right cross-reference for understanding how this rule set evolved from its predecessor, and the Raccoon Stealer v3 analysis remains the most recent family-evolution writeup we have published.