Visualização normal

Antes de ontemStream principal
  • ✇Security Boulevard
  • Q1 2026 Open Source Malware Index: Adaptive Attacks, Familiar Weaknesses Sonatype Security Research Team
    TL;DR Sonatype identified 21,764 open source malware packages in Q1 2026, bringing the total logged since 2017 to 1,346,867. npm accounted for 75% of malicious packages this quarter. Trojans dominated, with most activity focused on credential theft, host reconnaissance, and staged payload delivery. The quarter's defining pattern was trust abuse: attackers succeeded by hiding behind trusted packages, trusted release paths, and trusted workflows. Three incidents stood out: SANDWORM_M
     

Q1 2026 Open Source Malware Index: Adaptive Attacks, Familiar Weaknesses

TL;DR

  • Sonatype identified 21,764 open source malware packages in Q1 2026, bringing the total logged since 2017 to 1,346,867.

  • npm accounted for 75% of malicious packages this quarter. Trojans dominated, with most activity focused on credential theft, host reconnaissance, and staged payload delivery.

  • The quarter's defining pattern was trust abuse: attackers succeeded by hiding behind trusted packages, trusted release paths, and trusted workflows.

  • Three incidents stood out: SANDWORM_MODE, the LiteLLM compromise, and the axios compromise.

By the Numbers: What We Saw

In the first three months of 2026, Sonatype identified 21,764 open source malware packages across ecosystems, bringing the total number logged since 2017 to 1,346,867. Q1 activity was heavily concentrated in npm and focused on credential theft, host information exfiltration, and staged follow-on compromise.

The quarter was also defined by trojan-style malware, which outpaced brandjacking and hijacking as the dominant payload type. While access paths varied — typosquatting, maintainer compromise, and abuse of legitimate release channels — the pattern was consistent: attackers kept finding ways to push malware through software that looked legitimate enough to trust by default.

Three incidents illustrate that pattern especially clearly:

  • SANDWORM_MODE, which pointed to more adaptive and worm-like malware behavior.

  • The Trivy/litellm-linked campaign, which showed how release paths and high-value AI and security tooling can become the attack surface.

  • The axios compromise, which demonstrated how a small dependency change inside a highly trusted package can create outsized downstream risk.

Beyond the Numbers: Trust Abuse Was the Defining Pattern

Q1 saw one new malicious package every six minutes, and npm accounted for 75%, reinforcing that attackers still see JavaScript ecosystems as the fastest path to developers and build systems at scale. The prevalence of trojans far showed attackers did not need especially novel tactics to succeed. In many cases, the playbook was simple: publish something plausible, get it installed, and execute inside a trusted workflow.

The most common behaviors — credential theft, host information exfiltration, and droppers for follow-on compromise — point to the same conclusion. These campaigns were designed for access, persistence, and reuse inside developer and CI/CD environments.

The core risk in Q1 was not just malicious code entering the ecosystem. It was malicious code entering through trusted names, trusted workflows, and trusted environments.

SANDWORM_MODE: Supply Chain Malware Got More Worm-Like

SANDWORM_MODE was one of Q1's clearest signs that open source malware is becoming more adaptive and automated.

The campaign used typosquatted npm packages to harvest sensitive data from developer machines and CI environments. Sonatype observed theft of npm and GitHub tokens, environment variables, cryptographic keys, and API credentials, along with code aimed at spreading into additional repositories and workflows.

Researchers also found code designed to interact with a local Ollama instance, suggesting early experimentation with malware that could modify itself inside compromised environments.

What made SANDWORM_MODE important was not just that it spread. It showed attackers building malware to take advantage of the automation and trust built into modern software delivery.

Trivy Hijack: Trusted Release Paths Became the Attack Surface

The Trivy incident stood out because it was not just a compromised package story. It was a supply chain attack that linked trusted security tooling to malicious code insertion in another widely used project.

In March 2026, a compromised version of the Trivy security scanner was used to help facilitate the insertion of malicious code into the LiteLLM library. That made the attack especially significant: the issue was not simply a fake package or a one-off malicious upload, but the abuse of a trusted tool inside the software delivery chain.

