Files
Anthropic-Cybersecurity-Skills/skills/analyzing-golang-malware-with-ghidra/scripts/agent.py
T
mukul975 c47eed6a64 Production hardening: security fixes, code quality, 724 skills complete
- Fix 25 shell=True subprocess calls with list-based commands
- Fix 49 verify=False in defensive skills (env-var override)
- Add timeout to 231 HTTP/subprocess/socket calls
- Fix 6 SQL injection patterns with whitelist validation
- Replace 8 __import__() with standard imports
- Remove 701 unused imports across 442 files
- Add authorized-testing disclaimers to all offensive skills
- Complete 11 incomplete skill directories
- Expand 10 stub SKILL.md files with full content
- Fix 2 YAML parse errors in frontmatter
- Fix 5 pre-existing syntax errors
- Convert 22 hardcoded paths/ports to environment variables
- Back up 21 redundant skill pairs to .bak
- Fix 2 global declaration errors
- 724/724 skills with full folder anatomy (SKILL.md + agent.py + api-reference.md + LICENSE)
- 0 compile errors across all 724 agent.py files
2026-03-19 13:26:49 +01:00

268 lines
8.9 KiB
Python

#!/usr/bin/env python3
"""Go malware analysis agent for Ghidra-assisted reverse engineering.
Analyzes Go binaries to extract function names, strings, build metadata,
package information, and detects common Go malware characteristics.
"""
import os
import sys
import json
import hashlib
import re
import math
from collections import Counter
def compute_hash(filepath):
"""Compute SHA-256 hash of file."""
sha256 = hashlib.sha256()
with open(filepath, "rb") as f:
for chunk in iter(lambda: f.read(65536), b""):
sha256.update(chunk)
return sha256.hexdigest()
def shannon_entropy(data):
"""Calculate Shannon entropy."""
if not data:
return 0.0
freq = Counter(data)
length = len(data)
return -sum((c / length) * math.log2(c / length) for c in freq.values())
def detect_go_binary(filepath):
"""Detect if a binary is compiled with Go and extract version info."""
with open(filepath, "rb") as f:
data = f.read()
indicators = {
"is_go_binary": False,
"go_version": None,
"go_buildinfo": False,
"gopclntab_found": False,
}
# Go build info magic
buildinfo_magic = b"\xff Go buildinf:"
offset = data.find(buildinfo_magic)
if offset != -1:
indicators["is_go_binary"] = True
indicators["go_buildinfo"] = True
# Go version string
version_pattern = rb"go(\d+\.\d+(?:\.\d+)?)"
matches = re.findall(version_pattern, data)
if matches:
indicators["is_go_binary"] = True
versions = sorted(set(m.decode() for m in matches))
indicators["go_version"] = versions[-1] if versions else None
# gopclntab (Go PC line table) magic bytes
gopclntab_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"\xf0\xff\xff\xff\x00\x00", # Go 1.18+
b"\xf1\xff\xff\xff\x00\x00", # Go 1.20+
]
for magic in gopclntab_magics:
if magic in data:
indicators["gopclntab_found"] = True
indicators["is_go_binary"] = True
break
# Runtime strings
go_strings = [b"runtime.main", b"runtime.goexit", b"runtime.gopanic",
b"runtime.newproc", b"GOROOT", b"GOPATH"]
found_runtime = sum(1 for s in go_strings if s in data)
if found_runtime >= 2:
indicators["is_go_binary"] = True
indicators["runtime_strings_found"] = found_runtime
return indicators
def extract_go_strings(filepath, min_length=6):
"""Extract Go-style strings (length-prefixed, not null-terminated)."""
with open(filepath, "rb") as f:
data = f.read()
# Standard ASCII string extraction
ascii_pattern = re.compile(rb"[\x20-\x7e]{%d,}" % min_length)
strings = [m.group().decode("ascii", errors="replace") for m in ascii_pattern.finditer(data)]
return strings
def extract_go_packages(strings_list):
"""Identify Go packages from extracted strings."""
packages = set()
pkg_pattern = re.compile(r"^([a-zA-Z0-9_]+(?:/[a-zA-Z0-9_.-]+)+)\.")
for s in strings_list:
match = pkg_pattern.match(s)
if match:
packages.add(match.group(1))
# Also look for known Go import paths
for s in strings_list:
if s.startswith("github.com/") or s.startswith("golang.org/"):
parts = s.split("/")
if len(parts) >= 3:
packages.add("/".join(parts[:3]))
return sorted(packages)
SUSPICIOUS_GO_PACKAGES = {
"github.com/kbinani/screenshot": "Screen capture capability",
"github.com/atotto/clipboard": "Clipboard access",
"github.com/go-vgo/robotgo": "Desktop automation / keylogging",
"github.com/miekg/dns": "Custom DNS resolution (C2/tunneling)",
"golang.org/x/crypto/ssh": "SSH client (lateral movement)",
"github.com/shirou/gopsutil": "System enumeration",
"github.com/mitchellh/go-ps": "Process listing",
"github.com/gobuffalo/packr": "Binary resource embedding",
"github.com/Ne0nd0g/merlin": "Merlin C2 agent",
"github.com/BishopFox/sliver": "Sliver C2 framework",
"github.com/traefik/yaegi": "Go interpreter (dynamic execution)",
}
def detect_suspicious_packages(packages):
"""Flag suspicious Go packages commonly used in malware."""
findings = []
for pkg in packages:
for sus_pkg, description in SUSPICIOUS_GO_PACKAGES.items():
if sus_pkg in pkg:
findings.append({"package": pkg, "concern": description})
return findings
def analyze_sections(filepath):
"""Analyze PE/ELF sections for Go binary characteristics."""
with open(filepath, "rb") as f:
magic = f.read(4)
f.seek(0)
data = f.read()
sections = []
if magic[:2] == b"MZ": # PE
try:
import pefile
pe = pefile.PE(data=data)
for section in pe.sections:
name = section.Name.rstrip(b"\x00").decode("ascii", errors="replace")
entropy = section.get_entropy()
sections.append({
"name": name, "virtual_size": section.Misc_VirtualSize,
"raw_size": section.SizeOfRawData, "entropy": round(entropy, 3),
})
pe.close()
except ImportError:
sections.append({"note": "pefile not installed"})
elif magic[:4] == b"\x7fELF":
try:
from elftools.elf.elffile import ELFFile
from io import BytesIO
elf = ELFFile(BytesIO(data))
for section in elf.iter_sections():
sec_data = section.data() if section.header.sh_size > 0 else b""
entropy = shannon_entropy(sec_data) if sec_data else 0
sections.append({
"name": section.name, "size": section.header.sh_size,
"entropy": round(entropy, 3), "type": section.header.sh_type,
})
except ImportError:
sections.append({"note": "pyelftools not installed"})
return sections
def detect_obfuscation(go_info, strings_list):
"""Detect Go binary obfuscation (garble, gobfuscate)."""
indicators = {"obfuscated": False, "techniques": []}
# Garble replaces function names with hashes
hash_names = sum(1 for s in strings_list if re.match(r"^[a-f0-9]{16,}$", s))
if hash_names > 20:
indicators["obfuscated"] = True
indicators["techniques"].append("Possible garble obfuscation (hash-like function names)")
# Missing gopclntab suggests stripping
if not go_info.get("gopclntab_found"):
indicators["techniques"].append("gopclntab not found - may be stripped or modified")
# Low runtime string count
if go_info.get("runtime_strings_found", 0) < 2:
indicators["obfuscated"] = True
indicators["techniques"].append("Low Go runtime string count - possible obfuscation")
return indicators
def generate_report(filepath):
"""Generate comprehensive Go malware analysis report."""
report = {
"file": filepath,
"sha256": compute_hash(filepath),
"size": os.path.getsize(filepath),
}
go_info = detect_go_binary(filepath)
report["go_detection"] = go_info
if not go_info["is_go_binary"]:
report["conclusion"] = "Not identified as a Go binary"
return report
strings_list = extract_go_strings(filepath)
report["total_strings"] = len(strings_list)
packages = extract_go_packages(strings_list)
report["packages"] = packages[:50]
suspicious = detect_suspicious_packages(packages)
report["suspicious_packages"] = suspicious
sections = analyze_sections(filepath)
report["sections"] = sections
obfuscation = detect_obfuscation(go_info, strings_list)
report["obfuscation"] = obfuscation
return report
if __name__ == "__main__":
print("=" * 60)
print("Go Malware Analysis Agent (Ghidra-assisted)")
print("Go binary detection, package extraction, obfuscation detection")
print("=" * 60)
target = sys.argv[1] if len(sys.argv) > 1 else None
if not target or not os.path.exists(target):
print("\n[DEMO] Usage: python agent.py <go_binary>")
sys.exit(0)
report = generate_report(target)
go = report.get("go_detection", {})
print(f"\n[*] File: {target}")
print(f"[*] SHA-256: {report['sha256']}")
print(f"[*] Go binary: {go.get('is_go_binary', False)}")
print(f"[*] Go version: {go.get('go_version', 'unknown')}")
print(f"[*] Strings: {report.get('total_strings', 0)}")
print("\n--- Packages ---")
for pkg in report.get("packages", [])[:15]:
print(f" {pkg}")
print("\n--- Suspicious Packages ---")
for s in report.get("suspicious_packages", []):
print(f" [!] {s['package']}: {s['concern']}")
print("\n--- Obfuscation ---")
obf = report.get("obfuscation", {})
print(f" Obfuscated: {obf.get('obfuscated', False)}")
for t in obf.get("techniques", []):
print(f" {t}")
print(f"\n{json.dumps(report, indent=2, default=str)}")