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

admin 2021年5月10日01:32:27评论57 views字数 9421阅读31分24秒阅读模式

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

前言

在本文的第一部分,我们使用了Cutter来静态分析了Dropshot这个恶意软件的字符串解密部分。我们还使用python脚本对字符串进行了解密。
今天我们来分析恶意软件的payload。事不宜迟,让我们开始吧。

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

开始

假设您已经阅读了第一部分,已经熟悉了Cutter和r2pipe,对Dropshot有了基本的了解。在进行本次分析之前,请先在Jupyter中执行我们编写好的r2pipe脚本,并在‘Functions’小部件或上方的搜索栏中查找‘main’函数。

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

main()

如果您经常分析程序或者编程的话,main()对您并不陌生。我们可以通过查看程序的流程图,来帮我们快速找到恶意负载。我们可以看到,在0x403b30处是main()中第一个函数调用。

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

双击这一行,我们可以进入到fcn.00403b30内部。这是一个相当大的函数。在函数内我们观察到大量无意义的API函数调用。这是Dropshot狡猾的地方,使用抗仿真技术来扰乱我们的分析。

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

抗仿真

抗仿真技术是一种在恶意软件非常常用的技术,目的是欺骗仿真器。模拟器在分析中用来分析恶意软件的行为,解压缩样本和分析shellcoad。它是通过模拟目标系统的指令值,运行环境和数十种甚至上百种常用的API函数来模拟程序的工作流程。模拟器可以让恶意软件误以为是在受害用户的真实环境中执行的。

模拟器引擎模拟实际操作系统提供的API或系统调用。通常,它将从诸如user32.dll、kernel32.dll和ntdll.dll之类的动态库的调用函数。 大多数情况下,这将是一个虚拟实现,函数除了返回一个调用成功的返回值外,不会真正执行任何操作。

通过使用抗仿真技术,恶意软件作者可以欺骗通用或特定的模拟器。例如Dropshot中,通过调用不常见或者不存在的API函数来实现抗仿真的目的。我们还可以改进此技术,比如通过对某个API函数传入错误的参数(比如NULL),该函数应该在实际环境中导致访问冲突异常

有关仿真和反仿真机制的更多信息,请参阅以下书籍: The Antivirus Hacker's Handbook

为了方便分析,我们可以把fcn.00403b30函数重命名为’AntiEmulation’,或者其它你喜欢的名字。

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

在main函数调用AntiEmulation函数后,会有一个跳转。这是从Cutter的Disassembly小部件中复制的程序集:
language
| 0x004041a6 call AntiEmulation
| 0x004041ab mov eax, 1
| 0x004041b0 test eax, eax
| ,=< 0x004041b2 je 0x40429d
| | 0x004041b8 push 4

如您所见,代码永远不会跳转到0x40429d分支上。因为在之前的一条指令中,eax被赋值为1。Je跳转的条件是eax为0,很明显跳转不会发生。

我们将跳过下一个程序块,来到0x4041f9处。在此块中,Dropshot使用CreateDialogParamA函数创建一个非模态对话框,该对话框具有以下参数:CreateDialogParamA(0,0x410,0,DialogFunc,0);。

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

传入CreateDialogParamA()的一个参数,被radare2识别为fcn.DialogFunc。这个函数正是我们需要关注的函数,该函数包含dropper的主要逻辑。接下来程序又调用了ShowWindow()函数,但是这应该是一个虚拟窗口,实际运行时不会显示。它的功能是触发执行fcn.DialogFunc。

双击 fcn.DialogFunc 进入函数体。我们可以看到它正在对接收到的消息进行几次比较,然后调用一个非常有趣的函数fcn.00403240。

处理资源

fcn.00403240的第一个块非常简单。Dropshot使用GetModuleHandleA获取自身句柄。然后,通过FindResourceA函数查找类型0x6f(十进制:111)和名称0x6e(十进制:110)的资源。最后,使用LoadResource加载资源。

除了OD和IDA,你还用过什么逆向工具?(part 2)
使用Cutter,我们可以快速查找到该资源。只需转到“Resources”小部件并找到名称为“ 110”的资源即可。

