mcpField
A
analyzing-golang-malware-with-ghidra

analyzing-golang-malware-with-ghidra

github.com/mukul975安全35.2k442 次安装8 周前更新
代码仓库

关于 analyzing-golang-malware-with-ghidra

面向安全分析人员的技能,介绍如何使用 Ghidra 检查 Go 恶意软件。内容涵盖恢复函数名和元数据、识别依赖项,以及组织针对性二进制分析。

主要功能

  1. 01分析 Go 二进制元数据
  2. 02解析 pclntab 并恢复函数名
  3. 03提取依赖项
  4. 04Ghidra 分析流程

适用场景

  1. 01调查 Go 恶意软件事件
  2. 02从二进制中识别依赖项和能力
  3. 03根据分析结果构建检测规则

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/mukul975/anthropic-cybersecurity-skills --skill analyzing-golang-malware-with-ghidra

安装后在 Claude Code 中输入/analyzing-golang-malware-with-ghidra确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

LICENSE
assets/
template.md
references/
api-reference.md
standards.md
workflows.md
scripts/
agent.py
process.py

详细介绍

使用 Ghidra 分析 Golang 恶意软件

概述

Go(Golang)凭借交叉编译能力、可生成独立二进制文件的静态链接,以及给逆向工程带来的复杂性,已成为恶意软件作者常用的语言。Go 二进制文件会静态链接完整的运行时、标准库和所有依赖项,因此文件体积较大(通常为 5-15 MB),且包含数千个函数。Ghidra 在处理 Go 特有的字符串格式(非空字符结尾)、被剥离的函数名以及 goroutine 并发模式时会遇到困难。GoResolver(Volexity,2025)等专用工具会利用控制流图相似性,自动对被剥离或混淆的 Go 二进制文件进行反混淆并恢复函数名。

使用场景

  • 调查需要使用 Ghidra 分析 Golang 恶意软件的安全事件时
  • 为此领域构建检测规则或威胁狩猎查询时
  • SOC 分析师需要针对这类分析制定结构化流程时
  • 验证针对相关攻击技术的安全监控覆盖情况时

前提条件

  • Ghidra 11.0+ 和 JDK 17+
  • GoResolver 插件(用于恢复函数名)
  • Go 逆向工程工具包(go-re.tk)
  • Python 3.9+(用于辅助脚本)
  • 了解 Go 运行时内部机制(goroutine、channel、interface)
  • 熟悉 Go 二进制文件结构(pclntab、moduledata、itab)

关键概念

Go 二进制文件结构

Go 二进制文件会在 pclntab(PC 线表)结构中嵌入丰富的元数据,将程序计数器映射到函数名、源文件和行号。即使经过剥离,二进制文件仍会保留这些元数据。moduledata 结构包含指向类型信息、itab(接口表)和 pclntab 本身的指针。Go 字符串以指针和长度对的形式存储,而非以 C 字符串的空字符结尾。

恢复已剥离二进制文件中的函数

即使符号表已被剥离,Go 二进制文件仍会在 pclntab 中保留函数名。不过,garble 等混淆工具会将函数重命名为随机字符串。GoResolver 通过计算混淆函数的控制流图特征,并与已知 Go 标准库和第三方软件包函数的数据库进行匹配来解决这一问题。

软件包/依赖项提取

Go 的依赖管理会将模块路径和版本字符串嵌入二进制文件。提取这些信息可以揭示恶意软件的第三方依赖项(HTTP 库、加密软件包、C2 框架),从而在无需完整逆向工程的情况下深入了解其能力。

工作流程

步骤 1:初始二进制文件分析
#!/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])
步骤 2:Ghidra 分析脚本
# 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()

验证标准

  • 从二进制文件中提取 Go 版本和构建信息
  • 定位并解析 pclntab,以恢复函数名
  • 识别可揭示恶意软件能力的第三方依赖项
  • 枚举主软件包函数,以便开展针对性分析
  • 对网络、加密和操作系统执行函数进行分类
  • Ghidra 能正确标注 Go 运行时结构

参考资料

评论

支持 Markdown