xx银行app数据加解密

使用定制rom机绕过frida检测

但是这个app还有一些环境检测,在打开app一段时间后会弹窗一个弹窗,点击确定后就会退出

因为这是一个弹窗,所以可以从java的弹窗类进行入手

Java.perform(function () {
    // 获取 AlertDialog 类
    var AlertDialog = Java.use('android.app.AlertDialog');
  
    if (AlertDialog) {
      // Hook show 方法
      AlertDialog.show.implementation = function () {
        console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
        console.log('[*] AlertDialog is about to show.');
        
        // 可以在这里添加自定义逻辑,比如阻止显示
        //return; 
  
        // 调用原始的 show 方法
        return this.show();
      };
    }
  });

打印出调用弹窗的堆栈

这个类名含有risk,很像环境的检测的地方,jadx打开看一下代码

直接定位过来,代码很少,弹窗代码主要是通过a(relativeLayout)进行赋值

会返回一个AlertDialog类,那弹窗就是在这里,这里定位到builder.setPositiveButton(17039370, new a()); 看看弹窗的处理逻辑

定位到这个onClick方法监听,调用了so层的方法处理弹窗的逻辑,这里尝试先看看能不能将AlertDialog的show方法直接return,不让弹窗show出来

Java.perform(function () {
    // 获取 AlertDialog 类
    var AlertDialog = Java.use('android.app.AlertDialog');
  
    if (AlertDialog) {
      // Hook show 方法
      AlertDialog.show.implementation = function () {
        console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
        console.log('[*] AlertDialog is about to show.');
        
        // 可以在这里添加自定义逻辑,比如阻止显示
        return; 
  
        // 调用原始的 show 方法
        //return this.show();
      };
    }
  });

发现弹窗虽然不显示了,但是app会卡死,判断是弹窗的按钮处理逻辑处影响了后续的代码执行,得先看看so层代码是如何处理的

查看native方法调用处

是加载了这个bangcle_risk这个so文件,因为这个app frida检测已经绕过,并且app不会立马退出,可以尝试从内存dump出so文件,这样即使有加密后的so文件也能更清晰的分析代码

这里先frida调用app

修改一下py代码中的id,然后使用attach的方式将so dump出来

import frida
js_script = """
function dump_so(so_name) {
    console.log("开始dump")
    const module = Process.findModuleByName(so_name);
    const module_size = module.size;
    Memory.protect(ptr(module.base), module_size, 'rwx');
    const soMemory = Memory.readByteArray(module.base, module_size);
    send({name: so_name, base: module.base, size: module_size}, soMemory);
}
dump_so("libbangcle_risk.so")
"""

def on_message(message, data):
    if message['type'] == 'send':
        payload = message['payload']
        so_name = payload['name']
        base_address = payload['base']
        size = payload['size']
        print(f"Dumping {so_name} (Base: {base_address}, Size: {size})")
        with open(so_name, "wb") as f:
            f.write(data)
        print(f"{so_name} dumped successfully!")
    else:
        print(f"Error: {message}")

def main():
    # 附加到目标进程
    device = frida.get_usb_device()
    session = device.attach(10387)
    # 加载Frida脚本
    script = session.create_script(js_script)
    # 设置消息处理函数
    script.on("message", on_message)
    # 加载并执行脚本
    script.load()

if __name__ == "__main__":
    main()        

dump出来的so文件先修复一下

.\SoFixer-Windows-64.exe -s libbangcle_risk.so-o libbangcle_risk_fix.so

然后载入ida,搜索一下java

发现没有结果

并且存在大量的混淆方法名,猜测不是静态注册的代码,hook一下RegisterNatives看看动态注册

var ENV = null;
var JCLZ = null;

var method01addr = null;
var method02addr = null;
var method02 = null;

var addrNewStringUTF = null;

var NewStringUTF = null;


