本篇是加密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()函数和其他的逻辑细节。
看一下 314行, printf(“Searching the key…“);
图中的所有都是输入检查,来确保提供的输入值是合适的。
Main函数剩下的部分:
下一行是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如图所示:
***个调用是在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搜索的主要代码。
在变量初始化完成后,我们看一下loop的内容:
- while (true) {
- srand(seed);
一般在loop中勒索软件会重新创建生成随机数。这就是为什么用srand(seed)开始,seed就是传递的时间。这也决定了用rand调用后的序列数。
在我们建立的loop中,用随机数作为字符集的索引。如果生成的数字与用户提供的UID不匹配,就说明seed不准确,那么时间就递减,并再次尝试。
如果被调用来找ext扩展的函数在之前的调用中找到了UID,seed时间就会递增。下面的图解释了在不同的时间和阶段有递增和递减。
开始的阶段会决定传递给find_seed函数的是true还是false,也决定了是时间递增还是递减。
主函数
我们了解了find seed函数的细节后,下面看一下回到find_start_seed再回到主函数。
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行的代码:
在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行:
加密101系列:如何构建自己的解密工具
如果在有限的集合里没有找到正确的AES密码,就是说UID的seed时间是不正确的。这个过程会继续直到找到正确的UID seed为止。
结论
加密101系列为大家介绍了勒索软件加密过程的漏洞,并讲解了如何利用这些漏洞进行解密工具的开发。如果读者了解了这些,那么也很容易就可以创建一个新的勒索软件。
而其中最难的部分就是核心概念。我们看到了常见的一些概念和技术以一种不常见的方式出现,最终也理解了这些底层的技术。最重要的是理解这些概念,然后识别和创建自己的漏洞利用过程,这样就可以破解勒索软件的加密过程。