mssp299 · 2015/07/16 11:08
原文地址:http://blog.cylance.com/cracking-ransomware
0x00 背景
在2013年初,某公司向我们求助,因为他们有大量的重要文件被勒索软件加密,导致无法访问。这次攻击者使用的,是一款披着“反儿童色情邮件”外衣的勒索软件,它会遍历所有磁盘,并加密其中的重要文件。由于该软件发动攻击时,会安装备份驱动器,因此总的来说,公司的数据会面临一定的损失。不过幸运的是,我们能够破解其口令,从而将被加密的数据恢复如初。下面,我们详细介绍破解的流程。
在公布勒索软件本身漏洞方面,我们希望研究人员酌情处理;最稳妥的方案是将其提交给受害者信赖的机构或公司,这样,只要漏洞仍然存在于勒索软件中,就能为受害者争取更长的救助时间。
0x01 改进型的ACCDFISA
为此,我们来研究被加密一个的文件,这个文件已经被更名为:“”。它实际上是一个存放经过加密的RAR文件的WinRAR自解包程序。很明显,如果想要寻找WinRAR加密实现的漏洞的话,那是指望不了,所以,我们另辟蹊径,直接从破解口令下手。为此,我们需要了解创建该口令的那些代码。
在被感染的驱动器上,我们不仅发现了这个勒索软件,同时还发现其他一些看起来也与此有关的文件。其中一个文件,就是Microsoft Sysinternals中的sdelete工具,该工具的作用是永久性的删除文件,所以我们不仅畅想:要是该软件存在Bug的话,我们就能速战速决了。此外,我们还找到了一个名为“”的库,以及一个建立自解包程序的RAR工具。这些文件的存在不禁使人浮想联翩:攻击者已经制造出了一个窃取代码的弗兰肯斯坦怪物,然后与勒索软件的主要可执行代码简单缝合在一起,就像PureBasic 所写的那样。这里的RAR工具就是我们破解该勒索软件的突破口。由于这个工具需要提供密钥口令来作为命令行参数,因此我们推断,如果从勒索软件中启动这个压缩工具的代码处开始回溯,就能够找到构造该口令的代码了。
0x02 寻找口令生成器
首先,在我们一次性的机器上运行该勒索软件,然后,连接调试工具来拦截调用,这个调用的作用是启动加密文件的那个RAR工具。不一会儿,调试工具就停下来,这时就可以查看完整的命令行了,其中口令就位于“-hp”选项中。
当在调试工具中运行的勒索软件试图启动该RAR工具(这里它被伪装成“”)时,就会被我们截获。这样,我们可以看到执行勒索软件的命令的具体内容了,其中就包括用来加密文件的口令。
经过一番折腾后,我们得到了一个口令,虽然它未必能够用来解密受害者文件,但是,至少为逆向工程提供了有用的线索:我们可以寻找口令或者口令中的片段,搜索可能用于生成口令的“字母表”(如果它是随机生成的话),以及从命令行中寻找建立口令的“-hp”字符串。
拦截到的口令,看起来像是由57个字母、数字和标点符 混合而成的。对于这个口令,如果是由人键入的话,看起来好像太随意了一点,并且其前缀为字符串“aes”。后者也许只是一个巧合,就像汽车牌照上也会出现有意义的单词或者 码,同时,这个前缀也可能是勒索软件中的硬编码字符串造成的。事实上,当我们用反汇编程序打开该勒索软件时,我们不仅找到了字符串“aes”,而且发现,更为完整的前缀实际上是“”:
在这个勒索软件的反汇编代码中,出现了硬编码的字符串“”,这与我们之前拦截的口令的前七个字符非常吻合。同时,我们还为在之前的逆向工程期间得到的一些函数()和全局变量(“”)进行了命名。
这表明,该口令事实上是由“”后跟50个左右的随机字符组合而成的。
在上图中,部分高亮显示的指令,就是用来加载指向“”字符串的引用的。我们由此推断,后面的指令,将加载一个指向存储字符串的全局变量的引用。我们已经给该变量取名为“”,下面我们来定位该变量在内存中的具体地址。
该勒索软件的数据段包含了一些与生成随机口令有关的全局变量。就像以前一样,这里也对某些变量重新进行命名并加以注释。
知道了这些变量的地址后,我们就可以利用调试工具来查看在实际测试期间,这些变量到底存放了哪些值,具体如下所示:
尽管我们可以轻松地通过反汇编程序浏览勒索软件的程序代码,但是这样做的缺点是,这只能针对磁盘上的静态程序,或者说是“无生命的”程序。要想操作运行在内存中的、活蹦乱跳的勒索软件进程的话,我们还需借助于调试工具。下面,我们就通过它来观察三个字符串变量的取值情况。
正如我们所预料的那样,名为“”的变量指向前缀字符串“”的一个副本,名为“”的变量指向由50个随机字符构成的字符串,而名为“”的变量则指向由前面两部分连接而成的一个字符串。
然后,利用前面的发现和方法,继续跟踪字符串“-hp”。通过反汇编程序,我们迅速锁定了几个实例,下面便是其中之一:
我们在程序代码中发现了字符串“-hp”的一个实例,显然,它肯定位于构建含有口令的命令行并(通过 或 )执行该命令的代码中。 到此为止,我们虽然对该勒索软件有了更多的了解,但是仍然不知道是否足以解救受害者。不过有一件事情是肯定的,那就是该口令不能适用于所有受害者。幸运的是,通过反汇编程序我们可以轻松找出程序中所有访问某个变量的代码,这样我们就能追踪到构造“”的代码了:
上面的代码,通过连接“”和随机字符串而构造了“”所指向的字符串。
然后,我们利用交叉引用找到了“”。
这个循环(图中用粗虚线箭头加以指示)的作用是从由78个字符组成的“字母表”中挑选50个字符组成一个串,通过前面拦截的口令可以看出,它好像是随机挑选出来的。 我们还发现了一个循环,它的循环计数是从1到50,这跟口令中随机部分的那50个字符的长度非常吻合。同时,我们还在这个循环内部找到了一个字符串,看起来非常像是用来随机选择50个字符的“字母表”。在这个字母表中,包含了26个小写字母,26个大写字母,10个数字,以及16个标点符 。
接下来,我们需要找出选择随机字符的那个函数,当然,上面循环内部的指令肯定会调用这个函数的。尽管人们普遍认为计算机能够产生真正的随机数,但是现实是,它只能带来伪随机数。长期以来,人们不断探索针对伪随机数生成器的攻击技术,以便能够战胜加密术,这为我们的工作做好了很好的铺垫。上面,我们标出了一个名为“”的函数,其反汇编代码如下所示:
观察上面的反汇编代码不难发现,函数“”好像生成了一个随机数,并将其保存到了局部变量“”中。并且,这段循环代码会顺序遍历字母表的各个字符,直到抵达某个字符为止。然后,那个字符被转换成一个单字符字符串,其地址被存放到被我们命名为“”的全局变量内。
当确定出上面高亮显示的函数就是的随机数生成函数“Rnd”之后,这些汇编代码就立马变得非常容易阅读了。这个函数的代码如下所示:
随机数函数“Rnd”的作用是,确保初始化伪随机数生成器,以生成一个介于0到指定最大值“”之间的一个数值。当然,这个范围也包括了它的两个边界值。 函数“Rnd”封装了许多其他函数的调用,尤其是我们前面命名的那些函数。这里的“”函数,除了调用一些Windows函数外,还调用了该勒索软件自身的一个函数,该函数被命名为“”。下面,我们将这些函数并列显示,具体如下图所示。
在上图中,左窗口显示的是“”函数的反汇编结果,而右窗口中是我们更为感兴趣的“”函数的反汇编结果。 现在,我们终于取得了一个重大突破。在左边窗口可以看到,PRNG是通过由执行这个程序代码的线程的标示符和以毫秒计算的系统运行时间共同得到的一个32位数字来初始化或者说指定种子值的,而这两个数值相对来说都比较容易预测。在右边的窗口中,我们高亮显示了一个常量“magic”,这是一个专用的数字,通常用于 络搜索。在这里,这个数字好像采用了16进制的形式,具体为,当然,还有其他可能的表示形式,如,或者十进制表示形式。它后面的指令,可以转换为“”的形式,这意味着我们所看到的这个常量实际上可能是负数,如果看作是无符 数字的话,还可以表示为,或者。利用上面的这些内容作为搜索词进行 络搜索,我们竟然在论坛中找到了与这些随机数生成程序有关的源代码,以及相应的反汇编代码。
下面所谓“”函数的反汇编代码中明显的循环操作进一步印证了我们的发现,它跟我们从 络上找到的源代码的反汇编代码是一致的。
上面高亮显示的“”助记符表示的是循环左移指令,分别表示循环左移13比特和5比特。并且,我们从 上找到的源代码也在类似的上下文中执行循环左移操作,甚至移动的位数也是相同的。
好了,见证奇迹的时刻就要到了。由于它使用了32位的种子值,也就是说,可能的口令至多能有2的32次方种,而不是从78个字符组成的字母表中真正随机挑选出50个字符的可能组合数。之所以出现这种情况,是因为计算机无法真正实现随机性。对于任何给定的种子值,PRNG每次用它初始化的时候,都会以完全相同的次序生成完全相同的数值。由于这个随机数种子值是一个32位的数字,也就是说其取值范围是从0到2的32次方左右,因此可能的初始状态也会受到相同的限制。
0x03 猜解口令
当然,这些数据的价值不在于我们需要这些名称,而在于我们知道了它是PRNG在感染后输出的。在实际发生感染的时候,例如本次面临的这个案例,我们当然无法像测试运行这样获得相应口令,但是仍然有机会找到或者根据被感染驱动器的ProgramData目录中的内容来重构它。利用这些数据,我们就能够暴力破解所有可能的种子值,最多需要2的32次方次,就能找出PRNG生成这些随机名称时所对应的种子值了。对于目前的计算机来说,这种暴力搜索可能需要几个小时才能完成,但是相对于利用RAR工具逐个尝试推测的口令来说,速度要快上好几个数量级了。
这里有几点需要注意。首先,就像在随机函数中识别出的线程本地存储(TLS)调用所暗示的那样,每个线程都有自己的PRNG状态,并且在第一次被“Rnd”调用时,都会单独进行初始化。碰巧的是,那八个随机字母构成的名称是由勒索软件进程的主线程生成的,而密码是由分线程决定的。下面是生成21个名称的一段循环代码;标为绿色的代码交叉引用(“”)注释表明,该代码驻留在程序的“start”函数中,并且该函数是该进程第一个执行的线程。
在勒索软件的“start”函数中的这段循环代码,将创建21个随机的文件名和子目录名。
这是上面提到过的那个从A到Z的字母表中随机选择8个字母函数中的循环代码段。正如所料,该函数会针对每个字符都调用一次“Rnd”。
通过跟踪生成口令的代码,我们找到了一个标记为“”的函数,它会被我们命名为“”的函数调用,当勒索软件作为一个Windows服务运行的时候,这个函数将在一个单独的线程中运行。也就是说,我们关心的这两个代码段在执行时会使用不同的PRNG状态,每个状态都是由不同的随机数的种子值来产生的。通过暴力破解种子值,我们可以得到随机的文件名或子目录名,但是却无法直接得到口令的种子值,尽管如此,我们已经离成功越来越近了,这主要得益于口令种子值构成部分的简单性和可预测性。换句话说,种子值的系统运行时间部分和线程标识符部分还是有很大的不同的,因为线程可以在不同的时刻启动,并且如果多个线程同时运行的话,必须使用不同的ID,但是它们不会相差太大。因此,要是手头上有了第一个种子值,我们就能适当地将第二个种子值的取值范围缩小到几十万个之内。
第二个注意事项是,我们已经知道了字母的次序,但是这一点很容易就会被忽视。另外,从技术上讲PRNG会发出一个数字序列。就本例而言,就像前面的快照所显示的那样,数字0到25表示的就是字母A到Z,所以很明显,这个字母表就是用来将字母映射到一个数字的。然而,在其他情况下,这些字母可能会出现遗漏、重复、重新排序,或者穿插了中所见之外的字符。无论出现上述哪一种情况,都意味着我们需要花费许多小时来破除这些阻止我们暴力破解的障碍。
第三个需要注意的事项是,我们无法确定PRNG的种子值初始化之后,该勒索软件是否立即生成了这些名称或口令,也就是说,这中间否有什么岔子。如果其他代码先于这两个线程中的某一个,或先于这两个线程之前就已经调用过了Rnd的话,则意味着PRNG的状态已经不是我们所关心的随机数生成代码给它提供种子值时的原始状态了。我们需要弄清,“Rnd”被我们感兴趣的那个线程调用之前,已经被其他线程调用了多少次,同时,还要去掉在生成我们自己推测性的名称和口令之前的那些随机数。
因此,我们需要找出针对“Rnd”的各个调用。如果将反汇编代码稍微上滚一点,就会看到“start”函数中针对“Rnd”的唯一的一个调用,具体如下所示:
这是该勒索软件对“Rnd”的第一次调用,并且是生成文件和子目录名称的循环代码之前的唯一的一次调用。
另一方面,执行口令生成代码的“_ServiceMain”线程对“Rnd”总共调用了3次,并且都是在它使用PRNG构造口令之前调用的,具体见下图。
这三个针对“Rnd”的调用,都先于“_ServiceMain”线程中生成口令的那些代码。
因为存在这三个调用,因此,当我们准备开始生成候选密码的时候,每次初始化好随机数种子值后,最好放弃前三个随机数。
现在,我们终于要开始暴力破解了。对于寻找名称的种子值的代码,如下所示。需要注意的是,为了简洁起见,我们这里省略了实现PRNG的代码。
在一个单核机器上面,我们使用了大约4秒钟的时间,测试了31956209种可能的值,并发现最后一个种子值即生成的字母序列与“”找到的完全一致。这个数字说明,我们之前的观点是正确的。
尽管我们这个将这些信息转化为结果的系统算不上优雅,但作为原型却是行之有效的。即使我们靠硬猜的话,需要尝试的用来生成口令的种子值不会超过32768或者180000(以毫秒为单位的3分钟时间),就能找出前面恢复出来的名称对应的种子值。因此,我们可以根据这个范围内的种子值来生成一个大约包含200000个口令的清单,代码如下所示:
我们让这个批处理文件通宵运行,在第二天早晨终于找到了期盼已久的东西:正确的口令。至此,我们大功告成了! 我们没有辜负受害者对我们的信任,最终将其数据恢复如初。
相关资源:[手机软件]测谎仪.rar-Java文档类资源-CSDN文库
声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!