Skip to main content
Mallory
We Turned Claude Code Into a Malware Analysis System. Coordination Broke First.

We Turned Claude Code Into a Malware Analysis System. Coordination Broke First.

Maxim GofnungSeptember 2, 20268 min read

Earlier this summer at Black Hat USA, I gave a talk about what happens when you point Claude Code, a general-purpose coding harness, at malware. We call the resulting system Mortex.

We aren't malware analysts. That's the point. Getting there took more rebuilds than we expected, and one of the obstacles wasn't the malware at all.

From one result to a system

Attackers have to put something in the field, and that leaves a footprint. Take a sample apart and you can pull domains, IPs, execution behavior, and sometimes a trail into a wider campaign. One sample alone isn't attribution. Correlated context is what turns it into the bigger picture: variants, shared infrastructure, and the campaign behind them.

That correlation used to cost twice over. Malware analysis takes highly skilled labor, the kind of low-level OS knowledge and behavioral intuition that takes years to build. It also takes isolated, hardened lab infrastructure, because you're effectively detonating a bomb and the last thing you want is a sample escaping. Sandboxes already automated pieces of that workflow. They didn't eliminate the tooling, the maintenance, or the analyst judgment. Every additional sample still consumed both.

Then one result changed the question. Mallory CEO, Jonathan Cran, ran a quick spike, Claude against a real sample, and got back a coherent kill chain. That result proved Claude could do the work. Turning it into something we could trust on the next sample, and the one after that, was a separate problem.

So we made a bet: could one good result become a repeatable system? We called the experiment Mortex.

Version zero was deliberately thin. Claude Code became the harness, running on Cloud Run. A sample came in. Three personas (an orchestrator, an analyst, and a report writer) shared one mutable workspace. Skills wrapped the usual tools: triage, Ghidra, YARA, unpacking, report generation. Audit hooks fired on every tool call. Collection, interpretation, and authorship still lived in the same place.

Version zero architecture: a sample.exe enters a Cloud Run harness running Claude -p, with orchestrator, analyst, and report writer personas, audit hooks on every tool call, and skills for triage, Ghidra, YARA, unpacking, and write-report, producing a wiki, IOCs, report, and verdict.
Version zero: Claude Code as the harness. Sample in, three personas, skills, audit hooks, outputs.

Adaptivity isn't coordination

The report at the end was a verdict. It told us almost nothing about how the run actually happened.

So we built Hotwash, an after-action agent that reads the hook-generated event stream: dispatches, tool calls, failures, retries, and where the time went.

Hotwash re-reads that history to judge whether the report is good enough for a hunter, and whether the pipeline is healthy for operators. It doesn't change the underlying verdict.

Hooks generate an observed event stream of dispatches, tool calls, failures, retries, and time spent. An after-action agent reads that stream and writes to Hotwash, which covers report quality (what matters, hunter verdict) and run operations (run findings, pipeline health).
Hooks made the run visible. Hotwash made it legible.

The next wall wasn't the sample. It was the multi-agent system we'd built around it.

Hotwash surfaced the finding that forced a rebuild: two parallel extraction jobs had stepped on each other. One deleted a directory while the other was still writing to it, and the run only recovered after three failures and a full rerun. Recovery was the trap: shared state had no owner, and adaptivity wasn't coordination.

Hotwash finding, from the section 'What the run ran into': The agent raced itself. It unpacked Genesis-Loader.zip twice, deleted ./sample/extracted/Genesis while its tools were using it, and had to start over, creating three failures all by itself.
Hotwash finding: the agent raced itself.

That collision drew a boundary we hadn't had before. In version zero, the agent owned both taking the sample apart and analyzing it, which is exactly how a coordination failure ended up corrupting the collection itself. The agent was also spending a finite run budget discovering tool compatibility and repeating mechanical work.

Now code alone collects. It identifies the artifact, routes packed, native PE or ELF, and managed inputs into bounded analyzers, and seals the results into an evidence database:

  • DIE
  • YARA
  • capa
  • Ghidra
  • PE / .NET

The agent only ever interprets that sealed evidence afterward. It never receives sample bytes, and it never runs the analyzers. Mechanical work stays in code, deterministic and single-owner. The model's job is constrained synthesis on evidence it can't mutate.

Multiple specialist agents still need to work a case, so we added a blackboard: shared working memory with a single owner. Disposable analysts return typed proposals. An orchestrator alone validates and records them. References have to resolve, duplicates add no weight, and conflicts stay visible instead of being voted away. Known gaps drive the next pass. IOCs are derived from the board. The report and the verdict stay constrained synthesis, not a majority vote.

That's what the extraction collision was missing: shared memory without shared write authority. No analyst owns the whole answer, and no analyst writes the board.

The blackboard pipeline: multiple analysts (1, 2, N) propose claims into a grid, disputed claims get flagged with a question mark or exclamation point, and only the claims that survive dispute are kept on the board.
The blackboard: analysts propose and dispute. The board consolidates claims.