The related LiteLLM compromise involved malicious PyPI versions 1.82.7 and 1.82.8, which contained an obfuscated credential stealer and dropper. The malware targeted API keys, environment variables, SSH keys, Git credentials, cloud secrets, Kubernetes tokens, Terraform and Helm artifacts, and CI/CD configuration, then established persistence through sysmon.py.

What made this incident so important in Q1 was the attack path itself. Once attackers can compromise a trusted tool or release workflow, they no longer need to rely on obvious deception. They can use legitimate software and trusted delivery paths to move malicious code downstream.

Axios Compromise: Small Change, Large Blast Radius

The axios compromise showed how little an attacker needs to change to create downstream risk. Attackers hijacked an npm publishing account and released axios@1.14.1 and axios@0.30.4 with a hidden dependency on plain-crypto-js@4.2.1. That package acted as an obfuscated loader, using npm's postinstall hook to fetch and run a secondary payload.

Researchers found OS-specific launcher behavior for MacOS, Windows, and Linux, consistent with delivery of a remote access trojan. The attack also used cleanup and metadata tricks to make analysis harder.

The lesson was straightforward: attackers did not need to rewrite a popular library. They only needed to insert a malicious transitive dependency into a package developers already trusted.

What Development Teams Should Take Away

  • Screen components before use. New packages and updates should be evaluated before they reach developer machines or CI pipelines.

  • Inspect transitive dependencies. The axios incident showed how malware can arrive through a hidden child package, not just the top-level dependency.

  • Treat dev and CI environments as high-value targets. Q1 malware repeatedly targeted tokens, cloud credentials, SSH material, and pipeline secrets.

  • Assume credential exposure after execution. In incidents like LiteLLM or axios, package removal is not enough. Rotate secrets and review affected environments.

  • Watch release paths, not just package names. Maintainer accounts, publishing workflows, and release automation are part of the attack surface.

  • Do not rely on reputation alone. Familiar names and popular packages are no longer strong trust signals by themselves.

Looking Ahead

Q1 reinforced a consistent reality: the most effective attacks did not rely on obviously malicious packages. They relied on appearing trustworthy by hiding inside familiar names, legitimate workflows, and routine dependency updates.

Prevention is less about reacting after-the-fact and more about making better decisions before code is ever used.

In practice, that means having access to reliable, real-time intelligence about open source packages that highlights unusual behavior, known risks, or patterns that do not align with normal development activity.

Tools like Sonatype Guide are designed to surface that kind of context directly to developers, making it easier to evaluate dependencies and avoid high-risk components before they enter the build.

As Q1 showed, attackers consistently took advantage of assumed trust. The teams that reduce risk most effectively will replace that assumption with visibility and make informed decisions a routine part of development.

The post Q1 2026 Open Source Malware Index: Adaptive Attacks, Familiar Weaknesses appeared first on Security Boulevard.

  • ✇SentinelLabs
  • Building an Adversarial Consensus Engine | Multi-Agent LLMs for Automated Malware Analysis Phil Stokes
    Executive Summary Large Language Models can perform static malware analysis, but individual tool runs produce unreliable results contaminated by decompiler artifacts, dead code, and hallucinated capabilities. We built a multi-agent architecture for reversing macOS malware that treats each reverse engineering tool (radare2, Ghidra, Binary Ninja, IDA Pro) as an independent, skeptical analyst in a serial pipeline, where each agent must verify or reject the claims of the previous one. We examine a
     

Building an Adversarial Consensus Engine | Multi-Agent LLMs for Automated Malware Analysis

19 de Março de 2026, 07:00

Executive Summary

  • Large Language Models can perform static malware analysis, but individual tool runs produce unreliable results contaminated by decompiler artifacts, dead code, and hallucinated capabilities.
  • We built a multi-agent architecture for reversing macOS malware that treats each reverse engineering tool (radare2, Ghidra, Binary Ninja, IDA Pro) as an independent, skeptical analyst in a serial pipeline, where each agent must verify or reject the claims of the previous one.
  • We examine a concrete design decision: why we chose deterministic bridge scripts over the Model Context Protocol (MCP) for tool integration, and how this affects accuracy, latency, and token cost in production.
  • We document the model routing strategy and some real-world challenges encountered during development.

Why Single-Tool LLM Analysis Fails

Anyone who has taken decompiler output, a string dump or raw disassembly from a binary, pasted it into an LLM, and asked “what does this do?” will recognise the failure mode. The model produces a confident, well-structured report that looks plausible until a human reviewer checks the virtual addresses and finds half the cited functions are wrong, several “capabilities” are actually dead code from the compiler’s standard library, and the claimed C2 endpoint has an extra character because the string extraction tool mangled a forward slash.

These failures are not hallucinations in the usual sense. The model is doing what it was asked to do, reasoning over the data it sees. The problem is that the data is noisy. Each reverse engineering tool brings its own parsing quirks. Radare2 string blobs can mangle delimiters; Ghidra’s decompiler might misclassify compiler stubs as application logic; IDA’s Hex‑Rays pseudocode can elide important register‑level details. If an LLM treats these outputs as ground truth, artifacts can make it into the final report that lead to erroneous “confirmed” capabilities.

Our experience has long taught us the value of using multiple tools to enrich our understanding of malware design and capabilities. Therefore, we set out not to try to build better prompts for our LLM agents, but rather to build a system where multiple tool artifacts are evaluated before they reach the report writing stage.

The Serial Consensus Pipeline

The system currently runs on OpenClaw, an open-source agent framework, and is built around a central Orchestrator agent that manages a team of specialized subagents, one for each reverse engineering tool plus a dedicated report-writer agent.

In our current deployment, all agents run on Anthropic’s Claude models: Opus 4.6 for the Orchestrator and report-writer, and Sonnet 4.6 for the subagents. The architecture is itself provider-agnostic, and OpenClaw’s design allows the operator to specify multiple fallback models in case the default models are unavailable or exhausted. However, compaction becomes a real issue once we start switching to smaller models like the Qwen2.5 32b that we configured as the ultimate ‘fail-safe’, and performance both in terms of response time and response quality can start to suffer with less capable models.

The pipeline operates in three phases. In the first phase, four tool-specific subagents run in sequence: r2, then Ghidra, then Binary Ninja, then IDA Pro. Each agent receives the accumulated findings from all previous agents, encoded in a structured document called the Shared Context. Each agent’s job is to run its specific tool against the binary, verify or reject the claims in the Shared Context, and add any new findings of its own.

The orchestrator periodically reports back to the user as it works through the pipeline
The Orchestrator periodically reports back to the user as it works through the pipeline

Crucially, the Shared Context is an entirely in-memory construct. It is never written to disk during the analysis. When r2 finishes its analysis, its subagent outputs the Shared Context table as a conversational response back to the Orchestrator. The Orchestrator simply injects that exact text block into the prompt for the next subagent, controlling Ghidra. The LLM’s context window acts as the pipeline’s RAM, carrying the state of the analysis from one agent to the next until the final report is synthesized.

In the second phase, which we refer to internally as “the Gauntlet,” the same subagents run again in a different order, but this time they are explicitly tasked with peer-reviewing the assertions from the first round. Ghidra reviews IDA’s claims. Binary Ninja reviews Ghidra’s. IDA delivers the final verdict. Only findings that survive this adversarial review, or that present irrefutable evidence, proceed to the final stage.

Each tool dumps its analysis to disk before the final report is created
Each tool dumps its analysis to disk before the final report is created

In the third phase, the dedicated report-writer agent receives the finalized Shared Context and produces the output report, with every capability claim anchored to a specific virtual address and accompanied by a decompilation snippet.

Snippet from the final report on an old WizardUpdate sample
Snippet from the final report on an old WizardUpdate sample

The critical constraint is that the pipeline is serial, not parallel. Each agent sees what every previous agent has said, including what they rejected. This creates a cumulative evidence chain rather than independent votes.

Snippet from the final report on a recent FinderRAT sample
Snippet from the final report on a recent FinderRAT sample

The Active Rejection Mandate

The system prompts for the four tool-specific subagents include an explicit instruction to act as a “highly skeptical peer.” If Ghidra’s decompiler shows that a function flagged by r2 as a “decryption loop” is actually a compiler-generated string initialization stub, the Ghidra agent is not simply expected to note the discrepancy. It is instructed to formally reject the claim and document the reason.

The ‘Gauntlet’ and the Active Rejection Mandate
The ‘Gauntlet’ and the Active Rejection Mandate

