首页
社区
课程
招聘
[原创]一种还原白盒AES秘钥的方法
发表于: 2019-8-21 18:41 25632

[原创]一种还原白盒AES秘钥的方法

2019-8-21 18:41
25632

在日常逆向分析工作有遇到过一个白盒AES算法,在网上找到这样一篇还原该白盒算法秘钥的文章:
DFA分析白盒AES算法 ,通过学习该文章,总结了一些心得,在这里分享下。

详细的理论可以看上面那篇blog,我这里只挑一些重点来说明下。

AES算是我们日常开发中最常用一种对称加密算法了,加密过程如下:

主要有这四种操作:
1.S盒字节代换
2.行移位
3.列混淆
4.轮秘钥加

白盒算法是将秘钥混淆到算法中,让攻击者即便能够获取算法的内部细节(能够动态调试),也无法 还原出秘钥的一种算法,常见的白盒算法有:白盒AES,白盒SMS4。

由AES加密算法流程可以看出:第10次轮秘钥加之前是没有列混淆的。如果我们在第九轮列混淆之前构造如下两组数据:

从上图两个状态矩阵可以看出,状态矩阵中只有第一个字节不一样。如果当前状态继续往下推导,可以有如下:

这里的算法分为两个部分:
1.产生这些fault数据的方法:
该算法在DFA 产生Fault数据
该算法主要是通过静态修改二进制文件方式来修改R9的一个字节,从而输出Fault数据的。(这里面具体的实现细节没太弄懂,有兴趣都可以自行去看代码,后面的实战,我主要用IDA动态Patch方式实现输出Fault数据,没有用到这里算法。)

2.拿到这些fault数据后,怎么样还原出白盒AES秘钥
该算法实现是在github上:phoenixAES
下面主要对该算法的学习,解读。
首先该算法的输入一个格式化的文件,每一行分别是输入状态矩阵(16进制字符串表示),输出状态矩阵(16字节128位)

经过分析代码可以发现,其实代码中没有用到输入的值,只需对输出值进行计算,并且第一行的输出值定为golden_ref,即没有做改变的原始正常的值,其他输出值为diff,即在第九轮状态更改一字节后输出的值。

最终算法输出的是还原的第10轮的秘钥,要得到原始AES秘钥,还需逆推一下。

该算法实现中有这样一个列表,这个列表是来判断当前diff是属于哪一类,对应秘钥哪4个位置,比如
拿加密来说,在第九轮列变换之前改变状态矩阵第一列中某一个字节的值,最后的结果只会在(0,7,10,13)的位置上发生改变,也就对应第10轮秘钥的(0,7,10,13)位置上的值。同理改变第二列上的值,只会改变(1,4,11,14)位置上的值。


AES加密列变换阵数值只在:1,2 ,3 中取,
AES解密列变换的数值只在:9, 13, 11, 14中取
这里的列表
_AesMult[1]可以表示(0~255)中分别与1相乘的结果。
_AesMult[9]可以表示(0~255)中分别与9相乘的结果。

这里 j=S(Y0), ibox_j = invSBox(Y0) = Y0
由上述推导公式:diff = S(Y0) + S(Y0+2z) 可以变换得(加号均表示异或):
diff + S(Y0) = S(Y0+2z)
invSBox(diff + S(Y0)) = Y0 + 2z
invSBox(diff + S(Y0)) + Y0 = 2z
candi = [itab[ibox[j ^ diff] ^ ibox_j] for j, ibox_j in enumerate(ibox)]
所以这里这句是返回是S(Y0)从0~255取值时,对应z的取值。
所以后面会对z进行求交集操作,然后反过来根据z的值,去取S(Y0)的取值列表,最后通过多组数据的求解归并得到唯一确定的S(Y0)的值,最后求秘钥时会有一个:
key[Keys[j]]=list(c[index][0][j])[0] ^ Gold[j]

讲了这么多理论算法实现,还是来个具体的示例更容易说明问题,这里选的列子是这篇blog上提的:
LIFE破解白盒AES
它里面提到的解法,是通过用LIEF将Android so转化成Linux上的可执行程序,然后对接上述的DFA
静态产生Fault数据方法得到一组数据,然后调用[phoenixAES]进行秘钥还原。我这边主要是通过IDA 动态Patch来得到一组Fault数据。

里面的APK是SECCON2016 CTF中的一题:SECCON2016 Online CTF-Binary / Crypto500 Obfuscated AES


主要从资源文件arrays中随机取一个字符串传入到native函数 a 进行加密,然后每隔0.01s递归调用一次,所以程序运行后界面上的encrypted_flag一直在变化。
arrays列表:

用IDA打开lib-native.so可以发现该so经过了ollvm混淆了,而且题目也说清楚了这个是一个ollvm混淆的白盒AES算法。
通过IDA静态分析,可以定位到静态注册的JNI函数:Java_kr_repo_h2spice_crypto500_MainActivity_a
简单分析一下可以定位到关键函数: TfcqPqf1lNhu0DC2qGsAAeML0SEmOBYX4jpYUnyT8qYWIlEq
通过frida hook 一下该函数查看该函数的输入输出值:

这里有两点说明下:
1.在hook native层导出函数时用的replace的方式,而不是attach方式是为了能够在函数执行完后再打印一遍输入参数,因为在C中会经常传个指针,在函数中操作的结果也是保存在这个指针中。在本列中TfcqPqf1lNhu0DC2qGsAAeML0SEmOBYX4jpYUnyT8qYWIlEq函数a0参数是加密前java层传下来的数据,a1是经过加密后的数据。

2.这里也hook了Java层的JNI函数,为了让每次传下来的值保持一致,这里我是随机选取一个值:"Gew1cqzKp5K8sejh3FlTZlS/CISCpO81WmZ/oU4SJOk=",这样没隔0.01s都会触发一次调用。

经过上面的hook分析可以得出:
java层传下来的加密数据都是:32字节的数据,所以在C层调用了两次TfcqPqf1lNhu0DC2qGsAAeML0SEmOBYX4jpYUnyT8qYWIlEq函数,分别加密前16字节和后16字节(ECB模式),然后将加密后的数据Base64一下返回给java层,显示到界面。

有了上面分析,结合phoenixAES算法,要还原秘钥,只需要想办法去产生R9 Fault数据,也就是在
调用TfcqPqf1lNhu0DC2qGsAAeML0SEmOBYX4jpYUnyT8qYWIlEq函数内部,某个时间点(刚好在R9列变换前)更改状态矩阵的一个字节的数据,得到加密后的数据,对比输出数据与golden_ref是否刚好只有4字节不同。(如果只有1字节改变,说明patch太晚,如果有16字节不同说明patch太早)。

所以问题就变成如何刚好找到Patch的时间点,由于so被混淆了不能很直观的看到函数执行流程,这里用IDA Python 去打印该函数的核心块的执行过程:

这里我调试是armeabi-v7a的so,其中断点4个位置分别是:
1.OAES函数的开始
2.OAES函数的结尾
3.OAES函数其中一处明显的子函数调用
4.OAES函数其中一处明显的数据处理块的起始地址。
运行完可以明显观察到子函数调用了10次,每次调用之间调用了4次核心处理模块共九组,是不是可以类比AES10轮加密操作,所以我选择在第九组核心操作前进行patch一字节,然后观察到最后的输出结果果然是符合预期的,最后的patch代码如下:

这里在调试时需要用frida hook java层的输入以保持每次输入数据都一样,所以这里的调试步骤为:
1.运行android_server -p12345
2.运行frida_server
3.adb shell am start -D -n kr.repo.h2spice.crypto500/.MainActivity 进入调试模式
4.运行frida hook java 层代码(c层hook注释掉)
5.ida attach
6.jdb -connect com.sun.jdi.SocketAttach:hostname=127.0.0.1,port=8700
7.运行IDA Python patch脚本

注意这里的第4步不能跟第5步互换,不然frida会报错,通过这种方式意外的发现可以hook 到Actity的OnCreate函数,我之前用 frida spawn的方式老是报错。

整理脚本的输出的Fault结果,放入[phoenixAES]中:

还原出K10秘钥为: 040D08DA68001026F3DC0D68897148B4
再调用Key scheduling reversers中的
aes_keyschedule 逆推得到round0秘钥 即AES秘钥。

得到该AES秘钥为:6C2893F21B6185E8567238CB78184945