function hook_RegisterNatives() {
  var symbols = Module.enumerateSymbolsSync("libart.so");
  var addrRegisterNatives = null;
  for (var i = 0; i < symbols.length; i++) {
    var symbol = symbols[i];

    //_ZN3art3JNI15RegisterNativesEP7_JNIEnvP7_jclassPK15JNINativeMethodi
    if (symbol.name.indexOf("art") >= 0 &&
        symbol.name.indexOf("JNI") >= 0 && 
        symbol.name.indexOf("NewStringUTF") >= 0 && 
        symbol.name.indexOf("CheckJNI") < 0) {
      addrNewStringUTF = symbol.address;
      console.log("NewStringUTF is at ", symbol.address, symbol.name);
      NewStringUTF = new NativeFunction(addrNewStringUTF,'pointer',['pointer','pointer'])
    }
    if (symbol.name.indexOf("art") >= 0 &&
        symbol.name.indexOf("JNI") >= 0 && 
        symbol.name.indexOf("RegisterNatives") >= 0 && 
        symbol.name.indexOf("CheckJNI") < 0) {
      addrRegisterNatives = symbol.address;
      console.log("RegisterNatives is at ", symbol.address, symbol.name);
    }
  }

  if (addrRegisterNatives != null) {
    Interceptor.attach(addrRegisterNatives, {
      onEnter: function (args) {
        console.log("[RegisterNatives] method_count:", args[3]);
        var env = args[0];
        ENV = args[0];
        var java_class = args[1];
        JCLZ = args[1];
        var class_name = Java.vm.tryGetEnv().getClassName(java_class);
        //console.log(class_name);

        var methods_ptr = ptr(args[2]);

        var method_count = parseInt(args[3]);
        for (var i = 0; i < method_count; i++) {
          var name_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3));
          var sig_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize));
          var fnPtr_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize * 2));

          var name = Memory.readCString(name_ptr);
          var sig = Memory.readCString(sig_ptr);
          var find_module = Process.findModuleByAddress(fnPtr_ptr);
          console.log("[RegisterNatives] java_class:", class_name, "name:", name, "sig:", sig, "fnPtr:", fnPtr_ptr, "module_name:", find_module.name, "module_base:", find_module.base, "offset:", ptr(fnPtr_ptr).sub(find_module.base));
          if(name.indexOf("method01")>=0){
            // method01addr = fnPtr_ptr;
            continue;
          }else if (name.indexOf("decrypt")>=0){
            method02addr = fnPtr_ptr;
            method02 = new NativeFunction(method02addr,'pointer',['pointer','pointer','pointer']);
                        method01addr = Module.findExportByName("libroysue.so", "Java_com_roysue_easyso1_MainActivity_method01")
                    }else{
                        continue;
                    }

                }
            }
        });
    }
}


function invokemethod01(contents){
    
    console.log("method01_addr is =>",method01addr)
    var method01 = new NativeFunction(method01addr,'pointer',['pointer','pointer','pointer']);
    var NewStringUTF = new NativeFunction(addrNewStringUTF,'pointer',['pointer','pointer'])
    var result = null;
    Java.perform(function(){    
        console.log("Java.vm.getEnv()",Java.vm.getEnv())
        var JSTRING = NewStringUTF(Java.vm.getEnv(),Memory.allocUtf8String(contents))
        result = method01(Java.vm.getEnv(),JSTRING,JSTRING);
        console.log("result is =>",result)
        console.log("result is ",Java.vm.getEnv().getStringUtfChars(result, null).readCString())
        result = Java.vm.getEnv().getStringUtfChars(result, null).readCString();

    })
    return result;
}

function invokemethod02(contents){
    var result = null;
    Java.perform(function(){    
        var JSTRING = NewStringUTF(Java.vm.getEnv(),Memory.allocUtf8String(contents))
        result = method02(Java.vm.getEnv(),JSTRING,JSTRING);
        result = Java.vm.getEnv().getStringUtfChars(result, null).readCString();
    })
    return result;
}
rpc.exports = {
    invoke1:invokemethod01,
    invoke2:invokemethod02
};

setImmediate(hook_RegisterNatives);


/*
java_class: com.example.demoso1.MainActivity name: method01 sig: (Ljava/lang/String;)Ljava/lang/String; fnPtr: 0x73e2cd1018 module_name: libnative-lib.so module_base: 0x73e2cc1000 offset: 0x10018
java_class: com.example.demoso1.MainActivity name: method02 sig: (Ljava/lang/String;)Ljava/lang/String; fnPtr: 0x73e2cd0efc module_name: libnative-lib.so module_base: 0x73e2cc1000 offset: 0xfefc

function hookmethod(addr){
    Interceptor.attach(addr,{
        onEnter:function(args){
            console.log("args[0]=>",args[0])
            console.log("args[1]=>",args[1])
            console.log("args[2]=>",Java.vm.getEnv().getStringUtfChars(args[2], null).readCString())
        },onLeave:function(retval){
            console.log(Java.vm.getEnv().getStringUtfChars(retval, null).readCString())
        }
    })
}


function replacehook(addr){
    //> 能够hook上,就能主动调用
    var addrfunc = new NativeFunction(addr,'pointer',['pointer','pointer','pointer']);
    Interceptor.replace(addr,new NativeCallback(function(arg1,arg2,arg3){
        // 确定主动调用可以成功,只要参数合法,地址正确
        var result = addrfunc(arg1,arg2,arg3)
        console.log(arg1,arg2,arg3)
        console.log("result is ",Java.vm.getEnv().getStringUtfChars(result, null).readCString())
        return result;
    },'pointer',['pointer','pointer','pointer']))
}


*/

确实是动态注册,查看该处的函数偏移0x15574

