除了OD和IDA,你还用过什么逆向工具?

  • A+

译文声明
本文是翻译文章,文章原作者Itay Cohen,文章来源:https://www.megabeets.net
原文地址:https://www.megabeets.net/decrypting-dropshot-with-radare2-and-cutter-part-1/#Strings

前言

作为一名合格的逆向工程师和恶意软件研究人员,一款顺手的工具显得尤为重要(译者同感)。我不惜花费大量时间去搭建最佳的恶意软件分析环境,并为自己找寻最得力的工具。在过去的两年,我最常使用的工具是radare2,例如自动化RE辅助分析,脚本编写,打CTF,漏洞利用等等。但是我从来没有使用radare2进行恶意软件分析的工作,更准确的说,从未使用radare2进行Windows恶意软件分析。究其原因,radare2是命令行界面感觉过于笨拙,操作也复杂。相比较而言IDA Pro更适合这些任务,可以快速查看函数,数据结构,给函数或者变量重命名,添加注释等等。直到后来,我遇到了Cutter。

1.png

Cutter

多年来,radare2社区一直尝试为r2开发不同的图形界面。Cutter是基于QT C++的radare2图形用户界面。在我看来,这是radare2本应拥有的GUI。引用Github页面的描述

Cutter并不是针对现有的radare2用户。相反,它是针对不喜欢CLI应用或者使用有困难的用户。

Cutter是一个年轻的项目,它是radare2的官方GUI(第一个也是唯一一个被宣布为官方的GUI)。Cutter是一个跨平台的GUI,旨在将radare2的大量功能导出到用户友好的现代GUI中。

Cutter适用于所有平台(Linux,OS X,Windows)。您可以在此处下载最新版本 。如果您使用的是Linux,可以直接使用AppImage文件。

如果要使用最新版本,获取新的功能和bug修复,那么你可以从源代码构建,这并不是一个复杂的工作。
首先,您必须克隆库:
language
git clone --recurse-submodules https://github.com/radareorg/cutter
cd cutter

在Linux上构建:
./build.sh

在Windows上构建:
prepare_r2.bat
build.bat

如果以上任何方法均无效,请在此处查看更详细的说明 。

Dropshot StoneDrill

Dropshot(也称为StoneDrill),是一个与APT33组织有关的恶意软件,主要针对沙特阿拉伯。Dropshot是一个相对复杂的样本,它采用了先进的代码混淆技术,并具有许多有趣的功能。该恶意软件很可能与臭名昭著的Shamoon恶意软件相关。在本文中,我们将重点分析Dropshot是如何解密字符串,以逃避检测。

可以从此处 下载Dropshot样本(密码:infected)。

请使用此样品时要格外小心。它能擦除磁盘,清除硬盘并删除文件!请谨慎使用!

入门

通过双击其图标或者在命令行输入./Cutter来打开Cutter。在‘Open File’选项卡下,选择一个新文件并单击’open’。打开文件后,我们可以看到‘Load Options’窗口,我们可以设置radare2如何分析文件。我们还可以通过“Advanced options”选项卡来设置特定的体系结构,CPU和文件格式等等。

2.png

为了更准确地分析此样本,我修改了’level’级别,将进度条移至最右侧。然后,禁用’Autorename functions based on context (aan)’按钮 ,禁止函数的自动重命名。因为在这个示例中,aan背后的算法是用混乱的名称来重命名一些函数,这反而影响分析。

单击“OK”后,我们将看到Cutter的主窗口。您可以通过单击“View -> Preferences”,可以更改主题颜色并配置反汇编。这些小部件非常灵活方便,你可以放置在屏幕上的任何位置。您还可以通过从“Windows”菜单项中选择所需的窗口部件,并在屏幕上显示。

3.png

基本静态分析

在分析恶意软件样本时,我通常会从静态分析开始。基本的静态分析可以帮助我们整体感知样本的基本信息(确认文件是否为恶意软件,提供有关其功能的信息,)。尽管基本的静态分析简单明了而且速度快,但是对于复杂的恶意软件而言,它在很大程度上是无效的。在阅读程序代码前,让我们先浏览一些小部件。

Strings

在Strings中,我们看不到任何有趣的东西。有些字符串可能表示要删除的文件名称,例如“ C-Dlt-C-Org-T.vbs”和“ C-Dlt-C-Trsh-T.tmp”。其他字符串虽然看起来很独特,但是似乎没有什么含义,例如“Hello dear”。在其中,我们还可以看到一些熟悉的API函数和库文件名,但是没有“smoking gun”。

