加入收藏 | 设为首页 | 会员中心 | 我要投稿 52站长网 (https://www.52zhanzhang.com/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 服务器 > 安全 > 正文

运用Ghidra逆向分析Go二进制程序 上篇

发布时间:2021-11-13 12:17:47 所属栏目:安全 来源:互联网
导读:Go(又称Golang)是Google公司于2007年设计的一种开源编程语言,并于2012年向公众开放。多年来,它在开发者中广受欢迎,但它并不总是被用于善意的用途。正如经常发生的那样,它也吸引了恶意软件开发者的注意。 对于恶意软件开发者来说,使用Go语言是一个诱人的
Go(又称Golang)是Google公司于2007年设计的一种开源编程语言,并于2012年向公众开放。多年来,它在开发者中广受欢迎,但它并不总是被用于“善意”的用途。正如经常发生的那样,它也吸引了恶意软件开发者的注意。
 
对于恶意软件开发者来说,使用Go语言是一个诱人的选择,因为它支持交叉编译,也就是说,可以把Go语言编写的代码编译成在不同操作系统上运行的二进制文件。这样的话,就能够让攻击者的生活变得更加轻松,因为他们不必为每个目标环境开发和维护不同的代码库了,岂不快哉。
 
对Go二进制程序进行逆向分析的必要性
 
由于Go编程语言的某些特性的原因,逆向工程师在处理Go二进制文件时通常会遇到许多阻力。尽管目前的逆向分析工具(例如反汇编器)可以很好地分析非常流行的语言(例如C、C++、.NET)编写的二进制文件,但是Go语言却带来了新的挑战,使得分析工作变得更加繁琐。
 
Go二进制文件通常是静态链接的,这意味着所有必要的库都包含在编译后的二进制文件中。这会导致二进制文件的块头变大,从而使得恶意软件的分发对攻击者来说更加困难。另一方面,一些安全产品在处理大文件时也存在问题。这意味着大型二进制文件可以帮助恶意软件避开检测。静态链接的二进制文件对攻击者的另一个好处是,恶意软件可以直接在目标系统上运行,而不会遇到依赖问题。
 
当我们看到用Go编写的恶意软件持续增长,并预计会出现更多的恶意软件家族时,我们决定更深入地研究Go编程语言,并增强我们的工具集,以便更有效地调查Go恶意软件。
 
在本文中,我将讨论逆向工程师在分析Go二进制代码的过程中所面临的两个难题,以及相应的解决方案。
 
Ghidra是美国国家安全局开发的一个开源逆向分析工具,我们经常使用它来进行恶意软件的静态分析。我们可以为Ghidra创建自定义脚本和插件,以按需实现特定的功能。在这里,我们将利用Ghidra的这个特性,通过创建自定义的脚本来帮助我们分析Go二进制程序。
 
本文讨论的主题是在Hacktivity2020在线会议上公布的,相关的幻灯片和其他材料可以在我们的Github存储库中下载。
 
剥离型二进制代码中丢失的函数名
 
实际上,我们面对的第一个问题并不是Go二进制文件所特有的,而是所有剥离型二进制代码(stripped binaries,译者注:就是去掉调试信息后的二进制代码)所共同面对的一个问题。实际上,编译后的可执行文件是可以包含调试符号的,这能让调试和分析工作变得更加容易。当分析人员逆向分析带有调试信息的二进制代码时,他们不仅可以看到内存地址,还可以看到函数和变量的名称。然而,恶意软件作者通常在编译代码时剥离这些调试信息,从而创建所谓的剥离型二进制代码。他们这样做的目的有两个,一是为了减小文件的大小,二是增加逆向分析的难度。在使用剥离型二进制文件时,分析人员无法依赖函数名来帮助他们在代码中找到自己感兴趣的函数。在处理使用静态链接的Go二进制文件(其中包含所有必需的库)时,逆向分析的过程会显著减慢。
 
为了说明这个问题,我们将分别通过C语言和Go语言编写一个简单的“Hello Hacktivity”示例代码,并将它们编译成剥离型的二进制代码。在这里,请大家注意两个可执行文件在大小方面的差异。
 
利用Ghidra逆向分析Go二进制程序(上篇)
Ghidra的Functions窗口列出了二进制文件中已经定义的所有函数。在非剥离型的编译版本中,函数名称都会显示出来,这对逆向工程师来说具有很大的帮助。
 
这些例子清楚地表明,即使像“hello world”这样简单的G0程序的二进制代码,它们的体积也是非常庞大的:竟然含有一千多个函数。而在剥离型的二进制版本中,逆向工程师则无法依靠函数名来进行辅助分析。
 
注:由于剥离了调试信息,不仅函数名消失了,Ghidra也只能识别出1790个函数中的1139个。
 
我们感兴趣的是,是否有办法恢复剥离型二进制文件中的函数名。首先,我们运行了一个简单的字符串搜索来检查二进制文件中是否还有函数名。在C语言的例子中,我们找到了函数“main”,而在Go语言的例子中找到的则是“main.main”。
 
我们可以看到,虽然strings工具无法在C语言的剥离型二进制文件中找到函数名,但是,我们却可以在Go语言的剥离型二进制文件中找到字符串“main.main”。这个发现给我们带来了一丝希望,即在剥离型的Go二进制文件中可以恢复函数名。
 
实际上,将二进制文件加载到Ghidra中,然后搜索“main.main”字符串,就可以看到它的确切位置。如下图所示,函数名字符串位于.gopclntab段。
 
众所周知,从Go 1.2开始,就开始提供pclntab结构体了,并且提供了详尽的说明文档。该结构体以一个魔力值开头,后面是架构信息,再往后,是函数符号表,用于保存二进制代码中的函数信息,每个函数的入口点地址后面是函数元数据表。
 
在函数元数据表中,除其他重要信息外,还存储了函数名称的偏移量。
也就是说,我们可以通过这些信息来恢复函数名。为此,我们的团队为Ghidra创建了一个脚本(go_func.py),通过执行以下步骤来恢复剥离型Go ELF文件中的函数名:
 
找到pclntab结构体
 提取函数地址
 查找函数名偏移量
执行我们的脚本后,不仅可以恢复函数名,而且还可以定义以前未被识别的函数。
 
 
接下来,我们将以真实世界中的样本(eCh0raix勒索软件)为例,来展示该脚本的威力:
 
这个例子展示了函数名恢复脚本在逆向工程中所带来的巨大帮助:安全分析师只需瞄一眼函数名,就可以判断出当前处理的是一个勒索软件。
 
注意:在Windows Go二进制文件中,并没有专门为pclntab结构体提供相应的段,因此,研究人员需要显式地搜索该结构体的相关字段(如魔力值、可能的字段值)。对于macOS系统来说,_gopclntab段是可用的,类似于Linux二进制文件中的.gopclntab段。
 
挑战:未定义的函数名字符串
 
如果一个函数名字符串没有被Ghidra定义,那么函数名恢复脚本将无法重命名该特定函数,因为它无法在给定位置找到函数名字符串。为了解决这个问题,我们的脚本总是检查函数名地址是否有定义的数据类型,如果没有,则尝试在重命名函数之前在给定的地址定义一个字符串数据类型。
 
在下面的例子中,eCh0raix勒索软件样本中并没有定义函数名字符串“log.New”,所以在没有事先创建字符串的情况下,是无法重命名相应的函数的。

(编辑:52站长网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    热点阅读