JNIEnv *__fastcall cV(JNIEnv *result, __int64 a2, _QWORD *a3)
{
    __int64 v3; // x9
    unsigned int v6; // w8
    int v7; // w25
    void **v8; // x27
    __int64 *v9; // x23
    __int64 v10; // x26
    _QWORD *v11; // x21
    __int64 v12; // x0
    __int64 v13; // x0
    __int64 v14; // x0
    void *__ptr32 *v15; // x12
    void *__ptr32 *v16; // x11
    __int64 v17; // x9
    int v18; // w9
    unsigned int v19; // w9
    void *__ptr32 *v20; // x19
    __int64 v21; // x0
    int v22; // w9
    unsigned int v23; // w8
    int v24; // w22
    __int64 v25; // x8
    __int64 v26; // x8
    unsigned int v27; // w8
    int v28; // w9
    __int64 v29; // d8
    unsigned int v30; // s8
    __int64 v31; // x0
    void **v32; // x20
    unsigned __int16 v33; // w27
    int v34; // w8
    __int64 v35; // x0
    __int64 v36; // x2
    __int64 v37; // x0
    unsigned __int64 v38; // x9
    __int64 v39; // x28
    unsigned int v40; // [xsp+14h] [xbp-2FCh]
    __int64 v41; // [xsp+18h] [xbp-2F8h]
    __int64 v42; // [xsp+20h] [xbp-2F0h]
    __int64 v43; // [xsp+28h] [xbp-2E8h]
    int v44; // [xsp+34h] [xbp-2DCh]
    JNIEnv *v45; // [xsp+38h] [xbp-2D8h]
    __int64 v46; // [xsp+40h] [xbp-2D0h]
    int v47; // [xsp+4Ch] [xbp-2C4h]
    __int64 v48; // [xsp+50h] [xbp-2C0h]
    int v49; // [xsp+5Ch] [xbp-2B4h]
    __int64 v50; // [xsp+60h] [xbp-2B0h]
    __int64 v51[64]; // [xsp+68h] [xbp-2A8h]
    unsigned __int16 v52; // [xsp+268h] [xbp-A8h] BYREF
    unsigned __int16 v53; // [xsp+26Ah] [xbp-A6h]
    __int16 v54; // [xsp+26Ch] [xbp-A4h]
    __int64 v55; // [xsp+270h] [xbp-A0h]
    __int64 v56; // [xsp+278h] [xbp-98h]
    int v57; // [xsp+280h] [xbp-90h]
    int v58; // [xsp+284h] [xbp-8Ch]
    JNIEnv *v59; // [xsp+288h] [xbp-88h]
    bool v60; // [xsp+290h] [xbp-80h]
    int v61; // [xsp+29Ch] [xbp-74h]
    __int64 v62; // [xsp+2A0h] [xbp-70h] BYREF
    int v63; // [xsp+2ACh] [xbp-64h]

    v6 = 0;
    v61 = 0;
    v7 = 14621;
    v8 = j__Il0SIIIOI_IlIIS5SIIS0II_IlSl___I_0Il_0Il_0_lll0OS5__ptr;
    v9 = (_QWORD *)(&stru_DD8 + 6);
    v10 = 2LL;
    v50 = v3;
    LABEL_3:
    while ( v6 != 1 )
    {
        if ( !v6 )
        {
            v40 = v7 - 139 * ((unsigned int)(60350 * v7) >> 23);
            v11 = result;
            v45 = result;
            v44 = ((__int64 (__fastcall *)(JNIEnv *, __int64))(*result)->GetArrayLength)(result, a2) - 1;
            v12 = (*(__int64 (__fastcall **)(_QWORD *, __int64))(*v11 + 1384LL))(v11, a2);
            v13 = sub_8690(v11, v12, *(_QWORD *)((char *)&elf_hash_bucket[116] + (_QWORD)v8));
            v14 = sub_8A30(v13);
            v16 = &jpt_15924;
            v42 = v14;
            v7 = 3550 - v40;
            v17 = v50;
            while ( 2 )
            {
                v50 = v17;
                v43 = (int)v17;
                if ( (int)v17 >= v44 )
                    v18 = 2;
                else
                    v18 = 3;
                v47 = v18;
                v19 = v7;
                LABEL_12:
                v7 = v19;
                LABEL_14:
                v19 = 17638;
                switch ( v47 )
                {
                    case 0:
                        v17 = (unsigned int)(v50 + 1);
                        continue;
                    case 1:
                        goto LABEL_12;
                    case 2:
                        v41 = a2;
                        v63 = 0;
                        v22 = 20945;
                        v23 = 22;
                        v46 = 4294967267LL;
            v49 = -29;
            v48 = 4294967267LL;
            while ( 2 )
            {
              v24 = v22;
              switch ( v23 )
              {
                case 0u:
                  v23 = 30;
                  continue;
                case 1u:
                  v25 = (unsigned __int8)sub_8A80(v59, v10, *(_QWORD *)((char *)&elf_hash_bucket[124] + (_QWORD)v8)) != 0;
                  goto LABEL_55;
                case 2u:
                  v10 = v51[v49];
                  v23 = 23;
                  ++v49;
                  continue;
                case 3u:
                  v23 = 17;
                  continue;
                case 4u:
                  v46 = v51[v49];
                  v23 = 24 - v22 + 13 * ((unsigned int)(40330 * v22) >> 19);
                  ++v49;
                  continue;
                case 5u:
                  goto LABEL_65;
                case 6u:
                  v23 = 18;
                  continue;
                case 7u:
                  v49 = 0;
                  if ( v60 )
                    v23 = 20;
                  else
                    v23 = 4;
                  v9 = (__int64 *)(v11[1] + 8 * (v52 - (unsigned __int64)v53));
                  v22 = 11933;
                  continue;
                case 8u:
                  v26 = v56;
                  v11[6] = v59;
                  *v11 = v26 + 16;
                  sub_8DF0(v11, &v52, a3, v62);
                  *a3 = v11[2];
                  v23 = 24;
                  v22 = v24;
                  continue;
                case 9u:
                  v22 = 13710;
                  v23 = 14;
                  continue;
                case 0xAu:
                  v10 = 0LL;
                  v23 = 10;
                  continue;
                case 0xBu:
                case 0x11u:
                  *v9++ = v10;
                  ((void (__fastcall *)(__int64 *, __int64))loc_C484)(&v62, v10);
                  goto LABEL_17;
                case 0xCu:
                  *v9++ = v46;
                  ((void (__fastcall *)(__int64 *))loc_C484)(&v62);
                  v23 = 20;
                  v22 = v24;
                  continue;
                case 0xDu:
                  *v9++ = (int)sub_8690(v59, v10, *(_QWORD *)((char *)&elf_hash_bucket[116] + (_QWORD)v8));
                  (*v59)->DeleteLocalRef(v59, (jobject)v10);
                  v27 = 17 * ((unsigned int)(61681 * v24) >> 20);
                  v28 = 32;
                  goto LABEL_32;
                case 0xEu:
                  v29 = COERCE_UNSIGNED_INT128(sub_8860(v59, v10, *(_QWORD *)((char *)&elf_hash_bucket[130] + (_QWORD)v8)));
                  (*v59)->DeleteLocalRef(v59, (jobject)v10);
                  *v9 = v29;
                  v9[1] = 0LL;
                  v9 += 2;
                  v27 = 17 * ((unsigned int)(61681 * v24) >> 20);
                  v28 = 24;
LABEL_32:
                  v23 = v28 - v24 + v27;
                  v22 = v24;
                  continue;
                case 0xFu:
                  v22 = 24765;
                  v23 = 31;
                  continue;
                case 0x10u:
                  if ( *(_BYTE *)v48 )
                    v23 = 2;
                  else
                    v23 = 8;
                  continue;
                case 0x12u:
                  v30 = COERCE_UNSIGNED_INT128(sub_8710(v59, v10, *(_QWORD *)((char *)&elf_hash_bucket[128] + (_QWORD)v8)));
                  (*v59)->DeleteLocalRef(v59, (jobject)v10);
                  *v9++ = v30;
                  goto LABEL_17;
                case 0x13u:
                  v23 = 26;
                  continue;
                case 0x14u:
                  v10 = 0LL;
                  goto LABEL_17;
                case 0x15u:
                  v23 = 28;
                  continue;
                case 0x16u:
                  v48 = v55 + 1;
                  v62 = 0LL;
                  v31 = sub_88C0(64LL);
                  v32 = v8;
                  v33 = v52;
                  v11 = (_QWORD *)v31;
                  *(_QWORD *)(v31 + 8) = sub_88C0(8LL * v52);
                  sub_8B70();
                  *((_WORD *)v11 + 28) = v33;
                  v8 = v32;
                  v23 = 7;
                  v22 = v24;
                  continue;
                case 0x17u:
                  v34 = *(unsigned __int8 *)v48++;
                  if ( v34 > 89 && v34 == 90 )
                    goto LABEL_64;
LABEL_65:
                  v23 = 27;
                  continue;
                case 0x18u:
                  sub_8DE0(v11[1]);
                  sub_8DE0(v11);
                  sub_D9F8(&v62);
                  v23 = 32;
                  v22 = v24;
                  continue;
                case 0x19u:
                  v22 = 8431;
                  v23 = 13;
                  continue;
                case 0x1Au:
                  v35 = (__int64)v59;
                  v36 = *(_QWORD *)((char *)&elf_hash_bucket[122] + (_QWORD)v8);
                  goto LABEL_53;
                case 0x1Bu:
                  v37 = (__int64)v59;
                  do
                  {
                    do
                      v38 = __ldaxr((unsigned __int64 *)&qword_8C9A0);
                    while ( !v38 && __stlxr(1uLL, (unsigned __int64 *)&qword_8C9A0) );
                  }
                  while ( v38 == 1 );
                  if ( v38 != 2 )
                  {
                    WORD1(off_8CD18) = 24912;
                    strcpy((char *)&qword_8CD20, "args err type");
                    HIDWORD(off_8CD18) = 543519602;
                    qword_8C9A0 = 2LL;
                  }
                  (*(void (__fastcall **)(__int64, char *))(*(_QWORD *)v37 + 144LL))(v37, (char *)&off_8CD18 + 2);
                  goto LABEL_17;
                case 0x1Cu:
                  v35 = (__int64)v59;
                  v36 = *(_QWORD *)((char *)&elf_hash_bucket[118] + (_QWORD)v8);
LABEL_53:
                  v25 = (int)sub_8690(v35, v10, v36);
                  goto LABEL_55;
                case 0x1Du:
LABEL_64:
                  v23 = 1;
                  continue;
                case 0x1Eu:
                  v25 = (unsigned __int16)sub_86F0(v59, v10, *(_QWORD *)((char *)&elf_hash_bucket[120] + (_QWORD)v8));
LABEL_55:
                  *v9++ = v25;
                  (*v59)->DeleteLocalRef(v59, (jobject)v10);
LABEL_17:
                  v23 = 16;
                  v22 = v24;
                  continue;
                case 0x1Fu:
                  v39 = sub_8B40(v59, v10, *(_QWORD *)((char *)&elf_hash_bucket[126] + (_QWORD)v8));
                  (*v59)->DeleteLocalRef(v59, (jobject)v10);
                  *v9 = v39;
                  v9[1] = 0LL;
                  v9 += 2;
                  v23 = 121 - v24 + 137 * ((unsigned int)(61231 * v24) >> 23);
                  v22 = v24;
                  continue;
                case 0x20u:
                  v19 = 8789 - (-29 * ((unsigned int)(36158 * v7) >> 20) + v7);
                  a2 = v41;
                  v9 = (_QWORD *)(&stru_DD8 + 6);
                  v10 = 2LL;
                  v16 = v15;
                  goto LABEL_12;
                default:
                  continue;
              }
            }
          case 3:
            v11 = a3;
            v20 = v16;
            v21 = (__int64)(*v45)->GetObjectArrayElement(v45, (jobjectArray)a2, v50);
            v16 = v20;
            a3 = v11;
            v51[v43] = v21;
            goto LABEL_14;
          case 4:
            v17 = 0LL;
            v60 = *(_DWORD *)(v42 + 28) != 0;
            v59 = v45;
            continue;
          case 5:
            v52 = *(_DWORD *)(v42 + 16);
            v53 = *(_DWORD *)(v42 + 20);
            v54 = *(_DWORD *)(v42 + 24);
            v55 = *(_QWORD *)(v42 + 32);
            v56 = *(_QWORD *)(v42 + 8);
            v57 = *(_DWORD *)v42;
            v58 = *(_DWORD *)(v42 + 4);
            goto LABEL_14;
          case 6:
            v6 = 27 - v40;
            result = v45;
            goto LABEL_3;
          default:
            goto LABEL_14;
        }
      }
    }
  }
  return result;
}

