文章目录[隐藏]
转载 19 / 27
- 【转载】unidbg hook 使用大全
- 【转载】ida 基本使用 常用快捷键
- 【转载】Frida和调试器共存的问题
- 【转载】Charles抓包Android最佳流量转发APP
- 【转载】安卓自定义代码调用系统函数并编译so记录
- 【转载】jnitrace 手机卡死黑屏问题解决
- 【转载】Unidbg SO 逆向入门实战教程一 OASIS
- 【转载】Unidbg SO 逆向入门实战教程二 calculateS
- 【转载】Unidbg SO 逆向入门实战教程三 V2-Sign
- 【转载】Unidbg SO 逆向入门实战教程四 mfw
- 【转载】Unidbg SO 逆向入门实战教程五 qxs
- 【转载】Unidbg SO 逆向入门实战教程六 s
- 【转载】Unidbg SO 逆向入门实战教程七 main
- 【转载】Unidbg SO 逆向入门实战教程八 文件读写
- 【转载】Unidbg SO 逆向入门实战教程九 blackbox
- 【转载】Unidbg SO 逆向入门实战教程十 simpleSign
- 【转载】android app 加密参数分析研究 hash aes
- 【转载】第一讲——从黑盒攻击模型到白盒攻击模型
- 【转载】第二讲——白盒加密攻击方法的选择
- 【转载】第三讲——差分故障攻击的原理
- 【转载】第四讲——差分故障攻击的工具链
- 【转载】第五讲——使用源码进行DFA攻击
- 【转载】第六讲——使用Frida进行DFA攻击
- 【转载】第七讲——使用Unidbg进行DFA攻击
- 【转载】第八讲——案例
- 【转载】猿人学 - app 逆向比赛第四题 grpc 题解
- 【转载】猿人学 - app 逆向比赛第五题双向认证题解
说说在前面
本文转载龙哥星球 白盒 AES 密码学系列
,相关文件代码等,可加入星球下载
一、前言
在这篇文章中,我们讨论从白盒加密中分析出密钥的技术。首先我需要声明,这个系列文章中并不关注白盒加密具体的实现,只关注于如何提取密钥。这听起来有点怪,都不了解加密算法如何白盒化,又怎么能做分析甚至是提取出密钥?
二、识别白盒加密
2.1 确切特征
白盒化一个加密算法的办法有很多,最常见的方案是查找表技术,我们只考虑基于查找表技术的这一类。查找表意味着将密钥和算法的其他步骤融进一张大表,将加密的过程转变成查表的过程。
那么它就会有两个特征
- 程序中使用到了一个或多个很大的数组,这个大指数组至少包含几千个元素。
- 原先轮函数中的运算都变成了查表
举个例子,如下是一份白盒AES原代码。
import ctypes
dword_6661C0 = []
byte_6651C0 = []
with open("tables/6661C0.txt", "r")as F:
dword_6661C0 = eval(F.read().strip())
F.close()
with open("tables/6651C0.txt", "r")as F:
byte_6651C0 = eval(F.read().strip())
F.close()
byte_6650C0 = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,1,0,3,2,5,4,7,6,9,8,11,10,13,12,15,14,2,3,0,1,6,7,4,5,10,11,8,9,14,15,12,13,3,2,1,0,7,6,5,4,11,10,9,8,15,14,13,12,4,5,6,7,0,1,2,3,12,13,14,15,8,9,10,11,5,4,7,6,1,0,3,2,13,12,15,14,9,8,11,10,6,7,4,5,2,3,0,1,14,15,12,13,10,11,8,9,7,6,5,4,3,2,1,0,15,14,13,12,11,10,9,8,8,9,10,11,12,13,14,15,0,1,2,3,4,5,6,7,9,8,11,10,13,12,15,14,1,0,3,2,5,4,7,6,10,11,8,9,14,15,12,13,2,3,0,1,6,7,4,5,11,10,9,8,15,14,13,12,3,2,1,0,7,6,5,4,12,13,14,15,8,9,10,11,4,5,6,7,0,1,2,3,13,12,15,14,9,8,11,10,5,4,7,6,1,0,3,2,14,15,12,13,10,11,8,9,6,7,4,5,2,3,0,1,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0]
for i in range(len(dword_6661C0)):
dword_6661C0[i]=ctypes.c_uint32(dword_6661C0[i]).value
for i in range(len(byte_6651C0)):
byte_6651C0[i]=ctypes.c_uint8(byte_6651C0[i]).value
def swap(s):
res = [i for i in range(16)]
res[0] = s[0]
res[1] = s[5]
res[2] = s[10]
res[3] = s[15]
res[4] = s[4]
res[5] = s[9]
res[6] = s[0xe]
res[7] = s[3]
res[8] = s[8]
res[9] = s[0xd]
res[10] = s[2]
res[11] = s[7]
res[12] = s[0xc]
res[13] = s[1]
res[14] = s[6]
res[15] = s[0xb]
return "".join(res)
def xor(a,b):
return "".join(chr(ord(a[i])^ord(b[i])) for i in range(16))
def encrypt(inp):
for v8 in range(9):
res = [i for i in range(16)]
inp = swap(inp)
for v9 in range(4):
v3 = dword_6661C0[((4 * v9 + 16 * v8) << 8) + ord(inp[4*v9])]
v4 = dword_6661C0[((4 * v9 + 1 + 16 * v8) << 8) + ord(inp[4*v9+1])]
v5 = dword_6661C0[((4 * v9 + 2 + 16 * v8) << 8) + ord(inp[4*v9+2])]
v6 = dword_6661C0[((4 * v9 + 3 + 16 * v8) << 8) + ord(inp[4*v9+3])]
res[4*v9] = chr(byte_6650C0[(16*byte_6650C0[16*(v3 & 0xF)+(v4&0xf)]+byte_6650C0[16*(v5 & 0xF)+(v6&0xf)])] | byte_6650C0[16*byte_6650C0[16*((v3>>4)&0xf)+((v4>>4)&0xf)]+byte_6650C0[16*((v5>>4)&0xf)+((v6>>4)&0xf)]]*16)
v3 = v3 >> 8
v4 = v4 >> 8
v5 = v5 >> 8
v6 = v6 >> 8
res[4*v9+1] = chr(byte_6650C0[(16*byte_6650C0[16*(v3 & 0xF)+(v4&0xf)]+byte_6650C0[16*(v5 & 0xF)+(v6&0xf)])] | byte_6650C0[16*byte_6650C0[16*((v3>>4)&0xf)+((v4>>4)&0xf)]+byte_6650C0[16*((v5>>4)&0xf)+((v6>>4)&0xf)]]*16)
v3 = v3 >> 8
v4 = v4 >> 8
v5 = v5 >> 8
v6 = v6 >> 8
res[4*v9+2] = chr(byte_6650C0[(16*byte_6650C0[16*(v3 & 0xF)+(v4&0xf)]+byte_6650C0[16*(v5 & 0xF)+(v6&0xf)])] | byte_6650C0[16*byte_6650C0[16*((v3>>4)&0xf)+((v4>>4)&0xf)]+byte_6650C0[16*((v5>>4)&0xf)+((v6>>4)&0xf)]]*16)
v3 = v3 >> 8
v4 = v4 >> 8
v5 = v5 >> 8
v6 = v6 >> 8
res[4*v9+3] = chr(byte_6650C0[(16*byte_6650C0[16*(v3 & 0xF)+(v4&0xf)]+byte_6650C0[16*(v5 & 0xF)+(v6&0xf)])] | byte_6650C0[16*byte_6650C0[16*((v3>>4)&0xf)+((v4>>4)&0xf)]+byte_6650C0[16*((v5>>4)&0xf)+((v6>>4)&0xf)]]*16)
inp = "".join(res)
inp = swap(inp)
res = [i for i in range(16)]
for i in range(16):
res[i] = "{:02x}".format(byte_6651C0[256*i + ord(inp[i])])
inp = "".join(res)
return inp
print(encrypt("0123456789abcdef"))
可以发现,主体运算变成了对dword_6661C0,byte_6650C0,byte_6651C0 这三个表的查表操作。其中dword_6661C0是如下的大表,其余两个是小表。
白盒加密在SO中是否有其他特征?另一个比较好用的是符号和字符串信息,尽管听着有些不可思议,但我们经常可以看到一些SO叫 XXXWhiteBox,或者某个函数叫 Whitebox_init / WBAES / Whitebox_Sm4 这样的名字,这是非常明显的特征。
总结一下
- 逻辑上依赖"查大表"
- 函数或符号名涉及WhiteBox
需要注意,比如AES等算法存在表合并的实现,表合并和白盒化是两码事,表合并是将不涉及密钥的步骤合并在一起,白盒化是将密钥放进去。区分两者的主要方法是表合并的表是固定的,比如AES加密在表合并法后,就是OpenCL-AES这个项目中的Te0-Te3四个表。
- 表合并法中的表固定,google就可以查到;白盒加密中的表查不到
- 白盒加密的表远比表合并中的更大,而且白盒加密中找不到Key的痕迹,表合并法AES必然找得到Key
在业界,也有很多人尝试从其他方面寻找白盒加密的特征,然后做成类似于 FindCrypt
的 FindWhitBoxCrypt
,但总体来说并不顺利,我们来看一下学者们给出的特征。
2.2 辅助特征
首先,研究人员发现,白盒密码的实现函数,往往是“巨大”且“独立”的。前者指的是,函数比样本中大多数函数更大,这个“大”包括:汇编的行数/基本块的数量/伪代码的长度等等,后者指白盒密码程序往往是一个单独的模块或独立的几个函数,而且不会使用太多库函数。这确实是很合理的推断,但一个标准加密,或者任意的、其他的关键逻辑,也都符合这种描述。
其次,研究人员认为,白盒函数所需要的栈空间往往较大,因为需要大量栈空间存储中间值。事实上,标准加密函数或混淆严重的普通函数也具有同样特征。
最后,比较有参考价值的一点是,如果一个函数在功能上十分像加密函数,比如AES,但是 FIndCrypt 无法找到相关特征,基于对该种算法的了解也无法找到熟悉的逻辑,比如密钥编排,同时存在大表以及查表行为,那么可以往白盒加密的角度思考。
三、从白盒加密中提取密钥
如何提取出白盒加密程序中的密钥?目前有三种主流方法
3.1 密码学分析
白盒加密是一个密码学领域的知识,那么它必然也能被密码学的知识打败。
这要求分析者具有数学和密码学的知识储备,并且要具体分析样本中的白盒实现,这实在不是大多数人的长项(包括我)。
3.2 差分故障攻击(DFA)
DFA 的提出甚至早于白盒加密,它是灰盒攻击场景下的一个方案。Bos 等人惊喜的发现,用于对付标准加密实现的DFA 竟然也可以处理白盒加密。DFA 是目前最热门的方法之一,各种白盒加密破解赛中,都可见到它的身影。
稳定、好用、对工具依赖低是这个方案的优点。
3.3 差分计算攻击 (DCA)
DCA 的提出同样早于白盒加密,它本用于处理标准加密实现,过去它叫差分能量攻击(DPA),是灰盒攻击场景下的重要方案。同样是Bos 等人发现,在白盒攻击模型下稍微给它改一下,就能处理白盒加密。
因为,业界认为白盒加密目前的效果并不好,在设计上,它的强度本应能处理白盒威胁,但实际上,它却被更低一级的威胁,比如DCA/DFA 这样的灰盒攻击方法追着锤。
四、尾声
下一篇我们从DFA 开始说起。