除了OD和IDA,你还用过什么逆向工具?(part 2)
正如你在上面的截图中看到的,它资源的大小是28字节,它的语言是波斯语,这可能暗示了Dropshot背后的威胁因素。

让我们单击Hexdump小部件以查看其数据。在hexdump中,我们可以看到该资源包含以下字节:01 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 01 00 00 00 01 00 00 00 00 00 00 00。如果您对radare2比较熟悉,可以使用左下角的“Console”小部件快速执行以下操作:

除了OD和IDA,你还用过什么逆向工具?(part 2)
加载资源后,在下一个块中,我们可以看到循环的开始:

除了OD和IDA,你还用过什么逆向工具?(part 2)
此循环正在检查local_2ch 是否等于0x270f(十进制:9999),如果等于,则退出循环。在此循环内,还嵌套着999次迭代的循环。基本上,这是这个嵌套循环的基本逻辑:
language
for ( i = 0; i < 9999; ++i )
{
for ( j = 0; j < 999; ++j )
{
dummy_code;
}
}

其实这也是一种抗仿真技术,执行一些没有实质性作用的操作。在Dropshot还有很多这样的案例。
在此循环之后,程序进入右侧分支。

除了OD和IDA,你还用过什么逆向工具?(part 2)
首先,调用VirutalAlloc函数分配512字节大小缓冲区。然后调用fcn.00401560。双击进入函数

除了OD和IDA,你还用过什么逆向工具?(part 2)
嘿!我们意外发现了上一节中分析的两个函数decryption_function 和load_ntdll_or_kernel32。我们还观察到call decryption_function后面的注释,它告诉我们解密的字符串是GetModuleFileNameW。

在此函数中, GetModuleFileNameW将被解密。然后调用kernel32.dll中GetProcAddress 函数来获取它的地址。 GetProcAddress的返回值被赋值给[0x41dc04]。稍后在此函数中, [0x41dc04]将被调用。

现在,我们知道这个函数的目的就是为了调用GetModuleFileNameW()。这种手段在Dropshot的代码中很常见。我们将这个函数重命名为w_GetModuleFileNameW,其中“w_”代表“wrapper”。

在调用w_GetModuleFileNameW函数之后,Dropshot使用VirtualAlloc分配20(0x14)个字节。然后调用fcn.00401a40,这是一个与memset非常相似的函数。它有3个参数(地址、值和大小),它的功能是用给定的值填充从address到address+size的范围。通常,在程序中,此函数会使用零来填充缓冲区。其实这个操作挺奇怪的,因为VirtualAlloc函数默认会使用0来填充分配的内存。我把这个函数重命名为memset_。

在程序将分配的内存清零后。我们看到了另一个函数调用fcn.00401c90。有三个参数被传入该函数,分别是0x14、0x66和0x68。有时候我们更喜欢看十进制而不是十六进制。我们可以右键单击这些十六进制数中的任意一个,选择““Set Immediate Base to…””,然后选择“十进制”。

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

现在我们可以看到传递给fcn.00401c90的值是20、102和104。20是刚刚分配的缓冲区大小。102和104有可能像之前一样,表示资源名称和类型。

再次转到resource s小部件,我们可以看到确实有一个名为“102”的资源,它的类型是“104”。
fcn.00401c90是Dropshot的dropper所涉及的关键功能之一,这个函数庞大且复杂。当我在逆向工程中在面对可能相互关联的函数的调用链时,我通常会这样做:

首先,我们看到VirtualAlloc函数分配了20个字节,并将指向内存的指针移动到[local_ch]。之后,调用memset_ 为了将这20个字节的内存空间清零。紧接着,调用fcn.00401c90,并向它传递了3个参数:104、102和20。我们知道102是一个资源的名称,它的大小是20。虽然我们并不是很清楚这个函数的功能,但是我们知道它的返回值(eax)将与另外两个参数一起传递给另一个函数fcn.00401a80。快速查看fcn.00401a80,这是一个非常小的函数,会发现这个函数正在将缓冲区复制到已分配的内存中。此函数与memcpy非常相似,因此我们将其重命名为memcpy。所以现在我们可以做一个有根据的猜测,假设fcn.00401c90正在将资源读取到缓冲区并返回指向它的指针。

