字符串结构的动态分配第一种情况,字符串结构是在运行时创建的,为此,在字符串操作之前使用一系列汇编指令进行设置相应的结构。由于指令集的不同,不同体系结构之间的结构也不同。让我们通过几个示例来展示我们的脚本(find_dynamic_strings.py)查找的指令序列。x86架构下字符串结构的动态分配首先我们来看一下“HelloHacktivity”的例子。图20hello_go中字符串结构的动态分配图21hello_go中未定义的“hello,hacktivity”字符串运行脚本后,代码如下所示:图22执行find_dynamic_strings.py后,hello_go中动态分配的字符串结构可以看到字符串已经被定义:图23中的hello_go中已经定义了字符串“hellohacktivity”。同时在Ghidra的DefinedStrings视图中也可以找到字符串“hacktivity”。图24通过“hacktivity”过滤hello_go中定义的字符串实验证明,我们的脚本可以在32位和64位x86二进制文件中找到以下指令序列:图25eCh0raix字符串结构的动态分配图26在hello_go中动态分配的字符串结构ARM架构下字符串的动态分配针对32位ARM架构,我们以eCh0raix勒索样本为例,说明字符串恢复方法。图27eCh0raix中字符串结构的动态分配图28eCh0raix中字符串地址的指针图29eCh0raix中未定义的字符串执行脚本后,代码如下所示:图30执行find_dynamic_strings.py后,eCh0raix指针已经重命名,定义了字符串:图31执行find_dynamic_strings.py后,指针指向eCh0raix中字符串的地址图32执行find_dynamic_strings.py后,eCh0raix中的定义脚本在32位ARM二进制:对于64位ARM架构,我们将使用一个开机示例来演示字符串的恢复方法。这里,代码使用了两个指令序列,但只改变了一个序列:图33Kaiji中字符串结构的动态分配执行脚本后,代码变为:图34执行find_dynamic_strings.py后,Kaiji动态分配字符串结构我们可以看到已经定义了这些字符串:图35执行find_dynamic_strings.py后,Kaiji中定义的字符串这个脚本可以在64位ARM二进制文件中找到如下指令序列:如你所见,脚本可以动态恢复分配的字符串结构。这对于逆向工程师阅读汇编代码,或在Ghidra的定义字符串视图中查找可疑字符串非常有帮助。这种方法的挑战这种方法的最大缺点是每个架构(即使是同一架构中的不同解决方案)都需要向脚本添加一个新的分支。此外,很容易绕过这些预定义的指令集。在下面的示例中,对于Kaiji64位ARM恶意软件示例,由于字符串的长度被移入寄存器而丢失了字符串,这是脚本没有预料到的。图36Kaiji以一种不寻常的方式动态分配字符串结构图37Kaiji中未定义的字符串静态分配的字符串结构在下面的示例中,我们的脚本(find_static_strings.py)用于查找字符串结构。这意味着字符串指针后跟字符串长度。这是在x86eCh0raix勒索软件样本中找到的字符串指针及其长度:图38eCh0raix中静态分配的字符串结构在上图中,字符串指针后面是字符串长度值,但是Ghidra无法区分地址和整数数据类型,除了在代码中直接引用的第一个指针。图39eCh0raix中的字符串指针通过字符串地址可以找到未定义的字符串:图40eCh0raix中未定义的字符串执行该脚本后,将定义字符串地址、字符串长度值和字符串本身:图41执行find_static_strings后.py,eCh0raix中静态分配的字符串结构图42执行find_static_strings.py后,eCh0raix中定义的字符串Challenge:Eliminatefalsepositivesandstringomissions我们要消除误报。为此,我们需要:限制字符串的长度搜索可打印字符在二进制文件的数据段中搜索显然,由于这些限制,字符串很容易从裂缝中溜走。如果您使用此脚本,请随意尝试:不断更改这些值以找到最佳设置。其中,以下代码用于限制长度和字符集:图43find_static_strings.py。图44find_static_strings.py字符串恢复的进一步挑战Ghidra的自动分析可能会错误识别某些数据类型。如果发生这种情况,我们的脚本将无法在该特定位置创建正确的数据。要解决此问题,必须先删除不正确的数据类型,然后才能创建新的数据类型。例如,我们先来看一下eCh0riax勒索软件中静态分配的字符串结构。图45eCh0raix中静态分配的字符串结构这里,地址的标识是正确的,但是,字符串长度值(应该是整数数据类型)被错误地标识为未定义值。在我们的脚本中,使用以下代码行来删除不正确的数据类型:图46find_static_strings.py执行脚本后,不仅正确识别了所有数据类型,而且还定义了所有字符串:图47字符串结构的静态分配在执行find_static_strings.py后的eCh0raix中另一个问题来自于在Go二进制文件中,字符串将被连接并存储到一个大字符串blob中。在某些情况下,Ghidra会将整个blob定义为单个字符串。这些可以通过大量的边角料参考来识别。offcut引用是对已定义字符串的某些部分的引用,而不是对字符串起始地址的引用——请注意,它是对字符串内部特定位置的引用。以下内容来自ARMKaiji示例:图48Ghidra错误定义的字符串图49Kaiji对错误定义字符串的offcutreference要查找错误定义的字符串,可以使用Ghidra中的DefinedStrings窗口,根据offcutreferences的数量字符串被排序。在执行字符串恢复脚本之前,可以手动取消定义具有大量切断引用的大字符串。这样,脚本就可以成功创建正确的字符串数据类型。图50在Kaiji中定义的字符串一旦手动或通过我们的脚本成功定义了一个字符串,它就可以在Ghidra的列表视图中正确显示,帮助逆向工程师顺利读取汇编代码。但是,Ghidra中的反编译器视图无法正确处理固定长度的字符串,并且无论字符串的长度如何,它都会显示所有内容,直到找到空字符。幸运的是,这个问题将在下一个版本的Ghidra(9.2)中得到修复。下面,我们以eCh0raix样本为例来说明这个软件问题:图51eCh0raix在Listing视图中显示定义的字符串图52eCh0raix在Decompile视图中显示定义的字符串总结本文重点介绍Go二进制文件的逆向分析解决逆向工程师在使用Ghidra对Go编写的恶意软件进行静态分析时面临的两个难题。具体来说,我们首先讨论如何在剥离的Go二进制文件中恢复函数名称,并提出几种在Ghidra中定义字符串的解决方案。我们创建的脚本和本文示例中使用的文件是公开可用的,可以通过下面的链接找到。事实上,这只是Go二进制文件逆向之旅的一小步。接下来,我们计划深入研究Go函数的调用约定和类型系统。在Go二进制文件中,参数和返回值是通过堆栈而不是寄存器传递给函数的,Ghidra目前很难正确检测到这些。因此,帮助Ghidra支持Go的调用约定,将有助于逆向工程师理解分析函数的目的。另一个有趣的主题是Go二进制文件中的类型。Go二进制文件还存储有关所用类型的信息,正如我们通过从调查文件中提取函数名称所示的那样。恢复这些类型对逆向工程有很大的帮助。在下面的示例中,我们恢复了eCh0raix勒索软件样本的main.Info结构。这个结构可以告诉我们恶意软件期望从C2服务器获得什么信息。图53eCh0raix中的main.info结构图54eCh0raix中的main.info字段图55eCh0raix中的main.info结构可以看到,从逆向工程的角度来看,还有很多值得研究的地方,各位读者对此感兴趣的朋友请关注我们的下一篇文章。本文中使用的脚本和其他资料的Github存储库地址如下:https://github.com/getCUJO/ThreatIntel/tree/master/Scripts/Ghidrahttps://github.com/getCUJO/ThreatIntel/tree/master/Research_materials/Golang_reversing本文所使用的相关文件:FilenameSHA-256[1]hello.cab84ee5bcc6507d870fdbb6597bed13f858bbe322dc566522723fd8669a6d073[2]hello.go2f6f6b83179a239c5ed63cccf5082d0336b9a86ed93dcf0e03634c8e1ba8389b[3]hello_cefe3a095cea591fe9f36b6dd8f67bd8e043c92678f479582f61aabf5428e4fc4[4]hello_c_strip95bca2d8795243af30c3c00922240d85385ee2c6e161d242ec37fa986b423726[5]hello_go4d18f9824fe6c1ce28f93af6d12bdb290633905a34678009505d216bf744ecb3[6]hello_go_strip45a338dfddf59b3fd229ddd5822bc44e0d4a036f570b7eaa8a32958222af2be2[7]hello_go.exe5ab9ab9ca2abf03199516285b4fc81e2884342211bf0b88b7684f87e61538c4d[8]hello_go_strip.execa487812de31a5b74b3e43f399cb58d6bd6d8c422a4009788f22ed4bd4fd936c[9]eCh0raix–x86154dea7cace3d58c0ceccb5a3b8d7e0347674a0e76daffa9fa53578c036d9357[10]eCh0raix–ARM3d7ebe73319a3435293838296fbb86c2e920fd0ccc9169285cc2c4d7fa3f120d[11]Kaiji–x86_64f4a64ab3ffc0b4a94fd07a55565f24915b7a1aaec58454df5e47d8f8a2eec22a[12]Kaiji–ARM3e68118ad46b9eb64063b259fca5f6682c5c2cb18fd9a4e7d97969226b2e6fb4参考资料https://rednaga.io/2016/09/21/reversing_go_binaries_like_a_pro/https://2016.zeronights.ru/wp-content/uploads/2016/12/GO_Zaytsev.pdfhttps://carvesystems.com/news/reverse-engineering-go-binaries-using-radare-2-and-python/https://www.pnfsoftware.com/blog/analyzing-golang-executables/https://github.com/strazzere/golang_loader_assist/blob/master/Bsides-GO-Forth-And-Reverse.pdfhttps://github.com/radareorg/r2con2020/blob/master/day2/r2_Gophers-·AnalysisOfGoBinariesWithRadare2.pdf相关工具IDAProhttps://github.com/sibears/IDAGolangHelperhttps://github.com/strazzere/golang_loader_assistradare2/Cutterhttps://github.com/f0rki/r2-go-helpershttps://github.com/sibears/IDAGolangHelper//github.com/f0rki/r2-go-helpershttps//github.com/JacobPimental/r2-gohelper/blob/master/golang_helper.pyhttps://github.com/CarveSystems/gostringsr2BinaryNinjahttps://github.com/f0rki/bn-goloaderGhidrahttps://github.com/felberj/gotoolshttps://github.com/ghidraninja/ghidra_scripts/blob/master/golang_renamer.py本文翻译自:https://cujo.com/reverse-engineering-go-binaries-with-ghidra如有转载,转载请注明原文地址
