导语:本篇文章将介绍Princess Locker解密工具的源代码。学习这篇文章,可以通过了解解密工具的工作原理和代码来创建自己的解密工具。

本篇是加密101系列的最后一篇,将介绍Princess Locker解密工具的源代码。学习这篇文章,可以通过了解解密工具的工作原理和代码来创建自己的解密工具。

代码

先总体看一下程序中的所有函数,看看这些函数的功能以及如何配合使用。然后再详细分析。

该工具的全部源码如下:Princess Locker解密工具源代码。研究人员强烈推荐在阅读本文的时候在另一个窗口打开全部的源码,文章与源码对应,效果更佳。

首先看一下main.cpp文件:

#define CHARSET "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"

还记得Princess的RNG部分吗?生成的随机数会被用于查找字符串中字母的索引。如果生成的随机数是38,那么就在受害者ID字符串中加一个字母c,因为c在数组中第38的位置。这就是CHARSET的定义和用途。

下面是一些比较重要的函数:

· find_key

· dump_key

· check_key

· find_start_seed

· find_seed

· find_key_seed

· find_uid_seed

· make_key

还有一些没有提到的函数,这些函数要么与加密或密钥搜索没有直接相关,要么是自解释的函数。比如,read_buffer就是帮助从文件中读取缓存的函数。

· find_key – 用seed值递增地生成密钥,解密文件,检查原始文件

· dump_key – 将密钥写入文件

· check_key – 用可能的密钥,解密文件,与原始版本进行比对

· find_start_seed – 封装函数,调用其他函数找到用作UID或ext的seed值;

· find_seed – 递增的测试seed,产生随机字符串来验证UID

· find_key_seed – 未使用,NOT USED, find_seed的封装

· find_uid_seed – find_seed的封装,设置isKey参数为false

以上函数中, find_seed和find_key完成工作的主要部分。

Main函数

下面看一下wmain()函数和其他的逻辑细节。

1.png

看一下 314行,  printf(“Searching the key…“);

图中的所有都是输入检查,来确保提供的输入值是合适的。

Main函数剩下的部分:

2.png

下一行是DWORD init_seed = time(NULL),这是获取当前时间的函数。这也是第一个测试的seed。当前时间是随机数生成器(RNG)的一个seed,所以我们用当前时间来测试密钥的生成。

do {
 DWORD uid_seed = find_start_seed(unique_id, extension, init_seed);
 key = find_key(filename1, filename2, check_size, uid_seed, limit);
 init_seed = uid_seed - 1;
 } while (!key);

然后我们发现find_start_seed函数被调用了。其中的参数unique_id, extension, init_seed顺序如下:ransom note ID 或NULL,如果不使用的话就是NULL。

随着loop递进,init_seed会递减,因为必须在Princess感染发生时找到第二个值。一共有三个不同的第二个值用来生成RNG seed,分别是扩展,UID和AES密钥密码。一旦找到其中的一个值,就很容易找到其他两个。

下面看一下find_start_seed的细节,假设从勒索note中得到的UID如图所示:

3.png

第一个调用是在find_uid_seed(),这是find_seed()的封装函数。传递的false的布尔变量说明查找的UID只是递减的。这是因为我们以当前时间开始,所以很明显,感染是在过去发生的。find_uid_seed函数的结果就是seed值。大家注意UID变量的if,因为ransom ID不是必须的,这里将结果变得更准确了。如果有人的UID并不是ransom ID,那么就会用文件扩展名来解密。

因为UID是感染过程中RNG seed的一部分,随机文件类型是另一个,AES密码是第三个。所以,有了文件扩展或者UID就可以找出AES密码seed了。如果有其中的两个因素,就更加确认。

下面看一下扩展部分:

if (uid) 
{
    ext_seed = find_uid_seed(ext, uid_seed, true);
} 
else 
{
     ext_seed = find_uid_seed(ext, seed, false);
}

如果UID传递过来了,那就说明我们找到了UID的seed值。如果是这样就可以用seed值作为寻找扩展seed的入口。在Princess Locker的分析中,发现UID是第一个seed然后会生成扩展RNG。

我们首先通过UID seed时间去找扩展seed时间。如果UID不是用户提供的,就可以看到调用的false变量传递,seed就是当前时间的seed,也就是说在我们找到与扩展匹配的seed之前都需要进行回退。

if (uid && ext_seed - uid_seed > 100) {
 printf("[WARNING] Inconsistency detected!\n");
 }

在找到UID seed时间和扩展seed后,进行最后的检查,并确保差生小于100的。原因是因为在Princess Locker执行时,会生成UID seed,然后再很短的一个代码流后,就会生成扩展seed。如果这两个的时间超过100秒,就会发生一些奇怪的事情。