使用这种方法,我们可以理解(或至少猜测)一个复杂的函数的功能,甚至不需要分析它。只要看看一系列的函数调用,对函数进行快速分析 ,这样做确实很节省时间。

资源解析

我们将快速的浏览fcn.00401c90,请跟紧我们的脚步。
下面来看一下第一个程序块。您将看到一个call 和很多mov,add 以及偏移量的计算,这就是典型的PE解析的样子。
language
/ (fcn) fcn.00401c90 468
| fcn.00401c90 (int arg_8h, int arg_ch, int arg_10h);
| 0x00401c90 push ebp
| 0x00401c91 mov ebp, esp
| 0x00401c93 sub esp, 0x44
| 0x00401c96 mov dword [local_40h], 0
| 0x00401c9d push 0
| 0x00401c9f call GetModuleHandleW
| 0x00401ca5 mov dword [local_34h], eax
| 0x00401ca8 mov eax, dword [local_34h]
| 0x00401cab mov dword [local_20h], eax
| 0x00401cae mov ecx, dword [local_20h]
| 0x00401cb1 mov dword [local_24h], ecx
| 0x00401cb4 mov edx, dword [local_24h]
| 0x00401cb7 mov eax, dword [edx + 0x3c]
| 0x00401cba add eax, dword [local_24h]
| 0x00401cbd mov dword [local_38h], eax
| 0x00401cc0 mov ecx, dword [local_38h]
| 0x00401cc3 add ecx, 0x18
| 0x00401cc6 mov dword [local_3ch], ecx
| 0x00401cc9 mov edx, dword [local_3ch]
| 0x00401ccc add edx, 0x60
| 0x00401ccf mov dword [local_28h], edx
| 0x00401cd2 mov eax, 8
| 0x00401cd7 shl eax, 1
| 0x00401cd9 mov ecx, dword [local_28h]
| 0x00401cdc mov edx, dword [ecx + eax]
| 0x00401cdf mov dword [local_44h], edx
| 0x00401ce2 mov eax, 8
| 0x00401ce7 shl eax, 1
| 0x00401ce9 mov ecx, dword [local_28h]
| 0x00401cec mov edx, dword [local_20h]
| 0x00401cef add edx, dword [ecx + eax]
| 0x00401cf2 mov dword [local_10h], edx
| 0x00401cf5 mov eax, dword [local_10h]
| 0x00401cf8 mov dword [local_4h], eax
| 0x00401cfb mov ecx, dword [local_4h]
| 0x00401cfe movzx edx, word [ecx + 0xe]
| 0x00401d02 mov eax, dword [local_4h]
| 0x00401d05 movzx ecx, word [eax + 0xc]
| 0x00401d09 add edx, ecx
| 0x00401d0b mov dword [local_ch], edx
| 0x00401d0e mov dword [local_14h], 0
| ,=< 0x00401d15 jmp 0x401d20
...
...

首先,程序调用GetModuleHandleW函数接收当前进程的句柄。然后,句柄(eax)被赋值给各种局部变量。首先,它被赋值给位于0x00401ca5的[local_34h]。然后,您可以看到eax赋值给[local_20h],之后又使用ecx赋值给[local_24h]。

现在我们有一堆局部变量,它们都保存着当前进程的句柄。我们可以直接把这三个变量统一重命名为[hmodule_x],这样就更容易跟踪所有对hmodule的引用。我们可以使用Console小部件,执行afvn old_name new_name。例如,我执行了:afvn local_34h hmodule_1;afvn local_20h hmodule_2;afvn local_24h hmodule_3。

GetModuleHandle会返回模块的句柄,这基本上意味着hmodules指向二进制文件的基址。在0x00401cb4行中,我们可以看到[hmodule_3]被赋值给edx,然后在[edx+0x3c]处的值被赋值给eax,并且在0x00401cba处,[hmodule_3]与eax做了加法。最后,eax被移动到[local h]。我们可以简单的用一句伪代码来概述上面的操作:
language
[local_38h] =(BYTE *)hmodule + *(hmodule + 0x3c)