代码非常冗杂,先hook这个方法看看传进了什么数据

let JniLib = Java.use("com.fort.andJni.JniLib1700472038");
JniLib["cV"].implementation = function (objArr) {
    console.log(`JniLib.cV is called: objArr=${objArr}`);
    this["cV"](objArr);
};

会打印出很多信息,可能so层不仅仅是处理了弹窗逻辑,直接分析检测代码不太可行,直接patch掉这个方法也不行,回到java方法

这里会传入一个AlertDialog对象,看能不能通过过滤参数进行patch,在处理弹窗逻辑的时候进行return

function hook_tmp() {
    Java.perform(function () {
        // 获取 JniLib1700472038 类
        let JniLib = Java.use("com.fort.andJni.JniLib1700472038");
        if (JniLib) {
            // 保存原始的 cV 方法
            let originalCV = JniLib["cV"];
            // 重写 cV 方法
            JniLib["cV"].implementation = function (objArr) {
                console.log(`JniLib.cV is called: objArr=${objArr}`);

                // 检查参数中是否包含 AlertDialog 对象
                let hasAlertDialog = false;
                for (let i = 0; i < objArr.length; i++) {
                    if (Java.isInstanceOf(objArr[i], Java.use('android.app.AlertDialog'))) {
                        hasAlertDialog = true;
                        break;
                    }
                }

                if (hasAlertDialog) {
                    console.log("Parameter contains an AlertDialog object, skipping original method call.");
                    return null; // 可以根据实际需求返回合适的值
                } else {
                    // 调用原始的 cV 方法
                    return originalCV.call(this, objArr);
                }
            };
        }
    });
}

