第 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. 逐函数还原业务逻辑
动手练习
- 用 Go 写个带签名校验的程序(
go build后 strip),用 GoReSym 恢复符号并还原逻辑。 - 用 Rust 写个简单验证程序,练习 rustfilt 和识别 Option/Result 布局。
- 找一个 C++ 程序,练习 vtable 还原(Ghidra 里 Create Pointer 追 typeinfo)。
- 比较同一逻辑的 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.md、2026-05-15_lumine-go-reverse.md