DWORD find_uid_seed(wchar_t* uid, DWORD start_seed, bool increment = true)
{
 return find_seed(uid, start_seed, increment, false);
}

find_uid_seed只是sind_seed的封装函数,sind_seed是seed搜索的主要代码。

4.png

在变量初始化完成后,我们看一下loop的内容:

while (true) {
 srand(seed);

一般在loop中勒索软件会重新创建生成随机数。这就是为什么用srand(seed)开始,seed就是传递的时间。这也决定了用rand调用后的序列数。

在我们建立的loop中,用随机数作为字符集的索引。如果生成的数字与用户提供的UID不匹配,就说明seed不准确,那么时间就递减,并再次尝试。

如果被调用来找ext扩展的函数在之前的调用中找到了UID,seed时间就会递增。下面的图解释了在不同的时间和阶段有递增和递减。

5.png

开始的阶段会决定传递给find_seed函数的是true还是false,也决定了是时间递增还是递减。

主函数

我们了解了find seed函数的细节后,下面看一下回到find_start_seed再回到主函数。

6.png

find_start_seed是一系列loop,在调用后会找到一个工作的seed值。如果ransom ID和ext扩展都提供了,就会返回UID seed,UID seed的时间和AES密码seed的时间很接近。

如果不提供UID,就会返回找到的ext扩展的seed。从时间上看,UID seed时间是在AES seed时间之后的。也就是说需要在loop中做一些事情:

· Seed值递减1;

· 用随机seed生成AES密码;

· 加密和解密测试文件;

· 与已有的纯净版的文件做检查比对。

下面看一下find_key函数和上面的步骤:

wchar_t* find_key(IN wchar_t *filename1, IN wchar_t *filename2, size_t check_size, DWORD uid_seed, DWORD limit=100)

下面看一下第234行的代码:

7.png

在ransom note ID生成的时候设定key_seed变量,发现find_start_seed也被传入find_key函数中。

do {
 key = make_key(MAX_KEY, key_seed, true);

找key的loop中第一行就是调用make_key。因为这和生成UID和ext是一样的,所以这里不再详细描述。只是用字符集字符串中的索引来做seed并生成一个随机大小的ransom note ID字符串。

下面是创建AES密钥密码和进行哈希操作的loop,这也正是Princess Locker做的。它本身并不会使用随机生成的密码,而是创建一个随机的字符串,然后用sha256哈希,用哈希值作为最终的密钥。最后,解密检查key。

for (key_len = MIN_KEY; key_len <= MAX_KEY; key_len++) {
     if (check_key(in_buf, expected_buf, check_size, key, key_len)) {
         printf("\nMatch found, accuracy %d/%d\n", check_size, BLOCK_LEN);
         key[key_len] = 0;
         found = true;
         break;
     }
 }

check_key函数会执行下面的操作:

aes_decrypt(inbuf, outbuf, BLOCK_LEN, key_str, key_len)

用测试的随机生成的密码来创建AES加密,查看是否正常工作。

这个过程中可能有几个争议问题。比如,为什么浪费时间来检查其他的RNG?为什么要找ext扩展和UID seed,什么时候可以测试seed与测试的AES解密是否匹配?

理论上我们可以做,但是将字符串与下面列出的进行比较要更快一点:

· 生成随机字符串;

· 对字符串进行哈希;

· 创建AES密钥;

· 加密数据;

· 与源文件比对。

这个过程就偏计算一些,而且会让UID搜索时间变长。

因为AES key是在Princess Locker执行过程中生成的,ransom ID是在ransom note ID(UID)找到后马上生成的,这两个seed之间的时间差应该很短。所以,loop只需要执行一些加密检查就可以了。

在完成find key函数后,我们可以做一些基本的检查,确保找到正确的key。如果没有找到正确的key,就需要继续loop并且递减计数器。

下面看一下main函数的最后一部分,320行:

8.png

如果在有限的集合里没有找到正确的AES密码,就是说UID的seed时间是不正确的。这个过程会继续直到找到正确的UID seed为止。

结论

加密101系列为大家介绍了勒索软件加密过程的漏洞,并讲解了如何利用这些漏洞进行解密工具的开发。如果读者了解了这些,那么也很容易就可以创建一个新的勒索软件。

而其中最难的部分就是核心概念。我们看到了常见的一些概念和技术以一种不常见的方式出现,最终也理解了这些底层的技术。最重要的是理解这些概念,然后识别和创建自己的漏洞利用过程,这样就可以破解勒索软件的加密过程。

源链接

Hacking more

...