值得检查的另一个属性是文件的熵。
简而言之,熵(在我们的例子中)是一组给定值(数据)中随机性的度量。一个文件(或数据)的熵在不同的程序中都会以类似的方式计算。通常,它是一个介于0.0到8.0之间的数字。熵的值是一个可靠的标志,表明文件被打包、压缩或包含打包或压缩的数据。一个压缩的二进制可能有很高的熵值。有多高?有人会说6.0足够高,有人会说7.0及以上。我更喜欢将它放在中间的某个位置,并将6.8视为二进制文件或其某些组件被压缩或打包的良好标识。

通过查看Cutter的Dashboard小部件,我们可以轻松地看到Dropshot计算出的熵:

4.png

如您所见,我们的文件的熵为7.1,这是压缩打包数据的一个很好的指示。更具体地说,我们可以在Sections小部件中看到每个部分的熵:

5.png

我们可以观察到.rsrc段的熵值最高,这个段引起了我的兴趣。我们将在本系列的第二部分中对此进行介绍。

详解字符串解密过程

当我审计Dropshot代码时,我发现它其实使用了一种不是很复杂的方式来解密字符串。之所以认定该函数为解密函数,是因为该函数主要在LoadLibraryA和Getprocadaddress之前使用,并且在代码中被多次调用。这是一种动态加载库和函数以使分析复杂化的技术,恶意软件中经常使用的伎俩。

无论你是发现了它还是懒得找,解密函数位于0x4012a0,并且带有两个参数。

6.png

传递的两个参数,一个是0xb(十进制:11),第二个参数是0x41b8cc的地址。现在我们可以将fcn.004012a0重命名来简化分析。您可以点击fcn.004012a0并按Shift+N,或者右键点击并选择‘Rename fcn.004012a0’。我把它命名为‘strings_decrypter’。

7.png

接下来我们可以看到strings_decrypter ()函数的返回值(保存在eax中)还有参数1被推入fcn.004013b0()中。让我们看看这个函数:

8.png

如果eax=0,则程序会执行右分支。否则,程序执行左分支。但是程序流怎么执行,都会调用LoadLibraryA()对字符串进行解密。简而言之,程序正在加载动态链接库到内存,以便使用其中的函数。我将此函数重命名为load_ntdll_or_kernel32。让我们继续向前回溯。

当程序加载ntdll.dll或kernel32.dll后。会调用GetProcAddress(),传入被调用函数的 DLL 句柄和开始时解密的字符串。我们可以确定这个字符串就是kernel32.dll导出的API函数。

我们并不清楚调用的是哪个API函数。要解开这个疑惑,需要了解strings_decrypter 工作原理以及传入的每一个参数。

分析函数

strings_decrypter()的流程图:

9.png

该函数一共传入两个参数,一个是地址,另一个是整型数值。我们可以看到virtualloc()函数分配了arg_ch+1大小的缓冲区,然后将该缓冲区分配给local_8h。为了方便分析,我们把它重命名为buffer。

10.png
然后我们可以观察到,0被赋值给变量local_4h。然后进入了循环体,存储在arg_ch的整数被赋值给edx,我们猜测arg_ch是表示某种长度或大小的数值。然后与local_4h进行比较,这么看的话local_4h更像是一个循环索引。我们现在需要弄清变量arg_8h的内容。在代码中可以看到0x41b8cc处的值传入到了strings_decrypter 函数。让我们在Hexdump小部件中查看这个地址的数值。我们可以看到这是一个半字(2字节)的整数数组,从0x41b8cc开始,到0x0041b8e1结束。我们可以使用Cutter(屏幕右侧)的强大功能,我们可以把这个数组转换成C数组:

11.png

这确实是一个很棒的功能,对吧?Cutter可以生成不同类型的数组以简化脚本编写的工作。下面有些例子:
```language
C half-words (Little Endian):

define _BUFFER_SIZE 11

const uint16_t buffer[11] = {
0x0005, 0x0006, 0x000e, 0x0006, 0x001c, 0x0006, 0x0007, 0x000b,
0x000e, 0x0006, 0x0022};
Python:
import struct
buf = struct.pack ("22B", *[
0x05,0x00,0x06,0x00,0x0e,0x00,0x06,0x00,0x1c,0x00,0x06,
0x00,0x07,0x00,0x0b,0x00,0x0e,0x00,0x06,0x00,0x22,0x00])
Javascript:
var buffer = new Buffer("BQAGAA4ABgAcAAYABwALAA4ABgAiAA==", 'base64');
```