This adversarial approach is enforced through the output schema. Every finding must include a Consensus field with a value of AGREE or DISAGREE, and rejected claims are tracked in a dedicated table in the Shared Context alongside the tool that rejected them and the rationale.

The Shared Context schema
The Shared Context schema

In practice, this mechanism caught a real artifact during our first pipeline run against an old SysJoker sample. Radare2’s string parsing rendered the C2 API endpoint as /api/req_res (with an underscore), while Ghidra’s decompiler correctly extracted the literal string from the data segment as /api/req/res (with a forward slash). In another test, the Gauntlet prevented the analysis from mistaking standard Go runtime strings for what was at first classified as a Tor .onion C2 address.

The Gauntlet rejected two claims from Round 1
The Gauntlet rejected two claims from Round 1 in this Go infostealer

Without the rejection mechanism, these misinterpretations would have appeared in the final report. That kind of subtle corruption is exactly what makes automated reports untrustworthy, and precisely what the consensus pipeline is designed to prevent.

Similarly, the Gauntlet phase later caught a pure hallucination derived from a decompiler artifact in Binary Ninja’s Medium Level IL, which claimed the presence of a “download” instruction type. Because the agents reviewed each other’s work serially, this was actively rejected in the final report synthesis:

"Rejected claim R2: The command type 'download' does not exist in this binary. 
The strings 'exe' and 'cmd' are the only type discriminators. 
The 'download' string was a Binja MLIL decompiler artifact."

The adversarial design also helps solve the problem of different disassembler and decompiler output, with tools able to be evaluated against each other in real-time. In one of our tests, only Ghidra initially found the XOR-obfuscated strings in a WizardUpdate sample, but the others were able to confirm the finding once told to specifically weigh in on whether the Ghidra subagent was right or just hallucinating.


The adversarial pipeline allowed for a crucial discovery that a single-tool analysis could have missed
The adversarial pipeline allowed for a crucial discovery that a single-tool analysis could have missed

The Token Economics of Consensus

Running up to seven subagents per binary sounds computationally expensive, but the serial architecture creates an asymmetric token load that prompt caching handles exceptionally well.

OpenClaw Sessions UI showing the serial ‘Gauntlet’ execution and declining token consumption
OpenClaw Sessions UI showing the serial ‘Gauntlet’ execution and declining token consumption

The image above shows the Orchestrator managing Round 2 (the Gauntlet). Note the drop in token consumption as the analysis shifts from raw extraction to peer review. During Round 1, the agents consume significant context. A raw IDA Pro disassembly dump can push a subagent’s token count past 100,000.

However, because we use deterministic bridge scripts that dump each tool’s entire output to disk rather than interactive MCP endpoints that require sequential back-and-forth prompting, this represents a single massive context load. The evolving Shared Context state is injected dynamically on top of this static tool output, so the underlying tool data remains mathematically constant. According to Anthropic, prompt caching delivers “up to 90%” lower input costs and 85% lower latency for long prompts, making repeated use of large static tool outputs less expensive in practice.

More importantly, the token burden drops drastically during Round 2. When the Orchestrator spawns binja-r2-gauntlet for peer review, the subagent is no longer parsing the raw disassembly. It is only evaluating the distilled Shared Context document against specific contested claims, dropping its token consumption by more than half (approx. 44,000 tokens). The data has been refined, making the adversarial consensus phase both faster and cheaper.

Bridge Scripts Over MCP

One of the first architectural questions was whether to use the Model Context Protocol (MCP) as the interface between the LLM agents and the reverse engineering tools. IDA Pro, for example, has an existing MCP server that allows an LLM to interactively query the disassembly database: requesting the decompilation of a specific function, querying cross-references, renaming variables, and so on.

MCP is designed for interactive, human-in-the-loop workflows where an analyst works alongside an AI copilot. For fully automated batch analysis, it introduces two significant concerns.

The first is latency. An MCP-based agent must make sequential API calls to explore the binary, then request cross-references for a given function of interest, then another call to,  say, query the strings in .rodata. Each call requires a round-trip to the LLM to decide what to ask next. A typical function-level analysis might require 15 to 50 MCP tool calls. In a pipeline with seven subagent invocations across two rounds, this would compound into considerable wall-clock time and token cost.