On-disk isn't as-run

Coordination was fixed. The next wall to run through was visibility. Packers, VM protectors, staged loaders, and encrypted configs keep the real payload out of the static image. You detonate, or you never see it.

Detonation doesn't replace static analysis, and it's not a panacea. Static owns structure and on-disk facts. Detonation adds as-run behavior. Together they yield more than either lane alone. So we detonated. A cloud VM became a malware lab.

GCP became the control plane for detonation. A Cloud Run job launches a controller, which spins up a single, disposable Compute Engine VM from a pinned Windows image. Input and output move through an authenticated IAP and WinRM tunnel. The guest VM has no public IP and no general egress. We observe connection attempts. We don't allow completed C2.

Four sensors load at boot: our own kernel drivers.

  • Process notify
  • Filesystem minifilter
  • Registry callback
  • Observe-only WFP callout

Each copies events into a bounded kernel queue that a user-mode collector drains. Rows join on a stable process key, not a reused PID. The WFP callout never blocks or injects; it records connect attempts separately from established flows.

The four sensors join into a single ordered timeline instead of four separate logs: a process launches a child, that process attempts an outbound connection, a file gets written to a shared folder, a registry run key gets set for persistence. One run, one ordered story of what the sample actually did, joined back to whatever static analysis already found.

When execution finishes, the guest seals the case, the controller pulls it back through the same tunnel, verifies it outside the VM, and publishes it to Cloud Storage. Only a verified success triggers deletion of the VM and its disk. If the controller itself fails, we keep the machine around for diagnosis instead of tearing it down.

The Lab: On-disk is not as-run. A Cloud Run controller (no public IP) talks to a disposable Compute Engine VM running sample.exe, which hands off to a Cloud Storage sealed case, then the host is gone. Below, a timeline of four sensor events: PROC create (a child process), NET connect_attempt (not established), FILE write (a dropped disk), REG set (a Run key).
A disposable Windows VM records the run as one timeline, seals the case, and is gone.

The case only pays off in Mallory

Most of this is only useful if a case can leave the lab.

We ran a sample through Mortex. On the dynamic lane it captured a DNS lookup to fimmora.surf and a TCP attempt to 165.227.123.79:6504. The report calls the sample malicious with medium confidence and stops there: no confirmed protocol, beacon, persistence, or exfil. The actionable indicators are the domain and the egress IP the sample attempted to reach.

Mortex reference page titled 'Go-Compiled Windows Payload Exhibits Host Discovery and Outbound Network Activity', summarizing a malicious Go-compiled 64-bit Windows executable that resolved fimmora.surf and attempted a failed TCP connection to 165.227.123.79 on port 6504, with observables for the domain, file hashes, and IPv4 address.
Figure 1. Mortex report viewed in Mallory.

That report lands in Mallory as a Mortex-sourced reference on fimmora.surf.

On that indicator, Mortex sits next to Palo Alto Networks Unit 42, already naming it a Remus Info-Stealer C2 on the same IP and port. Open the Unit 42 reference and the campaign is named.

Mallory entity page for the domain fimmora.surf, showing 4 malicious verdicts and opinions from cyber_security_news, mortex, gurucul_threat_research, and palo_alto_networks_unit_42_timely_threat_intel, all tagging it as a Remus infostealer C2 domain.
Figure 2. Mallory entity for fimmora.surf. Mortex is a reference on that indicator, next to Unit 42, which already names it Remus C2.
Palo Alto Networks Unit 42 reference titled 'Remus Info-Stealer Uses Blockchain-Anchored C2 and Fake Cracked Software Lures', summarizing an active Remus infostealer campaign distributed through SEO-poisoned fake cracked software sites that resolves its C2 URL through an Ethereum smart contract.
Figure 3. Unit 42 Remus writeup opened from that entity.

Mortex produced indicators from what it observed. It couldn't attribute a family. Mallory could, by correlating those indicators against thousands of intel sources.

Make every case improve the next

A sealed case is still a single run. The next engineering work is to make every case improve the next: more believable Windows, deterministic Linux evidence, open-weight models evaluated against known cases, asynchronous gap-driven recursion, and a versioned corpus of sealed evidence so variant relationships can be joined exactly.

Historical reports stay immutable. We re-analyze; we don't rewrite the past.

Four pillars feeding case-to-case improvement: environments (collect what samples actually produce, Windows realism, Linux detonation), models (measure judgment on known cases, open-weight eval, known-case baselines), investigation (let gaps drive the next pass, gap-driven passes, async analyst loops), and corpus (make sealed history reusable context, versioned cases, joins and re-analysis), flowing from case to system to next case.
Make every case improve the next: environments, models, investigation, corpus.

This is the power of the graph. One indicator, and Mallory has the family, the industries they hit, the countries they operate in, and whether you're already in the blast radius.

See what Mallory already knows that's relevant to your environment

Start a 14-day free trial.

Start Free Trial