利用Ghidra逆向分析Go二进制程序(下篇)

开发 前端
在本文中,我将继续为读者介绍逆向工程师在分析Go二进制代码的过程中所面临的两个难题,以及相应的解决方案。

[[347506]]

动态分配字符串结构

在第一种情况下,字符串结构是在运行时创建的,为此,需要使用一系列汇编指令在字符串操作之前设置相应的结构。由于指令集的不同,不同的架构之间的结构也是不同的。让我们通过几个案例,来展示我们的脚本(find_dynamic_strings.py)寻找的指令序列。

x86架构下字符串结构的动态分配

首先,我们先来看看“Hello Hacktivity”这个例子。

 

利用Ghidra逆向分析Go二进制程序(下篇)

 图20 hello_go中字符串结构的动态分配情况

利用Ghidra逆向分析Go二进制程序(下篇)

 图21 hello_go中未定义的“hello, hacktivity”字符串

运行脚本后,代码是这样的:

 利用Ghidra逆向分析Go二进制程序(下篇)

 图22 执行find_dynamic_strings.py后,hello_go中动态分配的字符串结构

可以看到,该字符串已经被定义:

 利用Ghidra逆向分析Go二进制程序(下篇)

 图23 hello_go中已经定义了“hello hacktivity”字符串

同时,字符串“hacktivity”也可以在Ghidra的Defined Strings视图中找到。

 利用Ghidra逆向分析Go二进制程序(下篇)

 图24 通过"hacktivity"过滤在hello_go中已定义的字符串

实验证明,我们的脚本能够在32位和64位x86二进制文件中寻找以下指令序列:

 利用Ghidra逆向分析Go二进制程序(下篇)

 图25 eCh0raix字符串结构的动态分配

 利用Ghidra逆向分析Go二进制程序(下篇)

 图26 hello_go中动态分配的字符串结构

ARM架构下字符串的动态分配

对于32位ARM架构,我们将以eCh0raix勒索软件样本为例来说明字符串的恢复方法。

 利用Ghidra逆向分析Go二进制程序(下篇)

 图27 eCh0raix中字符串结构的动态分配

 利用Ghidra逆向分析Go二进制程序(下篇)

 图28 eCh0raix中指向字符串地址的指针

 利用Ghidra逆向分析Go二进制程序(下篇)

 图29 eCh0raix中未定义的字符串

执行脚本后,代码将变成下面的样子:

 利用Ghidra逆向分析Go二进制程序(下篇)

 图30 执行find_dynamic_strings.py后,eCh0raix中动态分配字符串结构

我们可以看到,指针已经被重新命名,并定义了字符串:

 利用Ghidra逆向分析Go二进制程序(下篇)

 图31 执行find_dynamic_strings.py后,eCh0raix中指向字符串地址的指针

 利用Ghidra逆向分析Go二进制程序(下篇)

 图32 执行find_dynamic_strings.py后,eCh0raix中定义的字符串

该脚本在32位ARM二进制文件中查找可以下指令序列:

 利用Ghidra逆向分析Go二进制程序(下篇)

对于64位ARM架构,我们将通过一个Kaiji样本来演示字符串的恢复方法。在这里,代码使用了两个指令序列,但是只在一个序列中发生了变化:

 利用Ghidra逆向分析Go二进制程序(下篇)

 图33 Kaiji中字符串结构的动态分配

执行脚本后,代码将变为:

 利用Ghidra逆向分析Go二进制程序(下篇)

 图34 执行find_dynamic_strings.py后,Kaiji中字符串结构的动态分配情况

我们可以看到,这些字符串已经被定义:

 利用Ghidra逆向分析Go二进制程序(下篇)

 图35 执行find_dynamic_strings.py后,Kaiji中定义的字符串