发现app可以正常运行,不会卡住,也没有弹窗

使用小黄鸟抓一下包,发现业务请求包处有"body"的加密字段,尝试在jadx搜索一下

能搜出来挺多的,但是看这些方法名都不太像,还是先hook toString和getBytes方法看看有没有突破点

function hook_toString(){
    Java.perform(function(){
        var strCls = Java.use("java.lang.StringBuilder");

       strCls.toString.implementation = function(){

            var result = this.toString();
            if(result.indexOf("136929")!=-1){
                console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
                console.log(result);
            }
            return result;

    }
    });
}

这个方法很像返回数据包接收处,jadx过去看一下

这里有个log方法,log方法会打印处返回数据,先hook 一下这个log方法看看数据先

function hook_log(){
    Java.perform(function(){
        let Logs = Java.use("com.yitong.mobile.component.logging.Logs");
        Logs["i"].overload('java.lang.String', 'java.lang.String').implementation = function (str, str2) {
            console.log(`Logs.i is called: str=${str}, str2=${str2}`);
            this["i"](str, str2);
        };
    })
}

确实打印出了参数信息,这里的网关原始报文是解密前的数据,接口原始报文就是解密后的,那先来看看jadx代码

查看n的赋值和定义

是通过这个getResponseString获取的,写出hook代码