那么这个地址是什么呢?我们用PEview进行查看。当然你也可以选择其它的二进制结构查看器,也可以使用radare2的二进制结构分析功能(详细信息请查看pf

在PEview中打开Dropshot并检查DOS标头:

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

如您所见,在偏移量为0x3c处,有一个指向新EXE头部的偏移量的指针(0x108)。所以[local_38h]保存的是NT头的地址。让我们将其重命名为NT_HEADER并继续。

在0x00401cc0处,我们可以观察到NT_HEADER被赋值给ecx,然后exc被加上了0x18。最终,ecx偏移到[local_3ch]。和前面一样,让我们再次打开PE解析器,检查NT_HEADER+0x18中的内容。也就是0x18+0x108,让我们看看这个偏移量是多少:

除了OD和IDA,你还用过什么逆向工具?(part 2)
如上图所示,0x120是IMAGE_OPTIONAL_HEADER的偏移量。此时我们可以重命名local_3ch 为OPTIONAL_HEADER。简而言之,Dropshot将解析IMAGE_DATA_DIRECTORY结构(OPTIONAL_HEADER + 0X60)和资源表,并迭代遍历不同的资源,并将资源类型和资源名称与函数参数进行比较。最后,它用于memcpy_ 将所需资源的内容复制到变量中并返回此变量。

现在我们可以把该函数重命名为get_resource,并将参数重命名为它们的相应含义afvn arg_8h arg_rsrc_type; afvn arg_ch arg_rsrc_name; afvn arg_10h arg_dwsize。

现在我们已经确定了该函数的功能,我们需要查看谁调用了它。右键单击该函数,然后选择“Show X-Refs”(或者直接按’x’)。我们将进入交叉引用窗口,该函数被调用了两次。一个位于0x0040336d,我们之前已经对其进行了分析。另外一处调用发送在0x00403439,这个函数对于我们来说是陌生的,它的作用是获取名为“ 101”(0x65)的资源内容。

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

还记得以前’ Resources小部件的截图吗?我们在那里看到了一个名为“101”的资源。它的大小是69.6kb!这是Dropshot的有效载荷。通过转到Resources小部件并双击“101”,我们将看到二进制文件中的资源偏移量。在Hexdump小部件中,我们可以看到该资源的内容杂乱无章,同时具有非常高的熵(7.8):

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

非常高的熵意味着数据以某种方式被压缩/加密,因此我们需要对其解密。

如何解密资源

为了解密资源,我们应当按照程序的执行流程来查看payload的运行方式和调用位置。在使用“101”和“103”调用get_resource之后,该资源将使用memcpy_(位于0x00403446)复制到[local_20h]。 我们可以把它重命名为compressed_payload。然后将压缩的缓冲区作为参数传入fcn.00401e70,这个函数看起来也没有实际意义,对数据执行虚拟计算。这可能是一种抗仿真技术,或者只是在浪费时间。我把它重命名为dummy_math。接下来,compressed_payload与另一个缓冲区[local_54h]一起传递给fcn.00401ef0。

对该函数的分析并不在本文的讨论范围之内,该函数使用zlib解压缓冲区并将解压后的缓冲区放入[local_54h]。例如,您可以看到,fcn.00401ef0正在调用fcn.004072f0,其中包含“unknown compression method”和“invalid window size”等字符串,这些字符串可都以在zlib库中的inflate.c找到。据此我将fcn.00401ef0重命名为zlib_decompress,将local_54h重命名为decompressed_payload。

在解压缓冲区后,程序还会对缓冲区执行一次简单的解密操作。在解压后,我们可以看到许多熟悉的抗仿真操作。最后,将解压的缓冲区传入fcn.00402620。该功能负责资源的最后解密,然后执行一种臭名昭著的技术,称为“Process Hollowing” 或 “RunPE”,以执行解密后的有效负载。

那么fcn.00402620如何解密payload呢?简单地说,它使用ror 3将解压缩缓冲区中的每个字节循环右移。3表示要右移的位数。

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

总结一下,让我们大概勾勒一下解密函数:
language
rsrc_101 = get_resource("101")
decompressed_payload = decompress(rsrc_101)
decrypted_payload = []
for b in decompressed_payload:
decrypted_payload.append(ror3(b))

编写解密脚本

我们仍可以使用r2pipe对脚本进行编写。
r2pipe api基于r_core_cmd_str()后面的一个r2原语,该原语是一个函数,传入要运行的r2命令的字符串参数,并返回带有结果的字符串。

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

幸运的是,Cutter将r2pipe的python集成到其Jupyter组件中。我们将编写一个r2pipe脚本,该脚本将执行以下操作:
• 将压缩的资源保存到变量中
• 使用zlib解压缩资源
• 对解压后的payload中的每个字节执行ror3
• 将解密的资源保存到文件中
与上一部分一样,让我们转到Jupyter小部件并打开解密字符串时编写的脚本(第1部分)。

首先将资源读取到文件中:
```language
rsrcs = cutter.cmdj('iRj')
rsrc_101 = {}

Locate resource 101 and dump it to an array

for rsrc in rsrcs:
if rsrc['name'] == 101:
rsrc_101 = cutter.cmdj("pxj %d @ %d" %
(rsrc['size'], rsrc['vaddr']))
```
iR 用于获取资源列表及其在文件中的偏移量。接下来,我们对资源列表进行遍历,查找资源名为’101’的资源。最后,使用px将资源的字节读入变量。这里我们使用了pxj命令,以便将它们输出为JSON。

接下来,我们要使用zlib解压缩缓冲区。Python默认会附带“ zlib”库,我们使用import zlib导入该模块,并使用以下代码解压缓冲区
```language

Decompress the zlibbed array

decompressed_data = zlib.decompress(bytes(rsrc_101))
```

现在缓冲区的数据已被解压并保存在decompressed_data变量中,剩下的操作就是对数据进行循环右移并将其结果保存在文件中。

定义以下的 ror 表达式:
language
def ror(val, r_bits, max_bits): return
((val & (2**max_bits-1)) >> r_bits % max_bits) |
(val << (max_bits-(r_bits % max_bits)) & (2**max_bits-1))

在代码中可以这样实现:
```language
decrypted_payload = []

Decrypt the payload

for b in decompressed_data:
decrypted_payload.append((ror(b, 3, 8)))
最后,将其保存到文件中:language

Write the payload (a PE binary) to a file

open(r'./decrypted_rsrc.bin', 'wb').write(bytearray(decrypted_payload))
```
现在我们把两个部分的脚本结合起来,并在Jupyter中进行测试
最终的脚本可以在这里找到。
执行结果如下:

除了OD和IDA,你还用过什么逆向工具?(part 2)
我们的脚本似乎已成功执行,并且保存为./decrypted_rsrc.bin”。
我们要做的最后一件事是验证decrypted_rsrc.bin确实是PE文件,并且我们没有该文件没有损坏。

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

好极了!Cutter将该文件识别为PE,代码似乎也得到了正确的解释。 我们刚刚保存的二进制文件正是Dropshot的Wiper模块。该模块同样使用大量抗仿真技术来解密字符串。您可以尝试使用Cutter,radare2和r2pipe自己进行分析。Good Luck!

结语

在这两部分的讲解中,我们分析了恶意软件Dropshot的一些组件。熟悉了Cutter,并在r2pipe的Jupyter中编写了一个解密脚本。

相关推荐: VolgaCTF2020 RE部分wp

VolgaCTF2020 比赛的时候只做出来excel_crackme,赛后复现学到了不少~ VolgaCTF_excel_crackme 前置知识 vba 没遇见过vba语法,记录一下~ <> 不等于,相当于!== Range 引用单元格 CLn…

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2021年5月10日01:32:27
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   除了OD和IDA,你还用过什么逆向工具?(part 2)http://cn-sec.com/archives/246583.html