该脚本能够在64位ARM二进制文件中找到以下指令序列:

 

 如您所见,该脚本可以恢复动态分配的字符串结构。这非常有助于逆向工程师阅读汇编代码,或在Ghidra中的Defined String视图中寻找可疑的字符串。

这种方法所面临的挑战

这种方法最大的缺点是,每种架构(甚至同一架构内的不同解决方案)都需要在脚本中添加一个新的分支。而且,规避这些预定义的指令集是很容易的。在下面的例子中,对于Kaiji 64位ARM恶意软件样本来说,由于字符串的长度被移到了一个寄存器中,而脚本却没有预料到这一点,因此会漏掉这个字符串。

 利用Ghidra逆向分析Go二进制程序(下篇)

 图36 Kaiji以不寻常的方式动态分配字符串结构

 利用Ghidra逆向分析Go二进制程序(下篇)

 图37 Kaiji中一个未定义的字符串

静态分配字符串结构

在接下来的这个案例中,我们的脚本(find_static_strings.py)用于查找静态分配的字符串结构。这意味着字符串指针后面是字符串长度。

这就是x86 eCh0raix勒索软件样本中找到的字符串指针及其长度:

 利用Ghidra逆向分析Go二进制程序(下篇)

 图38 eCh0raix中静态分配的字符串结构

在上图中,字符串指针后面是字符串长度值,然而,Ghidra无法区分地址和整数数据类型,但是代码中直接引用的第一个指针除外。

 利用Ghidra逆向分析Go二进制程序(下篇)

 图39 eCh0raix中的字符串指针

未定义的字符串可以通过字符串地址找到:

 利用Ghidra逆向分析Go二进制程序(下篇)

 图40 eCh0raix中未定义的字符串

执行该脚本后,将定义字符串地址、字符串长度值和字符串本身:

 利用Ghidra逆向分析Go二进制程序(下篇)

 图41 执行find_static_strings.py后,eCh0raix中静态分配的字符串结构

 利用Ghidra逆向分析Go二进制程序(下篇)

 图42 执行find_static_strings.py后,eCh0raix中定义的字符串

挑战:消除误报和字符串遗漏

我们希望消除误报,为此,我们需要:

  •  限制字符串的长度
  •  搜索可打印字符
  •  在二进制文件的数据段进行搜索

很明显,由于这些限制,字符串很容易成为漏网之鱼。如果你使用这个脚本,请随意试验:不停改变这些值,以找到最佳设置。其中,以下代码用于限制长度和字符集:

 利用Ghidra逆向分析Go二进制程序(下篇)

 图43 find_static_strings.py.

 利用Ghidra逆向分析Go二进制程序(下篇)

 图44 find_static_strings.py

字符串恢复所面临的进一步挑战

Ghidra的自动分析可能会错误地识别某些数据类型。如果发生这种情况,我们的脚本将无法在该特定位置创建正确的数据。为了解决这个问题,必须先删除不正确的数据类型,然后才能创建新的数据类型。

例如,先我们来看看eCh0riax勒索软件中静态分配的字符串结构。

 利用Ghidra逆向分析Go二进制程序(下篇)

 图45 eCh0raix中静态分配的字符串结构

在这里,地址的识别是正确的,但是,字符串长度值(应该是整数数据类型)被错误地识别为未定义的值。

在我们的脚本中,以下几行代码用于删除不正确的数据类型:

 利用Ghidra逆向分析Go二进制程序(下篇)

 图46 find_static_strings.py

执行该脚本后,不仅所有的数据类型都被正确识别出来了,而且所有字符串也被定义了:

 利用Ghidra逆向分析Go二进制程序(下篇)

 图47 执行find_static_strings.py后,eCh0raix中字符串结构的静态分配情况

另一个问题来自于这样一个事实:在Go二进制文件中,字符串将被串联并存储到一个大的字符串blob中。在某些情况下,Ghidra会将整个blob定义为单个字符串。这些可以通过大量的offcut引用来识别。Offcut引用是对已定义字符串的某些部分的引用,不是对字符串起始地址的引用——注意,它是对字符串内部的某个位置的引用。

