avatar

目录
第13章 数据加密

第13章 数据加密

13x1 分析加密算法的目的

恶意代码用加密来达到各种各样的目的。最常见的是加密网络通信,同时,恶意代码也会用加密来隐藏它的内部工作。例如,恶意代码编写者可能因为如下目的而使用加密:

·隐藏配置信息。例如,命令和控制服务器域名。
·窃取信息之前将它保存到一个临时文件。
·存储需要使用的字符串,并在使用前对其解密。
·将恶意代码伪装成一个合法的工具,隐藏恶意代码活动中使用的字符串。

分析加密算法时,我们的目标由两部分组成:识别加密算法,然后根据识别的加密算法解密攻击者的秘密。

13x2 简单的加密算法

1. 凯撒密码

2. XOR

XOR加密使用一个静态字节值,通过与该值执行逻辑异或运算来修改明文中的每个字节。

暴力破解XOR加密

由于文件中的每个字符只有256种可能的值,对于一个计算机来说很容易并且能够足够快地使用255个单字节密钥来异或文件头部,然后将输出与期望的可执行文件头部进行比较。可以使用一个脚本来执行用255个密钥的XOR加密。

保留NULL的单字节XOR加密

单字节加密的一个漏洞:它对使用十六进制编辑器手动扫描加密内容的用户缺乏有效的隐蔽性。如果加密内容中有大量的NULL字节,那么单字节密钥变得十分明显。

与标准的XOR加密策略不同,保留NULL的单字节XOR加密策略有两个例外。

  1. 如果明文中字符是NULL或者密钥本身,则被跳过。
  2. 如果明文中字符既不是NULL也不是密码本身,则将被使用XOR密钥加密。

用IDA Pro识别XOR循环

在反汇编中,通过循环语句中间使用XOR指令的小循环语句找到了XOR循环。用IDA Pro找到XOR循环的一个最简单方法是搜索指令中XOR指令,如下:
1.确保你正在查看代码(窗口的标题应该包含“IDAView”)。
2.选择Search→Text。
3.在文本搜索对话框中输入xor,选中Find all occurrences复选框,然后单击OK按钮。

搜索到XOR指令并不意味着它一定用于加密。XOR指令可以用于不同的目的,其用途之一就是清空寄存器的内容。XOR指令以三种形式存在。

  1. 用寄存器自身XOR。
  2. 用一个常量(或一个内存引用)XOR。
  3. 使用一个不同寄存器(或一个内存引用)XOR。

最常见的是第一种形式,因为寄存器与自身异或是清零寄存器的一种有效方式。幸运的是,清零寄存器与数据加密无关,所以可以忽略它。

XOR加密循环可能使用另外两种形式中的一种:用一个常量异或一个寄存器,或者用一个不同的寄存器异或一个寄存器。如果你幸运,XOR加密是一个常量异或寄存器的XOR,因为通过它,可以确认你可能发现了加密,并且也可以知道密钥。

加密的迹象之一就是含有一个包含XOR函数的嵌套循环。

3. 其他一些简单的加密策略

4. Base64

术语Base64来自于多用途Internet邮件扩展(MIME)标准。虽然Base64最初用于加密传输的邮件附件,但是现在它却广泛用于HTTP和XML。

Base64编码将二进制数据转换成64个字符的有限字符集。对于不同类型的Base64加密,有多种不同的策略或字母表。但是它们都使用64个主要的字符,另外,它们通常用一个额外字符表示填充,通常是“=”。

最常用的字符集是MIME Base64,它使用AZ、az和0~9作为前62个值,+和/作为最后两个值。由于需要将数据压缩成一个较小字符集,给Base64加密后的数据会比原始的数据长。对于3个字节的二进制文件,加密后是至少4个字节的Base64加密数据。

数据转化成Base64

原始数据转换成Base64的过程相当标准。它使用24位(3个字节)的块。第一个字符被放到最重要的位置,第二个字符放在中间的8位,第三个字符放在最不重要的后8位。接下来,从最重要位置的开始,位被读入到6个块中。

