Inside Akira, ransomware's Rust experiment

Ben Herzog (Security Researcher · Check Point)

Virus Bulletin Conference 2025 (VB2025) · Day 2 · Green room

Overview

In "Inside Akira, ransomware's Rust experiment," Ben Herzog, a Security Researcher at Check Point, delivers a compelling account of manually reverse engineering an optimized Rust-compiled variant of the Akira ransomware. This talk addresses a critical and often-overlooked challenge in cybersecurity: the inherent difficulty of analyzing malicious binaries written in modern, systems-level languages like Rust. Unlike traditional C/C++ malware, Rust's aggressive compiler optimizations, sophisticated type system, and extensive standard library can transform straightforward source code into highly opaque and challenging assembly, even without explicit obfuscation.

Watch on YouTube

Visual summary for Inside Akira, ransomware's Rust experiment by Ben Herzog
Visual summary for Inside Akira, ransomware's Rust experiment by Ben Herzog

Key moments

  1. 0:00 Introduction: The challenge of reversing Rust binaries
  2. 2:40 Akira's optimized Rust binary and strange execution chain
  3. 4:00 First breakthrough: Identifying copy-pasted seahorse boilerplate
  4. 6:00 Encountering the "final boss" basic block in defaultaction
  5. 10:30 Understanding aggressive inlining and loop unrolling by Rust compiler
  6. 12:00 Deciphering Rust thread spawning and complex control flow
  7. 18:00 Conclusion: Aggressive inlining is a major Rust RE challenge

Inside Akira, ransomware's Rust experiment

Speakers: Ben Herzog, Security Researcher, Check Point

Conference: Virus Bulletin

YouTube: https://www.youtube.com/watch?v=c5REVEaMDf4

Overview

In "Inside Akira, ransomware's Rust experiment," Ben Herzog, a Security Researcher at Check Point, delivers a compelling account of manually reverse engineering an optimized Rust-compiled variant of the Akira ransomware. This talk addresses a critical and often-overlooked challenge in cybersecurity: the inherent difficulty of analyzing malicious binaries written in modern, systems-level languages like Rust. Unlike traditional C/C++ malware, Rust's aggressive compiler optimizations, sophisticated type system, and extensive standard library can transform straightforward source code into highly opaque and challenging assembly, even without explicit obfuscation.

Herzog meticulously details Check Point's journey through Akira's Rust encryptor, highlighting three key obstacles that required unconventional insights to overcome. The research not only unmasked the inner workings of Akira's Rust implementation but also provided invaluable lessons on the practical realities of reverse engineering highly optimized Rust binaries in the wild. This work is significant because it demonstrates that despite advancements in automated tooling, deep manual analysis, coupled with an understanding of compiler behavior and language internals, remains indispensable for comprehending sophisticated threats.

The talk serves as a stark reminder that as adversaries adopt modern programming languages, defenders must adapt their analytical methodologies. By sharing their experience with Akira, Check Point aims to equip the security community with the knowledge and mindset necessary to tackle the next generation of Rust-based malware, emphasizing that what appears to be "normal programming" in Rust can inadvertently introduce significant reverse engineering hurdles.

Background

▶ Watch: Introduction: The challenge of reversing Rust binaries (0:00)

The landscape of malware analysis has been significantly altered by the adoption of modern programming languages. For years, security researchers have grappled with the question of what Rust binaries truly look like internally, especially when compiled in highly optimized "release mode" for malicious purposes. Ben Herzog points out that while academic efforts have been made to understand Rust compiler output and improve automated tooling, the problem of reverse engineering Rust binaries is "really far from solved" (00:00). The core issue lies in the complexity introduced by the Rust compiler, which, in its pursuit of performance, can generate assembly that bears little resemblance to the original source code. Herzog uses an analogy: to build a great automatic pizza maker, one must first master manual pizza baking. Similarly, to automate Rust binary reverse engineering, the manual process must first be perfected. Yet, "very few people want to do it" (01:00) for real-world, optimized Rust malware.

The opportunity for such an in-depth manual analysis arose in early 2024 when the Akira ransomware group released a Rust version of their encryptor (02:00). Akira is a prominent ransomware threat, active since at least 2023, responsible for attacking hundreds of organizations across the US, EU, and Australia, causing tens of millions of dollars in damages. They are known for exploiting exposed VPNs using zero-days, one-days, and credential stuffing. Their preference for cross-platform malware likely made Rust an attractive choice, given its robust support for various operating systems. The Check Point team seized this opportunity, undertaking a full, old-school reverse engineering effort of Akira's Rust binary, culminating in the insights shared in this talk. This research was driven by the need to understand how malicious actors leverage Rust and what challenges its unique characteristics pose to static analysis.

