wannacry勒索病毒加密解密过程分析
病毒介绍
*遭受WannaCry病毒侵害的电脑,其文件将被加密锁死,惯常来说,受害用户支付赎金后 可以获得解密密钥,恢复这些文件。但是根据火绒工程师的分析,遭受WannaCry攻击的 用户可能会永远失去这些文件。 WannaCry 病毒存在一个致命缺陷,即病毒作者无法明确认定哪些受害者支付了赎金,因 此很难给相应的解密密钥,所以用户即使支付了赎金,也未必能顺利获得密钥该电脑系统 及文件依旧无法得到恢复。 至于网上流传的各种“解密方法”,基本上是没用的,请大家切勿听信谎言,以防遭受更多 财产损失。一些安全厂商提供的“解密工具”,其实只是“文件恢复工具”,可以恢复一些被 删除的文件,但是作用有限。
因为病毒是生成加密过的用户文件后再删除原始文件,所以存在通过文件恢复类工具恢复 原始未加密文件的可能。但是因为病毒对文件系统的修改操作过于频繁,导致被删除的原 始文件数据块被覆盖,致使实际恢复效果有限。且随着系统持续运行,恢复类工具恢复数 据的可能性会显著降低。*
该病毒分为两个部分:
蠕虫部分,用于病毒传播,并释放出勒索病毒。
勒索病毒部分,加密用户文件索要赎金。
以下是对wnry的行为分析。关于病毒这一部分的分析,网上已经很丰富了。相应的分析在文末会有链接。我也没有必要再赘述,本文主要对其加解密部分进行分析。
t.wnry的解密过程
为什么要单独提这个呢,是因为这一部分的解密逻辑于后面是一致的。
t.wnry使用的是Rijndael S-Box(AES)的一种加密算法,可以使用Hash & Crypto Detector工具来检测加密算法。这里AES所使用的key是包含在t.wnry中的,同时处于加密状态
首选会调用CryptImportKey去导入key,再继续进入CryptImportKey函数
开始先获取加密的上下文,然后在导入key,在跟进CryptAcquireContextA
Key需要解密,使用的是系统自带的解密API,如下图所示:
这里的解密密钥是保存在进程内存中的(RSA2)
加密Key的是一个RSA2的算法,上面的数据是一个私钥
进入解密函数,这个函数的作用是对真实密钥进行扩展操作,根据算法位数进行相应的扩展。
然后就调用sub_40350F解密,解密的结果copy到缓冲区,
接下来主要分析加密dll文件,在加密前会加载密钥,在加密过程中程序会规避系统运行所需文件以及自身文件
这里为了分析方便,所以先将内存中的t.wnry.dll文件dump出来。dump的方法如下:
1)使用x64dbg动态调试至解密后的地方,然后记录解密内容的首地址和长度。
2)使用PETools工具将wcry.exe进程的内存dump下来(右键菜单Dump Partial)。
我们可以看到EAX寄存器中pe头“mz”
dump下来即可
加密过程
1.在本地生成一组密钥对:私钥B和公钥B
2.然后作者使用自己的公钥A对本地的私钥B进行RSA加密
3.加密文件前再生成一个16位的随机Key来
4.使用本地公钥B对随机Key进行RSA加密,然后再保存到加密文件的头部。注意:每个文件的加密Key都是随机不同的。每次加密都会重新生成key。所以同一个文件的两次密文是不同的。
5.然后使用随机Key对文件内容进行AES对称加密。
加密过程如下图所示
为什么会使用这么复杂的加密方式呢?这主要是效率上的考量,回顾密码学里的知识,AES作为对称加密技术,他的加密效率远高于RSA。但是RSA作为非对称加密技术,安全性更强。因此使用aes密钥加密文件(效率很高)后再将aes密钥加密。且加密两次,那么解密是十分困难的。
其加解密用到的是系统自带API,通过GetProcAddress来获取地址动态调用的。
CryptAcquireContext 函数用于获取特定加密服务提供程序中特定密钥容器的句柄 (CSP) 。 此返回的句柄用于调用使用所选 CSP 的 CryptoAPI 函数。
BCryptImportKey 函数从密钥 BLOB 导入对称密钥。cryptImportKey 函数 (wincrypt.h) - Win32 apps | Microsoft Learn[in] pbData
一个 BYTE 数组,其中包含一个 PUBLICKEYSTRUC BLOB 标头,后跟加密密钥。 此密钥 BLOB 由 CryptExportKey 函数创建,此应用程序或可能在另一台计算机上运行的其他应用程序创建。
CryptEncrypt 函数加密数据。 用于加密数据的算法由 CSP 模块持有的密钥指定,并由 hKey 参数引用。
CryptDecrypt 函数使用 CryptEncrypt 函数解密以前加密的数据。
加密是会加载相应dll
这里是生成AES密钥 -随机生成
要想实现解密可以通过拦截aes的key被加密即可,但是由于每次生成的key都为随机,所以这种解密方法不具有通用性。只能解密拦截的文档。
那我们就尝试拦截密钥对的生成,可这个生成是在病毒刚刚发作的时候进行的。我们是不可能预知这个操作的。但这里可能存在一个漏洞,就是生成的明文私钥,还可能残留在内存中,只要原来的明文内存没有被覆盖掉,那就有可能在内存中找到。这就是一个希望,类似于磁盘恢复一样,在内存中恢复私钥。
拦截的位置如下所示
如下图:
该函数的主要功能是导出密钥数据并使用另一个密钥进行分块加密,然后将结果存储在全局内存块中。在加密后会将原来的明文内容覆盖掉
密钥生成和处理的步骤
导出密钥:
- 使用
CryptExportKey
函数导出密钥数据到pbData
缓冲区。
- 使用
获取密钥参数:
- 使用
CryptGetKeyParam
函数获取用于加密的密钥参数,存储在v25
中。
- 使用
计算分块信息:
- 根据获取的密钥参数,计算每块的数据大小及总块数。
分块加密:
- 分配内存块
hMem
用于存储加密后的数据。 - 逐块处理数据,将每块数据存储到
v27
中,并使用dword_1000D948
函数对其进行加密,然后将加密后的数据复制到hMem
中。
- 分配内存块
清空缓冲区:
- 在加密完成或发生错误后,清空
pbData
缓冲区。 - 如果发生错误,还会清空
v27
缓冲区,并释放分配的全局内存块hMem
。
下面这里进行清空操作
- 在加密完成或发生错误后,清空
虽然这里进行了清空操作,但是依然会有一段时间会在内存中出现
我们定位 pbData
缓冲区:
用于存储
CryptExportKey
导出的密钥数据。如果
CryptExportKey
或其他操作失败,pbData
会被清零。在分块加密之后,
pbData
被清零。
这里无法对该dll进行动态调试,需要进行一些操作,需要讲application改为tasksche.exe
定位过程
在分析给定的代码时,pbData
的值在 GlobalFree
之前存放在内存位置中,而不是在特定的寄存器中。我们可以通过代码的执行路径来定位 pbData
的值存放在哪个寄存器或内存位置中。
首先,pbData
是一个 4096 字节的缓冲区,用于存储从 CryptExportKey
导出的密钥数据。整个分析过程可以分为几个关键步骤:
在分析给定的代码时,pbData
的值在 GlobalFree
之前存放在内存位置中,而不是在特定的寄存器中。我们可以通过代码的执行路径来定位 pbData
的值存放在哪个寄存器或内存位置中。
首先,pbData
是一个 4096 字节的缓冲区,用于存储从 CryptExportKey
导出的密钥数据。整个分析过程可以分为几个关键步骤:
CryptExportKey
导出密钥数据如果1
2
3call ds:CryptExportKey
test eax, eax
jnz short loc_100041C2CryptExportKey
调用成功(eax
非零),代码会跳转到loc_100041C2
继续执行。pbData
的值在内存中的存储pbData
在内存中的偏移量为0x30
(即esp+2024h+pbData
),这是一个固定的位置。- 清空
pbData
缓冲区
如果任何步骤失败,会清空pbData
缓冲区。在这个代码块中,1
2
3
4
5
6mov ecx, 1000h
loc_100041F2:
mov byte ptr [eax], 0
inc eax
dec ecx
jnz short loc_100041F2pbData
被清零。 - 成功路径中的
pbData
访问
在成功路径中,pbData
被复制到另一个缓冲区中以进行进一步处理。这里1
lea esi, [esp+eax+2028h+pbData]
esi
指向pbData
缓冲区。 - 清空
pbData
缓冲区的最后一步
在跳转到loc_10004300
时,pbData
缓冲区将被再次清空:1
2
3
4
5
6
7mov ecx, [esp+2024h+pbData]
mov edx, 1000h
loc_10004309:
mov byte ptr [ecx], 0
inc ecx
dec edx
jnz short loc_10004309ecx
被设置为指向pbData
缓冲区的起始地址,并通过循环清零该缓冲区。 - 释放内存前的
pbData
存储
在GlobalFree
调用之前,pbData
的值已经被存储在esp+2024h+hMem
:1
2
3mov eax, [esp+2024h+hMem]
push eax
call ds:GlobalFreepbData
的值在GlobalFree
之前存放在内存位置esp+2024h+pbData
和esp+2024h+hMem
中。具体的寄存器中并没有直接存储pbData
的值,而是通过内存位置间接访问。
rsa加密的私钥
作者公钥->本地私钥->本地公钥->随机Key->加密文档
整个过程都是环环相扣的。如果想要解密你的文档,可能只有将你的私钥提供给作者,然后作者再使用自己的私钥将你的私钥解密。然后在发送给你一个未加密的私钥。最后你使用这个私钥来解密自己的文档。
没有作者的私钥就永远无法解密文档。但本着探索的精神,可以下断点使得第二次rsa加密不进行,然后编写程序对文档进行还原:
1 | from cryptography.hazmat.primitives.asymmetric import rsa, padding |
总结
wannacry是一个相当复杂的样本,本人不觉得自己的分析会比从业多年的大牛更加深入有条理。本文对加密策略的分析与解密的尝试也是对网上已有文章的进一步拓展。
参考文章: