
GoReSym 是一个 Go 符号解析器,可提取程序元数据(如 CPU 架构、操作系统、字节序、编译器版本等)、函数元数据(起始与结束地址、名称、来源)、文件名与行号元数据,以及嵌入的结构和类型。这个跨平台程序直接基于开源 Go 编译器和运行时代码。
上游 Go 运行时代码经过扩展以处理:
moduledata 结构的位置有关逆向工程细节和用法示例,请参阅 https://www.mandiant.com/resources/blog/golang-internals-symbol-recovery 。
你可以从 Releases 标签页 下载预构建的 linux、macos 和 windows GoReSym 二进制文件。
要使用较新的 Go 编译器从源码构建,请调用 Go 编译器:
go build
构建完成后,像这样调用 GoReSym:
GoReSym.exe -t -d -p /path/to/input.exe
在此示例中,我们让 GoReSym 恢复嵌入在文件 /path/to/input.exe 中的类型名称(-t)、用户包名称、标准 Go 包名称(-d)以及输入文件路径(-p)。输出如下所示:
{
"Version": "1.14.15",
"BuildId": "Zb9QmokKTiOUgHKmaIwz/wd2rtE3W9PN-um1Ocdzh/qTdqcTY_jVajHy_-TtYv/Z_kJu9M77OjfijEiHMcF",
"Arch": "amd64",
"TabMeta": {
"VA": 5174784,
"Version": "1.2",
"Endianess": "LittleEndian",
"CpuQuantum": 1,
"CpuQuantumStr": "x86/x64",
"PointerSize": 8
},
"ModuleMeta": {
"VA": 5678816,
"Types": 4845568,
"ETypes": 5171904,
"Typelinks": {
"Data": 5171904,
"Len": 695,
"Capacity": 695
},
"ITablinks": {
"Data": 5174688,
"Len": 11,
"Capacity": 11
},
"LegacyTypes": {
"Data": 0,
"Len": 0,
"Capacity": 0
}
},
"Types": [ ... ],
"Files": [ ... ],
"Strings": [ ... ],
"UserFunctions": [ ... ],
"StdFunctions": [ ... ]
}
以下是所有可用的标志:
-d(“default”,可选)标志将在用户包之外额外打印标准 Go 包。-p(“paths”,可选)标志将打印嵌入在 pclntab 中的任何文件路径。-t(“types”,可选)标志将打印 Go 类型名称。-strings(可选)标志将通过分析字符串驻留表从二进制文件中提取嵌入的 Go 字符串。-m <virtual address>(“manual”,可选)标志将在给定的虚拟地址处递归转储 RTYPE 结构-v <version string>(“version”,可选)标志将覆盖自动版本检测并使用提供的版本。这对于某些被剥离的二进制文件是必需的。如果版本不准确,类型解析将失败。-human(可选)标志将打印纯文本列表而不是 JSON。在打印结构和接口类型时特别有用。-about(可选)标志将打印许可证信息要将此信息导入 IDA Pro,你可以运行位于 https://github.com/mandiant/GoReSym/blob/master/IDAPython/goresym_rename.py 的脚本。它将读取 GoReSym 生成的 json 文件并在 IDA 中设置符号/标签。
随着 Go 编译器和运行时的变化,嵌入的元数据结构也发生了变化。GoReSym 支持以下 Go 版本与元数据的组合:
pclntab 解析:>= Go 1.2moduledata 定位:>= Go 1.2moduledata 类型解析:>= Go 1.5用于提取类型的 moduledata 表在 Go 1.5 之前不存在,因此此库永远不会支持从非常旧的 Go 版本中提取类型。
此库目前处理 1.2 之前、1.2、1.16、1.18 和 1.20 的 pclntab 布局。请注意,pclntab 版本始终 <= Go 运行时版本(例如:Go 运行时 1.19 使用 1.18 的 pclntab 布局),我们的目标是_始终支持最新的运行时版本_。
GoReSym 的大部分源代码复制自上游 Go 编译器源代码目录 /internal。为了实现这一点,我们不得不对源代码进行一些调整。如果你想为 GoReSym 做出贡献,请继续阅读,以便我们解释这个导入过程。
由于 Go 包的工作方式,我们需要从源代码文件树中移除 /internal 路径。这导致大量复制 Go 内部文件,其中目录树基本保持完整,但对许多文件的导入做了小改动:对 /internal 路径的引用被替换为 github.com/mandiant/GoReSym/。
我们还修改了许多内部结构以导出字段和方法。这些在上游 Go 中不会被导出,因为用户不应依赖它们。然而,此工具的目的是提取内部信息,因此我们承担了维护这些结构的任务。这不是一个理想的情况,但也不容易避免。如果你更新此仓库,你必须注意保持这些修改完好无损。手动合并上游的提交可能比整体复制上游文件更好。
我愿意接受关于如何更好地组织此项目以避免这些问题,同时仍能使用典型的 go build 进行编译的建议。之前有一个涉及 Go 维护者的讨论,见此处。
忽略一些琐碎的更改,大多数新逻辑存在于 /objfile 中。例如,文件 objfile/internals 定义了 GoReSym 解析的反向内部 Go 结构。
pclntab 规范:golang.org/s/go12symtabpclntab 魔数:pclntab.go#L169objfile 缺陷:
buildID 遗留缺陷:golang/go#50809runtime_modulesinit 初始化方法的签名来查找 pclntab,并尝试修复 pclntab 魔数(在 pclntab 魔数被修改的情况下)。objfile/<fileformat> 中的 pcln() 函数,以支持对 pclntab 魔数进行字节扫描/debug/<fileformat> 的文件格式解析器中新增了 DataAfterSection 等例程,以支持签名扫描debug/gosym/symtab.go 的 walksymtab 中新增检查,当可选的 symtab 段为空时提前退出objfile/objfile.go 的 PCLineTable() 中的 goobj 行号支持loadPeTable(以及其他格式变体)新增了额外的健全性检查,以避免在符号存在但被恶意修改为无效时发生 panic(参考:golang/go#47981)read_memory 例程,以按虚拟地址读取文件数据moduledata 扫描例程,以帮助定位 moduledata,从而支持扫描类型和接口(通过 typelinks)readStringTable 新增了针对无效符号表的大小防护。解析失败也会被忽略。MIT