function hook_response1(){
    Java.perform(function(){
        let APPResponseHandler = Java.use("com.yitong.mobile.network.http.APPResponseHandler");
        APPResponseHandler["getResponseString"].implementation = function () {
            console.log(`APPResponseHandler.getResponseString is called`);
            let result = this["getResponseString"]();
            console.log(`APPResponseHandler.getResponseString result=${result}`);
            return result;
        };
    })
}

经测试确实能hook出数据,但是不全,比如个人信息处查询不到数据,猜测可能还有其它方法进行处理,回去再看看log

这里还有一个报文解密字段,猜测是个人信息加密处,直接定位

这里的str4是经过this.f.decrypt方法,点进去f方法查看

发现是一个接口,直接hook肯定没有数据,得找到哪里使用了这个接口,直接看f是什么对象即可

f对象在类定义中找不到,那得看继承的父类AppGatewayBaseResponseHandler

父类这里定义了f对象

在setDataProcessor这里进行了赋值,那还得看看传进来的IDataProcessor对象,但是IDataProcesso是接口,还还得看看setDataProcessor的调用,看看是哪里对这个方法进行赋值

只有3处调用,经过hook定位,确定到了此处代码

最终方法是这个V2DataProcessor类下的decrypt方法

function hook_decrypt2(){
     Java.perform(function(){
        let V2DataProcessor = Java.use("com.yitong.mobile.network.process.V2DataProcessor");
        V2DataProcessor["decrypt"].implementation = function (str) {
            console.log(`V2DataProcessor.decrypt is called: str=${str}`);
            let result = this["decrypt"](str);
            console.log(`V2DataProcessor.decrypt result=${result}`);
            return result;
        };
     })
}

参数请求比较容易,直接搜索"参数信息"即可定位

直接hook post方法即可

function hook_post(){
    Java.perform(function(){
        let APPRestClient = Java.use("com.yitong.mobile.network.http.APPRestClient");
        APPRestClient["post"].overload('java.lang.String', 'java.lang.String', 'java.lang.String', 'com.yitong.http.ResponseHandlerInterface', 'java.lang.String').implementation = function (str, str2, str3, responseHandlerInterface, str4) {
            console.log(`APPRestClient.post is called: str=${str}, str2=${str2}, str3=${str3}, responseHandlerInterface=${responseHandlerInterface}, str4=${str4}`);
            this["post"](str, str2, str3, responseHandlerInterface, str4);
        };
    })
}

完整的渗透脚本如下

'use strict';
var colors = {
    "resetColor": "\x1b[0m",
    "green": "\x1b[32m",
    "yellow": "\x1b[33m",
    "red": "\x1b[31m"
}

