| name | analyzing-golang-malware-with-ghidra |
| description | Reverse engineer Go-compiled malware using Ghidra with specialized scripts for function recovery, string extraction, and type reconstruction in stripped Go binaries. |
| domain | cybersecurity |
| subdomain | malware-analysis |
| tags | ["golang","ghidra","reverse-engineering","malware-analysis","binary-analysis","go-malware","disassembly"] |
| version | 1.0 |
| author | mahipal |
| license | Apache-2.0 |
Analyzing Golang Malware with Ghidra
Overview
Go (Golang) has become a popular language for malware authors due to its cross-compilation capabilities, static linking that produces self-contained binaries, and the complexity it introduces for reverse engineering. Go binaries contain the entire runtime, standard library, and all dependencies statically linked, resulting in large binaries (often 5-15MB) with thousands of functions. Ghidra struggles with Go-specific string formats (non-null-terminated), stripped function names, and goroutine concurrency patterns. Specialized tools like GoResolver (Volexity, 2025) use control-flow graph similarity to automatically deobfuscate and recover function names in stripped or obfuscated Go binaries.
Prerequisites
- Ghidra 11.0+ with JDK 17+
- GoResolver plugin (for function name recovery)
- Go Reverse Engineering Tool Kit (go-re.tk)
- Python 3.9+ for helper scripts
- Understanding of Go runtime internals (goroutines, channels, interfaces)
- Familiarity with Go binary structure (pclntab, moduledata, itab)
Key Concepts
Go Binary Structure
Go binaries embed rich metadata in the pclntab (PC Line Table) structure, which maps program counters to function names, source files, and line numbers. Even stripped binaries retain this metadata. The moduledata structure contains pointers to type information, itabs (interface tables), and the pclntab itself. Go strings are stored as a pointer-length pair rather than null-terminated C strings.
Function Recovery in Stripped Binaries
Despite stripping symbol tables, Go binaries retain function names within the pclntab. However, obfuscation tools like garble rename functions to random strings. GoResolver addresses this by computing control-flow graph signatures of obfuscated functions and matching them against a database of known Go standard library and third-party package functions.
Crate/Dependency Extraction
Go's dependency management embeds module paths and version strings in the binary. Extracting these reveals the malware's third-party dependencies (HTTP libraries, encryption packages, C2 frameworks), which provides insight into capabilities without full reverse engineering.
Practical Steps
Step 1: Initial Binary Analysis
"""Analyze Go binary metadata for malware analysis."""
import struct
import sys
import re
def find_go_build_info(data):
"""Extract Go build information from binary."""
magic = b'\xff Go buildinf:'
offset = data.find(magic)
if offset == -1:
return None
print(f"[+] Go build info at offset 0x{offset:x}")
go_version = re.search(rb'go\d+\.\d+(?:\.\d+)?', data[offset:offset+256])
if go_version:
print(f" Go Version: {go_version.group().decode()}")
return offset
def find_pclntab(data):
"""Locate the pclntab (PC Line Table) structure."""
magics = {
b'\xfb\xff\xff\xff\x00\x00': "Go 1.2-1.15",
b'\xfa\xff\xff\xff\x00\x00': "Go 1.16-1.17",
b'\xf1\xff\xff\xff\x00\x00': "Go 1.18-1.19",
b'\xf0\xff\xff\xff\x00\x00': "Go 1.20+",
}
for magic, version in magics.items():
offset = data.find(magic)
if offset != -1:
print()
offset, version
,
():
pclntab_offset :
[]
functions = []
func_pattern = re.(
,
)
func_pattern.finditer(data):
name = .group().decode(, errors=)
(name) > (name) < :
functions.append(name)
((functions))
():
strings = []
ascii_pattern = re.()
ascii_pattern.finditer(data):
s = .group().decode()
interesting = [
, , , , ,
, , , , ,
, , , , ,
, , , , ,
, , , ,
]
(kw s.lower() kw interesting):
strings.append(s)
strings
():
deps = []
dep_pattern = re.(
)
dep_pattern.finditer(data):
dep = .group().decode(, errors=)
deps.append(dep)
unique_deps = ((deps))
unique_deps
():
(filepath, ) f:
data = f.read()
()
()
( * )
find_go_build_info(data)
pclntab_offset, go_version = find_pclntab(data)
functions = extract_function_names(data, pclntab_offset)
()
categories = {
: [], : [], : [],
: [], : [], : [],
}
f functions:
f f.lower():
categories[].append(f)
f:
categories[].append(f)
f f:
categories[].append(f)
f f:
categories[].append(f)
f.startswith():
categories[].append(f)
f f:
categories[].append(f)
cat, funcs categories.items():
funcs:
()
fn funcs[:]:
()
deps = extract_dependencies(data)
()
dep deps[:]:
()
sus_strings = extract_go_strings(data)
()
s sus_strings[:]:
()
__name__ == :
(sys.argv) < :
()
sys.exit()
analyze_go_binary(sys.argv[])
Step 2: Ghidra Analysis Script
def analyze_go_binary_ghidra():
"""Ghidra script for Go binary analysis."""
from ghidra.program.model.mem import MemoryAccessException
program = getCurrentProgram()
memory = program.getMemory()
listing = program.getListing()
print("[+] Go Binary Analysis Script")
print(f" Program: {program.getName()}")
pclntab_magics = [
bytes([0xf0, 0xff, 0xff, 0xff]),
bytes([0xf1, 0xff, 0xff, 0xff]),
bytes([0xfa, 0xff, 0xff, 0xff]),
bytes([0xfb, 0xff, 0xff, 0xff]),
]
for magic in pclntab_magics:
addr = memory.findBytes(
program.getMinAddress(), magic, None, True, None
)
if addr:
print()
program.getSymbolTable().createLabel(
addr, , ,
ghidra.program.model.symbol.SourceType.ANALYSIS
)
()
symbol_table = program.getSymbolTable()
func_count =
symbol symbol_table.getAllSymbols():
name = symbol.getName()
( name
(pkg name pkg
[, , , , ])):
func_count +=
()
analyze_go_binary_ghidra()
Validation Criteria
- Go version and build information extracted from binary
- pclntab located and parsed for function name recovery
- Third-party dependencies identified revealing malware capabilities
- Main package functions enumerated for targeted analysis
- Network, crypto, and OS exec functions categorized
- Ghidra analysis correctly labels Go runtime structures
References