Key Findings

▶ Watch: First breakthrough: Identifying copy-pasted seahorse boilerplate (4:00)

The comprehensive reverse engineering effort into Akira's Rust encryptor yielded several critical findings that shed light on both the ransomware's implementation and the broader challenges of analyzing Rust malware:

  • Optimized Release Build: The Akira binary was confirmed to be an optimized release build, completely devoid of the usual metadata found in debug builds. This immediately presented a significant hurdle, as common static analysis tools rely heavily on such metadata (02:00).
  • Unusual Execution Chain: The initial analysis revealed an unconventional, albeit linear, execution flow: main calls default_action, which calls lock, which then calls lock_closure. This chain, particularly the default_action and lock_closure names, raised immediate suspicions (02:00).
  • Extensive Boilerplate Copy-Pasting: A major discovery was that the Akira developers extensively copied and pasted boilerplate code from legitimate, third-party Rust crates. Specifically, large portions of the main function were derived from the seahorse crate (a CLI application framework), and parts of default_action were from the indicatif crate (for colorful terminal progress bars) (04:00, 06:00). This insight dramatically accelerated the initial stages of reverse engineering.
  • Aggressive Compiler Optimizations as "Obfuscation": The most profound finding was how the Rust compiler's aggressive optimizations, particularly inlining and loop unrolling, effectively "obfuscated" the code. Even simple library calls, nested several levels deep, were inlined into a single, complex basic block at the same "visual layer" as the developer-authored code (10:00). This created "soup of basic blocks" and "final boss of basic blocks" that were incredibly difficult to decipher, demonstrating that "obfuscation happens on its own" when programming normally in Rust (12:00).
  • Complex Thread Spawning Mechanism: Understanding how Akira spawned worker threads for encryption required a deep dive into Rust's standard library internals. The use of trait objects (specifically Box<dyn FnOnce()>), vtables, and nested library calls for thread::scope::new and thread::scope::spawn made tracing the thread's actual entry point extremely challenging (14:00, 18:00). Again, all this complex library logic was heavily inlined.
  • AI as an Auxiliary Tool: The researchers leveraged OpenAI3 (a precursor to GPT-5) to analyze a particularly complex basic block containing over 200 XMM instructions. While the AI successfully identified its purpose (counting CPU cores), it highlighted that AI is a tool for specific tasks, not a replacement for human deduction and understanding of higher-level developer intent (08:00).

Technical Deep Dive

▶ Watch: Encountering the "final boss" basic block in default_action (6:00)

The technical analysis of Akira's Rust encryptor by Ben Herzog and his team revealed the intricate challenges and the innovative solutions required to dissect modern Rust malware.

Main Function: Unmasking Boilerplate

Upon initial inspection of the main function, the researchers were confronted with a series of opaque function calls and a large, complex object with numerous unknown fields (04:00). This object was then passed to default_action, which, curiously, was the only action taken by the malware. Suspicion arose that this was boilerplate code. A web search confirmed this: the Akira developers had copied substantial code from the README.md of the seahorse Rust crate. Seahorse is a framework for creating command-line interface (CLI) applications in Rust.

Armed with this insight, the previously confusing elements clicked into place. The "opaque function calls" were merely initializations for seahorse flag objects, and the "huge object" was an App object from the seahorse library (04:00). The team could then directly map the assembly to the seahorse source code, identifying fields like usage (which contained the string "CLI bracket name") and even a version string indicating the compilation date of the binary. This identification transformed what could have been "two or three days of really painful reverse engineering" into an "educated guess," demonstrating the power of identifying reused open-source components (06:00).

Default Action: Compiler Obfuscation and AI Assistance

The default_action function presented a far greater challenge. While initially suspected to also be boilerplate, this time from the indicatif crate (used for colorful terminal progress bars), reality was less accommodating. The assembly for default_action featured an "ugly basic block structure," direct calls to the CFFI (C Foreign Function Interface), and a particularly daunting "final boss of basic blocks" — a block comprising over 200 instructions, all operating on XMM words, impenetrable to quick analysis (06:00).