上图展示转换发生的过程,最上面一行是原始的字符串(ATT)。第二行是ATT的半字节表示(半字节等于4位)。中间一行用来表示ATT的实际位。第四行以6位段十进制表示位值。最后,最终的字符串通过十进制索引参考字符串得出字符。

由上图可见,字母A相应的比特位是01000001。A字母的前6位被转换成Base64加密的单字符Q。字母A的最后两位(01)和字母T前四位(0101)被转化成Base64加密的第二个字符V(010101)。

将Base64解密成原始的数据遵循相同的过程,但是过程是相反的。首先将每个Base64字符转换成6个比特位,将所有比特位按顺序存放。然后,以八位一组读取比特位,每组8个比特位表示原始数据的字节。

识别和解密Base64

在技术上,填充字符是可选的,并且它们对解密的准确性不是必须的。恶意代码知道避免使用这种填充字符,想必是为了使它们看起来不像Base64编码并避免网络特征。

因为Base64加密的实现通常使用索引字符串,含有Base64加密的代码经常会存在这个64字符组成的加密字符串。通常情况下,索引Base64的字符串由可打印字符组成(或者它会破坏该算法的意图),因此可以很容易用眼睛察觉字符串输出。

第二个可以被用来确认使用Base64加密算法的证据是存在一个单独填充字符(常为“=”),这个单独的填充字符常被硬编码到执行加密的函数中。

13x3 常见的加密算法

简单的加密算法与现代的加密算法不同,它们等同于替换算法。现代的加密算法考虑了增加指数级的计算能力,并且确保设计的算法需要大量的计算能力,从而使破解它们不切实际。

先前我们讨论的简单加密策略并不试图对暴力破解方法加以保护,它们的主要目的是隐藏。随着时间的推移,加密算法逐渐改进和发展,并且它们已经深入到计算机应用的各个方面,例如网页浏览器中的SSL,无线接入点使用的加密等等。那么,为什么恶意代码不使用这种加密算法来隐藏敏感信息呢?恶意代码使用简单的加密策略是因为它们容易且足够使用。此外,使用标准的加密存在一些潜在的漏洞,特别是对于恶意代码来说:

  1. 加密库很大,所以恶意代码需要静态的集成或者链接到已有的代码中。
  2. 链接主机上现有的代码可能降低可移植性。
  3. 标准加密库比较容易探测(通过函数导入,函数匹配或者加密常量标识)。
  4. 对称加密算法需要考虑如何隐藏密钥。

很多标准加密算法都依赖于一个强大的密钥来存储它们的秘密。算法本身是公开的,但是如果没有密钥,几乎不可能(也就说它需要大量工作)破解加密的密文。要确保解密费时费力,通常情况下,密钥必须足够长从而使得测试所有可能的密钥不那么容易。对于恶意代码可能使用的标准加密算法,关键是不仅要识别加密算法,而且还要识别密钥。

有一些简单方法可以识别标准加密。它们包括查找字符串和引用加密函数的导入,使用一些工具寻找特定的内容。

1. 识别字符串和导入

一种识别标准加密算法的方法是识别涉及加密算法使用的字符串。当加密库(如OpenSSL)被静态地编译到恶意代码时,这种情况便会发生。

另外一种查找标准加密算法的方法是识别引用导入的加密函数。

2. 查找加密常量

第三种检测加密的基本方法是使用可以搜索常见加密常量的工具,这里,我们使用IDA Pro的FindCrypt2和Krypto ANALyzer插件。

使用FindCrypt2

IDAPro有一个叫做FindCrypt2插件,包含在IDAPro的SDK中,它搜索程序中任何与加密算法相关的已知常量。这样做效果很好,因为多数加密算法会使用一些神秘的常量类型。所谓神秘常量则是与基本加密算法结构相关的一些固定位串。

注意:一些加密算法并不使用神秘常量,值得注意的是,国际数据加密(IDEA)算法和RC4算法动态地创建它们的结构,因此它们不在可识别的算法之中。恶意代码常使用RC4算法,因为它体积小,在软件中易于实现,并且没有明显的加密常量。

