mirror of
https://github.com/SHOGGOTH-SECTOR/sica-fondt.git
synced 2026-08-01 08:30:20 +00:00
Place useful parts of the surrounding repos into sica-fondt by layer, per the
body model (Ada = membrane; brain/endocrine/capabilities/knowledge non-Ada):
- brain/ LLM reasoning + providers (dapr, hermes, MoMoA)
- capabilities/ REPRAG sidecars: hermes tools/skills, dapr tools, parallel
dispatch, A51 channels, and the OSINT cluster
- knowledge/ LORAG corpus: 754 cyber-skills, agency personas, secure-coding,
MITRE ATT&CK data
- reference/ defensive threat-reference (C3, shhbruh doc) + AdaYaml parser
License handling: AGPL sources (worldosint, advanced_evolution, mercury,
Reticulum) and GPL DeTTECT are SPEC-only clean-room/port descriptions — no
copyleft code copied. MIT/Apache/data parts copied as working trees.
Safety: shhbruh escape/persistence material and C3 covert-C2 kept as reference
only, not wired into the running organism. See CONSOLIDATION.md.
https://claude.ai/code/session_01UehUqEXXJJCsHoA4voCU5c
312 lines
11 KiB
Markdown
312 lines
11 KiB
Markdown
---
|
|
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
|
|
nist_csf:
|
|
- DE.AE-02
|
|
- RS.AN-03
|
|
- ID.RA-01
|
|
- DE.CM-01
|
|
---
|
|
# 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.
|
|
|
|
|
|
## When to Use
|
|
|
|
- When investigating security incidents that require analyzing golang malware with ghidra
|
|
- When building detection rules or threat hunting queries for this domain
|
|
- When SOC analysts need structured procedures for this analysis type
|
|
- When validating security monitoring coverage for related attack techniques
|
|
|
|
## 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.
|
|
|
|
## Workflow
|
|
|
|
### Step 1: Initial Binary Analysis
|
|
|
|
```python
|
|
#!/usr/bin/env python3
|
|
"""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."""
|
|
# Go buildinfo magic: \xff Go buildinf:
|
|
magic = b'\xff Go buildinf:'
|
|
offset = data.find(magic)
|
|
if offset == -1:
|
|
return None
|
|
|
|
print(f"[+] Go build info at offset 0x{offset:x}")
|
|
|
|
# Extract Go version string nearby
|
|
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."""
|
|
# pclntab magic bytes vary by Go version
|
|
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(f"[+] pclntab found at 0x{offset:x} ({version})")
|
|
return offset, version
|
|
|
|
return None, None
|
|
|
|
|
|
def extract_function_names(data, pclntab_offset):
|
|
"""Extract function names from pclntab."""
|
|
if pclntab_offset is None:
|
|
return []
|
|
|
|
functions = []
|
|
# Function name strings follow specific patterns
|
|
func_pattern = re.compile(
|
|
rb'(?:main|runtime|fmt|net|os|crypto|encoding|io|sync|'
|
|
rb'syscall|reflect|strings|bytes|path|time|math|sort|'
|
|
rb'github\.com|golang\.org)[/\.][\w/.]+',
|
|
)
|
|
|
|
for match in func_pattern.finditer(data):
|
|
name = match.group().decode('utf-8', errors='replace')
|
|
if len(name) > 4 and len(name) < 200:
|
|
functions.append(name)
|
|
|
|
return sorted(set(functions))
|
|
|
|
|
|
def extract_go_strings(data):
|
|
"""Extract Go-style strings (pointer+length pairs)."""
|
|
# Go strings are not null-terminated; extract readable sequences
|
|
strings = []
|
|
ascii_pattern = re.compile(rb'[\x20-\x7e]{10,}')
|
|
|
|
for match in ascii_pattern.finditer(data):
|
|
s = match.group().decode('ascii')
|
|
# Filter for interesting malware strings
|
|
interesting = [
|
|
'http', 'https', 'tcp', 'udp', 'dns',
|
|
'cmd', 'shell', 'exec', 'upload', 'download',
|
|
'encrypt', 'decrypt', 'key', 'token', 'password',
|
|
'c2', 'beacon', 'agent', 'implant', 'bot',
|
|
'mutex', 'persist', 'registry', 'scheduled',
|
|
]
|
|
if any(kw in s.lower() for kw in interesting):
|
|
strings.append(s)
|
|
|
|
return strings
|
|
|
|
|
|
def extract_dependencies(data):
|
|
"""Extract Go module dependencies from binary."""
|
|
deps = []
|
|
# Module paths follow pattern: github.com/user/repo
|
|
dep_pattern = re.compile(
|
|
rb'((?:github\.com|gitlab\.com|golang\.org|gopkg\.in|'
|
|
rb'go\.etcd\.io|google\.golang\.org)/[^\x00\s]{5,80})'
|
|
)
|
|
|
|
for match in dep_pattern.finditer(data):
|
|
dep = match.group().decode('utf-8', errors='replace')
|
|
deps.append(dep)
|
|
|
|
unique_deps = sorted(set(deps))
|
|
return unique_deps
|
|
|
|
|
|
def analyze_go_binary(filepath):
|
|
"""Full analysis of Go malware binary."""
|
|
with open(filepath, 'rb') as f:
|
|
data = f.read()
|
|
|
|
print(f"[+] Analyzing Go binary: {filepath}")
|
|
print(f" File size: {len(data):,} bytes")
|
|
print("=" * 60)
|
|
|
|
# Build info
|
|
find_go_build_info(data)
|
|
|
|
# pclntab
|
|
pclntab_offset, go_version = find_pclntab(data)
|
|
|
|
# Functions
|
|
functions = extract_function_names(data, pclntab_offset)
|
|
print(f"\n[+] Recovered {len(functions)} function names")
|
|
|
|
# Categorize functions
|
|
categories = {
|
|
"network": [], "crypto": [], "os_exec": [],
|
|
"file_io": [], "main": [], "third_party": [],
|
|
}
|
|
for f in functions:
|
|
if 'net/' in f or 'http' in f.lower():
|
|
categories["network"].append(f)
|
|
elif 'crypto' in f:
|
|
categories["crypto"].append(f)
|
|
elif 'os/exec' in f or 'syscall' in f:
|
|
categories["os_exec"].append(f)
|
|
elif 'os.' in f or 'io/' in f:
|
|
categories["file_io"].append(f)
|
|
elif f.startswith('main.'):
|
|
categories["main"].append(f)
|
|
elif 'github.com' in f or 'golang.org' in f:
|
|
categories["third_party"].append(f)
|
|
|
|
for cat, funcs in categories.items():
|
|
if funcs:
|
|
print(f"\n [{cat}] ({len(funcs)} functions):")
|
|
for fn in funcs[:10]:
|
|
print(f" {fn}")
|
|
|
|
# Dependencies
|
|
deps = extract_dependencies(data)
|
|
print(f"\n[+] Dependencies ({len(deps)}):")
|
|
for dep in deps[:20]:
|
|
print(f" {dep}")
|
|
|
|
# Suspicious strings
|
|
sus_strings = extract_go_strings(data)
|
|
print(f"\n[+] Suspicious strings ({len(sus_strings)}):")
|
|
for s in sus_strings[:20]:
|
|
print(f" {s}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
if len(sys.argv) < 2:
|
|
print(f"Usage: {sys.argv[0]} <go_binary>")
|
|
sys.exit(1)
|
|
analyze_go_binary(sys.argv[1])
|
|
```
|
|
|
|
### Step 2: Ghidra Analysis Script
|
|
|
|
```python
|
|
# Ghidra script (run within Ghidra's script manager)
|
|
# Save as AnalyzeGoBinary.py in Ghidra scripts directory
|
|
|
|
# @category MalwareAnalysis
|
|
# @description Analyze Go binary structure and recover metadata
|
|
|
|
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()}")
|
|
|
|
# Find pclntab
|
|
pclntab_magics = [
|
|
bytes([0xf0, 0xff, 0xff, 0xff]), # Go 1.20+
|
|
bytes([0xf1, 0xff, 0xff, 0xff]), # Go 1.18-1.19
|
|
bytes([0xfa, 0xff, 0xff, 0xff]), # Go 1.16-1.17
|
|
bytes([0xfb, 0xff, 0xff, 0xff]), # Go 1.2-1.15
|
|
]
|
|
|
|
for magic in pclntab_magics:
|
|
addr = memory.findBytes(
|
|
program.getMinAddress(), magic, None, True, None
|
|
)
|
|
if addr:
|
|
print(f"[+] pclntab found at {addr}")
|
|
# Create label
|
|
program.getSymbolTable().createLabel(
|
|
addr, "go_pclntab", None,
|
|
ghidra.program.model.symbol.SourceType.ANALYSIS
|
|
)
|
|
break
|
|
|
|
# Fix Go string definitions
|
|
# Go strings are ptr+len, not null terminated
|
|
print("[+] Fixing Go string references...")
|
|
|
|
# Search for function names containing package paths
|
|
symbol_table = program.getSymbolTable()
|
|
func_count = 0
|
|
for symbol in symbol_table.getAllSymbols(True):
|
|
name = symbol.getName()
|
|
if ('.' in name and
|
|
any(pkg in name for pkg in
|
|
['main.', 'runtime.', 'net.', 'crypto.', 'os.'])):
|
|
func_count += 1
|
|
|
|
print(f"[+] Found {func_count} Go function symbols")
|
|
|
|
|
|
# Execute
|
|
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
|
|
|
|
- [CUJO AI - Reverse Engineering Go Binaries with Ghidra](https://cujo.com/blog/reverse-engineering-go-binaries-with-ghidra/)
|
|
- [Volexity GoResolver](https://www.volexity.com/blog/2025/04/01/goresolver-using-control-flow-graph-similarity-to-deobfuscate-golang-binaries-automatically/)
|
|
- [Go Reverse Engineering Tool Kit](https://go-re.tk/about/)
|
|
- [SentinelOne AlphaGolang](https://www.sentinelone.com/labs/alphagolang-a-step-by-step-go-malware-reversing-methodology-for-ida-pro/)
|
|
- [Go Binary Reversing Notes](https://gist.github.com/0xdevalias/4e430914124c3fd2c51cb7ac2801acba)
|