Faced with this computational wall, the team employed a novel approach: delegating the analysis of this specific basic block to OpenAI3. The AI successfully determined that the block's purpose was to count the number of CPU cores available to the process, a common task for parallel computation (08:00). However, this functionality was absent from the indicatif boilerplate. Through human deduction, the researchers theorized that the Akira developers had likely modified the boilerplate's hardcoded worker thread count (e.g., four threads) to a user-supplied CLI argument, falling back to dynamically determining the available parallelism if no argument was provided (08:00). This would involve calling a function like available_parallelism().

Tracing available_parallelism() revealed a deep call stack: available_parallelism calls quota, which calls quota_v1 or quota_v2, which calls CPU_count, which calls CPU_count_s, which iterates and calls count_ones, finally relying on the pop_count_64 LLVM intrinsic (10:00). The mystery of the "final boss" was solved by realizing two key compiler optimizations:

  1. Inlining: The Rust compiler had inlined every single function in this five-level-deep call stack (available_parallelism down to pop_count_64) directly into the default_action function's basic block graph. This meant library code appeared side-by-side with developer-authored code, creating a "Christmas tree display" of interleaved functionality (10:00).
  2. Loop Unrolling: The iterated calls to count_ones were subjected to loop unrolling, expanding the loop into a linear sequence of instructions. This, combined with inlining, produced the massive, complex basic block that initially stumped human and nearly AI alike (10:00).

This example strikingly illustrated how "obfuscation happens on its own" in Rust; the developers merely requested a feature (available_parallelism), and the compiler's optimizations generated an assembly nightmare (12:00).

Lock Function: Navigating Rust's Threading Internals

The lock function, despite its name, didn't perform encryption directly but delegated it to worker threads. Tracing how control flow transferred from lock to these worker threads proved exceptionally difficult. The familiar "soup of basic blocks" from various call hierarchies reappeared, culminating in a call to thread::scope::new with an "oblique pointer" and no apparent thread entry point (12:00). This lack of a clear control flow mechanism was particularly unnerving for the reverse engineers.

Solving this required an "unthinkable" step: reading the Rust standard library documentation and, in some cases, directly poring over the Rust standard library source code (12:00, 14:00). The starting point was again boilerplate from indicatif, specifically the thread::scope::spawn function, which creates worker threads and takes a closure (a function that the worker thread will execute) as an argument (14:00). Akira replaced this harmless closure with their malicious file encryption logic, explaining the name lock_closure.

The thread::scope::new function takes two parameters: stack size and an object of type Box<dyn FnOnce()>. This Box<dyn FnOnce()> is a crucial Rust concept: a trait object. In essence, it's a pointer to an object whose concrete type is known only at runtime, similar to a C++ virtual table (vtable) (14:00). The FnOnce trait signifies that the object can be called like a function exactly once. The challenge was that Box<dyn FnOnce()> is a contract, not a concrete type, making it impossible to deduce the thread's entry point from its definition alone.

To understand the concrete type and the entry point, the researchers had to trace the object's construction back to thread::scope::spawn. This function initializes a Builder object. The Builder::spawn method then calls spawn_unchecked, which finally calls spawn_unchecked_. It is spawn_unchecked_ that constructs the actual vtable entry, wrapping the "naked entry point" of the thread (f) with additional bookkeeping logic (16:00). The ultimate entry point for the thread was found to be "inside of a call instruction inside of the shim pointed at by a vtable entry where the vtable is constructed by a library function which got called by a library function which got invoked by a builder object which was initialized by the original library function thread::scope::spawn" (18:00). Once again, the Rust compiler had inlined this entire, complex sequence of library calls into the lock function, making the control flow extremely difficult to follow without this detailed understanding of Rust internals.

Demo / Proof of Concept

▶ Watch: Deciphering Rust thread spawning and complex control flow (12:00)

This talk focused on the intricate process and findings of reverse engineering the Akira Rust encryptor rather than a live demonstration of the malware or a proof-of-concept exploit. The core "proof" was the successful, detailed analysis of the Rust binary itself, illustrating the methodologies and insights required to dissect such a challenging piece of malware.

Defensive Implications

▶ Watch: Conclusion: Aggressive inlining is a major Rust RE challenge (18:00)

