reverse-skills-max: initial public release (restored, redacted, hardened)
This commit is contained in:
@@ -0,0 +1,267 @@
|
||||
#!/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)}")
|
||||
@@ -0,0 +1,162 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Go Malware Binary Analyzer
|
||||
|
||||
Extracts metadata, function names, dependencies, and suspicious
|
||||
indicators from Go-compiled malware binaries.
|
||||
|
||||
Usage:
|
||||
python process.py --file malware.exe --output report.json
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import re
|
||||
import struct
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
PCLNTAB_MAGICS = {
|
||||
b'\xf0\xff\xff\xff': "Go 1.20+",
|
||||
b'\xf1\xff\xff\xff': "Go 1.18-1.19",
|
||||
b'\xfa\xff\xff\xff': "Go 1.16-1.17",
|
||||
b'\xfb\xff\xff\xff': "Go 1.2-1.15",
|
||||
}
|
||||
|
||||
|
||||
def find_pclntab(data):
|
||||
for magic, version in PCLNTAB_MAGICS.items():
|
||||
offset = data.find(magic)
|
||||
if offset != -1:
|
||||
return offset, version
|
||||
return None, None
|
||||
|
||||
|
||||
def extract_go_version(data):
|
||||
match = re.search(rb'go(\d+\.\d+(?:\.\d+)?)', data)
|
||||
return match.group(1).decode() if match else "unknown"
|
||||
|
||||
|
||||
def extract_functions(data):
|
||||
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|gopkg\.in)[/\.][\w/.]+)'
|
||||
)
|
||||
functions = set()
|
||||
for match in func_pattern.finditer(data):
|
||||
name = match.group(1).decode('utf-8', errors='replace')
|
||||
if 4 < len(name) < 200:
|
||||
functions.add(name)
|
||||
return sorted(functions)
|
||||
|
||||
|
||||
def extract_dependencies(data):
|
||||
dep_pattern = re.compile(
|
||||
rb'((?:github\.com|gitlab\.com|golang\.org|gopkg\.in|'
|
||||
rb'go\.etcd\.io|google\.golang\.org)/[\w./-]{5,80})'
|
||||
)
|
||||
deps = set()
|
||||
for match in dep_pattern.finditer(data):
|
||||
dep = match.group(1).decode('utf-8', errors='replace')
|
||||
# Clean up trailing artifacts
|
||||
dep = dep.rstrip('/.')
|
||||
deps.add(dep)
|
||||
return sorted(deps)
|
||||
|
||||
|
||||
def extract_suspicious_strings(data):
|
||||
interesting_patterns = [
|
||||
rb'https?://[\w./:?&=-]+',
|
||||
rb'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(?::\d+)?',
|
||||
rb'(?:cmd|powershell|bash|sh)(?:\.exe)?',
|
||||
rb'(?:HKLM|HKCU)\\[^\x00]+',
|
||||
rb'/etc/(?:passwd|shadow|crontab)',
|
||||
]
|
||||
|
||||
results = {}
|
||||
for pattern in interesting_patterns:
|
||||
matches = re.findall(pattern, data)
|
||||
if matches:
|
||||
decoded = [m.decode('utf-8', errors='replace') for m in matches]
|
||||
results[pattern.decode('utf-8', errors='replace')] = list(set(decoded))
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def categorize_functions(functions):
|
||||
categories = {
|
||||
"main_logic": [],
|
||||
"networking": [],
|
||||
"cryptography": [],
|
||||
"os_execution": [],
|
||||
"file_operations": [],
|
||||
"third_party": [],
|
||||
"runtime": [],
|
||||
}
|
||||
|
||||
for func in functions:
|
||||
fl = func.lower()
|
||||
if func.startswith('main.'):
|
||||
categories["main_logic"].append(func)
|
||||
elif any(x in fl for x in ['net/', 'http', 'tcp', 'udp', 'dns']):
|
||||
categories["networking"].append(func)
|
||||
elif 'crypto' in fl:
|
||||
categories["cryptography"].append(func)
|
||||
elif any(x in fl for x in ['os/exec', 'syscall']):
|
||||
categories["os_execution"].append(func)
|
||||
elif any(x in fl for x in ['os.', 'io/', 'ioutil']):
|
||||
categories["file_operations"].append(func)
|
||||
elif any(x in fl for x in ['github.com', 'golang.org', 'gopkg.in']):
|
||||
categories["third_party"].append(func)
|
||||
elif func.startswith('runtime.'):
|
||||
categories["runtime"].append(func)
|
||||
|
||||
return {k: v for k, v in categories.items() if v}
|
||||
|
||||
|
||||
def analyze(filepath):
|
||||
with open(filepath, 'rb') as f:
|
||||
data = f.read()
|
||||
|
||||
report = {
|
||||
"file": str(filepath),
|
||||
"size": len(data),
|
||||
"go_version": extract_go_version(data),
|
||||
}
|
||||
|
||||
pclntab_offset, pclntab_version = find_pclntab(data)
|
||||
report["pclntab"] = {
|
||||
"offset": f"0x{pclntab_offset:x}" if pclntab_offset else None,
|
||||
"version": pclntab_version,
|
||||
}
|
||||
|
||||
functions = extract_functions(data)
|
||||
report["total_functions"] = len(functions)
|
||||
report["function_categories"] = categorize_functions(functions)
|
||||
|
||||
report["dependencies"] = extract_dependencies(data)
|
||||
report["suspicious_strings"] = extract_suspicious_strings(data)
|
||||
|
||||
return report
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Go Malware Analyzer")
|
||||
parser.add_argument("--file", required=True, help="Go binary to analyze")
|
||||
parser.add_argument("--output", help="Output JSON report")
|
||||
|
||||
args = parser.parse_args()
|
||||
report = analyze(args.file)
|
||||
|
||||
print(json.dumps(report, indent=2))
|
||||
|
||||
if args.output:
|
||||
with open(args.output, 'w') as f:
|
||||
json.dump(report, f, indent=2)
|
||||
print(f"\n[+] Report saved to {args.output}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user