逆向分析Go语言生成的可执行程序时,会发现创建对象的代码类似下面这样:
Go语言通过调用runtime_newobject函数创建对象,并通过参数描述创建什么对象。在这个样例中,这个参数保存在rax寄存器中,值是地址0x4C4CA0。
我们最想知道的是到底创建的是什么对象,但是从这个地址处的内容并不能直接获得有用的信息:
在Go源码中该参数的类型为 rtype。
type rtype struct {
size uintptr
ptrdata uintptr // number of bytes in the type that can contain pointers
hash uint32 // hash of type; avoids computation in hash tables
tflag tflag // extra type information flags
align uint8 // alignment of variable with this type
fieldAlign uint8 // alignment of struct field with this type
kind uint8 // enumeration for C
// function for comparing objects of this type
// (ptr to object A, ptr to object B) -> ==?
equal func(unsafe.Pointer, unsafe.Pointer) bool
gcdata *byte // garbage collection data
str nameOff // string form
ptrToThis typeOff // type for pointer to this type, may be zero
}
其中最重要的成员有两个。
第一个是kind,这个一个枚举值,描述对象的基础类型,比如:Boolean、integers of various lengths、arrays、maps、interfaces,在源码文件中可以找到完整列表。
另一个是nameOff,这个成员指向一个字符串结构,描述要创建的对象的类型。
我们可以在IDA中给rtype对象创建一个结构:
struct go_rtype{
UINT * size;
UINT * ptrdata;// number of bytes in the type that can contain pointers
DWORD hash // hash of type; avoids computation in hash tables
BYTE tflag; // extra type information flags
BYTE align;// alignment of variable with this type
BYTE fieldAlign; // alignment of struct field with this type
BYTE kind; // enumeration for C
void * func;
BYTE * gcdata;// garbage collection data
DWORD nameOff; // string form
DWORD typeOff; // type for pointer to this type, may be zero
}
在IDA中对runtime_newobject函数的参数应用该结构后的结果如下:
这里的nameOff成员为0x3A2E。这是相对.rdata段的偏移。我们可以通过16进制编辑器找到nameOff指示的内容。
在我的样例程序中.rdata段的文件偏移为0x9C00:
所以nameOff成员指示的内容在文件偏移0x9C00+0x3A2E=0xA062E处:
这是一个字符串结构,包含三字节字符串长度和字符串内容。我们可以看到,这里创建的是md5.digest对象。
一个Go语言程序中可能会创建数百个对象,而且runtime_newobject不是唯一使用rtype结构的函数,比如:runtime.makechan、runtime.makemap。若是人工分析每个rtype结构是很费时的。这里给大家推荐一个python脚本,AlphaGoLang。这个脚本会寻找所有参数中有rtype类型的函数,然后自动解析rtype描述的对象类型,并以注释形式标注出来:
这将给Go语言程序逆向分析带来很大方便。
欢迎关注我的微博:大雄_RE。专注软件逆向,分享最新的好文章、好工具,追踪行业大佬的研究成果。