加密101系列:如何构建自己的解密工具

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

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

代码

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

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

[[226867]]

首先看一下main.cpp文件:

  1. #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,所以我们用当前时间来测试密钥的生成。

  1. do { 
  2.  DWORD uid_seed = find_start_seed(unique_id, extension, init_seed); 
  3.  key = find_key(filename1, filename2, check_size, uid_seed, limit); 
  4.  init_seed = uid_seed - 1; 
  5.  } 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了。如果有其中的两个因素,就更加确认。

下面看一下扩展部分:

  1. if (uid)  
  2.     ext_seed = find_uid_seed(ext, uid_seed, true); 
  3. }  
  4. else  
  5.      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之前都需要进行回退。

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

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

  1. DWORD find_uid_seed(wchar_t* uid, DWORD start_seed, bool increment = true
  2.  return find_seed(uid, start_seed, increment, false); 

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

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

  1. while (true) {  
  2.  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函数和上面的步骤:

  1. 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函数中。

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

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

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

  1. for (key_len = MIN_KEY; key_len <= MAX_KEY; key_len++) { 
  2.      if (check_key(in_buf, expected_buf, check_size, key, key_len)) { 
  3.          printf("\nMatch found, accuracy %d/%d\n", check_size, BLOCK_LEN); 
  4.          key[key_len] = 0; 
  5.          found = true
  6.          break; 
  7.      } 
  8.  } 

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

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

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

责任编辑:武晓燕 来源: 4hou
相关推荐

2021-07-25 21:28:55

人脸识别人工智能工具

2020-12-31 14:30:23

机器学习人工智能程序

2016-09-18 10:08:38

Linux发行版SUSE Studio

2012-12-14 17:49:44

加密解密DLP

2022-06-04 12:25:10

解密加密过滤器

2021-03-09 13:18:53

加密解密参数

2021-02-01 08:00:00

vimLinux加密

2010-04-14 20:57:13

2011-08-01 14:14:36

加密技术

2018-07-30 11:56:17

解密加密开发

2020-02-24 11:11:10

IT企业技术

2022-06-23 09:44:01

LinuxLive CD

2022-09-26 08:35:53

磁盘Java解密

2010-07-06 10:35:59

2016-05-11 10:51:53

Airbnb数据科学知识仓库

2012-12-13 21:50:43

2016-04-22 17:30:50

软件加密软件授权

2018-05-23 15:58:27

Spring Clou微服务架构

2022-03-23 16:03:51

加密货币私钥网络安全

2023-03-06 08:49:02

加密和解密SpringBoot
点赞
收藏

51CTO技术栈公众号