Even if those weren’t an issue, the second problem is non-determinism. Because the LLM decides what to query, it can and will miss things. If the agent does not think to ask about cross-references to a specific crypto constant, it will not discover the decryption routine. A deterministic bridge script, by contrast, is programmed to extract everything: all strings, all imports, all cross-references, all function signatures, in a single sweep, regardless of whether the LLM would have thought to ask for them.

In our design, we built thin bridge scripts, one per tool, that invoke each tool’s headless analysis mode and dump comprehensive output to a text file. The bridge for IDA Pro, for example, is a 40-line shell script that calls idat64 in batch mode with a universal IDAPython analysis script. The bridge for Binary Ninja is a Python wrapper that invokes the Binary Ninja API in headless mode.

# The IDA bridge: core execution and error handling
"$IDAT_PATH" -A -B -S"$UNIVERSAL_SCRIPT" -L"$OUTPUT_DIR/ida_analysis.log" "$BINARY"
EXIT_CODE=$?
if [[ $EXIT_CODE -ne 0 ]]; then
  echo "ERROR: IDA Pro analysis failed with exit code $EXIT_CODE" >&2
  exit $EXIT_CODE
fi

The trade-off here is that while we lose the interactive exploration capability that MCP provides, we gain deterministic, comprehensive extraction with predictable latency. For an automated pipeline leveraging probabilistic inference machines, our view is the trade-off strongly favors the bridge approach.

Tiered Reasoning Across the Pipeline

Not all tasks in the pipeline require the same level of reasoning. The Orchestrator must synthesize conflicting findings, decide what to reject, and construct structured handoff prompts. A subagent, by contrast, has a narrower job: parse tool output, fill in a schema, and flag disagreements.

We configured the system to use a stronger model for the Orchestrator and report-writer (the two highest-reasoning roles) and a faster, cheaper model for the four tool-specific subagents, where the task is essentially structured extraction from well-formatted decompiler output. OpenClaw supports this through its agents.defaults.subagents.mode configuration, which sets a default model for all spawned subagents independently of the main agent’s model.

The cost implication is that seven of the nine LLM invocations in a full pipeline run use the less expensive model, while the two highest-value calls (orchestration and report synthesis) use the stronger one. In practice, this produces a roughly 30% to 50% cost increase over a single-model configuration using the less expensive model, but it is a cost that buys us a disproportionate improvement in report quality. The stronger model is better at detecting when a subagent finding contradicts an earlier one, and better at maintaining the strict output formatting required by the report template.

However, there is a practical constraint to this approach. The stronger model has tighter rate limits, and during our initial testing, we found that API congestion caused the Orchestrator to fall back to the secondary model mid-run. To avoid saturating the provider’s rate ceiling, we reduced the main agent concurrency cap from four to two. The next section describes how this played out during the first full pipeline run.

Lessons From the Early Runs

To test our design, we began with a known Mach-O sample of the SysJoker malware. Using a known sample allowed us to evaluate the LLMs output against that of several human analysts and public reporting. The initial full pipeline run surfaced several issues that were not visible during isolated testing of individual components.

The most disruptive early issue was duplicate session handling. Due to display issues in OpenClaw’s TUI, we chose to drive the analysis through its open source Web UI. A browser automation glitch caused three identical analysis requests to be submitted simultaneously, each of which spawned its own complete pipeline. The resulting load triggered API rate limiting, causing the Orchestrator to fall back to the secondary model, and creating multiple competing report-writer sessions trying to produce the same output. The architectural fix was to cap the main agent’s concurrency limit, reducing it from four to two, but the debugging cost both time and a non-trivial number of API tokens.

However, this rate-limit congestion also proved the resilience of the Orchestrator model. During one test run, a subagent worker thread was silently killed by an upstream API timeout midway through the pipeline (specifically, the final report-writer was lost during the model handoff). Because the Orchestrator maintains the entire accumulated state in its conversational history rather than delegating it to the subagents, the analysis did not crash.

The Orchestrator recovering from a dropped subagent session
The Orchestrator recovering from a dropped subagent session

When we prompted OpenClaw that the report had not arrived, the Orchestrator simply observed that the subagent had stopped responding, preserved the Shared Context from the previous round, and explicitly commanded a respawn of the dead subagent to continue the pipeline. By decoupling state management (the Orchestrator) from computation (the subagents), the system is capable of resuming the task and avoids wasting tokens or entire runs starting from scratch.

A subtler issue was output schema inconsistency across the four specialist skills. We initially had minor differences between them: radare2’s output schema lacked a Consensus field since it runs first and has nothing to compare against, and some skills included a two-line safety block while others had only one line. These small differences created parsing ambiguity for the Orchestrator when it attempted to align findings across tools. The fix was to normalize all four schemas to be structurally identical, with r2 using Consensus: N/A - First Pass as a placeholder value.

The Orchestrator’s handoff format also required explicit definition. Initially, without a specified Shared Context schema, the LLM would invent its own handoff format for each subagent, making inter-agent communication fragile and difficult to parse programmatically. We defined a strict markdown table format with markers (SHARED_CONTEXT_START / SHARED_CONTEXT_END) and three categorized tables: Verified Capabilities, Flagged for Review, and Rejected Claims. This made the inter-agent communication deterministic enough for the Orchestrator to reliably merge findings across rounds.

Finally, bridge scripts needed explicit failure handling. When the underlying tool failed (for instance, if IDA could not import the binary), the original scripts printed “Analysis complete” regardless of the exit code. The subagent would then attempt to parse an empty output file and produce nonsensical findings. Adding exit code propagation, where a non-zero tool exit terminates the bridge with a clear error message, gives the Orchestrator a reliable signal to handle the failure rather than proceeding with garbage input.

Conclusion

The primary challenge with LLM-driven malware analysis is not so much a given model’s reasoning capability but the quality of the data the model reasons over. Decompiler artifacts, string parsing quirks, and dead code all create noise that an LLM will faithfully amplify into a report unless the system is specifically designed to catch and reject those artifacts before they reach the synthesis stage.

The multi-agent consensus pipeline described here is one approach to that problem. By treating each reverse engineering tool as an independent analyst with an explicit mandate to challenge the claims of other tools, the system produces reports where every capability is backed by cross-validated evidence anchored to specific virtual addresses.

The architecture is intentionally simple: bridge scripts extract data, subagents evaluate it, the Orchestrator synthesizes consensus. There is no vector database, no fine-tuning, and no custom model. The reliability comes from the pipeline structure, the serial handoff, the rejection mandate, and the structured Shared Context, not from the model itself.

Sample Hashes

60c8128c48aac890a6d01448d1829a6edcdce0d2 WizardUpdate
678aa572faa73f6873d24f24e423d315e7eb2c2d Go Infostealer
ad7d2eb98ea4ddc7700db786aadb796b286da04 FinderRAT
f5149543014e5b1bd7030711fd5c7d2a4bef0c2f SysJoker

  • ✇Volexity
  • GoResolver: Using Control-flow Graph Similarity to Deobfuscate Golang Binaries, Automatically Volexity
    KEY TAKEAWAYS Go language (Golang) is increasing in popularity with developers of both legitimate and malicious tooling. Volexity frequently encounters malware samples written in Golang that apply obfuscators to hinder analysis. Obfuscated Golang malware samples are significantly harder to statically analyze for reverse engineers. Volexity has developed an open-source tool, GoResolver, to retrieve obfuscated functions names. GoResolver’s control-flow graph similarity techniques offer a signifi
     

GoResolver: Using Control-flow Graph Similarity to Deobfuscate Golang Binaries, Automatically

1 de Abril de 2025, 10:37

Volexity-Blog-GoResolver-Using Control-flow-Graph-Similarity-to-Deobfuscate-Golang-Automatically

KEY TAKEAWAYS
  • Go language (Golang) is increasing in popularity with developers of both legitimate and malicious tooling.
  • Volexity frequently encounters malware samples written in Golang that apply obfuscators to hinder analysis.
  • Obfuscated Golang malware samples are significantly harder to statically analyze for reverse engineers.
  • Volexity has developed an open-source tool, GoResolver, to retrieve obfuscated functions names.
  • GoResolver’s control-flow graph similarity techniques offer a significant advantage in recovering symbol information.

In the course of its investigations, Volexity frequently encounters malware samples written in Golang. Binaries written in Golang are often challenging to analyze because of the embedded libraries and the sheer size of the resulting binaries. This issue is amplified when samples are obfuscated using tools such as Garble, an open-source Golang obfuscation tool.