进入循环,eax保存循环索引,ecx保存前边提到的数组,然后把一个word字节大小的变量[ecx+eax*2]赋值给edx。接下来,缓冲区被赋值给eax,eax本身加上[local_4h]中保存的循环索引的值。这样做的目的是为缓冲区设置特定的偏移量。在0x004012ed处,我们可以看到一个字节被移动到cl。双击可以查看完整的字符串- AaCcdDeFfGhiKLlMmnNoOpPrRsSTtUuVvwWxyZz32.EbgjHI _YQB:"/@x0ax0dx1a。紧接着,cl中的字节被复制到缓冲区特定的位置。循环将循环数次,直至条件不成立。

经过这一系列混淆后,我们可以得出结论。解密函数中arg_8h参数,只是该字符串中一个偏移量的数组,而length是要构建的字符串长度。让我们通过python脚本来验证这一说法。

Cutter是一个集成的开发环境,我们不需要打开任何外部Python Shell,可以使用Cutter的Jupyter小部件。

12.png

让我们写一个概念证明脚本来正是这是解密函数的工作原理。这是python中的快速POC:
```language

The pre-defined decryption table (the string)

decryption_table = 'AaCcdDeFfGhiKLlMmnNoOpPrRsSTtUuVvwWxyZz32.EbgjHI _YQB:"/@x0ax0dx1a'

The offsets array (0x41b8cc) which is passed to the function

offsets_array = [
0x05,0x00,0x06,0x00,0x0e,0x00,0x06,0x00,0x1c,0x00,0x06,
0x00,0x07,0x00,0x0b,0x00,0x0e,0x00,0x06,0x00,0x22,0x00]

The length which is passed to the function

length = 11

decrypted_string = ''

for i in range(length):
decrypted_string += decryption_table[ offsets_array[ i*2 ] ]

print ("Decrypted: %s" % (decrypted_string))
```
在Jupyter中运行:

13.png

如图所示程序返回了一个字符串,“DeleteFileW”。这是一个API函数。所以我们有理由把最后一个参数arg_8h重命名为“offset_array”。

既然我们已经知道了strings_decrypter()的工作原理。我们可以通过查看交叉引用,来查看哪里还引用了此函数,解密了哪些字符串。请单击其名称并按键盘上的X键。Cutter还将向我们显示对该函数的每个引用的预览,这使得检查外部参照的任务更加简洁。

14.png

从图中我们可以看到数十次调用strings_decrypter,对于手动解密来说太繁琐。这就是radare2和Cutter脚本编写功能的用武之地!

编写脚本

借助r2pipe,编写radare2脚本非常容易。它是radare2的最佳编程接口。
r2pipe api基于r_core_cmd_str()后面的一个r2原语,该原语是一个函数,传入要运行的r2命令的字符串参数,并返回带有结果的字符串。

r2pipe支持许多编程语言,包括 Python, NodeJS, Rust, C等。

幸运的是,Cutter将r2pipe的python集成到其Jupyter组件中。我们将编写一个r2pipe脚本,该脚本将执行以下操作:
•为我们已知的地址声明常量和变量(解密函数,解密表)
•将解密表的内容转储到变量
•遍历对解密表的所有引用,并保存传入的参数
•手动解密字符串
•将解密的函数打印到屏幕上,并在程序集中添加内联注释

列表中的第一项是定义我们已经检测到的组件的地址:解密表和解密函数。
```language
import cutter

Declaration of decryption-table related variables

decryption_table = 0x41BA3C
decryption_table_end = 0x41BA77
decryption_table_len = decryption_table_end - decryption_table
decryption_function = 0x4012A0
```
接下来,我们需要具体分析二进制文件。首先radare2将检测外部参照和函数。aa 是radare2的基本分析命令。cutter.cmd 是一个接收radare2命令并返回其输出的函数(如果有回显的话)。
cutter.cmd('aa')

让我们继续并将转储表的内容转储到变量中。pxj用于打印hexdump,其中j可用于大多数radare2命令以获得JSON输出。cutter.cmdj 将为我们解析JSON输出。
```language

Dump the decryption table to a variable

decryption_table_content = cutter.cmdj(
"pxj %d @ %d" % (decryption_table_len, decryption_table))
```
在这段代码中,我们告诉radare2从decryption_table的地址中获取decryption_table_len。现在有了所需的数据,我们便开始遍历所有的交叉引用。

