数据加密是指以隐藏真实意图为目的的内容修改。
分析加密算法的目的
恶意代码用加密来达到各种各样的目的。最常见的是加密网络通信,同时,恶意代码也会用加密来隐藏它的内部工作。例如,恶意代码编写者可能因为如下目的而使用加密:
- 隐藏配置信息。例如,命令和控制服务器域名。
- 窃取信息之前将它保存到一个临时文件。
- 存储需要使用的字符串,并在使用前对其解密。
- 将恶意代码伪装成一个合法的工具,隐藏恶意代码活动中使用的字符串。
分析加密算法时的目标:
识别加密算法
根据识别的加密算法解密攻击者的秘密
简单的加密算法
简单的加密算法对恶意代码带来的好处
- 足够小,可以用在空间受限的环境中,例如
Shellcode
编写。 - 没有复杂加密算法明显特征
- 开销低,对性能无影响
凯撒密码
介绍
在密码学中,恺撒密码(英语:Caesar cipher),或称恺撒加密、恺撒变换、变换加密,是一种最简单且最广为人知的加密技术。它是一种替换加密的技术,明文中的所有字母都在字母表上向后(或向前)按照一个固定数目进行偏移后被替换成密文。例如,当偏移量是3的时候,所有的字母A将被替换成D,B变成E,以此类推。
破解
-
穷举法
由于使用凯撒密码进行加密的语言一般都是字母文字系统,因此密码中可能使用的偏移量也是有限的。例如使用 26 个字母的英语,它的偏移量最多就是 25(偏移量 26 等同于偏移量 0;偏移量超过 26,等同于偏移量 1-25),因此可以通过穷举法,轻易地进行破解。
-
频率分析法
当密文长度足够大的情况下,可以先分析密文中每个字母出现的频率,然后将这一频率与正常情况下的该语言字母表中所有字母出现的频率作比较。例如在英语中,正常明文中字母 e 和 t 出现的频率特别高。通过这一特点,分析密文字母出现的频率,可以估计出正确的偏移量。 但是频率分析也有其局限性,它对于故意省略元音字母或者其他缩写方式写成的明文加密出来的密文并不适用。
XOR
介绍
XOR加密使用一个静态字节值,通过与该值执行逻辑异或运算来修改明文中的每个字节。逆向解密与加密使用同一个函数。要解密用XOR加密算法加密的数据,你仅需使用加密密钥再次XOR加密数据即可。
破解
- 暴力破解XOR加密
由于文件中的每个字符只有256种可能的值,对于一个计算机来说很容易并且能够足够快地使用255个单字节密钥来异或文件头部,然后将输出与期望的可执行文件头部进行比较。可以使用一个脚本来执行用255个密钥的XOR加密。
- 暴力破解多个文件
暴力破解方法也可以主动使用。例如,为了从多个文件中找出使用XOR加密的PE文件,可以为所有XOR组合创建255个特征码,留意你认为文件可能存在的元素。
XOR算法漏洞
如果XOR算法遇到大量的NULL(0x00
)时会暴露密钥。
解决方案:
保留NULL的单字节XOR加密
保留NULL的单字节XOR加密
特殊策略:
- 如果明文中字符是NULL或者密钥本身,则被跳过。
- 如果明文中字符既不是NULL也不是密码本身,则将被使用XOR密钥加密。
使用保留NULL的单字节加密算法,很难识别它是XOR加密,并且也没有明显的密钥。这种保留NULL的XOR加密技术在shellcode
中特别流行,因为使用少量的代码,就能够执行加密,这在shellcode
中非常重要。
使用IDA Pro识别XOR循环
在反汇编中,通过循环语句中间使用XOR指令的小循环语句找到了XOR循环。用IDA Pro找到XOR循环的一个最简单方法是搜索指令中XOR指令,如下:
1.确保你正在查看代码(窗口的标题应该包含IDAView
)。
2.选择Search→Text。
3.在文本搜索对话框中输入xor
,选中Find all occurrences
复选框,然后单击OK按钮。
Lab12-02.exe
中就有xor
加密,下面以此文件为例,找到加密函数所在位置,并找到密钥。
搜索到XOR指令并不意味着它一定用于加密。XOR指令可以用于不同的目的,其用途之一就是清空寄存器的内容。XOR指令以三种形式存在。
- 1、用寄存器自身XOR。
- 2、用一个常量(或一个内存引用)XOR。
- 3、使用一个不同寄存器(或一个内存引用)XOR。
最常见的是第一种形式,因为寄存器与自身异或是清零寄存器的一种有效方式。清零寄存器与数据加密无关,所以可以忽略它。
XOR加密循环可能使用另外两种形式中的一种:用一个常量异或一个寄存器,或者用一个不同的寄存器异或一个寄存器。如果你幸运,XOR加密是一个常量异或寄存器的XOR,因为通过它,可以确认你可能发现了加密,并且也可以知道密钥。
所以sub_401000
就是第二种情况,最后两个是第三种情况,其他都属于第一种情况,sub_401000
疑似为加密函数。
查看函数确定sub_401000
确实为加密函数。
直接查看sub_401000的参数,即可发现密钥为0x41
。
加密的迹象之一就是含有一个包含XOR函数的嵌套循环。
使用WinHex
处理XOR
可以使用Winhex
处理简单加密的文件。
其他一些简单的加密策略
Base64
介绍
Base64
是一种基于64个可打印字符来表示二进制数据的表示方法。每6个比特为一个单元,对应某个可打印字符。3个字节相当于24个比特,对应于4个Base64
单元,即3个字节可由4个可打印字符来表示。在Base64
中的可打印字符包括字母A-Z
、a-z
、数字0-9
,这样共有62个字符,+
和/
作为最后两个字符,组成64个字符的字符集。
Base64
常用于在通常处理文本数据的场合,表示、传输、存储一些二进制数据,包括MIME的电子邮件及XML的一些复杂数据。
Base64索引表如下
如果要编码的字节数不能被3整除,最后会多出1个或2个字节,那么可以使用下面的方法进行处理:先使用0字节值在末尾补足,使其能够被3整除,然后再进行Base64
的编码。在编码后的Base64
文本后加上一个或两个=
号,代表补足的字节数。也就是说,当最后剩余两个八位(待补足)字节(2个byte)时,最后一个6位的Base64
字节块有四位是0值,最后附加上两个等号;如果最后剩余一个八位(待补足)字节(1个byte)时,最后一个6位的base字节块有两位是0值,最后附加一个等号。 参考下表:
标准定义
RFC4648:https://www.rfc-editor.org/rfc/rfc4648.txt
非标Base64加密
恶意代码可能会使用非标准的自定义所以来进行base64加密,此时需要直到原始索引才能成功解密。下面给出自定义索引的base64加解密算法。例如CTF题目:https://adworld.xctf.org.cn/media/task/attachments/989ca07c3f90426fa05406e4369901ff.apk 就是关于非标准Base64解码。
```
# coding:utf-8
#s = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
s = "vwxrstuopq34567ABCDEFGHIJyz012PQRSTKLMNOZabcdUVWXYefghijklmn89+/"
def My_base64_encode(inputs):
# 将字符串转化为2进制
bin_str = []
for i in inputs:
x = str(bin(ord(i))).replace('0b', '')
bin_str.append('{:0>8}'.format(x))
#print(bin_str)
# 输出的字符串
outputs = ""
# 不够三倍数,需补齐的次数
nums = 0
while bin_str:
#每次取三个字符的二进制
temp_list = bin_str[:3]
if(len(temp_list) != 3):
nums = 3 - len(temp_list)
while len(temp_list) < 3:
temp_list += ['0' * 8]
temp_str = "".join(temp_list)
#print(temp_str)
# 将三个8字节的二进制转换为4个十进制
temp_str_list = []
for i in range(0,4):
temp_str_list.append(int(temp_str[i*6:(i+1)*6],2))
#print(temp_str_list)
if nums:
temp_str_list = temp_str_list[0:4 - nums]
for i in temp_str_list:
outputs += s[i]
bin_str = bin_str[3:]
outputs += nums * '='
print("Encrypted String:\n%s "%outputs)
def My_base64_decode(inputs):
# 将字符串转化为2进制
bin_str = []
for i in inputs:
if i != '=':
x = str(bin(s.index(i))).replace('0b', '')
bin_str.append('{:0>6}'.format(x))
#print(bin_str)
# 输出的字符串
outputs = ""
nums = inputs.count('=')
while bin_str:
temp_list = bin_str[:4]
temp_str = "".join(temp_list)
#print(temp_str)
# 补足8位字节
if(len(temp_str) % 8 != 0):
temp_str = temp_str[0:-1 * nums * 2]
# 将四个6字节的二进制转换为三个字符
for i in range(0,int(len(temp_str) / 8)):
outputs += chr(int(temp_str[i*8:(i+1)*8],2))
bin_str = bin_str[4:]
print("Decrypted String:\n%s "%outputs)
print()
print(" *************************************")
print(" * (1)encode (2)decode *")
print(" *************************************")
print()
num = input("Please select the operation you want to perform:\n")
if(num == "1"):
input_str = input("Please enter a string that needs to be encrypted: \n")
My_base64_encode(input_str)
else:
input_str = input("Please enter a string that needs to be decrypted: \n")
My_base64_decode(input_str)
```
常见的加解密算法
对于恶意代码来说使用标准的加密存在一些潜在的漏洞:
- 1、加密库很大,所以恶意代码需要静态的集成或者链接到已有的代码中。
- 2、链接主机上现有的代码可能降低可移植性。
- 3、标准加密库比较容易探测(通过函数导入,函数匹配或者加密常量标识)。
- 4、对称加密算法需要考虑如何隐藏密钥。
对于恶意代码分析而言,识别密钥与识别加密算法同等重要。
下面的一些简单方法可以识别标准加密。它们包括查找字符串和引用加密函数的导入,使用一些工具寻找特定的内容。
识别字符串和导入
- 一种识别标准加密算法的方法是识别涉及加密算法使用的字符串。当加密库(如OpenSSL)被静态地编译到恶意代码时,这种情况便会发生。
- 另外一种查找标准加密算法的方法是识别引用导入的加密函数。涉及加密的微软函数都以
Crypt
、CP
(对于加密提供者)或者Cert
为前缀。
查找加密向量
IDA Pro的插件FindCrypt2
和Krypto ANALyzer
插件可以搜索常见的加密向量。
使用FindCrypt2
IDA Pro
有一个叫做FindCrypt2
插件,包含在IDA Pro
的SDK
中,它搜索程序中任何与加密算法相关的已知常量。这样做效果很好,因为多数加密算法会使用一些神秘的常量类型。所谓神秘常量则是与基本加密算法结构相关的一些固定位串。
注意:一些加密算法并不使用神秘常量,值得注意的是,国际数据加密(IDEA)算法和RC4算法动态地创建它们的结构,因此它们不在可识别的算法之中。恶意代码常使用RC4算法,因为它体积小,在软件中易于实现,并且没有明显的加密常量。
使用Krypto ANALyzer
KANAL
是PEiD
的一个插件,它拥有一个范围更广的常量集合(作为结果可能更加容易产生误报)。除此之外,KANAL
还能够识别Base64
编码表以及加密相关的导入函数。
查找高熵值内容
识别加密算法的另一方法是查找高熵值的内容。除了识别潜在的明显的加密常量或者加密密钥外,这种技术也可以识别加密内容本身。由于这种技术的影响广泛,可以适用于没有找到加密常量的地方(如RC4
)。
警告:高嫡内容技术相当迟钝,最好作为最后一种使用手段。多种类型的内容,如图片、电影、音频文件以及其他压缩数据等,也会显示高篇值,除了它们的头部特征之外,很难与加密内容进行区分。
IDA的熵值插件(http://www.smokedchicken.org/2010/06/idaentropy-plugin.html )是针对PE文件使用这种技术的一个工具。将ida-entplw
文件放置到IDA Pro
的插件目录,就可以将这个插件载入到IDA Pro。
自定义加密
恶意代码常使用自定义的加密手段,其中一种方案就是将多种简单加密结合起来。
识别自定义加密
如果怀疑输出中包含加密数据,那么加密函数应该出现在输出之前。
相反,解密则出现在输入之后。
攻击者使用自定义加密的优势
- 自定义加密保留了简单加密策略的特点(体积小和加密不明显),同时使逆向工作变得十分困难。
- 不存在一个免费的函数库供我们实现解密,恶意代码分析人员可能要自行编写解密函数。
解密
重现恶意代码中的加密或解密函数的两种基本方法
- 1、重新编写函数
- 2、使用恶意代码中存在的函数
自解密
最经济的解密方法是:无论算法是否已知,让程序正常活动期间自己完成解密。我们称这种方法为自解密。
手动执行解密函数
对于简单的加密和编码方法,通常你可以使用编程语言提供的标准函数。
在处理非常复杂而且不标准的加密算法时,由于很难模拟算法,因此会成为更艰巨的挑战。
Base64
脚本(Python3
版本)
import base64
example_string = 'VChpcyBpcyBhIHR1c3Qgc3RyawSn'
string = str(base64.b64encode(example_string.encode("utf-8")), "utf-8")
print(string)
bbs = str(base64.b64decode(bs), "utf-8")
print(bbs) # 解码
保留NULL字节的XOR加密算法的Python脚本
def null_preserving_xor(input_char,key_char):
if(input_char == key_char or input_char == chr(0x00)):
return input_char
else:
return chr(ord(input_char)^ord(key_char))
"""
example_str = "abcdef"
result_data = []
for i in example_str:
res = null_preserving_xor(i, "s")
result_data.append(res)
"""
对于标准的加密算法,最好是使用代码库中提供的现有实现。一个基于Python的加密库叫做PyCrypto
,它提供了各种各样的加密函数。类似的库也存在于其他不同的语言中
使用通用的解密规范
使用ImmDbg
调试器,编写脚本处理恶意代码中的加密文件
- 1、在调试器中启动恶意代码。
- 2、准备读取的加密文件和准备写入的输出文件。
- 3、在调试器中分配存储空间,让恶意代码能够引用这块内存。
- 4、加载加密文件到分配的存储区域。
- 5、为恶意代码的加密函数设置合适的变量和参数。
- 6、运行加密函数执行加密。
- 7、将新加密的存储区域写入到输出文件。