导语:本文将会为读者详细介绍如果针对加密代码的弱点来设计相应的解密程序。
在本系列的第1、2、3部分中,我们分别介绍了加密的基础知识,深入剖析了一个勒索软件样本,并讨论了加密系统可能存在的弱点。在本文中,我们将对一个带有弱加密算法的勒索软件进行深入分析,然后,围绕该勒索软件逐步展开设计相应解密程序的具体思路。
本文旨在帮助恶意软件分析师在分析和破解勒索软件加密的道路上迈出坚实的第一步。通过阅读本文,读者就会明白在破译密文的过程中,深入考察勒索软件本身是多么重要的一项工作了。
尽管现代操作系统都内建了相应的加密软件包和API,但许多恶意软件作者在使用这些软件的过程中仍然会出现纰漏,这就给我们(分析师)利用其糟糕的编码技能设计解密程序创造了良机。
关于勒索软件样本
在本文中,我们使用的勒索软件样本是PrincessLocker。当然,Hasherezade已经针对该软件进行了深入的分析,并创建了相应的keygen和解密程序。如果读者对这些感兴趣的话,请访问:https://hshrzd.wordpress.com/2016/11/17/princess-locker-decryptor/。
在继续阅读下文之前,我强烈建议读者首先阅读关于这个勒索软件的分析报告,熟悉其内部运行机制,为将来的解密工作打下坚实的基础,为此可以访问下面这篇文章:https://blog.malwarebytes.com/threat-analysis/2016/11/princess-ransomware/。
软件分析
在本文中,我们不会对软件的脱壳技术进行介绍,相反,这里假设您已经掌握了PrincessLocker的脱壳技术,并已经了解其基本功能。下面给出该样本未加壳之前的哈希值:
4142a59be1f59dbd8e1be832df893d08
以及初次加壳处理后的哈希值:
14c32fd132942a0f3cc579adbd8a51ed
首先,我们会对未加壳的样本进行静态分析,之后,再对其经过加壳的样本进行动态分析。
静态分析
既然我们只关心代码中的加密部分,那么就从搜索加密字符串或API并进行交叉引用开始吧。经过初步分析,我们发现在函数地址10007980处使用了CryptDeriveKey API。
这里,传递给CryptDeriveKey的、用于算法ID的参数是0x660E,即CALG_AES_128。
由此得知,在这个地方生成了一个AES密钥,这个AES密钥的“密码”就是pHash变量的值。如果我们跟踪这个变量的话,我们会看到它被传入10007BB9,即call esi指令。实际上,这些字符串都经过了混淆处理,所以使用静态分析很难看出它们的真面目。因此,我们下面将借助调试器来对付加壳后的文件14c32fd132942a0f3cc579adbd8a51ed。
调试代码
对于加过壳的文件,为了定位脱壳后的目标代码所在位置,我通常会从脱壳的样本中寻找一个API调用,并设置一个断点以进入该库代码。如果我们转到被调用的主函数,并转至这个派生密钥的调用,那么说明它就是导出函数“Zero”。
就这里来说,我们可以在TranslateMessage和OpenMutexW处设断点。不过,我不想在TranslateMessage处试运气,所以选择了OpenMutexW。
如果Olly软件已经安装了StringOD插件的话,按下control + G组合键就会弹出如下所示的菜单:
在文本框中,输入将带我们进入库代码的目标API调用的名称,这样就可以在这个调用处设置一个断点,接着,单击运行直至触发这个断点为止,然后跟踪,直到这个函数返回,将我们带至脱壳后的文件中,这样我们就可以进行调试了。
如果我们继续该操作,并在之前所在的call esi处设置断点,就会发现,它就是cryptHashData,因此,它会对lpString进行了哈希处理,并将该字符串输出的哈希值用于生成AES密钥的输入。
静态分析
如果我们利用IDA跟踪lpString的话,就会发现,它实际上就是这个函数的参数arg_0,同时,从表面上看,它也是一个随机生成的字符串。
跳出这个函数后,我们看到:
10008B11 lea ecx, [esp+98h+var_54] 10008B15 call sub_10007980
var_54是刚才考察的那个函数的参数。它是一个字符串,后面先对其进行哈希运算,然后将其用于AES密钥。我想通过跟踪这个变量,弄清楚它会被填充到什么地方。通过olly,我们可以为这个地址的内存写入设置断点,但是就本例来说,只要考察将它用作参数的函数就可以轻松找到答案了。
图中高亮显示的函数是我无意中发现的,这里将其标注为RandomGenFunc。该函数以edx为参数,该参数是一个用于生成密钥的变量。所以,这里很可能就是变量填充和随机密码字符串被创建的地方。
我们发现,它使用了srand(),并且以time64()为种子值。为了确保不会搞混,这里给出time64的精确描述。
如果我们仔细查看各个子函数的话,就会发现这种行为发生在一个循环中。srand()函数会再次被调用,并且还是使用time64()的返回值作为种子值,然后在循环中调用rand以生成新的数字。
从这里可以看出,它正在使用当前系统时间作为一个种子值,如果您还记得srand()和rand()的功能的话,自然会明白seed参数实际上决定了每次调用rand()所输出的随机数集。所以,如果种子相同,那么生成的序列也是一样的。这一点,正是我们要利用的主要弱点。
如上所示,这里使用了一个随机的字符集“0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabc…”,并且在一个循环内部,rand()会被重复地调用。实际上,这是在通过字符集的子字符串位置来逐字符构建字符串。
所以,现在我们的问题已经有眉目了:AES密钥是由当前时间的种子创建的随机字符串生成的。如果我们能够找到加密过程中使用的当前时间,我们就可以再现AES密钥。当然,这些都可以通过暴力破解方法在合理的时间内完成。
这方面的知识,我们在上一篇中做过了详细说明;如果读者还不熟悉的话,可以重新回顾一下。
小结
在本文的上篇中,我们通过静态分析和动态调试技术,深入考察了勒索软件加密代码的内部工作机制,找出了其主要的弱点。在下篇中,我们为读者介绍如何针对加密代码的弱点来设计相应的解密程序。