My Adversary Emulation Goes to the Moon… Until False Flag
Antonio Villani (RETooling), Silvio La Porta (CEO · RETooling), Giulio Barabino
REcon 2025 · Day 3 · Main Track · Reverse Engineering
Overview
"Adversary emulation" has become a marketing term rather than a technical discipline, and RETooling came to REcon 2025 to make that case sharply. In a talk that blended red team philosophy, LLVM inter

Key moments
- 2:19 Introduction: 12 years of offensive security — adversary emulation journey
- 8:55 Obfuscation in implants: evading detection through code obfuscation
- 15:50 Protection layer: dynamic API resolution to evade static analysis
- 22:49 LLVM IR-based implant: target-independent emulation framework
- 29:44 Dynamic analysis: identifying and extracting dispatch functions
- 36:12 Code graph analysis: forward and backward reference classification
- 42:09 Infrastructure analysis: resolver routine reverse engineering
My Adversary Emulation Goes to the Moon… Until False Flag
Speakers: Antonio Villani, RETooling; Silvio La Porta, CEO, RETooling; Giulio Barabino
Conference: REcon 2025
YouTube: https://www.youtube.com/watch?v=46hXXg9OCXc
Overview
"Adversary emulation" has become a marketing term rather than a technical discipline, and RETooling came to REcon 2025 to make that case sharply. In a talk that blended red team philosophy, LLVM internals, and hands-on APT41 malware analysis, Silvio La Porta, Antonio Villani, and Giulio Barabino presented their re-implementation of APT41's Scatterbrain obfuscator — a project that started as a master's thesis and ended with the team discovering previously undocumented weaknesses in the Scatterbrain deobfuscator's own heuristics. The talk is both a detailed technical blueprint for how to implement instruction-dispatcher-based obfuscation using LLVM, and a principled argument for why high-fidelity adversary emulation — what the speakers call "false flag emulation" — is a fundamentally different (and far more demanding) discipline than generic red teaming.
Background
▶ Watch: Introduction: 12 years of offensive security — adversary emulation journey (2:19)
The Problem with "Adversary Emulation" as Practiced
Red team engagements have evolved over roughly twelve years from vulnerability assessment and penetration testing through generic "adversary simulation" (mimicking the TTP class of a threat actor using commercial tools like Cobalt Strike) to what is now marketed as adversary emulation. The RETooling team drew a hard distinction between what vendors claim and what they deliver.
Consider process discovery — MITRE ATT&CK T1057. The MITRE description names CreateToolhelp32Snapshot as the typical API. But if you look at actual malware samples, PlugX implements process discovery using EnumerateProcesses and OpenProcessToken. Lumma Stealer uses a completely different API set. With 195 distinct malware families documented as implementing T1057, there may be 195 distinct implementations — and EDR detection logic is necessarily artifact-specific, not technique-specific. A red team exercise that covers T1057 with a Cobalt Strike beacon tells the blue team essentially nothing about their detection coverage against PlugX specifically.
False flag emulation is the speakers' term for emulation that replicates not just TTPs but the specific binary artifacts: the same API call sequences, the same obfuscation, the same binary structure, the same import protection mechanisms. The goal is to produce an implant that an EDR or sandbox would classify as the original malware family, not as "custom red team tooling." This requires a reverser, a malware developer, and a CTI analyst working together — and takes proportionally longer.
APT41 and Scatterbrain
APT41 is a Chinese state-sponsored threat actor engaged in both cyber espionage and financially motivated cybercrime. They are notable for developing their own obfuscation compiler — an LLVM-based toolchain that the security community calls Scatterbrain. Mandiant published a detailed technical analysis of Scatterbrain and later open-sourced a functional deobfuscator tool (credited to "Neo" in the talk). The RETooling team used both resources as ground truth for their own re-implementation.
Key Findings
▶ Watch: Protection layer: dynamic API resolution to evade static analysis (15:50)
The team produced:
- An LLVM-based re-implementation of Scatterbrain's two signature techniques: instruction dispatching and import protection via LCG-encrypted API names.
- Two independent displacement computation approaches (post-compilation patching vs. inline assembly labels), one of which obviates any post-processing step.
- A machine function pass-based approach that more closely replicates the per-instruction granularity of the original.
- Novel heuristic-breaking modifications to the dispatcher design that cause Mandiant's own Scatterbrain deobfuscator to fail completely.
Technical Deep Dive
▶ Watch: LLVM IR-based implant: target-independent emulation framework (22:49)
Scatterbrain's Instruction Dispatcher
The signature technique of Scatterbrain is the instruction dispatcher, which radically transforms a program's control flow. Rather than allowing basic blocks to flow sequentially, each individual instruction is isolated in its own basic block, and execution between blocks passes through a dedicated dispatcher function.
Dispatcher mechanics:
Each basic block contains:
- A legitimate instruction performing actual work.
- A
CALLto a block-specific dispatcher function. - An encoded 32-bit displacement value immediately following the call.
Each dispatcher:
- Retrieves the displacement from the bytes following the call site (the return address saved on the stack points directly to it).
- Decodes the displacement using one of several available methods (each dispatcher has a randomly generated encoding key).
- Adds or subtracts the decoded displacement from the return address on the stack.
- Returns — not to the original return address, but to the computed destination, which is the address of the next legitimate instruction.
- Preserves register flags throughout by pushing and popping them around the decoding operation.
Opposing conditional jumps (GCC pairs): After the dispatcher call and displacement, each basic block ends with two conditional branches with opposite conditions (e.g., JZ target and JNZ target) pointing to the same destination. Regardless of flag values, exactly one branch is always taken, and both lead to the same address. This creates a guaranteed jump that confuses disassemblers, which typically assume conditional jumps are genuinely conditional.
The net effect: the disassembler interprets the displacement bytes as instructions, the opposing conditional jumps make control flow appear non-deterministic, and the basic blocks are shuffled throughout the binary. Even modern decompilers struggle significantly with this pattern.
Import Protection: LCG Encryption
Scatterbrain obscures all Windows API imports at runtime using a custom encryption scheme:
- A random 32-bit seed is generated. Its first four bytes seed a Linear Congruential Generator (LCG) with parameters
A = 17and a sample-specific constantC. - The LCG state is advanced per character of the DLL name and API name strings, and the current state's top bytes form the XOR key for each character.
- A termination sentinel (the final LCG state) is appended to the encrypted string.
At runtime, each import has a corresponding encrypted structure containing:
- RVA of the encrypted DLL name.
- RVA of the encrypted API name.
- Pointer to store the resolved function address.
A dedicated resolver routine iterates these structures, decrypts the names using the LCG, calls LoadLibrary + GetProcAddress, and stores the results. The original IAT entries are replaced with stubs that call the resolver.
LLVM Re-Implementation
The team chose the LLVM infrastructure for their implementation, leveraging out-of-source LLVM IR passes (shared objects that can be loaded dynamically against an existing LLVM installation without recompiling LLVM itself). The pipeline:
- Clang compiles source code to LLVM IR.
- The obfuscation pass shared object is loaded and applied to the IR.
- The obfuscated IR is compiled to x86 native code.
Implementing the instruction dispatcher in LLVM IR:
The key challenge is that LLVM IR is target-independent — there are no concrete addresses, no notion of call stack layout, and no way to emit raw bytes using directives like DW or .long. The team worked around this through two approaches:
Approach 1 — Post-compilation patching:
- Use
splitBasicBlock()to relocate each instruction into its own block. - For each block, emit the dispatcher call using LLVM inline assembly (to control the exact instruction sequence).
- Replace the direct jump at the end of each block with an indirect jump (to prevent the compiler from merging relocated blocks back together).
- After compilation, use Python's
pefilelibrary and the Capstone disassembler to locate the inserted placeholder patterns (call-to-dispatcher followed by LEA+JMP), compute actual displacements, encode them, and overwrite the placeholders. - Clean up by erasing the export directory (which was used to mark dispatcher and obfuscated functions) and overwriting indirect jumps used as block terminators.
Three function attributes are applied to all obfuscated functions: naked, noinline, and optnone — preventing the compiler from inserting prologues/epilogues, inlining the function, or applying optimizations that might undo the obfuscation.
Approach 2 — Inline assembly labels (preferred):
LLVM's inline assembly syntax supports local labels and arithmetic on labels. This makes it possible to compute the displacement as (next_block_start_label - current_block_end_label - 4) XOR encoding_key entirely within the inline assembly, letting the linker resolve the concrete addresses. No post-compilation patching step is needed.
The implementation:
- Insert a
start_labelat the beginning of each block. - Insert an
end_labelafter the dispatcher call. - Emit a
.longdirective computingstart_label_of_successor - end_label - 4, XORed with the encoding key, using local label arithmetic. - Forward references (
fsuffix) and backward references (bsuffix) are handled by tracking basic block positions within each function.
Machine function passes (the most accurate approach): LLVM allows passes that operate on machine instructions immediately before binary emission. This solves the fundamental problem that the IR-to-machine-code translation is not 1:1 — a single IR instruction can expand to multiple machine instructions, breaking the assumption that each basic block contains exactly one legitimate instruction. Machine function passes operate at the machine instruction level, allowing precise per-instruction block splitting. The tradeoff is that these passes cannot be loaded dynamically; LLVM must be partially recompiled for each change. This approach produced the most faithful replication of the original Scatterbrain samples.
Import Protection Re-Implementation
The import protection LLVM pass:
- Iterates over module functions and identifies those with
DLLImportstorage class (Windows API calls). - Pre-compiles the source to extract the actual DLL-to-function mapping (LLVM IR alone doesn't expose which DLL exports which function).
- Encrypts DLL and API names using the LCG scheme.
- Creates global variables for both encrypted strings.
- Builds the import structure (RVA of encrypted DLL name, RVA of encrypted API name, result pointer).
- Emits a per-import stub using inline assembly that saves caller-saved registers, invokes the resolver, and restores registers.
- Replaces every call to the original Windows API import with a call to the corresponding stub.
The import decryption and resolver routines are distributed as a separate pre-compiled LLVM bitcode file, linked into each protected binary via llvm-link.
Validation Against Real Scatterbrain Samples
The team validated their implementation by running it against test cases and comparing output structure with known APT41 samples using Mandiant's open-source deobfuscator. Their obfuscated binaries:
- Passed the deobfuscator's dispatcher detection and recovery.
- Showed correct import structure recovery.
- Exhibited a 20× increase in execution time and 30× increase in binary size relative to unobfuscated input — consistent with each original instruction being replaced by 18–30 obfuscated instructions including dispatcher calls, opposing conditional jumps, and flag save/restore sequences.
Side-by-side CFG comparison showed that the de-obfuscated CFG of their test binary closely matched the CFG of the originally unobfuscated binary.
Breaking Scatterbrain's Own Deobfuscator Heuristics
In investigating the Mandiant deobfuscator's internals, the team identified three heuristic constraints the tool relies on:
- Brute-force search for
E8(near CALL) instructions: Any 0xE8 byte is a candidate dispatcher call. - Push FQ / Pop FQ verification: The deobfuscator looks for the flags-save/restore pair within 15 instructions of a candidate dispatcher.
- Invalidity checks: A candidate dispatcher is rejected if it contains any CALL, JMP, or privileged instruction.
To break all three simultaneously:
- Replace PUSH FQ / POP FQ with SAHF / LAHF: These are more limited flag-register instructions (they only touch the low byte of RFLAGS via AL). The deobfuscator's verification fails because it specifically looks for the PUSHFQ/POPFQ pair. The one caveat is the overflow flag (
OF) cannot be saved/restored by SAHF/LAHF; the team worked around this by manually triggering an overflow (loading 0xFF into AL and incrementing) to reconstruct it. - Extend the dispatcher beyond 15 instructions: The deobfuscator rejects candidates with more than 15 instructions before the return. Adding legitimate-looking instructions (including the overflow recovery sequence) pushes the count past this threshold.
- Insert a useless jump: Adding a dead jump instruction also qualifies as the "JMP found → invalid" heuristic trigger.
Result: after modifying their dispatcher design with all three evasions, Mandiant's deobfuscator failed to find a single dispatcher function in the obfuscated binary — the very test case that previously passed cleanly. This was independently confirmed by notifying the Mandiant tool author ("Neo"), who verified the fix was needed on his end as well.
The team's blog post (13 pages) provides the full technical detail beyond what could be covered in the REcon time slot.
Demo / Proof of Concept
▶ Watch: Dynamic analysis: identifying and extracting dispatch functions (29:44)
A video demonstration showed:
- An APT41 real-world sample disassembled, showing the opposing conditional jumps interleaved with dummy NOPs and the obfuscated function structure.
- The team's obfuscated test binary, showing the same structural pattern.
- The unobfuscated test binary.
- The deobfuscated version of their test binary — whose CFG matches the original unobfuscated CFG closely despite the two being generated by different toolchains.
The final demo showed the modified dispatcher (with SAHF/LAHF and the useless jump inserted) failing entirely under the Mandiant deobfuscator — zero dispatchers recovered from a fully obfuscated binary.
Defensive Implications
▶ Watch: Code graph analysis: forward and backward reference classification (36:12)
For blue teams and detection engineers:
- Artifact-level detection is brittle. The deobfuscator's heuristics (PUSHFQ/POPFQ pair, 15-instruction limit, JMP invalidity) can be bypassed with minor code changes. Detection logic built solely on specific byte patterns in the dispatcher will be defeated by trivial variations.
- Behavioral detection is more robust. An implant that calls
LoadLibraryandGetProcAddressat runtime to resolve all imports is behaviorally distinctive regardless of how the dispatcher is constructed. Runtime API resolution patterns remain a more reliable detection signal. - CFG-based detection is also viable — the opposing conditional jumps that always target the same destination are structurally anomalous and can be flagged statically.
For red teams:
- False flag emulation requires a reverser dedicated to understanding the target obfuscator's internals, not just a developer who can implement TTPs.
- Even small implementation deviations can change detection outcomes significantly. The test-iterate cycle is long and resource-intensive.
- Tools like Mandiant's open-source Scatterbrain deobfuscator are useful for ground-truth validation but their heuristics should be understood as attack surfaces, not guarantees.
For malware analysts:
- The LLVM-based Scatterbrain re-implementation provides a controlled environment for studying the obfuscator's effects without requiring access to live APT41 samples.
- The deobfuscator weakness described here should prompt updates to any static analysis pipeline that relies on PUSHFQ/POPFQ pattern matching for Scatterbrain identification.
Key Takeaways
- "Adversary emulation" as commonly practiced is not emulation — it is generic red teaming using commercial tools. True false flag emulation requires replicating specific binary artifacts, including obfuscation.
- APT41's Scatterbrain obfuscator uses instruction dispatchers (redirecting return addresses via encoded displacements) and LCG-encrypted import tables to defeat static analysis.
- LLVM IR passes can replicate Scatterbrain's instruction dispatcher and import protection, but achieving per-instruction basic block granularity requires machine function passes rather than IR-level manipulation.
- The inline assembly label approach to displacement computation eliminates the need for post-compilation patching, making the obfuscation pipeline cleaner and more maintainable.
- Three simple modifications to the dispatcher — replacing PUSHFQ/POPFQ with SAHF/LAHF, extending dispatcher length past 15 instructions, and inserting a dead jump — fully defeat Mandiant's Scatterbrain deobfuscator heuristics.
- The implementation carries a 20–30× size and execution time overhead, consistent with the original samples.
About the Speaker(s)
▶ Watch: Infrastructure analysis: resolver routine reverse engineering (42:09)
Silvio La Porta is the CEO of RETooling, an Italian security firm. He has worked as a malware reverser for nearly 20 years and is now pursuing a PhD. He has been a regular presenter at REcon, and RETooling's core business is reversing real malware samples and re-implementing them in their adversary emulation platform.
Antonio Villani is a malware developer and red team specialist at RETooling, responsible for the practical implementation work described in this talk.
Giulio Barabino contributed the core LLVM implementation work as part of his master's degree thesis at RETooling. His thesis formed the technical basis of the Scatterbrain re-implementation presented here.
Reviews
Dr. Zero (Offensive Security Researcher) — STRONG ACCEPT
RETooling built a working LLVM re-implementation of APT41's Scatterbrain obfuscator, then turned around and broke Mandiant's own deobfuscator with three trivial modifications — that's not a thesis project, that's a funded research outcome dressed in academic clothes.
Heather Calloway (CISO) — SOLID
RETooling makes a sharp and overdue argument that 'adversary emulation' as practiced is marketing, not methodology — and then proves the point by rebuilding APT41's obfuscator well enough to break the tool designed to detect it.