一、实例
在国内很多朋友都觉得golang的逆向分析很难,其实不然,和其他编程语言相比,go语言的函数太多。但是如果go语言不去符号话,则觉得和.net差不多难度(还是未混淆的)。本文就是要解决golang逆向过程中所遇到去符号化问题。
图1是没有去掉符号信息的反汇编,可以看到GOLANG运行时的各函数名 , 第二张图是去掉符号信息的反汇编,GOLANG运行时的函数名都没了(样本准备,最好是在linux下自己编译一个helloworld用于分析对比)。
图1 go语言没有去掉符号信息的反汇编
图2 go语言去掉符号信息的反汇编
二、分析
这里先感谢https://rednaga.io/2016/09/21/reversing_go_binaries_like_a_pro/链接的作者所提供的的脚本和资料(Reversing GO binaries like a pro)。其提供了golang_loader_assist.py脚本可用于还原符号信息。不过该脚本并不能直接用于windows环境下编译的go程序。在golang_loader_assist.py代码中使用了.gopclntab, .gopclntab 是一个非常重要的segments 。在windows环境下去符号化编译go程序逆向分析时不包含该segments。如图3所示:
图3 windows环境下去符号化编译go程序无.gopclntab
图4 linux下没有去除符号信息的go。
图4 含有.gopclntab
因此在他人的肩膀上,对于windows环境go语言的去符号化还原,核心就是要确认.gopclntab的位置:
1、首先旅一下golang_loader_assist.py代码的流程,之后再说windows环境下的go符号还原。
先将golang_loader_assist.py中核心代码贴出,如代码1所示:
Line 1: def renamer_init(): Line 2: renamed = 0 Line 3: gopclntab =idaapi.get_segm_by_name('.gopclntab') Line 4: ifgopclntab is not None: Line 5: # Skip unimportant header and gotosection size Line 6: addr = gopclntab.startEA + 16 Line 7: size, addr_size = create_pointer(addr) Line 8: addr += addr_size Line 9: # Unsure if this end is correct Line 10: early_end = addr + (size * addr_size *2) Line 11: while addr < early_end: Line 12: func_offset, addr_size =create_pointer(addr) Line 13: name_offset,addr_size = create_pointer(addr + addr_size) Line 14: addr += addr_size * 2 Line 15: func_name_addr = Dword(name_offset +gopclntab.startEA + addr_size) +gopclntab.startEA Line 16: func_name =GetString(func_name_addr) Line 17: MakeStr(func_name_addr,func_name_addr + len(func_name)) Line 18: appended = clean_func_name =clean_function_name(func_name) Line 19: debug('Going to remap function at0x%x with %s - cleaned up as %s' % (func_offset, func_name, clean_func_name)) Line 20: ifidaapi.get_func_name(func_offset) is not None: Line 21: if MakeName(func_offset,clean_func_name): Line 22: renamed += 1 Line 23: else: Line 24: error('clean_func_nameerror %s' % clean_func_name) Line 25: return renamed |
---|
|
代码 1 golang_loader_assist.py中核心代码
如图4所示:.gopclntab的start地址为0813a140,下面为程序单步计算过程。
由line 6可以看到addr = gopclntab.startEA + 16的位置的值是3fcc;
由line 13可知name_offset =3fcc
由line 15可知 name_offset + gopclntab.startEA+ addr_size = 0813a140+3fcc+4=0813e110
Dword(0813e110)=3ffc
func_name_addr=0813e13c
func_name ==(main.main)
图5代码1单步执行结果地址展示
那么如果已知字符串位置,反推.gopclntab是不是也合理呢,答案是一定的。
2、window 环境go语言环境去符号化还原:
IDA使用快捷键shif+f12调出字符串窗口搜索runtime或 main一类的关键字,如图6所示:
图6 ida字符串检索结果
随意点开一个字符串,如runtime.memhash_varlen,如图7 所示:
图 7 runtime.memhash_varlen字符串所在地址
runtime.memhash_varlen 的地址是004c1d8c ,图7中鼠标选择部分是该函数所在的地址,其下方20f6c 为该函数名(runtime.memhash_varlen字符串)相对于.gopclntab的偏移地址。
那么由反推.gopclntab的地址为:004c1d8c-20f6c=004a0e20 如图8 所示:
图8 .gopclntab的地址
心细的朋友可能早就发现, .gopclntab位置是包含特征码的啊!!!!!!FB FF FF FF 其实就是这么简单。
三、其它提示
a) 修改IDA的segments 新添加的一定要叫.gopclntab,之后便可以使用idapython(ida的插件)
segment name .goclntab
start address (search FB FF FF FF)
end address (.text segment 的尾部)
b)之后IDA加载golang_loader_assist.py,
i. gopclntab =ida_segment.get_segm_by_name('.gopclntab')
if gopclntab is notNone:
ii. # Skip unimportant header andgoto section size
iii. addr = gopclntab.startEA + 8
iv. size, addr_size =create_pointer(addr)
v. addr += addr_size 这里 addr = gopclntab.startEA + 8 (也有可能是+16)
c) IDA处理go语言的逆向是f5会失效,原因在于每个函数后面都跟随了runtime_morestack或者runtime_morestack_noctxt函数,因此对这两个函数直接字符串化,就可以使用f5了。
d) runtime_newproc,启动了一个协程,别跟丢。
e) runtime_makechan,go语言比较特殊的channel。
f) golang_loader_assist.py中涉及到IDApython的版本,如果不能直接使用,可将原python文件中的ida_segment .get_segm_by_name替换成idaapi.get_segm_by_name就可以了。其它地方类似将idat_segment,ida_search,ida_funcs,ida_xref换成idaapi)。
四、总结
在很多论坛里很少看到关于go去符号化还原的文章,也经常听周边的人说go语言逆向有多难,可能是被硕大的golang吓到了吧,其实国外的朋友已经解决了该问题,但是由于使用环境差异造成无法复用。
*本文作者Mworld,转载请注明来自FreeBuf.COM