前言
第二届阿里移动安全赛第二题,花了很多时间来彻底理清楚逻辑,做完这道题感觉自己在动态调试、arm汇编代码技巧上都有不少收获,所以首先我大概总结一下这道题的解题过程,题中几个难点如下:
- 反调试:题中出现多次反调试,但是套路都相同,都可以用相同的方法绕过
- 动态恢复:程序在执行的过程中不断通过mmap申请一块内存 ,mprotect修改内存为可执行 ,动态构造执行代码 。在执行完后又将代码加密回去,继续执行下一段代码。
- AES加密:由于在函数中并不是直接调用AES库,而是直接用AES源码进行处理,所以考验我们的是要把AES源码识别出来并用AES进行解密
题目链接:
由于程序分析过程太长了,长得都要忘了我们是要看输入怎么处理的,所以我把对输入进行处理的方式放在最前边,有个大体印象才不致忘记
首先是将输入补全,对后边的补上0到15的数
进行加操作
- 与数组相加
- 进行AES加密,密钥为6BCDC67A6B2B7C9D8DA459B1AB9D0680,最后与固定字符串进行比对,求逆的过程非常简单
所以求逆的过程并不复杂,只需要整理出处理流程逆回去即可
动态调试
由于java端代码并不复杂,所以直接加载程序进入动态调试,直接程序崩溃,找到init_array段,看到了原因,一共有两个函数用来反调试,第一个和最后一个,这里我们直接修改so库来绕过反调试
绕过第一个的方法只用简单改一下程序逻辑
![
以上为两种修改方法,总之只要让他直接返回而不去执行下面的kill函数即可,用ASShell找到arm的汇编代码,直接用winHex修改so文件
第二个反调试我们直接将汇编代码改为返回,即直接把最后一句指令改到函数执行的顶部,则函数直接返回,这种方法也适用于第一个反调试
接下来就可以直接进入主函数进行调试了,找到ch函数
进行输入的第一次处理,补全输入,将输入覆盖到0到15的前几个值
然后是一段代码反调试,这个程序所有的反调试都是这一个套路就是读取TracerPid看他的值是不是为0
有两种方法,一个是下断点到该地址,然后修改寄存器r0的值不断地改来通过这段反调试代码,一种方法是直接在内存中找到读取tracerpid时的数据修改掉它的值就可以直接跳过,所以第二种方法较为简单,特别是在应付如此多反调试的程序中,毫无疑问是最省事的。我们知道我们读取的是r6的值,所以我们希望直接修改这个数据段的tracerpid的值来直接跳过。
随后进行的一段代码就是动态解密函数段,解密的函数段为loc_24c8做的操作是异或,此时我们直接下断点到调用函数处,然后该跟进函数即可,点入该函数,ida中按p重新分析,f5生成伪码,在关键函数处下断,分析完了,我们可以进行动态调试了
进入函数后,程序先将前一段代码又加密回去,然后对输入进行第二步处理
接着又是一个反调试,同样的方法绕过,继续解密代码段,并且执行
进入到第三层,又要绕过一个反调试,我们直接定位输入到处理函数
汇编代码可知,将r0加r5的值赋给r3,r8加r5的值赋给r2,再将r2与r3相加,其中r8为我们前边处理过后的输入部分,再将加后的结果给r1加r5,r5从0取到16
处理过后的值
与前边方法一样,终于进入最后一个代码段了,这一部分代码为手写AES加密
这之前先简单的分析一下AES加密算法
AES加密算法一共有十轮,每一轮都要经过s盒代换,行移位和列混淆、轮密钥加的操作
s盒代换
s盒代换是一个查表的过程,是AES算法中唯一一步非线性的过程,所以关键就是S盒,s盒的构造方法如下:
1)初始化S盒,按行升序排列的字节初始化。行x列y的字节是xy,行号和列号从0开始计数。
2)求出每一个元素在GF(2^8)中的逆。00被映射为它自身。
3)仿射变换。对上一步中的每一个字节的每一位作如下变换
yi=xi + x(i+4)mod8 + x(i+5)mod8 + x(i+6)mod8 + x(i+7)mod8 + ci
s盒:
|
|
输入 6A ,查找S盒x=6, y=a => 02。反过来,输入02, 查找逆S盒x=0, y=2 => 6a
行移位
第一行不变,第二行循环左移1字节,第三行循环左移2字节,第三行循环左移3字节。
列混淆
轮密钥加
我在网上找了一点AES代码进行大概分析
|
|
sm_te数组生成过程
|
|
首先看t1的生成过程,是分别将w1>>24,w2>>16,w3>>8,w4>>4分别对应的就是D0,D5,D10,D15,其中w代表一个字也就是4个字节,AES处理都是一个字一个字地读入
然后我们就找sm_te数组的生成过程,拿te1举例,就是将sbox的相应位置代入,也就是sbox[i]进行了s盒变换,然后列混淆并拼成一个字,t1整个生成过程转换成数学公式就是
|
|
所以不难看出生成的每个t1就是竖行排列的字,再与轮密钥异或,第一段代码是行移位和轮密钥加的混合,第二段则是列混淆和s盒代换的混合。
AES算法差不多就介绍完了,至于其他的密钥扩展就不再深究了。现在来分析反汇编中的AES算法部分
轮密钥加的过程
每个数值取出来的是字,DWORD也就是四个字节,AES是分组密码这个是第一步,密钥我们可以通过查看到为6BCDC67A6B2B7C9D8DA459B1AB9D0680
v18为输入的第一个字的地址
查看v25的值可以看到这是一个s盒,每隔一个字节为s盒
然后我们进入十轮循环,首先是字节代换的操作
行移位过程,生成的第二个字的顺序D5|D6|D7|D1
看到这里我们已经大概看出来了这是一个AES加密过程,好吧,是我分析不下去了。手动分析AES的过程实在是太艰辛,而且又是反汇编的看都看不懂,所以我们就根据这个题的特征来看,这个题表现为AES的特征主要有两点,第一是十轮加密,第二是AES盒,第三是数据之间相互组合的过程都符合AES的三轮数据处理,本来加密算法我们知道也不是你随便编一个就行了,所以现存的最保险的就只有AES加密了,而且再怎么自己编写个算法也不可能进行十轮迭代,有这个水平的只有AES了吧!为了证明他是AES,我们可在后边找到结果进行验证,现在我们让程序继续执行下去,执行到最后加密结果处。
整理一下加密之前的数据为:
加密过后的结果
将加密后的结果与下面的数组作比较
好了,现在整个程序的分析就到这了,只需逆向解密就行了
总结
没有绝对的安全,只有相对的攻防。这个题整个解题过程不算很难,但是调试的过程需要耐心,最难的应该是最后对AES源代码的分析吧,这个分析不出来前边做的所有工作都废了,之前的反调试和动态解密过程都是一个套路。里边用到的很多加固技巧也对以后做题有很大的启示。终于把一道困扰我大半年的安卓题给分析完了!加油!
最伟大的思想和行动往往需要最微不足道的开始