导语:在上一篇文章中,我们详细介绍了解决Malwarebytes CrackMe挑战第一阶段任务的相关方法,如查找关键变量和引用,了解变量的用途和填充方式等。在本文中,我们将继续为读者详细介绍完成第二阶段任务的具体过程。
在上一篇文章中,我们详细介绍了解决Malwarebytes CrackMe挑战第一阶段任务的相关方法,如查找关键变量和引用,了解变量的用途和填充方式等。在本文中,我们将继续为读者详细介绍完成第二阶段任务的具体过程。
检查流量
我们已经知道,该软件将从互联网上下载一些东西(使用解密的URL),所以,如果对这些网络流量进行仔细检查的话,应该会有所帮助。当然,检查URL的方法有很多种,比如,我们可以借助于Wireshark或Fiddler(链接地址:https://www.telerik.com/fiddler)来完成这项任务:
请求和应答:
我们看到,内容是从网址http://pastebin.com/raw/9FugFa91下载的。
这些内容经过了Base64编码,但是用Base64解码器(链接地址:https://www.freeformatter.com/base64-encoder.html)解码后,得到的结果还是不像明文(据我们猜测,原因是这些内容经过了压缩处理和/或进一步的加密)。那么,让我们再次深入考察这个应用程序!
了解payload
首先,让我们来做一些静态分析,以便了解这个payload到底是干什么的,以及其具体用法。好了,让我们来查找初次显示“Nope :(”消息框的位置。我们看到,这里有一项用于确定缓冲区是否从"MZ"开始的检查。众所周知,MZ是一个著名的魔术数字,它位于DOS应用程序以及Windows可执行文件(PE文件)的开头部位。
通过仔细观察这个函数,我们会发现,处理这个下载的文件的函数为数不多。
首先,需要用一个函数进行base64解码。然后,输出未压缩的文件:
通过查看里面的内容,同时根据发现的诸如RtlDecompressBuffer之类的API调用,我们可以判断出,这个函数负责解压缩工作:
然后,我们注意到一个从剪贴板中读取内容的函数:
进入该函数后,我们也可以很轻松地找到相关的API调用,例如:
我们发现,从剪贴板中读取内容的格式为CF_TEXT。
然后,我们发现从剪贴板读取的内容会用于另一个函数。这些内容将作为XOR密钥来解密下载的内容:
注意,这个结果是从“MZ”魔数开始的。此后,它将被注入rundll32。
在函数sub_4011F0的内部,我们可以看到具体的注入机制。这里使用的是一种经典的RunPE技术。新进程被创建后,会被立即挂起。然后,payload会被写入该进程的内存空间,在链接到它的PEB后,该进程会恢复运行:
当然,这里不会对该技术做更加详细的解释,因为这超出了本文的范围(如果读者对此非常感兴趣的话,可以访问这里(链接地址:https://www.adlice.com/runpe-hide-code-behind-legit-process/))。但是,脱壳实际上是非常容易的——我们只需要在解密之后转储payload,然后转换为虚拟格式并写入远程进程即可。接下来,我将为读者介绍一些可用的脱壳方法。
对payload进行解密
在静态分析过程中,我们发现了如下所示的信息:
· 通过解密后的URL下载payload
· 进行Base64解码
· 利用RtlDecompressBuffer解压缩
· 利用从剪贴板读取密钥进行XOR解密
现在,我们必须找到XOR密钥。此外,我们还知道XOR操作是自我可逆的。但是,首先,我们要在解压缩之后转储payload,以便可以得到需进一步分析的相关资料。
我将在调试器(例如ImmunityDbg)下运行修改版的CrackMe,并转到API调用RtlDecompressBuffer处:
我在解压缩函数的末尾设置断点,然后运行CrackMe。
我们可以在堆栈上看到传递给函数的相关变量。现在,我们来看看未经压缩的缓冲区:
我们可以看到一些重复的模式和字符串“malwarebytes”。不难猜到,这可能就是通过剪贴板传递的XOR密钥。虽然我们可以选择不同的脱壳方法,但是限于篇幅,这里只演示其中的一种。
对经过了XOR混淆处理的payload进行解码
对缓冲区进行解压后,我们把它转储到一个文件中,然后利用外部工具dexor.py(链接地址:https://github.com/hasherezade/crypto_utils/blob/master/dexor.py)来进行解码。
首先,我们来转储缓冲区:
然后,我们必须进行相应的调整,以使其起始地址与相应的偏移量相匹配。我们可以通过在XVI32 hexeditor中打开转储的内存页面,导航到输出缓冲区的开始部分,然后选择:
Edit->Dump to cursor
然后,我们可以通过脚本和XOR密钥轻松进行解码。就本例来说,我们可以很容易地猜到,密钥就是“malwarebytes”,因为这个字符串在解码后的缓冲区中重复出现了许多次。
dexyor.py –file dump.bin –key "malwarebytes"
正如我们预期的那样,根据之前的发现,解码后的输出内容是一个新的PE文件。
第二阶段
第二阶段的任务,将在新得到的可执行文件中完成。在我们完成转储之后,可以把它作为一个完全独立的模块来运行。 运行后,它会弹出以下消息:
让我们利用IDA打开它,以便于进一步的观察。这个文件没有被混淆,并且结构非常简单。
了解检查条件
首先,我们终于明白为什么之前会显示“Fail”消息了。这里,它做的第一件事情,就是检查模块路径,并与rundll32.exe的路径进行比较。这项检查并非直接比较字符串,而是计算路径的校验和,然后比较校验和:
简而言之,如果当前的PE没有被注入到rundll32.exe中,那么检查将会失败,从而导致前面介绍过的消息框。现在,我们想要将这个PE文件作为独立的单元运行,而不是通过rundll32来运行。 所以,我们需要摆脱这个检查。 我们可以通过直接修改条件跳转来达到这一目的(与我们在第一阶段修改条件跳转的方式是相同的)。
或者,我们可以利用调试器加载可执行文件,在检查点上设置断点,并更改标志以绕过它。
为了弹出旗标,还需要满足两个条件:
1.系统中必须运行着一个使用给定类型的窗口的进程。
首先,调用EnumWindows函数。相应的校验和位于回调函数的参数中:
在回调函数中,将每个窗口的类名与相应的校验和进行比较。如果匹配,则打开相应的进程,以便于进行下一步的注入工作:
有人可能会注意到,这个检查的实现代码与这里的代码(链接地址:https://github.com/hasherezade/snippets/blob/master/neutrino_env_check.cpp#L223)非常相似。相应的窗口类属于ProcessExplorer。
2.应用程序必须加载到调试器中。
检查到调试器的存在后,它会设置影响XOR密钥值的旗标。
如果我们在调试器下运行可执行文件和ProcessExplorer(32位)的话,带有旗标的MessageBox将被注入到那里,我们将立即得到答案。
转储并运行shellcode
如果我们运气好的话,我们可能会很快找到它。但是在现实生活中,找到必须注入的那个进程可能会有些难度。此外,在64位版本的Windows上运行CrackMe的用户也会遇到问题,因为这个shellcode是32位的,只能注入到32位版本的Process Explorer中。但是,为了解决这个问题,根本就不需要知道进程的名称。我们可以在注入之前转储shellcode,并用我们自己的loader进行加载。
首先,我们看看IDA,这里要注意注入代码的那部分。之前,计算出来的shellcode的校验和为:
所以,现在我们已经将有效的shellcode存储到了缓冲区中。我们并不关心它被注入的位置——我们可以将其转储,然后运行它。为此,我们只需要绕过基于给定校验和的窗口搜索就行了。我们可以通过修改条件语句来达到目的(或者在调试器中更改相应的旗标)。下面是必须修改的条件语句:
在附加的视频中,我们可以看到完整的解决方案:转储shellcode并独立运行。在这个给定的例子中,shellcode是在PE-bear的帮助下,作为一个新节被添加到原始的CrackMe中
这样,我们就会得到最终的旗标:
小结
在本教程中,我们详细演示了CrackMe的一个可行解决方案。我建议你看看下面的Write Up,从而学习不同的观点,掌握更多的解决方法。当然,我鼓励读者亲自动手尝试一下,并将自己的解决方案写下来,因为这是最好的学习方式。
附录
下面是我们收到的一些Write Up:
https://mauronz.github.io/mb-crackme/ – by @FraMauronz
https://drive.google.com/file/d/0Bzb5kQFOXkiSSURnMUZmaVpWRlk – by @JR0driguezB
https://drive.google.com/file/d/0Bzb5kQFOXkiSUmgwN2dWT21jdXM – by @ShAd0wHuNt3r_0
https://29wspy.ru/reversing/SolutionHasherezadeCrackme2017.pdf – by @ValthekOn