The popularity of Golang amongst malware developers, and the use of obfuscators to make reverse-engineering harder, raised the need for better tooling to assist in reverse-engineering efforts. Volexity developed GoResolver, an open-source tool that uses control-flow graph similarities to retrieve obfuscated functions names. GoResolver is available for download on GitHub here.

Note that throughout this blog post, Garble obfuscation is used to demonstrate GoResolver’s effectiveness. However, the same principles apply to other closed-source Golang obfuscators observed by Volexity.

Garble Obfuscation Properties Analysis

One of the obfuscators commonly encountered by Volexity is Garble. Its functionality is described in the README, a screenshot of which is shown below:

Despite these listed features, all of which significantly hinder manual static analysis, binaries obfuscated using Garble present interesting properties that weaken the overall apparent strength of its obfuscation.

For example, the randomization employed by Garble for function and package names is not truly random. Due to how Golang resolves function names, randomized names must stay consistent across all functions of a package, so they are highly reused:

As shown in the image above, the “E2AKV7TQjQ” string is reused across many functions as it represents the package name. Manually looking at the functions called by this garbled string, it can be inferred that this represents the os/exec package. Consequently, if one function from a package is identified, the package can be identified for the entire binary. All other obfuscated functions can then be associated with this package. This logic greatly improves the symbol recovery.

Existing Go Tools

Some tools already exist to help retrieve lost symbol information, the most well-known of which is Mandiant’s GoReSym. GoReSym works by bypassing the symbol table entirely. Instead, it extracts function names and type information from Go’s internal runtime structures, ModuleData and PcLineTab, in the same way as the Golang runtime would, using the Golang runtime’s own parser whenever possible. As these structures are necessary for Golang binaries to work properly, Garble cannot strip them.

As the retrieved symbols are still randomized, Volexity recognized an opportunity to go further by using control-flow graph similarities to identify the original package and function names.

Control-flow Graph Similarities

Control-flow graphs can be used to represent different paths a binary may take during execution. Even when recompiled across different compiler versions, the resulting control-flow graph of a given algorithm will remain similar. By measuring the similarity between the control-flow graph of two functions in two different binaries, it is sometimes possible to assert if they result from the same algorithm.

A paper published in 2020 describes an efficient technique to compute the normalized similarity (0.0 to 1.0) between two control-flow graphs by computing the weighted ratio of similar assembly instructions across nodes of the control-flow graph. By implementing this technique and comparing Garbled samples against generated clean template Golang samples, it could then be possible to fully resolve the randomized symbol names to their original form.

An illustration of this process is below:

Two graphs are compared, and the similarity between each set of two blocks in the graph is computed. In the illustration above, the red and blue blocks contain the exact same instructions but in a different order, and thus have a similarity of 1.0.

GoResolver Installation

The GoResolver toolchain is composed of four projects:

Project Description
GoResolver A Golang symbol recovery tool through symbol extraction and control-flow graph similarity
GoGrapher A binary similarity tool that computes the control-flow graph similarity between two binaries
GoStrap A Golang reference sample bootstrapping tool that allows users to generate Go samples of any Golang version and feature the requested set of libraries
GitToolFetcher A simple tool to install, manage, and run multiple, concurrent versions of GitHub-hosted projects

An overview of the interactions within the projects of the GoResolver toolchain is shown below:

Image3

To install the GoResolver toolchain, please ensure your system’s dependencies match the following minimum requirements:

Language Version
Go 1.20.6+
Python 3.12+

Each Python-based component of the toolchain is available on pypi, so you can simply run the following command to install:

pip install goresolver

GoResolver also provides plugins for IDA Pro and Ghidra. The following versions are supported:

Tool Version Python Version
IDA Pro (IDAPython) 9+ Python 3.12+
Ghidra (PyGhidra) 11.3+ Python 3.12+

In Volexity’s experience, analysis of Golang binaries is significantly faster in IDA Pro.

Case Study

To illustrate usage of the GoResolver toolchain, Volexity built the Stowaway agent using Garble. The sample (1df2cd0d12e5028d5dbda33a4e4404e0) is available on VirusTotal.

When the sample is disassembled using IDA Pro, most functions have generic names using the format sub_OFFSET. This is to be expected, since Garble stripped the symbol table. Performing a complete analysis would be tedious; instead, the sample is submitted to GoResolver.

GoResolver will first try to identify the version of Golang used to build the malware sample. This is usually done by parsing the BuildInfo or identifying Golang version string fragments in the binary. For garbled Golang binaries, however, neither approach works, since Garble ensures no version fragments or BinaryInfo remain in the compiled executable.

While Garble removes all directly identifiable information, it is still bundled with the Golang runtimes. Since the Golang runtime changes between different versions, GoResolver can use this information to fingerprint the runtime version. By generating reference data for each major Golang version and computing the similarity of each runtime with the malware sample, the tool can pinpoint the Golang version used to compile the malware sample. Volexity has identified that testing only 2% of the Golang runtime was sufficient to discriminate between major versions. This improves both efficiency and resource usage when computing the similarity of each major Golang version of the malware sample.

To submit a sample to GoResolver, simply call the tool with the path to the sample as argument, for example:

goresolver /path/to/sample.exe

The submission of the malware sample to GoResolver is shown below:

Once the Golang version of the malware sample is identified, the template compiled with this specific version will be used as a reference. GoResolver then computes the full binary similarity, which is combined with the symbols extracted similarly to those used by GoReSym. This results in the final symbol report, which can then be imported into your SRE tool of choice.

The before-and-after import comparison of resolved symbols is illustrated below:

As shown above, numerous symbols have been resolved to more meaningful names, even though they are still partially obfuscated. Even in cases where symbols haven’t been fully resolved by the similarity algorithm, statistical analysis helps resolve the package and modules names, as can be seen with the “os” package shown below:

Even though these symbols are only partially resolved, knowing their package name helps understand the binary layout. This also helps the analyst avoid reversing runtime and library methods, allowing them to instead focus on the malware’s actual core logic.

SRE Plugins

To ease the integration of the GoResolver toolchain into analyst workflows, Volexity has made plugins available for both IDA Pro and Ghidra. Both plugins allow easy import of recovered symbol data generated by the GoResolver CLI tool into their respective symbol databases. While their interface differs, both plugins rely on the same codebase and thus provide the same capabilities.

When using IDA Pro, the following dialog is shown:

The GoResolver plugin features two modes of operation, depending on how you want the GoResolver toolchain to integrate into your workflow: “Analyze the current file” or “Import a previous report”. These options are selectable on the left-hand side.

The Analyze option lets you run GoResolver directly within IDA Pro. Once the analysis is complete the generate report file will be automatically imported into the symbol database.

The Import option requires you to generate a report file using the standalone GoResolver CLI beforehand. This can then be imported into the symbol database by selecting the path to the previously generated report file.

When using Ghidra, the following dialogs are shown:

These dialogs offer the same options as the IDA Pro plugin; the Analyze and Import modes behave the same way.

Both plugins are available in the “Plugin” directory within GoResolver’s repository.

Conclusion

Golang is more often the language of choice for many malware authors, due to embedded libraries and the size of the resulting binaries. To add additional complexity, malware written in Golang is often obfuscated. The ability to reverse engineer obfuscated malware written in Golang is an important part of digital forensic investigations.

Volexity has developed GoResolver, an open-source tool that uses control-flow graph similarities to retrieve obfuscated functions names. The use of control-flow graph similarity, in conjunction with pre-existing symbol extraction techniques, allows the resolution of more complete symbols than previously achievable. This ensures a better understanding of the binary, and helps analysts focus on reversing the core malware logic. Volexity plans to integrate more features, such as automatic Golang strings parsing and various improvements to facilitate the Golang binaries reversing, so watch for updates in the GoResolver repository on GitHub.

Acknowledgements

Volexity would like to thank Mandiant’s threat intelligence division for its documentation of the Golang runtime’s symbol-parsing mechanism, as well as their work on GoReSym.

Volexity also thanks Mr. Hyun-il Lim from the Kyungnam University for the research and paper on control-flow graph similarity algorithms.

The post GoResolver: Using Control-flow Graph Similarity to Deobfuscate Golang Binaries, Automatically appeared first on Volexity.

❌
❌