Feeding Gophers To Ghidra

Max Kersten (Malware Analyst and Reverse Engineer · Trellix)

HITBSecConf2023 - Amsterdam · Day 2 · CommSec Track

Overview

In "Feeding Gophers To Ghidra," Max Kersten, a Malware Analyst and Reverse Engineer at Trellix, delves into the intricate challenges of analyzing Golang (Go) binaries using the NSA's open-source reverse engineering framework, Ghidra. The talk highlights Go's increasing popularity among malware authors, exemplified by threats like the Swift Slicer wiper, and addresses the unique difficulties its compilation process presents for static analysis. Kersten introduces a suite of custom Java scripts designed to significantly enhance Ghidra's capability to dissect these binaries, effectively "feeding" Go's mascot (the gopher) into the reverse engineering tool.

Watch on YouTube

Visual summary for Feeding Gophers To Ghidra by Max Kersten
Visual summary for Feeding Gophers To Ghidra by Max Kersten

Key moments

  1. 0:00 Introduction to Ghidra and Golang analysis challenges
  2. 6:00 Ghidra's UI, project features, and P-code fundamentals
  3. 18:00 Recovering Golang symbols and handling packed strings
  4. 22:00 Overview of the 4 Golang analysis scripts
  5. 34:00 Live demo: Dramatically improving function and type visibility
  6. 39:00 Live demo: Recovering strings and analyzing a Golang wiper
  7. 54:00 Q&A: Golang malware trends and anti-analysis concerns

Feeding Gophers To Ghidra

Speakers: Max Kersten, Malware Analyst and Reverse Engineer, Trellix

Conference: HITB Amsterdam

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

Overview

In "Feeding Gophers To Ghidra," Max Kersten, a Malware Analyst and Reverse Engineer at Trellix, delves into the intricate challenges of analyzing Golang (Go) binaries using the NSA's open-source reverse engineering framework, Ghidra. The talk highlights Go's increasing popularity among malware authors, exemplified by threats like the Swift Slicer wiper, and addresses the unique difficulties its compilation process presents for static analysis. Kersten introduces a suite of custom Java scripts designed to significantly enhance Ghidra's capability to dissect these binaries, effectively "feeding" Go's mascot (the gopher) into the reverse engineering tool.

The core problem tackled is Go's embedded runtime and its peculiar handling of symbols, even in "stripped" binaries. This often leaves analysts facing a sea of generically named functions (e.g., fun_082...), unparsed strings, and undefined data structures. Kersten's research and tools aim to automate the recovery of meaningful function names, dynamically reconstruct strings, and identify complex data types, thereby transforming a tedious and often fruitless manual effort into an efficient, insightful analysis. By bridging the gap between Ghidra's powerful but general capabilities and Go's specific compilation artifacts, this work empowers defenders to gain a much clearer understanding of Go-based malware.

This article explores Kersten's methodology, the technical underpinnings of his Ghidra scripts, and the profound implications for improving the speed and depth of Go malware analysis. The presented solution dramatically reduces the time required to understand Go binaries, offering a critical advantage in the ongoing cat-and-mouse game against sophisticated adversaries.

Background

▶ Watch: Introduction to Ghidra and Golang analysis challenges (0:00)

Ghidra, developed by the National Security Agency (NSA) and open-sourced in 2019, stands as a formidable Software Reverse Engineering Framework. Its primary components are written in Java for the main framework and graphical user interface, with its powerful decompiler implemented in C++. Ghidra supports scripting in Jython (Python 2) natively, with community-provided bridges for Python 3. Its project-based nature facilitates collaboration, allowing teams to work on analysis projects locally or on remote servers. Key features include integrated disassembly and decompiled views, hierarchical program and symbol trees, and a flexible data type manager where users can define custom structures and libraries. Ghidra's ability to analyze diverse architectures stems from its use of P-code, an intermediate language that abstracts processor-specific instructions.

Golang, or Go, has emerged as a language of choice for developers due to its cross-platform capabilities and performance. However, these same attributes have made it attractive to malware authors. A significant challenge in reverse engineering Go binaries arises from its compilation model: the Go runtime is embedded directly into the binary, resulting in larger executables compared to languages like C. This embedding often obscures the distinction between library code and user-defined malicious logic. Furthermore, while Go compilers allow for "stripping" binaries to remove symbols, a unique characteristic is that these "stripped" binaries often retain a crucial piece of metadata: the PCLNTAB (Program Counter Line Table). This table, originally intended for efficient crash reporting and debugging, inadvertently preserves valuable information, including original function names.

The combination of Go's embedded runtime, its non-standard string storage, and the partial stripping of symbols creates a perfect storm for reverse engineers. Default Ghidra analysis of Go binaries typically yields generic function names like fun_082..., making it exceedingly difficult to discern the purpose of code blocks. Go strings are not null-terminated; instead, they are packed together into a large contiguous block and referenced at runtime by an address, an offset, and a length. This makes Ghidra's default string recovery mechanisms largely ineffective. The absence of native Ghidra support for Go-specific constructs (such as its unique type system and function naming conventions) compounds these difficulties, leading to prolonged and often frustrating manual analysis. Max Kersten's work builds upon the foundational efforts of Dorka Palotain, who developed initial Python 2 scripts to tackle some of these Go analysis challenges, refining and reimplementing them in Java for seamless integration and enhanced functionality within Ghidra.

Key Findings

▶ Watch: Recovering Golang symbols and handling packed strings (18:00)

The talk "Feeding Gophers To Ghidra" unveils several pivotal findings that fundamentally transform the landscape of Golang binary analysis within the Ghidra framework. Max Kersten's research demonstrates that despite the challenges posed by Go's compilation model, crucial information can be systematically extracted and leveraged to dramatically improve reverse engineering efficiency.

One of the most significant discoveries is that Golang binaries, even when compiled with stripping enabled, retain valuable metadata within the PCLNTAB. This Program Counter Line Table contains the original function names from the source code, along with line number information. This revelation provides a direct pathway to overcoming the pervasive issue of generic function names (e.g., fun_082...) that Ghidra typically assigns to Go functions.

Kersten also highlights the unique way Golang handles strings. Instead of traditional null-terminated strings, Go packs all strings into a single, large memory block. At runtime, these strings are accessed via a base address, an offset within that block, and a specified length. This mechanism, while efficient for Go, renders conventional string recovery tools ineffective. The key finding here is that these string access patterns are consistent and can be programmatically identified to reconstruct the original strings.

The practical application of these insights culminates in the development of custom Java scripts for Ghidra that provide robust solutions for function name recovery, static and dynamic string recovery, and type recovery. The impact of these scripts is profound: in a real-world example, the analysis of the notorious Swift Slicer wiper, the percentage of usable function names (i.e., not generic address-based names) skyrocketed from a mere 0.05% to an astounding 98% after running the scripts. This almost complete restoration of symbolic information fundamentally changes the analysis workflow, allowing analysts to understand code purpose rather than wrestling with low-level assembly.

Furthermore, the research demonstrates that Go binaries embed sufficient metadata about custom structures and types, including field names and their types, which can also be programmatically recovered. This provides context for complex data objects, making the decompiler output far more intelligible. A critical design choice for the scripts is their implementation in Java, leveraging Ghidra's stable API to ensure portability and version independence. This means analysts can use the scripts across different Ghidra versions without the need for recompilation, a significant user experience improvement over custom loaders or analyzers.

In essence, Kersten's work proves that Go's internal structures, initially designed for runtime efficiency and debugging, can be effectively repurposed by reverse engineers to peel back layers of obfuscation and dramatically accelerate the analysis of Go-based malware.

Technical Deep Dive

▶ Watch: Overview of the 4 Golang analysis scripts (22:00)

Max Kersten's approach to dissecting Golang binaries in Ghidra is deeply rooted in understanding both Ghidra's internal architecture and Go's unique compilation artifacts.

Ghidra's Architecture and P-code:

Ghidra's design relies on Java for its primary framework and user interface, offering a flexible and extensible platform. The core heavy lifting of code transformation, however, is handled by its C++ decompiler. A crucial component is the Sleigh framework, which translates machine code from various architectures into a common intermediate representation called P-code. This P-code allows Ghidra to perform architecture-agnostic analysis. The talk distinguishes between low P-code, which closely mirrors individual assembly instructions and their effects (e.g., copy, store), and high P-code, which the decompiler uses to construct a higher-level, C-like representation. High P-code introduces concepts like varnodes, which represent variables and allow for backward traversal of data flow, crucial for understanding how values are assigned and modified throughout a function. While P-code offers a path to architecture independence, Kersten notes some limitations that occasionally necessitate architecture-specific patterns in his scripts.

Golang Internals Exploited:

Kersten's scripts specifically target several key Golang internal structures:

  • PCLNTAB (Program Counter Line Table): This is the cornerstone of function name recovery. Despite being "stripped," Go binaries retain this table, which maps program counters (addresses) to source code line numbers and, critically, to the original function names. The PCLNTAB's structure can vary slightly across different Go versions, requiring the scripts to account for these nuances.
  • String Storage: Go strings are not null-terminated. Instead, they are stored contiguously in specific data segments and are represented at runtime by a pointer to their base address, an offset from that base, and a length. This "address + offset + length" triplet is a consistent pattern that the scripts exploit to correctly identify and reconstruct strings.
  • Type Information: Go binaries embed metadata about user-defined structs and other types. This metadata includes the names of the types and their constituent fields, providing valuable context for understanding complex data structures.

The Ghidra Script Suite:

Kersten developed a suite of five Java scripts, building upon Dorka Palotain's original Python 2 work, to automate the recovery process:

  1. Static String Recovery: This script iterates over specific data segments within the binary. It identifies instruction patterns that define string literals using the "address + offset + length" mechanism. Rigorous sanity checks are performed to ensure that the identified patterns genuinely correspond to valid strings and not arbitrary data. Once a string is confirmed, the script automatically creates a Ghidra data structure representing the string and relevant pointers, making it visible in both the disassembly and decompiler views.
  1. Dynamic String Recovery: This script focuses on strings constructed or manipulated at runtime. It employs pattern matching specific to different architectures (e.g., 32-bit and 64-bit Intel, 32-bit and 64-bit ARM). By parsing instruction operands and applying additional logic and sanity checks, it identifies dynamically generated strings, creating corresponding Ghidra string objects and pointers.
  1. Function Name Recovery: This is arguably the most impactful script. It first locates and parses the PCLNTAB. For each entry, it extracts the original Golang function name (e.g., main.encrypt, crypto.parsePublicKey). The script then renames existing Ghidra functions (which are typically named fun_address) to their original, meaningful names. It also identifies and creates functions that Ghidra might have initially missed. To ensure compatibility with Ghidra's naming conventions, the script sanitizes function names by removing illegal characters like dots or slashes inherent in Go's package-based naming. The script also incorporates logic to handle differences in Go versioning, noting that Go versions are compared by the number after the dot (e.g., 1.3 vs 1.21).
  1. Type Recovery: This script searches for embedded type metadata within various sections and structures of the binary. When it encounters a user-defined structure, it processes it recursively, as structures can contain other structures. The script renames the recovered types within Ghidra and adds comments detailing the types and field names of the structure members. Acknowledging a current limitation, Kersten notes the difficulty in automatically determining the precise size of certain complex fields (e.g., an RSA public key string) without further heuristics, an area for future improvement.
  1. Wrapper Script (The "Fifth Script"): Recognizing the overhead and version dependency of custom Ghidra loaders and analyzers (which require recompilation for each Ghidra version), Kersten created a simple wrapper script. This script orchestrates the sequential execution of the four recovery scripts. By implementing the core logic as standalone scripts, Kersten ensures maximum portability. Scripts are compiled just-in-time by Ghidra and, when using the stable Ghidra API, maintain backward compatibility across versions, making them easy for the community to deploy and use.

Documentation and Extensibility:

A significant emphasis was placed on comprehensive Javadoc documentation within the Java scripts. This includes detailed explanations of design choices, hardcoded Golang type values (e.g., 0x13 for a function type, 0x14 for an interface), and hyperlinks to relevant Go source code or external research. This focus on clear documentation aims to lower the barrier for other analysts to understand, modify, and extend the scripts for their specific use cases or to adapt to new Go versions.

Demo / Proof of Concept

▶ Watch: Live demo: Recovering strings and analyzing a Golang wiper (39:00)

Max Kersten vividly demonstrated the power of his Ghidra scripts through live analysis of Golang binaries, illustrating a dramatic transformation in the reverse engineering workflow. The primary sample used for demonstration was a Linux-based Golang ransomware, with specific examples drawn from the Swift Slicer wiper.

Initially, a fresh Ghidra import of the ransomware presented a typical challenge: the function list was dominated by generic labels such as fun_08200f30 or thunk_fun_0804a120. String references in the decompiler often appeared as raw memory addresses or vast, unparsed blocks of characters, making it nearly impossible to quickly ascertain their content or purpose. This "before" state perfectly encapsulated the analytical roadblock Go binaries usually present.

Running the Function Name Recovery script immediately initiated a visible change. A progress bar appeared, and the console output began to stream renamed functions. For instance, a function previously named fun_082... was transformed into main.encrypt, instantly revealing its malicious intent. The Ghidra UI's function tree populated with intelligible names like crypto.parsePublicKey, main.get_info, and various runtime functions. This allowed Kersten to quickly distinguish user-defined malicious logic (prefixed with main.) from standard Go runtime operations, a critical first step in targeted analysis.

The Type Recovery script further enhanced clarity. Focusing on the main.get_info function, which initially showed an ambiguous runtime.createnewobject call, the script identified the created object's true nature. After execution, the object was correctly labeled as a main.info object. Opening this newly defined structure revealed its internal fields, such as RSA public key string and readme string. This immediate contextualization clarified that the function was gathering sensitive information (likely for C2 communication) and was involved in cryptographic operations, further evidenced by the script's ability to highlight related network activities like HTTP transport client and new web request.

The String Recovery scripts (both static and dynamic) were demonstrated by showcasing how Ghidra's decompiler transformed raw address references into literal string values. Kersten highlighted a common Go string pattern: a LEA (load effective address) instruction followed by values representing an offset and length being pushed onto the stack. Where Ghidra initially displayed data_082... or a large unparsed block, after script execution, the exact string malware name with a length of 8 characters was clearly identified and rendered. This dramatically improved readability and understanding of hardcoded values.

A compelling example with the Swift Slicer wiper illustrated the collective impact. A function call that was previously an opaque sequence of arguments became intelligible. After the scripts ran, the command to execute WMI C (Windows Management Instrumentation Command-line) was clearly identified. The analysis also revealed a loop traversing specific file paths and a function pointer to a walk function, indicating the wiper's file system interaction.

Finally, Kersten discussed the broader utility of the recovered information, noting that the extracted function names and offsets could be exported and loaded into external debuggers like x64dbg. This allows analysts to leverage Ghidra's deep static analysis capabilities to enhance dynamic debugging sessions with meaningful symbols, all without necessarily needing to interact with Ghidra's GUI directly, supporting headless automation. The live demonstrations effectively showcased the transition from an impenetrable binary to a well-structured, semantically rich representation, proving the scripts' efficacy in accelerating Go malware analysis.

Defensive Implications

▶ Watch: Q&A: Golang malware trends and anti-analysis concerns (54:00)

The advancements presented in "Feeding Gophers To Ghidra" carry significant implications for cybersecurity defenders, offering tangible improvements in their ability to analyze, detect, and respond to Golang-based threats.

Firstly, the scripts enable faster and deeper analysis of Go malware. By automating the recovery of function names, strings, and data types, analysts can drastically reduce the time spent on initial triage and reverse engineering. What might have taken days of painstaking manual effort to decipher generic fun_address functions can now be accomplished in minutes, allowing security teams to pivot quickly from initial detection to actionable intelligence. This speed is crucial in rapidly evolving threat landscapes where every minute counts.

Secondly, the enhanced visibility translates directly into improved threat intelligence generation. With meaningful function names like main.encrypt or crypto.parsePublicKey, analysts can precisely identify malicious capabilities, extract critical indicators of compromise (IOCs) such as C2 domains from recovered strings, and understand the full scope of a Go malware's functionality. This detailed intelligence allows for more accurate reporting and better-informed strategic defense planning.

Thirdly, the recovered symbolic information is invaluable for creating highly specific and effective detection rules. Knowing the exact function calls (e.g., os.exec for command execution, net.Dial for network connections) and the context in which they are used enables the development of robust YARA rules, network signatures, and EDR detections that are less prone to false positives and more resilient to minor malware variations. For instance, identifying a main.ransom.encryptFile function directly informs detection logic targeting ransomware behavior.

Moreover, the scripts provide crucial context for behavioral analysis tools like sandboxes and EDR systems. When a sandbox reports suspicious activity from a Go binary, the recovered symbols can help correlate observed behaviors with the underlying code. This allows defenders to move beyond generic alerts and understand why a certain behavior occurred, discerning legitimate Go application behavior from truly malicious actions.

By making Go binaries more transparent, Kersten's work helps security teams proactively stay ahead of adversaries. As malware authors increasingly adopt Go for its cross-platform appeal and the obfuscation challenges it presents, providing defenders with effective tools to counter these techniques levels the playing field. This proactive capability is essential to prevent successful attacks and minimize their impact.

Finally, the commitment to open-sourcing these Java scripts, coupled with extensive documentation, fosters community collaboration and continuous improvement. This allows a broader range of security professionals to benefit from the research, adapt the tools to specific use cases, and contribute to their ongoing development, collectively strengthening the defense against Go-based threats.

Key Takeaways

  • Golang's unique compilation, particularly its embedded runtime and the persistence of the PCLNTAB (Program Counter Line Table) even in "stripped" binaries, presents both significant challenges and unique opportunities for reverse engineering. These internal structures, while obscuring initial analysis, contain rich metadata that can be programmatically recovered.
  • Max Kersten's suite of Java scripts for Ghidra offers a robust solution for automated function name, static and dynamic string, and type recovery in Golang binaries. These tools are designed to overcome Ghidra's native limitations in handling Go's specific compilation artifacts.
  • The scripts dramatically enhance analysis efficiency, boosting usable function name recovery from a negligible percentage (e.g., ~0.05%) to approximately 98% in complex samples like the Swift Slicer wiper. This transformation makes Go code immediately more readable and understandable.
  • Leveraging Ghidra's architecture-agnostic P-code and stable API, the Java scripts are highly portable and largely version-independent. This design choice simplifies deployment and ensures broad accessibility for analysts across different Ghidra versions, without the need for complex recompilation.
  • Automated recovery of symbols, strings, and types provides critical context for malware analysis, enabling faster threat intelligence generation, more precise detection rule creation, and a deeper understanding of Go-based malware behavior. This empowers defenders to create more effective countermeasures.
  • The project emphasizes open-source contribution and detailed, hyperlinked documentation within the scripts, fostering community adoption, adaptation, and further development of Golang analysis capabilities.

About the Speaker(s)

Max Kersten, known by his nickname Libra, is a dedicated Malware Analyst and Reverse Engineer working within the Advanced Research Center at Trellix. His professional focus lies in dissecting complex malware and contributing to the broader cybersecurity community through open-source initiatives. Kersten is a staunch advocate for open-sourcing his research and tools, aiming to make his findings and methodologies accessible to others.

His past work includes the development of Dot Dumpster, an automatic unpacker specifically designed for .NET malware, which he open-sourced. He has also engaged in research related to Android security and regularly shares his insights through blogs on reverse engineering topics. Kersten's commitment to transparency and collaboration underscores his efforts to simplify challenging analysis tasks, as demonstrated by his work on "Feeding Gophers To Ghidra."

Reviews

Dr. Zero (Offensive Security Researcher) — STRONG ACCEPT

Solid, practical tooling work that solves a real pain point for anyone doing Go malware analysis. Not groundbreaking research, but the kind of thing that actually makes analysts' lives better. Kersten clearly did the work himself and knows the internals.

Heather Calloway (CISO) — SOLID

Practical tooling for a real defender problem. If your IR team is regularly hitting Go malware and struggling with Ghidra's output, this saves hours per sample. Not strategic, but operationally useful for the teams who need it.

→ Top-rated talks at HITBSecConf2023 - Amsterdam

All talks from HITBSecConf2023 - Amsterdam