浅记Go语言逆向
一、查看版本号
go version xx.exe
二、查看地址以及依赖库
go version -m xxx.exe

mod 显示出了该程序的依赖库:luago
三、pclntab
pclntab 全名是 Program Counter Line Table,直译为 程序计数器行数映射表,也叫Runtime Symbol Table
1、pclntab结构

2、pclntab定位
第一种方法是根据二进制文件中的 Section Name 来定位
如,elf文件中方.gopclntab Section 就对应于 pclntab 结构,MachO 文件中 __gopclntab Section 对应于 pclntab 结构
但这种方法不靠谱,如果我们利用ASLR技术在编译的时候加入下面命令就不会产生.gopclntab 节
go build -buildmode=pie -ldflags “-s -w”
第二种方法就是暴力搜索 pclntab 的 Magic Number
ELF 文件中的firstmoduledata 总是在 .noptrdata 这个 Section 里,PE 文件中可能会在 .data 或 .noptrdata Section,而 MachO 文件的 firstmoduledata 在 __noptrdata Section 中,我们可以按照uintptr 为单元遍历section,检查每个uintptr 指向的地址前四个字节是否为pclntab 的 Magic Number 0xFFFFFFFB 或 0xfffffffa

3、pclntab 结构图(针对性的)

开头四字节是Magic Number: 一般为 0xFFFFFFFB 或0xFFFFFFFA
Go 1.16之前的MagicNumber是0xffffffb,之后是0xfffffffa
第五、六字节无意义
第七个字节 instruction size quantum, 1 为 x86, 4 为 ARM;
第八个字节为地址的大小,32bit 的为 4,64 bit 的为 8
这前八个字节就是pclntab的Header
第九个字节是函数表(function table)的起始位置

uintptr 代表一个指针类型,在 32bit 二进制文件中,等价于 uint32
函数表结束后,下面就是 Source file table(源码文件路径列表)

4、函数表(function table)
起始地址为pclntab的地址+8,第一个元素代表函数个数

每两个元素一组,每一组第一个元素代表函数地址,第二个元素代表函数结构体相对于pclntab的偏移
函数定义结构体:
struct Func { uintptr entry; // start pc int32 name; // name (offset to C string) int32 args; // size of arguments passed to function int32 frame; // size of function frame, including saved caller PC int32 pcsp; // pcsp table (offset to pcvalue table) int32 pcfile; // pcfile table (offset to pcvalue table) int32 pcln; // pcln table (offset to pcvalue table) int32 nfuncdata; // number of entries in funcdata list int32 npcdata; // number of entries in pcdata list }; //name 是函数名的偏移,所以存int类型
5、源码文件表
函数表结束后隔一个uintptr 的位置,就是源码文件表的偏移,长度为4字节(相对于pclntab的起始位置)
go_parser的解析如下

四、moduledata
在 Go 语言的体系中,Module 是比 Package 更高层次的概念,具体表现在一个 Module 中可以包含多个不同的 Package,而每个 Package 中可以包含多个目录和很多的源码文件。
1、moduledata结构
type moduledata struct { pclntable []byte // pclntab address ftab []functab // function table address filetab []uint32 // source file table address findfunctab uintptr minpc, maxpc uintptr // minpc: first pc(function) address text, etext uintptr // [.text] section start/end address noptrdata, enoptrdata uintptr data, edata uintptr // [.data] section start/end address bss, ebss uintptr // [.bss] section start/end address noptrbss, enoptrbss uintptr // [.noptrbss] section start/end address end, gcdata, gcbss uintptr types, etypes uintptr // types data start/end address textsectmap []textsect typelinks []int32 // offset table for types itablinks []*itab // interface table ptab []ptabEntry pluginpath string pkghashes []modulehash modulename string modulehashes []modulehash hasmain uint8 // 1 if module contains the main function, 0 otherwise gcdatamask, gcbssmask bitvector typemap map[typeOff]*_type // offset to *_rtype in previous module bad bool // module failed to load and should be ignored next *moduledata }

Go语言中定位firstmoduledata 的方法是通过找pclntab的位置来进行定位
2、moduledata 结构的迭代

五、Go语言逆向所注意的小点
1、IDA打开Go程序,会自动跳转到一个入口函数的位置
64bit PE 文件通常会自动跳转到
_rt0_amd64_windows
64bit ELF 文件,通常会自动跳转到
_rt0_amd64_linux 函数
这只不过是 Go runtime 的入口函数,而Go的入口函数是main.main()函数
2、main函数执行之前会执行 init()函数

3、11.4 runtime.growslice()切片
4、Go语言调试
网上搜 Go debug,绝大部分资料跟 dlv 调试工具和 GDB 的 Go 调试插件有关。但是这两个工具的使用有一个前提:需要有调试符号甚至 Go 项目的源码。它们是给常规的 Go 语言开发者使用的。然而我们要分析的 Go 恶意软件,绝大部分是没有调试符号的,strip 处理的很干净。
六、Go语言恢复符号
1、IDAGolangHelper插件

2、待补
七、Go语言逆向需要注意的地方
Go语言与C语言用'\0'表示字符串结束不同,Go语言会将所有字符串连接在一起,通过起始指针和字符串长度来表示整个串

浙公网安备 33010602011771号