逆向工程实战手册 第 14 章

第 14 章 编译语言逆向(Go / Rust / Swift 等)

现代 CTF 和真实软件大量用 Go/Rust 编写。它们的特征是编译时类型/符号信息丰富(Go 的 pclntab、Rust 的 panic 字符串),只要知道怎么挖,逆向反而有捷径。

📍 知识点地图 | 主题:编译语言逆向 | 前置:第3章 | 后续:— | 核心概念:Go pclntab、Rust、vtable、符号恢复

14.1 Go 二进制

识别

特征: 文件巨大(C 程序 ~20KB,Go 最小 ~2MB,静态链接 runtime)
字符串: go1.xx / runtime. / net/http / 编译器版本
rabin2 或 strings 直接看到大量 package 名

符号恢复(strip 也能恢复!)

Go 即使 strip,也会保留 pclntab(PC Line Table)——函数名、行号全在里面。

工具 用途
GoReSym 从 pclntab 恢复全部函数名/类型/接口(首选)
redress 恢复 stripped Go 二进制符号
GoResolver 去混淆(Garble)
GoStringUngarbler 恢复 Garble 字符串
Ghidra 的 golang 脚本 恢复符号
# GoReSym 使用
GoReSym -t binary          # 输出函数表
GoReSym -l binary          # 行号表

内存布局(还原的关键)

String:   {ptr, length}        16 字节,非 null 结尾!length 是关键
Slice:    {ptr, length, cap}   24 字节
Interface: {type_descriptor, data_pointer}  16 字节
Map:      ptr 到 runtime.hmap
Channel:  ptr 到 runtime.hchan

常见模式

goroutine 创建: 反汇编里找 newproc1 / go func 调用点
错误处理: 函数返回 (result, error),call 后 test rax + jne error_handler
embed.FS: 二进制里嵌文件 → 搜文件签名(PK/PNG)定位

实战三连(Go 逆向工作流)

1. 未 strip: 符号全在,直接看
2. stripped: GoReSym 恢复 → Ghidra/IDA 导入
3. Garble 混淆: GoResolver + GoStringUngarbler 处理

案例:Go 恶意软件(seed-002)和 Go 商业程序(2026-05-15_lumine-go-reverse)都靠符号恢复大幅降低难度。

14.2 Rust 二进制

识别

字符串: rustc / core:: / alloc:: / panic 信息
巨大二进制 + 大量 panic 字符串(路径/行号/变量名!)

符号还原

# Rust 用 Itanium ABI mangle(同 C++)
rustfilt < symbols.txt        # 专用 demangle
c++filt                        # 通用(大部分 Rust 符号可解)
# Ghidra: 启用 DemangleAllScript 自动 demangle

内存布局

Option<T>:  {discriminant(0=None/1=Some), value}
Result<T,E>: {discriminant(0=Ok/1=Err), union{ok,err}}
String:     {ptr, cap, len}   24 字节
&str:       {ptr, len}        16 字节
Vec<T>:     同 String 布局({ptr, cap, len})

识别特征

String 创建: 找 alloc::string::String::from
迭代器链: .iter().map().filter().collect() → 内联紧凑循环
panic 字符串: 泄露源码位置和变量名 → 直接看路径猜逻辑

工具

  • cargo-bloat:按函数分析二进制大小
  • ghidra-rust:Ghidra Rust 辅助脚本

14.3 Swift / Kotlin / 其他

Swift

识别: swift_allocObject / swift_release / swift_once
Demangle: xcrun swift-demangle
String: 小字符串(≤15B)内联, 大字符串堆上 {ptr, len}
动态分发: Protocol Witness Table(如 vtable)

Kotlin/JVM

识别: kotlin.Metadata 注解、kotlin/jvm/internal/*
反编译: 直接 javap/CFR 级工具(JVM 字节码好读)
模式: Companion 对象、data class、协程状态机(ContinuationImpl+invokeSuspend)、
     When 表达式 → tableswitch/lookupswitch

Kotlin/Native

无 JVM: konan/kotlin.native 字符串特征
LLVM 后端 → 看起来像 C++,难很多(无反射元数据)

Haskell / D 语言(冷门但出现过)

Haskell: hsdecomp 工具反编译到伪 Haskell;GHC CMM 中间语言分析递归
D: _D 前缀 mangle(GDB set language d / 在线 demangle)

14.4 C++(绕不开的基础)

vtable 还原

对象前 8 字节 → 指向 vtable
vtable 条目: [typeinfo_ptr, 析构, method1, method2, ...]
多态调用: mov rax, [rdi]; call [rax + 0x18]   # 第 4 个虚方法

RTTI 还原

typeinfo: {vtable_for_typeinfo, name_string, base_class_ptr}
未 strip → 从 vtable 引用找 typeinfo,恢复类层次
Ghidra: 在 vtable 地址 Create Pointer,追 typeinfo

STL 模式

std::string: 小字符串优化(SSO)
std::vector: {begin, end, cap}
std::map/set: 红黑树节点
用 FLIRT/Lumina 先识别库函数再分析业务

14.5 通用方法论

1. 识别语言(字符串/特征/大小)
2. 恢复符号(GoReSym / rustfilt / demangle / RTTI)
3. 建立内存布局(类型表)
4. 从 main 和 panic/日志字符串建立行为地图
5. 逐函数还原业务逻辑

动手练习

  1. 用 Go 写个带签名校验的程序(go build 后 strip),用 GoReSym 恢复符号并还原逻辑。
  2. 用 Rust 写个简单验证程序,练习 rustfilt 和识别 Option/Result 布局。
  3. 找一个 C++ 程序,练习 vtable 还原(Ghidra 里 Create Pointer 追 typeinfo)。
  4. 比较同一逻辑的 Go/Rust/C 二进制大小和字符串,感受差异。

深入阅读

  • 仓库:skills/reverse-engineering/languages-compiled.md(Go/Rust/Swift/Kotlin/Haskell/D/C++ 全量)
  • 仓库:skills/reverse-engineering/go-reverse.md(Go 专项:GoReSym/redress/IDA 中的 Go)
  • 仓库:skills/reverse-engineering/languages.md(Python 字节码、WASM、UEFI 等)
  • 仓库:skills/reverse-engineering/languages-platforms.md(平台/框架专项)
  • 实战案例:skills/field-journal/seed-002_go-malware-stripped.md2026-05-15_lumine-go-reverse.md