Modern stealer config formats: how Lumma, Atomic, and DanaBot store their C2 data

The original malwareconfig.com indexed 25,473 configs across families that mostly belonged to a single design school: njRat, DarkComet, Xtreme RAT, adWind, NanoCore. Looking at the 2015-era stat pages now, the config layouts were strikingly homogeneous. A header byte or two for versioning, a length-prefixed list of C2 hosts, an RC4 or single-byte XOR key applied uniformly across the blob, occasionally a mutex name and an installation path, sometimes a build ID. You could write a generic-ish extractor that handled half the families with parameter tweaks. Kevin Breen’s RATDecoders repo is still the cleanest reference for what that era looked like in code.

The three currently-active stealer families I want to walk through (Lumma, Atomic, DanaBot) tell a more complicated story. They each solve the same general problem: the loader needs to know where to send the stolen data, what to steal, and which build it is. They each solve it differently, and the differences are interesting both for what they imply about the families' operational maturity and for what they mean for static extraction in this period.

Two important context notes before any of the technical detail. First, two of these three families had law-enforcement actions against them in 2025: Microsoft DCU plus DOJ plus Europol seized roughly 2,300 Lumma C2 domains in May 2025, and DanaBot infrastructure was targeted in the second-round Operation Endgame action a few weeks later. The samples I am describing reflect both the pre-takedown variants and the residual / reboot variants we still see hitting sandboxes. Second, none of the analysis below is novel. The Lumma encryption-layer breakdown was published by several researchers in 2024-2025 and the DanaBot layout has been documented since 2018 in various forms. What I am doing here is contrasting them against each other and against the 2015 baseline, because that contrast is the part you do not get from any single family writeup.

Lumma: per-build keys, multi-layer obfuscation, dead drops

The 2015 mental model for “stealer config” is that the config is a static blob inside the unpacked binary, decrypted with a hardcoded key at runtime, parsed by a known struct. Lumma broke each of those assumptions independently.

The blob is not statically embedded in the same place across builds. Different build chains drop it at different offsets in the unpacked image, and at least one obfuscator family (which cycled in and out of use during 2024) prepends junk PE sections specifically to push the blob past static signatures that anchor on offset.

The decryption key is per-build. There is no single key that works across samples. Each build embeds its own key, derived during build by the affiliate’s panel, and the affiliate panel itself rotates the key generation logic on a rough monthly cadence. By 2024 the key derivation had moved through at least three documented schemes: a simple XOR-with-build-id, a PRNG seeded from a hardcoded constant plus build-id, and an AES-128 derivation that uses a host fingerprint computed at first run. The static extractor has to identify which scheme the build is using before it can decrypt anything.

The decrypted blob is itself layered. After the outer-layer decryption, what you get is not the plaintext config. It is a base64-encoded payload, which decodes to a second-layer structure that uses its own length-prefix scheme. The inner structure carries the C2 list, the credential targets, the build ID, and a fallback resolver address. The resolver address is the dead-drop. When the primary C2 list is unreachable, the loader hits a Steam profile bio or a Telegram channel description and pulls a fresh C2 from there. That gave the family meaningful resilience against sinkhole campaigns.

For static extraction, this changes the workflow. The 2015-era approach (find the embedded blob, find the key, decrypt, parse) collapses into a single function. The Lumma approach is at least three functions, with the first one (identify the build’s key derivation scheme) being the hardest to automate. Several public extractors handle this by carrying signature lists for each derivation scheme, and the signatures need maintenance. The community implementations I have seen that survived 2025 are the ones whose maintainers were willing to update signatures every two to three weeks during peak Lumma activity. Most others fell behind, then quietly stopped working. Which is annoying, because when an extractor stops working without a clear error, analysts assume the sample is something else and waste time on the wrong family.

Malpedia’s Lumma entry is the canonical starting point if you want sample collections and existing yara coverage to test against.

Atomic Stealer (AMOS): the AppleScript / Mach-O config split

Atomic is the macOS infostealer that did not exist when malwareconfig.com was actively cataloguing configs in 2017. It is interesting precisely because the design constraints are different. macOS sample distribution is heavily skewed toward fake-installer DMGs, the user has to bypass Gatekeeper to run the binary, and the family operators clearly optimised for the assumption that the user is going to be in front of the screen and motivated to make the thing work.

Earlier AMOS variants (2023 era) stored basically all of the config as plaintext strings inside an AppleScript that drove the data theft. The script would invoke osascript to steal browser data, then curl the result to a hardcoded C2 URL. The “config” in those samples was effectively the script itself. Static extraction was trivial in the same way the 2015-era njRat samples were trivial: read the strings, you have the answers.

What changed in 2024-2025 is that the family has been progressively moving config data out of the AppleScript and into a Mach-O binary. The AppleScript still exists for its UX role (draws the fake password prompt, walks the victim through the disable-Gatekeeper steps), but the C2, the file-collection paths, and the keychain-targeting list increasingly live in a Mach-O section called __data or sometimes __cstring. The config blob there is currently lightly obfuscated rather than encrypted, mostly with single-byte XOR or a fixed XOR pattern. It is solvable but it is a meaningful step up from the prior plaintext design.

For static extraction, the relevant change is that any tooling that handled AMOS by parsing AppleScript strings stopped getting the full picture during 2024. You have to also examine the Mach-O sections, identify the obfuscated config region (usually clear from entropy plus length plus its offset relative to known function imports), and apply the obfuscation routine. None of this is hard, but it is different from what the family looked like a year before, and it caught some tooling out.