[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

最后于 2019-8-22 10:46 被lfyyy编辑 ,原因:
收藏
免费 16
支持
分享
打赏 + 11.00雪花
打赏次数 3 雪花 + 11.00
 
赞赏  bengou   +1.00 2019/08/25
赞赏  orz1ruo   +5.00 2019/08/22 助人为乐~
赞赏  junkboy   +5.00 2019/08/21 感谢分享~
最新回复 (23)
雪    币: 25
活跃值: (1111)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
2
大佬牛逼
2019-8-21 19:40
0
雪    币: 48
活跃值: (3434)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
大佬牛逼
2019-8-22 10:47
0
雪    币: 2
活跃值: (157)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
遇到同样的白盒aes,但其实现的解密操作,请问该如何获取密钥
2019-8-22 14:41
0
雪    币: 268
活跃值: (630)
能力值: ( LV3,RANK:35 )
在线值:
发帖
回帖
粉丝
5
fxbfxb 遇到同样的白盒aes,但其实现的解密操作,请问该如何获取密钥
https://github.com/SideChannelMarvels/JeanGrey/tree/master/phoenixAES
解密同样可以呀。
2019-8-22 14:56
0
雪    币: 2
活跃值: (157)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
我使用c++还原了那个白盒aes解密算法,使用工具没有任何输出,麻烦大佬帮忙看看,算法已经验证没有问题。
上传的附件:
2019-8-22 19:10
0
雪    币: 268
活跃值: (630)
能力值: ( LV3,RANK:35 )
在线值:
发帖
回帖
粉丝
7

看这个代码很好还原哟,如果确定是AES的话。

 

你patch这里看对比输出结果是否跟没有patch之前有4字节不同,如果是说明patch对地方了。

最后于 2019-8-22 19:56 被lfyyy编辑 ,原因:
2019-8-22 19:55
0
雪    币: 2
活跃值: (157)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
lfyyy 看这个代码很好还原哟,如果确定是AES的话。 ![](upload/attach/201908/608531_AVPGZQQPKAB8CHE.png) 你patch这里看对比输出结果是否跟没 ...
感谢,成功了,主要是phoenixAES.crack_file函数的参数设置的有问题,导致没有解出来,其实没有必要还原算法的
2019-8-22 20:22
0
雪    币: 1096
活跃值: (374)
能力值: ( LV2,RANK:15 )
在线值:
发帖
回帖
粉丝
9
实现过白盒SM4,也借鉴了白盒AES,和我印象中不一样。
当初是硬生生屏蔽了自己逆向的思维,仅从密码学角度实现和破解。
密码学破解角度是开源白盒算法,破解白盒库(查找表),那种方式。目前都是可被攻破的,因为都是线性白盒,非线性是国际难题。
本就是防御两种人,会密码学的,会逆向的。这篇符号执行破解方式也着实然我慌得一逼。
2019-8-23 09:58
0
雪    币: 63
活跃值: (324)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
10
mark
2019-8-24 17:33
0
雪    币: 159
活跃值: (339)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
11
牛逼了。mark一下
2019-8-26 14:54
0
雪    币: 8209
活跃值: (4518)
能力值: ( LV15,RANK:2473 )
在线值:
发帖
回帖
粉丝
12
意思是要hook?那为什么不能hook在一个更好的位置直接拿到密钥?
2019-8-26 15:48
0
雪    币: 268
活跃值: (630)
能力值: ( LV3,RANK:35 )
在线值:
发帖
回帖
粉丝
13
ccfer 意思是要hook?那为什么不能hook在一个更好的位置直接拿到密钥?
这个是白盒AES,秘钥都混淆到查找表里面了,所以需要通过算法还原秘钥。
2019-8-27 09:40
0
雪    币: 734
活跃值: (458)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
14
侧信道和错误注入在白盒里焕发新生
2019-8-27 14:52
0
雪    币: 734
活跃值: (458)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
15
还有另外一种方法,抓取下来关键函数的二进制,之后用unicorn去跑,随机输入明文(密文),之后抓取内存和寄存器的变化,最后通过侧信道的方法去跑出密钥
2019-8-27 14:56
1
雪    币: 574
活跃值: (405)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
16
学习了
2019-8-28 09:27
0
雪    币: 208
活跃值: (479)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
17
硬核...
2019-9-22 22:11
0
雪    币: 208
活跃值: (479)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
18
backahasten 还有另外一种方法,抓取下来关键函数的二进制,之后用unicorn去跑,随机输入明文(密文),之后抓取内存和寄存器的变化,最后通过侧信道的方法去跑出密钥
大佬 这个方法有相关的参考文章吗?想学习一下
2019-9-22 22:12
0
雪    币: 6257
活跃值: (1177)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
19
mark 收藏了
2019-9-23 08:35
0
雪    币: 159
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
20
不会,有大神帮搞一个密码不,有偿
2019-10-9 00:26
0
雪    币: 477
活跃值: (1412)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
21
配合unidbg完美
2020-10-22 13:21
0
雪    币: 3180
活跃值: (4072)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
22
哇,太牛了
2021-6-23 15:14
0
雪    币: 163
活跃值: (509)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
23
mark
2021-7-5 09:41
0
雪    币: 102
活跃值: (2050)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
24
mark
2023-3-21 19:12
0
游客
登录 | 注册 方可回帖
返回
//