序列密码之RC4实践篇
本章主要使用上一章开发的RC4代码作为例子进行分析,如何在逆向分析中识别RC4算法,以及如何通过编写frida代码来获取RC4的密钥。
没有加壳和混淆
通过反编译工具能直接完美的反编译出来RC4算法的代码
- 此时不论是java还是native代码都可以直接反编译出来RC4的算法代码
apk加密混淆后
使用ollvm对代码做一些简单的字符串混淆后
此时用IDA打开之后就看不到so文件中原本的字符串了
这种情况需要我们继续跟进分析一下,进入sub_89F4
进来之后可以看到这里面有两个函数,我们可以先进sub_86D8()分析一下。这里我们可以看到参数V7是256长度的数组,V4是a1的长度。
这里把参数v7改名为了arrar_256,这里我们还是可以分辨出来这里是我们源码中的s[i]和s[j]的交换。如下面这个rc4_init处源码的截图
从sub_86D8()出来再来分析另一个函数sub_88B8()
这里的参数v7是一个256的数组,a2是一个字符串,v2是这个字符串的长度
- 通过对比反编译代码和源码的循环次数和结构,可以看出来这里反编译的是rc4_crypt()
分析完成之后,我们就可以编写frida或者xposed代码,去对分析出来的函数去进行主动调用
编写frida代码对函数进行主动调用
通过前面的分析,这里我们基本可以通过分析结果来认定这是一个RC4算法
- 我们先对sub_89F4()进行调用,它的两个参数都是字符串地址
function activeinvokesub_89F4(content1, content2){
var offset = 0x89F4 + 1;
var nativelibmodule = Process.getModuleByName("libnative-lib.so");
var sub_89F4addr = nativelibmodule.base.add(offset);
var arg0 = Memory.allocUtf8String(content1);
var arg1 = Memory.allocUtf8String(content2);
var sub_89F4 = new NativeFunction(sub_89F4addr, 'void', ['pointer', 'pointer']);
console.log("input_arg0: ",hexdump(arg0),'\n input_agr1: ', hexdump(arg1));
console.log('====================================');
var result = sub_89F4(arg0, arg1);
console.log("result_arg0: ",hexdump(arg0),'\n result_arg1: ', hexdump(arg1));
}
这里分别在函数执行前和执行后,打印一下它的参数变化
传入参数调用函数执行
activeinvokesub_89F4('a','abcd')
在执行结果中,仅有函数执行后的arg1产生了变化,变化的位数跟我们传入的字节数是相同的
又经过几次测试,改变arg1传入字符串的长度发现result_agr1变化的位数跟我们传入的长度是一致的,基本可以说明它是一个序列密码
序列密码在不改变密钥的情况下,它的子密钥序列是特定的
测试一下:我们将arg1参数改为
abcde
得到- 可以看到这个结果的前四个字节是跟之前一样的
接下来我们如果想要获取这个RC4算法加密用的密钥就要去在刚刚分析的加密执行函数(sub_86D8、sub_88B8)调用前,通过hook拿到它的key
应该怎么选取hook时机呢
首先,so文件呢都是通过System.loadLibrary方法去加载的。
需要在加载完so文件之后,执行JNI_Onload之前的流程中用到的一些函数进行hook
function hooklibnativelib(){
// sub_86D8((int)v7, (int)a1, v4);
// sub_88B8(v7, a2, v2);
var nativelibmodule = Process.getModuleByName("libnative-lib.so");
var sub_86D8addr = nativelibmodule.base.add(0x86D8 + 1);
var sub_88B8addr = nativelibmodule.base.add(0x88B8 + 1);
Interceptor.attach(sub_86D8addr, {
onEnter: function(args){
console.log("RC4_init onEnter");
console.log("key: ", hexdump(args[1]),"\n keyLen: ", args[2]);
},onLeave:function(retval){
console.log("RC4_init onLeave");
}
})
Interceptor.attach(sub_88B8addr, {
onEnter: function(args){
this.arg1 = args[1];
console.log("RC4_crypt onEnter");
console.log("content: ", hexdump(args[1]), "\n contentLen: ", args[2]);
},onLeave:function(retval){
console.log("RC4_crypt onLeave");
console.log("cryptResult: ", hexdump(this.arg1));
}
})
}
function main(){
if(Java.available){
Java.perform(function(){
var RuntimeClass = Java.use("java.lang.Runtime");
RuntimeClass.loadLibrary0.implementation = function(arg0,arg1){
var result = this.loadLibrary0(arg0, arg1);
console.log("loadLibrary0: ", arg1);
if(arg1.indexOf("native-lib") != -1){
hooklibnativelib();
}
return result;
}
})
}
}
setImmediate(main);
RC4加密算法在逆向分析过程中的快速识别方法:
- 首先判断铭文和密文长度是否相等,登场则代表是序列密码。(可以通过编写frida脚本或xposed插件完成对响应函数的主动调用,判断当输入明文未任意长度时的密文长度)
- 接下来判断是否是RC4。RC4算法中的初始化算法(KSA)中有两轮非常显著的长度为256的循环体,用于根据给定的key生成S盒;伪随机子密码生成算法(PRGA)会根据上一步得到的扰乱的S盒,进一步生成子密钥流,最终和给定的明文进行逐字节的异或。
附件:
本章代码地址
- 标题: 序列密码之RC4实践篇
- 作者: xiaoeryu
- 创建于 : 2024-02-21 13:33:27
- 更新于 : 2024-02-21 13:56:58
- 链接: https://github.com/xiaoeryu/2024/02/21/序列密码之RC4实践篇/
- 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。