我们将使用Python for循环,遍历axtj的输出。这个命令代表分析外部引用,并列出特定地址的所有数据和代码引用。在我们的例子中,这个地址是我们的解密函数。我们在每次迭代中要做的第一件事是解析传递给解密函数的两个参数(偏移量数组和要解密的字符串的长度)。我们将使用[email protected]解析参数。pdj代表打印反汇编。将-2传递给pdj是告诉radare2在给定地址之前打印2条指令。我们假设这两个参数是在程序调用函数之前传递给函数。
```language

Iterate x-refs to the decryption function

for xref in cutter.cmdj('axtj %d' % decryption_function):
#Get the arguments passed to the decryption function: length and encrypted string
length_arg, offsets_arg = cutter.cmdj('pdj -2 @ %d' % (xref['from']))

#String variable to store the decrypted string
decrypted_string = ""

#Guard rail to avoid exception
if (not 'val' in length_arg):
    continue

最有趣的部分是解密字符串。如今我们已经知道解密的工作原理,使用for循环很容易模拟实现:language
#Manually decrypt the encrypted string
for i in range(0, length_arg['val']):
decrypted_string += chr(decryption_table_content[cutter.cmdj(
'pxj 1 @ %d' % (offsets_arg['val'] + (i*2)))[0]])
现在decypted_string 正在保存解密的字符串。我们所要做的就是将其打印到控制台上,并在每个调用中添加内联注释。命令 CC 将用于添加注释。language
#Print the decrypted and the address it was referenced to the console
print(decrypted_string + " @ " + hex(xref['from']))

#Add comments to each call of the decryption function
cutter.cmd('CC Decrypted: %s @ %d' % (decrypted_string, xref['from']))

完整的exp:language
import cutter

Declaration of decryption-table related variables

decryption_table = 0x41BA3C
decryption_table_end = 0x41BA77
decryption_table_len = decryption_table_end - decryption_table
decryption_function = 0x4012A0

cutter.cmd('aa')

Dump the decryption table to a variable

decryption_table_content = cutter.cmdj(
"pxj %d @ %d" % (decryption_table_len, decryption_table))

Iterate x-refs to the decryption function

for xref in cutter.cmdj('axtj %d' % decryption_function):
#Get the arguments passed to the decryption function: length and encrypted string
length_arg, offsets_arg = cutter.cmdj('pdj -2 @ %d' % (xref['from']))

#String variable to store the decrypted string
decrypted_string = ""

#Guard rail to avoid exception
if (not 'val' in length_arg):
    continue

#Manually decrypt the encrypted string
for i in range(0, length_arg['val']):
    decrypted_string += chr(decryption_table_content[cutter.cmdj(
        'pxj 1 @ %d' % (offsets_arg['val'] + (i*2)))[0]])

#Print the decrypted and the address it was referenced to the console
print(decrypted_string + " @ " + hex(xref['from']))

#Add comments to each call of the decryption function
cutter.cmd('CC Decrypted: %s @ %d' % (decrypted_string, xref['from']))

#Refresh the interface
cutter.refresh()

```

现在我们在Cutter内的Jupyter执行它。我们可以在Comment小部件中看到我们的脚本可以工作并更新了注释:

15.png

我们还可以在反汇编中内联查看以下注释:

16.png
太棒了!我们解密了加密的字符串并添加了内联注释以简化分析过程。最终的脚本可以在这里找到。

结语

至此,本文的第一部分已经结束了,关于用Cutter和r2pipe解密Dropshot。我们熟悉了Cutter,radare2的GUI,并在r2pipe编写了一个解密脚本。在下一节中,我们将看到如何解密Dropshot中的payload。

PS:radare2这款工具是译者打ctf时经常使用的工具,这是个开源的二进制分析工具–Radare2。它的功能非常强大,反混淆,反汇编,patch和强大的脚本执行能力等等。如果把OD和IDA称为倚天剑和屠龙刀,那这个可以叫做方天画戟,嘻嘻。后来又推出了GUI--Cutter,更是如虎添翼。

相关推荐: ThinkPHP系列漏洞之ThinkPHP 2.x 任意代码执行

ThinkPHP是一个免费开源用户数量非常多的一个PHP开发框架,这个框架曾经爆出各种RCE和SQL注入漏洞。斗哥将带来ThinkPHP各个版本的漏洞分析文章,此为第一篇从TP最早的版本开始分析。 0x00 漏洞描述 在ThinkPHP ThinkPHP 2.…