序列密码之RC4实践篇

xiaoeryu Lv5

本章主要使用上一章开发的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加密算法在逆向分析过程中的快速识别方法:

  1. 首先判断铭文和密文长度是否相等,登场则代表是序列密码。(可以通过编写frida脚本或xposed插件完成对响应函数的主动调用,判断当输入明文未任意长度时的密文长度)
  2. 接下来判断是否是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 进行许可。
评论