下面的内容来自ARM Kaiji样本:

 利用Ghidra逆向分析Go二进制程序(下篇)

 图48 Ghidra错误定义的字符串

 利用Ghidra逆向分析Go二进制程序(下篇)

 图49 Kaiji对错误定义的字符串的offcut引用

要找到错误定义的字符串,可以使用Ghidra中的Defined Strings窗口,按照offcut引用数对字符串进行排序。在执行字符串恢复脚本之前,可以手动取消对具有大量offcut引用的大型字符串的定义。这样,脚本就可以成功地创建正确的字符串数据类型。

 利用Ghidra逆向分析Go二进制程序(下篇)

 图50 Kaiji中定义的字符串

一旦通过手动方式或通过我们的脚本成功定义了一个字符串,它就能够在Ghidra的列表视图中正确的显示出来,从而帮助逆向工程师顺利阅读汇编代码。但是,Ghidra中的反编译器视图无法正确处理固定长度的字符串,并且,无论字符串的长度如何,它都会显示所有内容,直到找到空字符为止。幸运的是,这个问题将在Ghidra(9.2)的下一个版本中得到解决。

下面,我们以eCh0raix样本为例来说明这个软件问题:

 利用Ghidra逆向分析Go二进制程序(下篇)

 图51 eCh0raix显示在Listing视图中的已定义字符串

 利用Ghidra逆向分析Go二进制程序(下篇)

 图52 eCh0raix显示在Decompile视图中的已定义字符串

小结

本文重点探讨了逆向分析Go二进制文件时所面临的两个难题的解决方法,以帮助逆向工程师使用Ghidra对使用Go编写的恶意软件进行静态分析。具体来说,我们首先讨论了如何恢复剥离型Go二进制文件中的函数名,并提出了几种在Ghidra中定义字符串的解决方案。我们创建的脚本和本文中的例子所使用的文件都是公开的,大家可以通过下面的链接找到它们。

实际上,这只是在Go二进制程序的逆向之旅中迈出的一小步。接下来,我们计划深入研究Go函数的调用约定和类型系统。

在Go二进制代码中,参数和返回值是通过栈而不是寄存器传递给函数的,而Ghidra目前很难正确检测到这些内容。因此,帮助Ghidra支持Go的调用约定将有助于逆向工程师理解所分析的函数的用途。

另一个有趣的话题是Go二进制文件中的类型。正如我们从被调查的文件中提取函数名称所显示的那样,Go二进制文件也存储有关所用类型的信息。恢复这些类型对逆向工程有很大的帮助。在下面的例子中,我们恢复了一个eCh0raix勒索软件样本中的main.Info结构体。这个结构体能够告诉我们,恶意软件希望从C2服务器得到哪些信息。

 利用Ghidra逆向分析Go二进制程序(下篇)

 图53 eCh0raix中的main.info结构体

 利用Ghidra逆向分析Go二进制程序(下篇)

 图54 eCh0raix中的main.info字段

 利用Ghidra逆向分析Go二进制程序(下篇)

 图55 eCh0raix中的main.info结构体

正如你所看到的,从逆向工程的角度来看,在Go二进制代码中还有很多有趣的地方有待考察,对此感兴趣的读者,请关注我们的下一篇文章。

保存本文中所用脚本和其他材料的Github仓库的地址如下所示:

  •  https://github.com/getCUJO/ThreatIntel/tree/master/Scripts/Ghidra
  •  https://github.com/getCUJO/ThreatIntel/tree/master/Research_materials/Golang_reversing

本文所使用的相关文件:

File name SHA-256

[1] hello.c ab84ee5bcc6507d870fdbb6597bed13f858bbe322dc566522723fd8669a6d073

[2] hello.go 2f6f6b83179a239c5ed63cccf5082d0336b9a86ed93dcf0e03634c8e1ba8389b

[3] hello_c efe3a095cea591fe9f36b6dd8f67bd8e043c92678f479582f61aabf5428e4fc4

[4] hello_c_strip 95bca2d8795243af30c3c00922240d85385ee2c6e161d242ec37fa986b423726

[5] hello_go 4d18f9824fe6c1ce28f93af6d12bdb290633905a34678009505d216bf744ecb3

[6] hello_go_strip 45a338dfddf59b3fd229ddd5822bc44e0d4a036f570b7eaa8a32958222af2be2

[7] hello_go.exe 5ab9ab9ca2abf03199516285b4fc81e2884342211bf0b88b7684f87e61538c4d

[8] hello_go_strip.exe ca487812de31a5b74b3e43f399cb58d6bd6d8c422a4009788f22ed4bd4fd936c

[9] eCh0raix – x86 154dea7cace3d58c0ceccb5a3b8d7e0347674a0e76daffa9fa53578c036d9357

[10] eCh0raix – ARM 3d7ebe73319a3435293838296fbb86c2e920fd0ccc9169285cc2c4d7fa3f120d

[11] Kaiji – x86_64 f4a64ab3ffc0b4a94fd07a55565f24915b7a1aaec58454df5e47d8f8a2eec22a

[12] Kaiji – ARM 3e68118ad46b9eb64063b259fca5f6682c5c2cb18fd9a4e7d97969226b2e6fb4

参考资料

  • https://rednaga.io/2016/09/21/reversing_go_binaries_like_a_pro/
  •  https://2016.zeronights.ru/wp-content/uploads/2016/12/GO_Zaytsev.pdf
  •  https://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.pdf
  • https://github.com/radareorg/r2con2020/blob/master/day2/r2_Gophers-· AnalysisOfGoBinariesWithRadare2.pdf

相关工具

IDA Pro

  •  https://github.com/sibears/IDAGolangHelper
  •  https://github.com/strazzere/golang_loader_assist

radare2/Cutter

  •  https://github.com/f0rki/r2-go-helpers
  •  https://github.com/JacobPimental/r2-gohelper/blob/master/golang_helper.py
  •  https://github.com/CarveSystems/gostringsr2

Binary Ninja

  •  https://github.com/f0rki/bn-goloader

Ghidra

  •  https://github.com/felberj/gotools
  •  https://github.com/ghidraninja/ghidra_scripts/blob/master/golang_renamer.py

 

本文翻译自:https://cujo.com/reverse-engineering-go-binaries-with-ghidra如若转载,请注明原文地址。

 

责任编辑:姜华 来源: 嘶吼网
相关推荐

2020-10-19 11:35:47

Ghidra逆向分析G

2020-05-22 18:00:26

Go二进制文件编程语言

2021-01-14 09:40:54

漏洞macOS属性表文件

2024-10-30 09:50:51

WebGo语言

2009-02-27 09:37:33

Google二进制代码

2018-10-22 14:37:16

二进制数据存储

2022-10-31 08:02:42

二进制计算乘法

2020-05-06 09:51:37

二进制Linux命令行工具

2009-12-16 10:49:42

Ruby操作二进制文件

2017-04-11 10:48:53

JS二进制

2022-07-26 13:00:01

安全符号源代码

2009-08-12 18:06:53

C#读取二进制文件

2010-06-09 13:02:29

MySQL启用二进制日

2010-10-13 15:45:23

MySQL二进制日志

2021-02-01 15:11:08

Radare2开源开源工具

2013-04-28 15:37:35

JBoss

2011-05-25 14:10:38

浮点数

2021-11-10 09:15:00

CPU01 二进制Linux

2013-07-29 11:19:16

iOS开发iOS开发学习FMDB更新二进制图片

2024-02-01 09:04:12

点赞
收藏

51CTO技术栈公众号