【技术推荐】正向角度看Go逆向
字数 2195 2025-08-24 07:48:09
Go语言逆向工程分析指南
一、Go语言特性概述
Go语言作为编译型语言具有以下重要特性:
-
编译与运行时特性:
- 强类型检查的编译型语言,接近C但拥有原生的包管理、内建网络包和协程
- 编译型语言运行速度快,但通过内置运行时符号信息实现反射等动态特性
- 内存安全语言,内建垃圾回收和大量安全检查
-
二进制文件特点:
- 静态链接,程序体积较大
- 三方库、标准库与用户代码混在一起
- 需要区分编译时和运行时操作
-
动态能力:
- 通过接口与反射提供动态能力
- 类型系统保留必要类型定义和对象-类型关联
- 逆向时可获取大量符号信息
二、Go语言数据结构分析
1. 数值类型
| 类型 | 32位平台 | 64位平台 |
|---|---|---|
| bool, int8, uint8 | 8bit | 8bit |
| int16, uint16 | 16bit | 16bit |
| int32, uint32, float32 | 32bit | 32bit |
| int64, uint64, float64, complex64 | 64bit | 64bit |
| int, uint, uintptr | 32bit | 64bit |
| complex128 | 128bit | 128bit |
2. 字符串(string)
字符串结构:
type StringHeader struct {
Data uintptr // 字符串首地址
Len int // 字符串长度
}
- 占两个机器字
- 不以
\0终止 - 常见操作:
- 拼接:
concatstringN(N=2-5)或concatstrings - 与[]byte转换:
slicebytetostring和stringtoslicebyte
- 拼接:
3. 数组(array)
数组结构:
type arrayHeader struct {
Data uintptr // 数据指针
Len int // 长度
}
- 占两个机器字
- 存储位置:
- 元素少:栈上
- 元素多:数据区
- 返回时:堆上(
runtime.newobject)
4. 切片(slice)
切片结构:
type SliceHeader struct {
Data uintptr // 数据指针
Len int // 当前长度
Cap int // 容量
}
- 占三个机器字
- 相关函数:
growslice(扩容)
5. 字典(map)
常见操作函数:
- 创建:
makemap,makemap_small - 读取:
mapaccess1,mapaccess2(带ok语法) - 写入:
mapassign - 遍历:
mapiterinit,mapiternext - 特定类型优化:
mapassign_faststr,mapaccess1_fast32等
6. 结构体(struct)
类型结构:
type structType struct {
rtype
pkgPath name // 包名
fields []structField // 域数组
}
type structField struct {
name name // 属性名
typ *rtype // 类型
offsetEmbed uintptr // 偏移和嵌入标志
}
- 域顺序与定义顺序一致
- 对齐规则与C一致
7. 接口(interface)
两种接口类型:
- 非空接口:
type nonEmptyInterface struct {
itab *struct {
ityp *rtype // 接口类型
typ *rtype // 实际类型
fun [100000]unsafe.Pointer // 方法表(按名称排序)
}
word unsafe.Pointer
}
- 空接口:
type emptyInterface struct {
typ *rtype // 实际类型
word unsafe.Pointer // 数据指针
}
转换函数:
convT2E: 类型转空接口convT2I: 类型转非空接口convI2I: 接口转接口assertE2I/assertI2I: 接口类型断言
三、Go语言语法特性分析
1. 对象创建
- 栈分配:优先在栈上分配
- 堆分配:指针逃逸时使用
runtime.newobject - 切片创建:
makeslice - 字典创建:
makemap,makemap_small
2. 函数与方法
-
栈空间:
- 底部:局部变量
- 顶部:函数调用参数和返回值
-
变参:
- 转换为slice,占3个参数位(ptr, len, cap)
-
匿名函数:
- 命名格式:
外部函数名_funcX - 闭包:外部变量以引用形式传入
- 命名格式:
-
方法:
- 转换为普通函数,接收者作为第一个参数
- 方法表按名称排序
-
函数反射:
- 反射使用的函数会保存完整签名信息
3. 调用约定
- 通过栈传递参数和返回值
- 调用者维护栈空间
- 参数从左到右顺序入栈(内存从下到上)
- 返回值可能在调用前初始化
4. 协程(goroutine)
- 使用
runtime.newproc创建 - 封装函数和参数
- 创建协程执行信息
5. 延迟执行(defer)
- 通过
runtime_deferproc注册 - 在当前调用栈返回前执行
- 参数:函数指针、参数大小、参数
6. CGO调用
- Go侧:
XXX_CFunc__YYY - 中间层:
NNN_cgo_abcdef123456_CFunc__ZZZ - 实际C函数
- 使用
runtime_cgocall转换状态
7. 其他特性
- 伸缩栈:
runtime·morestack扩展栈空间 - 写屏障:垃圾回收时置位标志
- panic处理:以panic开头的分支通常可忽略
四、逆向分析技巧
- 查看汇编代码的方法:
go tool compile -N -l -S file.go
go tool compile -N -l file.go; go tool objdump -gnu -s Do file.o
go build -gcflags -S file.go
- 常见模式识别:
- 字符串拼接:
concatstringN调用 - 类型转换:
convT2E/convT2I等 - 接口调用:通过itab的方法表
- 工具推荐:
- go_parser
- redress
- 注意事项:
- 栈上数据结构可能不连续
- 方法第一个参数通常是接收者
- 接口方法按名称排序而非定义顺序
五、参考资源
- 《Go二进制文件逆向分析从基础到进阶——综述》
- Go内部实现文档:https://tiancaiamao.gitbooks.io/go-internals/content/zh/
- Go标准库源码:
src/runtime和cmd/compile/internal/gc/builtin/runtime.go
通过掌握这些Go语言特性和逆向分析方法,可以更有效地分析Go语言编写的二进制程序,理解其内部数据结构和运行机制。