Java.perform(function () {
    // Request Class & Method
    var request_class = Java.use('com.yitong.mobile.network.http.APPRestClient');
    var request_method = request_class.post.overload('java.lang.String', 'java.lang.String', 'java.lang.String', 'com.yitong.http.ResponseHandlerInterface', 'java.lang.String');

    // Response Class & Method
    var response_class = Java.use('com.yitong.mobile.network.http.APPResponseHandler');
    var response_method = response_class.getResponseString.overload();

    var response_class2 = Java.use('com.yitong.mobile.network.process.V2DataProcessor');
    var response_method2 = response_class2.decrypt.overload('java.lang.String');

    var path = null;

    request_method.implementation = function (str1,str2,str3,responseHandlerInterface,str4) {
         
         path = str1;
         var jsString = str2;

        console.log(colors.green, "[Original Request Body]\n", colors.resetColor, jsString, '\n');

        send({ from: '/http', payload: jsString, api_path: path });

        var rcv_data = "FAILURE";

        var op = recv('input', function (value) {
            console.log(colors.green, "Data type: " + typeof value.payload);
            if (typeof value.payload === 'string') {
                try {
                    rcv_data = value.payload;

                } catch (e) {
                    console.error("Error parsing JSON for request:", e);
                }
            } else {
                console.error("Received payload is not a string:", value.payload);
            }
        }).wait(5000); // 等待 5 秒,防止阻塞

        if (rcv_data === "FAILURE") {
            var result = this.post(str1, str2, str3, responseHandlerInterface, str4);
        } else {
         var result = this.post(str1, rcv_data, str3, responseHandlerInterface, str4);
        }
        return result;
    }

    response_method.implementation = function () {
        var js = this.getResponseString();
        console.log(colors.green, "[Original Response Body]\n", colors.resetColor, js, '\n');
        path = "response";
        send({ from: '/http', payload: js, api_path: path});

        var rcv_data = "FAILURE";

        var op = recv('input', function (value) {
            console.log(colors.green, "Data type: " + typeof value.payload);
            if (typeof value.payload === 'string') {
                try {
                    // 尝试将字符串转换为对象
                    rcv_data = value.payload
                    console.log("调用了string处代码")
                } catch (e) {
                    console.error("Error parsing string payload:", e);
                }
            } else if (typeof value.payload === 'object') {
                rcv_data = value.payload;
            } else {
                console.error("Received payload has an unexpected type:", typeof value.payload);
            }
        }).wait(5000);

        if (rcv_data === "FAILURE") {
            var result = this.getResponseString();
            return result;
        } else {
            return rcv_data;
        }
        
    }

    response_method2.implementation = function (encrypted_data) {
        //console.log(colors.green, "[Original Response Body]\n", colors.resetColor, JSON.stringify(response_data), '\n');
        var js = this.decrypt(encrypted_data);
        path = "response";
        send({ from: '/http', payload: js, api_path: path});

        var rcv_data = "FAILURE";

        var op = recv('input', function (value) {
            console.log(colors.green, "Data type: " + typeof value.payload);
            if (typeof value.payload === 'string') {
                try {
                    // 尝试将字符串转换为对象
                    rcv_data = value.payload
                    console.log("调用了string处代码")
                } catch (e) {
                    console.error("Error parsing string payload:", e);
                }
            } else if (typeof value.payload === 'object') {
                rcv_data = value.payload;
            } else {
                console.error("Received payload has an unexpected type:", typeof value.payload);
            }
        }).wait(5000);

        if (rcv_data === "FAILURE") {
            var result = this.decrypt(response_data);
            return result;
        } else {
            return rcv_data;
        }
        //return result;
    }
});

function hook_tmp() {     //绕过环境检测
    Java.perform(function () {
        // 获取 JniLib1700472038 类
        let JniLib = Java.use("com.fort.andJni.JniLib1700472038");
        if (JniLib) {
            // 保存原始的 cV 方法
            let originalCV = JniLib["cV"];
            // 重写 cV 方法
            JniLib["cV"].implementation = function (objArr) {
                //console.log(`JniLib.cV is called: objArr=${objArr}`);

                // 检查参数中是否包含 AlertDialog 对象
                let hasAlertDialog = false;
                for (let i = 0; i < objArr.length; i++) {
                    if (Java.isInstanceOf(objArr[i], Java.use('android.app.AlertDialog'))) {
                        hasAlertDialog = true;
                        break;
                    }
                }

                if (hasAlertDialog) {
                    //console.log("Parameter contains an AlertDialog object, skipping original method call.");
                    return null; // 可以根据实际需求返回合适的值
                } else {
                    // 调用原始的 cV 方法
                    return originalCV.call(this, objArr);
                }
            };
        }
    });
}

hook_tmp()
import frida
import requests
import time
import sys
import os
import socket
import argparse
from log import *
import psutil
print ('''\033[1;31m \n
  ___       _                          _        _    ____ ___ 
 |_ _|_ __ | |_ ___ _ __ ___ ___ _ __ | |_     / \  |  _ \_ _|
  | || '_ \| __/ _ \ '__/ __/ _ \ '_ \| __|   / _ \ | |_) | | 
  | || | | | ||  __/ | | (_|  __/ |_) | |_   / ___ \|  __/| | 
 |___|_| |_|\__\___|_|  \___\___| .__/ \__| /_/   \_\_|  |___|
                                |_|
        https://noobpk.github.io      #noobboy       
           Intercept Api in iOS/Android Application       
''')

print ("\033[1;34m[*]___author___: @noobpk\033[1;37m")
print ("\033[1;34m[*]___version___: 1.3\033[1;37m")
print ("")

BURP_HOST = "127.0.0.1"
BURP_PORT = 26080

def check_platform():
    try:
        platforms = {
            'linux'  : 'Linux',
            'linux1' : 'Linux',
            'linux2' : 'Linux',
            'darwin' : 'OS X',
            'win32'  : 'Windows'
        }
        if sys.platform not in platforms:
            sys.exit(logger.error("[x_x] Your platform currently does not support."))
    except Exception as e:
        logger.error("[x_x] Something went wrong, please check your error message.\n Message - {0}".format(e))

# def check_ps_for_win32():
#     try:
#         if sys.platform == "win32":
#             PROCESSNAME = "iTunes.exe"
#             for proc in psutil.process_iter():
#                 try:
#                     if proc.name() == PROCESSNAME:
#                         return True
#                 except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess) as e:
#                     pass
#             return sys.exit(logger.error("[x_x] Please install iTunes on MicrosoftStore or run iTunes frist."))              
#     except Exception as e:
#         logger.error("[x_x] Something went wrong, please check your error message.\n Message - {0}".format(e))

def check_echo_server():
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    result = sock.connect_ex(('127.0.0.1',27080))
    if result == 0:
        logger.info("[*] Connect to echoServer successfully.")
    else:
        sock.close()
        sys.exit(logger.error("[x_x] Please start echoServer."))

def run():
    #check platform support
    check_platform()
    #check process iTunes for Win32s
    # check_ps_for_win32()
    #check python version
    if sys.version_info < (3, 0):
        logger.error("[x_x] iOS hook requires Python 3.x")
        sys.exit(0)
    else:
        handle_del_log()
        main()

def handle_del_log():
    try:
        pwd = os.getcwd()
        path = pwd + '/errors.log'
        file_stats = os.stat(path)
        if (file_stats.st_size > 1024000000): #delete errors.log if file size > 1024 MB
            os.remove(path)
        else:
            return True
    except Exception as e:
        logger.error("[x_x] Something went wrong when clear error log. Please clear error log manual.\n [Error Message] - {0}".format(e))

def main():
    def frida_process_message(message, data):
        handled = False
        print ('message:',  message)
        if message['type'] == 'input':
            handled = True
            print (message["payload"])
        elif message['type'] == 'send':
            body = message['payload']
            API_PATH = body['api_path']

            if body['from'] == '/http':
                try:
                    req = requests.request('FRIDA', 'http://%s:%d/%s' % (BURP_HOST, BURP_PORT, API_PATH), headers={'content-type':'text/plain'}, data=body['payload'].encode('utf-8'))
                    script.post({ 'type': 'input', 'payload': req.text })
                    handled = True
                except requests.exceptions.RequestException as e:
                    logger.error("[x_x] Connection refused, please check configurage on BurpSute.\n [Error Message] - {0}".format(e))

    parser = argparse.ArgumentParser()
    parser.add_argument("-p", "--package")
    parser.add_argument("-n", "--name")
    parser.add_argument("-s", "--script", help='custom handler script')
    args, leftovers = parser.parse_known_args()

    try:
        # Spawing application with custom script
        if args.package is not None and args.script is not None:
            #check echoServer
            check_echo_server()
            #
            if os.path.isfile(args.script):
                logger.info('[*] Spawning: ' + args.package)
                logger.info('[*] Script: ' + args.script)
                time.sleep(2)
                device = frida.get_device_manager().add_remote_device('127.0.0.1:11223')
                pid = device.spawn(args.package)
                session = device.attach(pid)
                # device.resume(pid)
                time.sleep(1)
                # session = device.attach(pid)
                # device = frida.get_local_device()
                # pid = device.spawn(args.package)
                # session = device.attach(pid)
                # device.resume(pid)
                # time.sleep(1)
                with open(args.script,'r',encoding="utf-8") as f:
                    script = session.create_script(f.read())
                script.on("message", frida_process_message)
                script.load()
                device.resume(pid)
                input()
            else:
                logger.error('[?] Script not found!')
        #Attaching default script to application
        if args.name is not None and args.script is not None:
            #check echoServer
            check_echo_server()
            #
            logger.info('[*] Attaching: ' + args.name)
            logger.info('[*] Script: ' + args.script)
            time.sleep(2)
            process = frida.get_usb_device().attach(args.name)
            with open(args.script) as f:
                script = process.create_script(f.read())
            script.on("message", frida_process_message)
            script.load()
            input()

    #EXCEPTION FOR FRIDA
    except frida.ServerNotRunningError:
        logger.error("Frida server is not running.")
    except frida.TimedOutError:
        logger.error("Timed out while waiting for device to appear.")
    except frida.TransportError:
        logger.error("[x_x] The application may crash or lose connection.")
    #EXCEPTION FOR OPTIONPARSING

    #EXCEPTION FOR SYSTEM
    except Exception as e:
        logger.error("[x_x] Something went wrong, please check your error message.\n Message - {0}".format(e))

    except KeyboardInterrupt:
        logger.info("Bye bro!!")
        # sys.exit(0)

if __name__ == '__main__':
    run()





posted @ 2025-04-08 17:39  GGBomb  阅读(181)  评论(0)    收藏  举报