与IDAPRO的插件FindCrypt2原理相同的一个工具是Krypto ANALyzer(KANAL)。KANAL是PEiD的一个插件,它拥有一个范围更广的常量集合(作为结果可能更加容易产生误报)。除此之外,KANAL还能够识别Base64编码表以及加密相关的导入函数。

3. 查找高熵值内容

识别加密算法的另一方法是查找高熵值的内容。除了识别潜在的明显的加密常量或者加密密钥外,这种技术也可以识别加密内容本身。由于这种技术的影响广泛,可以适用于没有找到加密常量的地方(如RC4)。

警告:高嫡内容技术相当迟钝,最好作为最后一种使用手段。多种类型的内容,如图片、电影、音频文件以及其他压缩数据等,也会显示高篇值,除了它们的头部特征之外,很难与加密内容进行区分。

IDA的熵值插件(http://www.smokedchicken.org/2010/06/idaentropy-plugin.html )是针对PE文件使用这种技术的一个工具。将ida-entplw文件放置到IDAPro的插件目录,就可以将这个插件载入到IDA Pro。

包含64个不同字节值的64字节字符串拥有最高可能的熵值,这64个值与熵值6相关(指6比特位的熵),因为6比特位表示的数字是64。

另外一种可能有用的设置是大小为256的块,其熵值大于7.9。这也意味一个256个连续字节的字符串几乎反应出所有256个可能的字节值。

IDAPro的熵值插件还有一个提供图形化概览感兴趣区域的工具,可以用它来引导你选择最大熵评分值,同时也帮助你确定关注范围。绘图按钮产生一个图,它将高熵值区域显示为亮条,将低熵值区域显示为暗条。在图上移动鼠标光标,可以看到图中某些点的原始熵值。因为熵的地图在打印格式下很难准确表达。

13x4 自定义加密

恶意代码常使用自创的加密方案,一种方案是将多个简单加密方法组装到一起。例如,恶意代码可以先执行一次XOR加密,然后在XOR加密基础上执行Base64加密。另外一种方案就是开发一种与标准加密算法相似的自定义加密算法。

1. 识别自定义加密

发现加密算法的艰难历程是从可疑的输入或者输出开始跟踪运行的线程。输入和输出是一个通用分类,无论恶意代码是发送网络数据包,写入文件或者写入标准输出,这都是输出。如果怀疑输出中包含加密数据,那么加密函数应该出现在输出之前。

相反,解密则出现在输入之后。例如,假设你确认了一个输入函数,则你首先应该识别出跟输入有关的数据元素,然后向后跟踪执行路径,找到访问相关数据元素的新函数。如果你到达一个函数的末尾,你应该在调用函数发生的地方继续,并且再次注意数据的位置。多数情况下,加密函数离输入函数不远。除了反向跟踪执行路径外,输出函数也是类似的。

2. 攻击者使用自定义加密的优势

对于攻击者,自定义加密方法拥有它自身的优势,主要是因为它们保留了简单加密策略的特点(体积小和加密不明显),同时使逆向工作变得十分困难。逆向这种类型的加密(也就是识别加密过程并且开发解密器)比标准加密方法是否更加困难存在一些争议。

对于多种标准加密来说,如果识别了加密算法并且发现了密钥,则很容易使用标准的函数库来编写解密器。对于自定义加密,攻击者可以根据自己的需要创建任意的加密方案,它们可能使用也可能不使用一个明显的密钥。正如前面的例子中介绍的,密钥嵌入到代码中,甚至攻击者使用了一个密钥并且被我们发现,但是,不可能存在一个免费的函数库供我们实现解密。

13x5 解密

重现恶意代码中的加密或解密函数的两种基本方法

  1. 重新编写函数
  2. 使用恶意代码中存在的函数

1. 自解密

最经济的解密方法是:无论算法是否已知,让程序正常活动期间自己完成解密。我们称这种方法为自解密。如果你在调试器中暂停了恶意程序,并且注意到内存中的一个字符串,在你运行字符函数时你没有看到它,那么你已经使用自解密技术。无论先前被隐藏的信息在何处被解密,中断解密过程并做进一步分析,比尝试着确定使用了何种加密机制和尝试构造解密器更加容易虽然自解密是一种廉价且有效的解密方式,但是它也有缺点。首先,为了确认执行的每一个解密实例,你都必须要分离解密函数,并且在解密例程之后直接设置断点。更重要的是,如果恶意代码没有解密你感兴趣的信息(或你不知道如何欺骗恶意代码让其这么做),那你就会很倒霉。由于这些原因,使用这种技术的关键是提供更多控制。

2. 手动执行解密函数

对于简单的加密和编码方法,通常你可以使用编程语言提供的标准函数。

对于缺少标准解密函数的简单加密方法,如XOR加密或使用修改字母表的的Base64加密,最简单的方法是使用你选择语言中的程序或脚本的加密函数。

Base64脚本

Code
import string
import base64

example_string=' VChpcyBpcyBhIHR1c3Qgc3RyawSn'
print base64. decodestring(example_string)

保留NULL字节的XOR加密算法的Python脚本

Code
def null_preserving_xor(input_char,key_char):
if(input_char s= key_char or input_char == chr(0x00)):
return input_char
else:
return chr(ord(input_char)^ord(key_char))

这个函数需要两个字符:一个输入字符,一个密钥字符,并且输出一个转换后的字符。为了使用保留NULL的单字节XOR加密算法来转换一个字符串或者一个更长的内容,将它们的每一个输入字符和一个相同密钥一起传给这个子例程(函数)。

自定义Base64编码的脚本

Code
import string
import base64

s = ""
custom="9ZABCDEFGHIJKLMNOPQRSTUVWXYabcdefghijklmnopqrstuvwxyz012345678+/"
Base64="ABCDEFCHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"

ciphertext='TEgobxZobxZgCFPkb20'

for ch in ciphertext:
if(ch in Base64):
s = s + Base64[string.find(custom, str(ch))]
elif(ch == '='):
s += '='
result = base64.decodestring(s)

对于标准的加密算法,最好是使用代码库中提供的现有实现。一个基于Python的加密库叫做PyCrypto(hup://www.dlitz.net/software/pycrypto/ ),它提供了各种各样的加密函数。类似的库也存在于其他不同的语言中

DES脚本

Code
from Crypto.Cipher import DES 
import sys

obj = DES.new("password", DES. MODE_ECB)
cfile=open('encrypted_file','r')
cbuf=f.read()
print obj.decrypt(cbuf)

利用导入的PyCrypto库,脚本打开一个名为encrypted_file的加密文件,并且使用密码pas sword,用电子密码本(ECB)模式进行了DES破解。

分组加密与DES加密类似,它使用不同的加密模式将一个单密钥应用到任意长度的明文流中,并且在库调用时指定模式。最简单的模式是ECB,它将分组密码应用到每个单独的明文块上。

脚本加密算法有多种可用的变种。前面的例子为编写自己的解密器提供了一个有效的选项类型。

攻击者编写自己版本的加密算法,通常要保留加密算法的简单性与良好的定义(以标准加密算法为例)。在处理非常复杂而且不标准的加密算法时,由于很难模拟算法,因此会成为更艰巨的挑战。

3. 使用通用的解密规范

在自解密中,试图让恶意代码自己解密时,你应该让恶意代码以正常方式运行,并且让其在合适的时间停止。但是,当你能够控制它的时候便没有了让其按照正常方式运行的理由。

一旦分离了加密或者解密例程,并且知道了它们的参数,则完全有可能使用规范让恶意代码解密任意内容,因此应该有效利用恶意代码本身来对付恶意代码。

文章作者: kabeor
文章链接: https://kabeor.github.io/%E7%AC%AC13%E7%AB%A0%20%20%E6%95%B0%E6%8D%AE%E5%8A%A0%E5%AF%86/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 K's House

评论