The reverse engineering of Akira's Rust encryptor provides several critical defensive implications for cybersecurity professionals:

  1. Manual RE Skills Remain Paramount: Despite continuous advancements in automated tooling for malware analysis, the experience with Akira unequivocally demonstrates that manual, "old-school" reverse engineering is still essential for understanding highly optimized Rust binaries. Automated tools currently struggle with the level of compiler optimization (inlining, loop unrolling) that Rust inherently performs (00:00, 18:00).
  2. Understand Rust Compiler Behavior: Defenders must develop a deep understanding of the Rust compiler's aggressive optimization strategies. The fact that "obfuscation happens on its own" (12:00) means that even legitimate Rust code can become extremely difficult to analyze at the assembly level. Familiarity with how inlining, loop unrolling, and dead code elimination manifest in assembly is crucial.
  3. Familiarity with Rust Standard Library Internals: The talk highlighted that understanding complex features like thread spawning or trait objects often requires delving into the Rust standard library's source code and documentation. This level of internal knowledge is necessary to correctly interpret the compiler's output when these library functions are heavily inlined (12:00).
  4. Leverage Open-Source Intelligence: Identifying boilerplate code copied from legitimate open-source Rust crates (like seahorse and indicatif) can provide significant shortcuts in reverse engineering. Defenders should be aware of popular Rust libraries and consider cross-referencing suspicious code with known open-source projects.
  5. AI as an Augmentation, Not a Replacement: While AI tools like OpenAI3 can assist in deciphering specific, complex assembly blocks (e.g., 200+ XMM instructions), they are not a silver bullet. Human analysts are still required to interpret the AI's output, deduce developer intent, and connect low-level findings to the broader malware functionality (08:00).
  6. Prepare for Modern Language Malware: The adoption of Rust by ransomware groups like Akira signals a trend. Defenders need to invest in training their teams on Rust's unique characteristics, common idioms, and the challenges it poses to traditional analysis techniques. The "initial shell shock and confusion" (20:00) experienced with Akira can be mitigated by proactive education.
  7. Focus on Higher-Level Logic: When faced with heavily inlined code, it's often more productive to try and deduce the original developer's intent and the logical flow, rather than getting lost in every single assembly instruction. This deduction, as shown with the available_parallelism example, can guide the reverse engineering process.

Key Takeaways

  • Rust's Aggressive Optimizations: The Rust compiler's inherent optimizations, such as inlining and loop unrolling, can significantly complicate reverse engineering, effectively "obfuscating" code even without malicious intent, making control flow extremely difficult to trace.
  • Boilerplate Reuse as a Shortcut: Malware authors often reuse legitimate open-source Rust crates (e.g., seahorse, indicatif). Identifying these common boilerplate sections can drastically reduce reverse engineering time and provide crucial context for complex code.
  • Deep Dive into Rust Internals: Understanding advanced Rust concepts like trait objects (Box<dyn FnOnce()>), vtables, and the internal workings of the standard library (especially for features like thread spawning) is critical for deciphering complex control flow in optimized binaries.
  • Manual Reverse Engineering is Essential: Despite advancements in automated tools, "old-school," meticulous manual reverse engineering remains indispensable for analyzing sophisticated, optimized Rust malware that evades simpler analysis techniques.
  • AI as an Analytical Aid: AI models (like OpenAI3) can assist in deciphering specific, highly complex assembly blocks, but human analytical skill and deduction are still necessary to interpret findings and connect them to higher-level developer intent and overall malware functionality.
  • Proactive Preparedness: The Akira ransomware's Rust variant exemplifies the growing trend of malware adopting modern languages. Defenders must proactively educate themselves on Rust's unique characteristics and the challenges it poses to analysis to effectively counter future threats.

About the Speaker(s)

Ben Herzog is a Security Researcher at Check Point. His work focuses on deeply understanding the inner workings of malicious software and the challenges posed by modern programming languages in cybersecurity analysis. As demonstrated in this talk, he possesses extensive expertise in reverse engineering, particularly with Rust binaries, and is dedicated to sharing practical insights to advance the defensive capabilities of the security community.

Reviews

Dr. Zero (Offensive Security Researcher) — STRONG ACCEPT

Solid, hands-on Rust RE war story that actually teaches something. Herzog walks through real compiler-induced pain points—inlining hell, trait object vtable chasing, loop unrolling nightmares—with enough specificity that you leave knowing more than when you arrived. Not novel research in the academic sense, but exactly the kind of practitioner knowledge transfer that's hard to find.

Heather Calloway (CISO) — SOLID

Competent deep-dive into Rust reverse engineering challenges using Akira as the case study. Useful for malware analysis teams investing in Rust tooling; limited direct applicability for most security programs beyond awareness that Rust malware is harder to analyze.

→ Top-rated talks at Virus Bulletin Conference 2025 (VB2025)

All talks from Virus Bulletin Conference 2025 (VB2025)