The other macOS-specific complication is that AMOS samples are sometimes distributed already-decrypted-at-rest because the operators are trusting the DMG distribution chain to provide their tamper-resistance. So the same family can have samples where the static-extraction approach is “read the strings” and samples where it is “find the Mach-O section, XOR-decode, parse.” Sample triage matters more than it does on the Windows side. Malpedia’s AMOS entry lists current variants.

DanaBot: a modular config that survived a takedown

DanaBot pre-dates the disruption. The family has been documented since 2018 and the original config layout reflects that history. The unpacked main binary contains an embedded RC4-encrypted blob with the key embedded a known offset away. The blob, once decrypted, is a structure that lists which modules to load and what configuration each module needs.

This modular design is the part worth pointing at. The 2015-era families tended to bundle every behavior into one binary with one blob describing it. DanaBot’s config is more of a deployment manifest. The main binary is small. It loads modules by hash, each module gets its own per-module config slice from the parent, and the modules themselves can be updated by the operator without changing the main binary. From an extraction standpoint, you decrypt the parent config, parse the module list, then optionally walk into each module’s config slice. From an analyst standpoint, this is closer to how legitimate enterprise software is built than to how 2015-era stealers were built.

The blob format itself is not exotic. Length-prefixed strings, a few enum values, the C2 list, a campaign ID. The parsing logic is straightforward once you have the RC4 key. What is exotic is the operational layer wrapped around it: the family used signed updates, the C2 protocol included a mutual-authentication step, and the module-loading mechanism was robust enough that several public extractors built in 2019-2020 still produced usable output against 2024 samples without modification. That is rare for a family that age.

The May 2025 takedown disrupted the live infrastructure but not the design. Residual samples still get sandboxed. Their config blobs still decrypt cleanly. The C2 endpoints are mostly sinkholed or non-routing now, but the modular structure is preserved, and a future reboot under different operators would inherit the same architecture if it picked up the codebase. That is one of the reasons family attribution becomes harder after a takedown: the pre-takedown samples remain the cleanest reference for the post-takedown reboot, even if the operators are different. Malpedia’s DanaBot entry tracks the sample collection and family-tree notes.

What happened to the 2015 homogeneity

If you put the three families above next to njRat, DarkComet, and adWind, the differences are not really about the technical sophistication of any individual primitive. RC4 was used in 2015 and is still used in DanaBot. Length-prefixed string lists were used in 2015 and are still used in all three modern families. AES-128 was available in 2015 and was occasionally used in higher-effort families even then.

What changed is the operator economics. The 2015 families were largely individual or small-group projects: a single author or a handful of collaborators, a single distribution channel, a single panel. The config layout could afford to be uniform because there was nothing pulling it toward heterogeneity. The 2026 families are mostly malware-as-a-service, with affiliates who operate independently from the developers, panels that issue per-build configs at scale, and an explicit incentive to make each build look different from the last so signature-based detection lags behind. Per-build keys are not a security feature for the malware. They are a logistics feature for the operator network.

The other thing that changed is that the families are much more aware of the static-extractor community. Lumma’s key-rotation cadence is essentially designed to invalidate the public extractors faster than they can be updated. Atomic’s move from AppleScript-strings to Mach-O sections is a direct response to existing AppleScript-aware tooling. DanaBot’s modular config makes the family resilient to extractor breakage in any single module, because the extractor still works on the rest. None of this is novel research; what is worth noticing is how systematic it has become.

What this means for extraction tradecraft

The practical takeaways, for anyone writing or maintaining static extractors in this period:

  1. Per-family extractors are still the right shape. Generic-ish extractors that handled half the 2015 families with parameter tweaks do not survive contact with the 2026 families. Each modern family needs its own implementation, maintained by someone who is willing to update it on a rolling cadence.
  2. Identify which decryption scheme the build is using before you try to decrypt anything. For Lumma specifically, this is the difference between an extractor that produces correct output and one that produces convincing-looking garbage. Mismatched-scheme failures are quiet failures.
  3. Sample triage matters more than it used to. AMOS samples in particular can be radically different in structure within the same family, and the “right” approach depends on the variant.
  4. The community references that survived this period are the ones whose maintainers committed to monthly updates. The dead repos in this space outnumber the living ones at this point. If you are picking a starting reference, look at last-commit dates, not popularity.
  5. For testing, Malpedia plus MalwareBazaar plus URLhaus cover most of what you need. CAPE Sandbox (kevoreilly/CAPEv2 ) is the most useful for runtime-extracted configs when the static path is too brittle. ANY.RUN is fine if you need to see network behaviour without standing up your own sandbox.

Static config extraction is not dead, but it is not the cheap reliable thing it was in 2015. The families that are worth writing extractors for in 2026 are the ones with enough sample volume and analyst interest that the maintenance cost is justified. For everything else, dynamic extraction via a sandbox is usually faster, cheaper, and produces better data. That is a different blog post.

Aside: njRat is still around

If you came of age in malware analysis during the original malwareconfig.com era you will be unsurprised to hear that njRat samples still arrive at sandboxes regularly. The config layout is unchanged from the 2014-era spec. The single-byte XOR is still there. The extractor I last touched in 2018 still works on samples that landed in MalwareBazaar last week.

I find that funnier than it probably should be. The point of writing this post is the contrast between the 2015 design school and the 2026 design school, and njRat is the reminder that the 2015 design school did not actually go away. It just stopped being where the action is.

  • Maren / halt0p