Frida动静态结合分析Base64签名校验的四个变种
目标app为algorithmbase_10.apk。
点击CHECK会有加密后的字符串,看样子是Base64编码:
用jadx查看源码中关键代码:
输出的值由MainActivity类的静态方法encodeFromJni_10生成,该方法需要传入一个16个字符的随机字符串。
encodeFromJni_10是一个native方法:
其实现应该在libnative-lib.so中:
在ida中打开so文件,查看encodeFromJni_10方法的具体实现。
用findcrypt脚本定位到base64编码表:
既然能被识别,那应该是个标准的编码表,先从logcat中得到一批参数密文对:
分别放入CyberChef中直接尝试下:
看起来就是单纯的Base64编码。
为了方便对更多输入值进行确认,写了个frida脚本,将encodeFromJni_10执行后的值和调用Base64.encodeToString后的值进行比较,查看是否相等。
多次测试后发现确实都相等,确定就是标准的Base64编码。
目标app为algorithmbase_11.apk.
打开测试后,和之前的algorithmbase_10.apk结果十分相似,也像是用Base64编码:
直接定位其so中的encodeFromJni_111方法实现.
用findcrypt没有找到可用信息:
但离返回值最近的方法(后面做内存回收的不算)调用中可以发现类似base64编码表的内容:
双击查看完整内容:
猜测应该是非标准的base64编码,使用了自定义的编码表,将编码表复制到CyberChef中(还要加上等号):
从logcat中取两个测试案例,验证没有问题。
目标app为algorithmbase_12.apk。
点击测试后发现比之前两个app结果更加杂乱:
还是直接查看so文件中对应native方法encodeFromJni_112的实现。发现与之前的111极为相似。而加密的关键函数内部的实现,除了使用的编码表外一模一样。
按理说就是使用了另一种自定义编码表的base64编码才对。但是提取出其编码表并放入CyberChef中测试,发现密文结果并不一致。
CyberChef的结果为:
Logcat获取到的实际结果为:
会不会是base64编码完后还有什么步骤?还是说传入的参数在base64编码之前有被修改呢?为了确定这点,用frida hook了做base64操作的函数,输出其执行前后参数的内容。
发现传入的第二个参数就是原始生成的随机字符串,而函数执行完后第一个参数中存放的内容就是最后打印出来的密文。
看来没有什么额外加密步骤。那唯一的变数就是编码表的内容了。会不会是运行的时候编码表被改了呢。直接用一段脚本打印运行时内存中的编码表内容。
发现果不其然和静态看的不一样。
将这段编码表内容放入CyberChef中,就可以得到正确的结果了:
那么编码表是何时被修改的呢?在ida中查找下编码表的引用:
发现有一处指向了另外一个之前没关注到的函数内部。很明显编码表就是在这里做了修改。
这个方法也是在encodeFromJni_112方法比较前期的地方就被调用。
其他应该就没什么问题了。
目标app为algorithmbase_13.apk.
看结果又是变种base64编码的样子:
直接去观察so中native方法encodeFromJni_113用到的编码表:
和12用到的一样,不过测试发现用这个表得到的结果和打印出来的值不一致。猜测又是运行时被修改了。
先用之前的脚本确定没有其他加密过程:
再查看下运行时内存中的编码表内容:
将这部分内容作为编码表,到CyberChef上测试,发现提示编码表长度不对。
检查后发现CyberChef中的base64编码表对‘-’的位置有要求,如果单纯的放在两个字母或者数字中间会被判定为表示区间的字符。可以用’\’进行转义:
但是转完后生成的结果和目标结果不一致:
之前已经用frida hook脚本确定了所有加密只可能在函数sub_8b04中完成,那问题肯定还在这个函数中。
在ida中对照12和13两个apk的sub_8b04函数,发现13的sub_8b04函数中有两行代码与12不一致,多了一步异或操作。这里异或的变量v4值等于传入的第三个参数的值,也就是待编码内容的长度。
看来是base64编码的另一种魔改方式,这在CyberChef上应该是模拟不了了,需要自己实现一段代码。
只需要在标准的base64编码实现上,修改对应的地方即可。
再次运行就可以得到正确的内容了:
