“ 前言:初学逆向 请多多指教”
学习到的内容
—
1、对JNI接口提供的常用方法的了解和学习
2、在so层加密的未必不能用java层来进行hook
3、对IDA工具的了解和学习
4、RSA 1024位加密,如何加密的长度超过了117个字节会如何处理
01
—
APP登陆界面:
HTTP请求抓取的数据包:
POST http://mobile.ximalaya.com/passport-sign-mobile/v2/signin/password HTTP/1.1Cookie: 1&_device=android&00000000-765b-f767-ffff-ffffb1f6f2ee&6.3.90;channel=and-f5;impl=com.ximalaya.ting.android;osversion=19;XUM=CAAnCf6e;XIM=31508d6f5a927;c-oper=%E6%9C%AA%E7%9F%A5;net-mode=WIFI;res=720%2C1280;NSUP=;AID=MGEERkcGWHQ=;manufacturer=OPPO;xm_grade=1;domain=.ximalaya.com;path=/;x_xmly_resource=xm_source%3Ahomepage%26xm_medium%3Ahomepage;x_xmly_tid=3854071131308217476;x_xmly_ts=1603115956979;Cookie2: $version=1Accept: */*user-agent: ting_6.3.90(OPPO+R11+Plus,Android19)Content-Type: application/json; charset=utf-8Content-Length: 349Host: mobile.ximalaya.comConnection: Keep-AliveAccept-Encoding: gzipcaZsssMPghps7hYeuO/h3lG2ujXN6dzCSKwRJiOLGp/Dl1L22kRHxCXwpwaCsk8Blmlit2n7wXUpF9rfsgPKNiHQZEVLuDMpCzdKBk/8HTOg5SoI5Ee9iqN/x3EZLjgyKRzvw6rcKbN8CykAgXYJmvSZrXnROCAX7lDmdq53kNU AlqzqooeenWw2hUK+LtCdJvL8j2S6SYpG4eOU64C/6wGdlvdG0nR/1Sbv9AHBY4VyMm/z/3ENPhu6+ygJiGp8HCOjvaveMFqiW4+KrLusph1CV2Z2nPVwRYxDz7xGo0GYVuJB7Lg9ZYYyMSU2Tcjwer0if1u6UIIeQlx/+3dkbQ==
登陆hook日志如下:
10-19 21:59:16.976 3383-3383/com.ximalaya.ting.android D/MyHook: = = = = = = = = = =javax.crypto.Cipher's doFinal hooking was Starting = = = = = = = = = = =10-19 21:59:16.976 3383-3383/com.ximalaya.ting.android E/MyHook: The Stack: java.lang.Throwable: Stack Dump at com.example.xposedtest.Test$6.afterHookedMethod(Test.java:225) at de.robv.android.xposed.XposedBridge.handleHookedMethod(XposedBridge.java:645) at javax.crypto.Cipher.doFinal(Native Method) at com.ximalaya.ting.android.host.util.common.EncryptUtil.encryptByPublicKeyNative(Native Method) at com.ximalaya.ting.android.host.manager.i.a$1.a(LoginRequest.java:82) at com.ximalaya.ting.android.host.manager.i.a$1.onSuccess(LoginRequest.java:54) at com.ximalaya.ting.android.host.manager.request.a$a.run(ExecutorDeliveryM.java:92) at android.os.Handler.handleCallback(Handler.java:733) at android.os.Handler.dispatchMessage(Handler.java:95) at android.os.Looper.loop(Looper.java:136) at android.app.ActivityThread.main(ActivityThread.java:5001) at java.lang.reflect.Method.invokeNative(Native Method) at java.lang.reflect.Method.invoke(Method.java:515) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:793) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:609) at de.robv.android.xposed.XposedBridge.main(XposedBridge.java:132) at dalvik.system.NativeStart.main(Native Method)10-19 21:59:16.976 3383-3383/com.ximalaya.ting.android D/MyHook: Algorithm: RSA/ECB/PKCS1Padding Normal doFinal function's args[0]: {"account":"19817353426","nonce":"4786336759083248576","password":"e9bc0e13a8a16cbb07b175d92a113126","signature":"d8610-19 21:59:16.976 3383-3383/com.ximalaya.ting.android D/MyHook: Algorithm: RSA/ECB/PKCS1Padding Base64 Result Data: caZsssMPghps7hYeuO/h3lG2ujXN6dzCSKwRJiOLGp/Dl1L22kRHxCXwpwaCsk8Blmlit2n7wXUp F9rfsgPKNiHQZEVLuDMpCzdKBk/8HTOg5SoI5Ee9iqN/x3EZLjgyKRzvw6rcKbN8CykAgXYJmvSZ rXnROCAX7lDmdq53kNU=10-19 21:59:16.976 3383-3383/com.ximalaya.ting.android D/MyHook: = = = = = = = = = =javax.crypto.Cipher's doFinal hooking was Ending = = = = = = = = = = = = =10-19 21:59:16.976 3383-3383/com.ximalaya.ting.android D/MyHook: Algorithm: RSA/ECB/PKCS1Padding Normal doFinal function's args[0]: cf1da2325f7191630b265fefcb384"}10-19 21:59:16.976 3383-3383/com.ximalaya.ting.android D/MyHook: Algorithm: RSA/ECB/PKCS1Padding Base64 Result Data: AJas6qKHnp1sNoVCvi7QnSby/I9kukmKRuHjlOuAv+sBnZb3RtJ0f9Um7/QBwWOFcjJv8/9xDT4b uvsoCYhqfBwjo72r3jBaoluPiqy7rKYdQldmdpz1cEWMQ8+8RqNBmFbiQey4PWWGMjElNk3I8Hq9 In9bulCCHkJcf/t3ZG0=
这里会发现RSA分为两段进行加密了,原因是这里的RSA加密长度是1024bit,最大的加密长度是128-11,也就是117个字节,因为要加密的长度大于所以就分为两段加密!
HOOK日志中的RSA密钥:
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCVhaR3Or7suUlwHUl2Ly36uVmboZ3+HhovogDjLgRE9CbaUokS2eqGaVFfbxAUxFThNDuXq/fBD+SdUgppmcZrIw4HMMP4AtE2qJJQH/KxPWmbXH7Lv+9CisNtPYOlvWJ/GHRqf9x3TBKjjeJ2CjuVxlPBDX63+Ecil2JR9klVawIDAQAB
加密的明文:account=>手机号 password=>md5加密密码 nonce未知(其实这个是随机数 知不知道都可以 直接固定就好了) signature签名值
{"account":"19817353426","nonce":"4786336759083248576","password":"e9bc0e13a8a16cbb07b175d92a113126","signature":"d86cf1da2325f7191630b265fefcb384"}
JEB反编译 根据函数调用栈开始分析
重点java层关注如下内容:
LoginParameterModel v1 = new LoginParameterModel();v1.setAccount(this.b.get("account"));v1.setPassword(MD5.md5(this.b.get("password")));v1.setNonce(arg7);HashMap v0 = new HashMap();((Map)v0).put("account", v1.getAccount());((Map)v0).put("password", v1.getPassword());((Map)v0).put("nonce", arg7);v1.setSignature(d.a(((Map)v0)));v0_1 = EncryptUtil.getInstance(MainApplication.getMyApplicationContext()).encryptByPublicKeyNative(new Gson().toJson(v1, LoginParameterModel.class));
公钥的加密是在so层 public native String encryptByPublicKeyNative(String arg1)
重要的加密函数如下,自己就不说啥了,因为把注释全写在里面了,主要的作用就是将json的数组放进去然后进行RSA加密 最后Base64编码返回,这里大家不知道会不会想到怪不得hook能hook到RSA的公钥呢,是的,因为终究调用的还是java的RSA加密库!
然会看下面的数据,因为RSA加密的时候是分段加密了,所以就将hook的数据两个(这两个是分别进行base64编码的)进行拼接了起来 但是你会发现数据不一样!
前面部分还一样,但是后面部分就不一样了
caZsssMPghps7hYeuO/h3lG2ujXN6dzCSKwRJiOLGp/Dl1L22kRHxCXwpwaCsk8Blmlit2n7wXUpF9rfsgPKNiHQZEVLuDMpCzdKBk/8HTOg5SoI5Ee9iqN/x3EZLjgyKRzvw6rcKbN8CykAgXYJmvSZrXnROCAX7lDmdq53kNU AlqzqooeenWw2hUK+LtCdJvL8j2S6SYpG4eOU64C/6wGdlvdG0nR/1Sbv9AHBY4VyMm/z/3ENPhu6+ygJiGp8HCOjvaveMFqiW4+KrLusph1CV2Z2nPVwRYxDz7xGo0GYVuJB7Lg9ZYYyMSU2Tcjwer0if1u6UIIeQlx/+3dkbQ==上面是HTTP数据包中的数据=============================================下面是hook数据日志中的数据的两段base64编码的rsacaZsssMPghps7hYeuO/h3lG2ujXN6dzCSKwRJiOLGp/Dl1L22kRHxCXwpwaCsk8Blmlit2n7wXUpF9rfsgPKNiHQZEVLuDMpCzdKBk/8HTOg5SoI5Ee9iqN/x3EZLjgyKRzvw6rcKbN8CykAgXYJmvSZrXnROCAX7lDmdq53kNU=AJas6qKHnp1sNoVCvi7QnSby/I9kukmKRuHjlOuAv+sBnZb3RtJ0f9Um7/QBwWOFcjJv8/9xDT4buvsoCYhqfBwjo72r3jBaoluPiqy7rKYdQldmdpz1cEWMQ8+8RqNBmFbiQey4PWWGMjElNk3I8Hq9In9bulCCHkJcf/t3ZG0=拼接完的字符串,发现如下的和上面的不一样caZsssMPghps7hYeuO/h3lG2ujXN6dzCSKwRJiOLGp/Dl1L22kRHxCXwpwaCsk8Blmlit2n7wXUpF9rfsgPKNiHQZEVLuDMpCzdKBk/8HTOg5SoI5Ee9iqN/x3EZLjgyKRzvw6rcKbN8CykAgXYJmvSZrXnROCAX7lDmdq53kNU =AJas6qKHnp1sNoVCvi7QnSby/I9kukmKRuHjlOuAv+sBnZb3RtJ0f9Um7/QBwWOFcjJv8/9xDT4buvsoCYhqfBwjo72r3jBaoluPiqy7rKYdQldmdpz1cEWMQ8+8RqNBmFbiQey4PWWGMjElNk3I8Hq9In9bulCCHkJcf/t3ZG0=
所以还是得去看SO文件中的函数对json数据是如何进行处理的,RSA加密的函数分析如下,注释都写好了,其实这里最坑的就是下面循环的地方,导致RSA数据不一样的原因就是:RSA分成两段数据进行加密完了,之后就对这字节数组进行一次base64编码然后整体返回
int __fastcall rsa_encode(_JNIEnv *env, int a2, int a3, int a4){ _JNIEnv *env_3; // r4 int KeyFactory_class; // r0 int v6; // r5 int v7; // r0 int v8; // r0 const struct JNINativeInterface *v9; // r1 int result; // r0 int v11; // r0 int v12; // r5 int v13; // r0 int v14; // r6 int v15; // r0 int v16; // r5 int v17; // r0 int v18; // ST38_4 int v19; // r0 int v20; // ST34_4 struct _jmethodID *v21; // r0 int v22; // r0 const struct JNINativeInterface *v23; // r1 int v24; // r0 int v25; // ST38_4 int v26; // r0 int v27; // r5 int v28; // r0 int v29; // r6 int v30; // r0 int v31; // r4 int v32; // r6 int v33; // r5 jobject (*v34)(JNIEnv *, jclass, jmethodID, ...); // r4 int v35; // r0 int v36; // r0 void *v37; // r5 int v38; // r0 int v39; // r6 int v40; // r0 int v41; // r0 int v42; // r0 int v43; // r5 int v44; // r0 int v45; // ST4C_4 int getBytes_function; // r4 jobject (*v47)(JNIEnv *, jobject, jmethodID, ...); // ST20_4 int v48; // r0 int src_data_bytes; // r4 int src_data_bytes_length; // r4 _JNIEnv *env_1; // r6 int v52; // r3 int v53; // r5 int v54; // r2 int v55; // ST4C_4 jobject v56; // ST48_4 int v57; // ST58_4 jobject (*v58)(JNIEnv *, jclass, jmethodID, ...); // ST54_4 int v59; // r0 int v60; // r5 int v61; // r0 int OutputStreamObject_toByteArray; // [sp+Ch] [bp-60h] int OutputStreamObject_close; // [sp+10h] [bp-5Ch] int need_encode_bit_num; // [sp+14h] [bp-58h] struct _jmethodID *doFinal_function; // [sp+18h] [bp-54h] int src_data_bytes_array; // [sp+1Ch] [bp-50h] int OutputStreamObject; // [sp+20h] [bp-4Ch] int v68; // [sp+24h] [bp-48h] int v69; // [sp+28h] [bp-44h] int b64_2; // [sp+2Ch] [bp-40h] void *v71; // [sp+30h] [bp-3Ch] int default_1024_bit_rsa_max_size; // [sp+34h] [bp-38h] int OutputStreamObject_wriTe; // [sp+38h] [bp-34h] int src_data; // [sp+40h] [bp-2Ch] int src_data_bytes_length_1; // [sp+40h] [bp-2Ch] int v76; // [sp+48h] [bp-24h] jobject v77; // [sp+48h] [bp-24h] void *v78; // [sp+54h] [bp-18h] int v79; // [sp+58h] [bp-14h] _JNIEnv *env_2; // [sp+5Ch] [bp-10h] v76 = a4; src_data = a3; env_3 = env; v79 = (env->field_0->NewStringUTF)(); KeyFactory_class = (env_3->field_0->FindClass)(env_3, "java/security/KeyFactory"); v6 = KeyFactory_class; v7 = (env_3->field_0->GetStaticMethodID)( env_3, KeyFactory_class, "getInstance", "(Ljava/lang/String;)Ljava/security/KeyFactory;"); v78 = (env_3->field_0->CallStaticObjectMethod)(env_3, v6, v7, v79);// 调用getInstance获得KeyFactory实例 v8 = (env_3->field_0->ExceptionCheck)(env_3); v9 = env_3->field_0; if ( v8 ) { (v9->ExceptionClear)(env_3); result = 0; } else { v11 = (v9->FindClass)(env_3, "android/util/Base64");// 拿到Base64类 v12 = v11; env_2 = env_3; v13 = (env_3->field_0->GetStaticMethodID)(env_3, v11, "decode", "(Ljava/lang/String;I)[B"); b64_2 = v12; v14 = (env_2->field_0->CallStaticObjectMethod)(env_2, v12, v13, v76, 0); v15 = (env_2->field_0->FindClass)(env_2, "java/security/spec/X509EncodedKeySpec"); v16 = v15; v17 = (env_2->field_0->GetMethodID)(env_2, v15, "", "([B)V"); v69 = v14; v18 = (env_2->field_0->NewObject)(env_2, v16, v17, v14); (env_2->field_0->DeleteLocalRef)(env_2, v16); v19 = (env_2->field_0->GetObjectClass)(env_2, v78); v20 = v19; v21 = (env_2->field_0->GetMethodID)( env_2, v19, "generatePublic", "(Ljava/security/spec/KeySpec;)Ljava/security/PublicKey;"); v77 = env_2->field_0->CallObjectMethod(&env_2->field_0, v78, v21, v18);// 获取PublicKey RSA的公钥 下面开始进行RSA加密 (env_2->field_0->DeleteLocalRef)(env_2, v20); (env_3->field_0->DeleteLocalRef)(env_3, v78); (env_3->field_0->DeleteLocalRef)(env_3, v18); v22 = (env_3->field_0->ExceptionCheck)(env_3); v23 = env_3->field_0; if ( v22 ) goto LABEL_19; v24 = (v23->GetObjectClass)(env_3, v77); v25 = v24; v26 = (env_3->field_0->GetMethodID)(env_3, v24, "getModulus", "()Ljava/math/BigInteger;"); v27 = (env_3->field_0->CallObjectMethod)(env_3, v77, v26);// 调用 getModulus (env_3->field_0->DeleteLocalRef)(env_3, v25); v28 = (env_3->field_0->GetObjectClass)(env_3, v27); v29 = v28; v30 = (env_3->field_0->GetMethodID)(env_3, v28, "bitLength", "()I"); v31 = (env_3->field_0->CallIntMethod)(env_3, v27, v30);// 调用 bitLength (env_2->field_0->DeleteLocalRef)(env_2, v27); (env_2->field_0->DeleteLocalRef)(env_2, v29); need_encode_bit_num = v31 / 8; // 计算要加密的位数 default_1024_bit_rsa_max_size = v31 / 8 - 11;// /8 - 11 是RSA1024 能加密的最大的位数 v32 = (env_2->field_0->FindClass)(env_2, "javax/crypto/Cipher"); v33 = (env_2->field_0->GetStaticMethodID)(env_2, v32, "getInstance", "(Ljava/lang/String;)Ljavax/crypto/Cipher;"); v34 = env_2->field_0->CallStaticObjectMethod; v35 = (env_2->field_0->NewStringUTF)(env_2, "RSA/ECB/PKCS1Padding"); v68 = v32; v36 = (v34)(env_2, v32, v33, v35); env_3 = env_2; v37 = v36; v38 = (env_2->field_0->ExceptionCheck)(env_2); v23 = env_2->field_0; if ( v38 ) goto LABEL_19; v71 = v37; env_3 = env_2; v39 = (v23->GetMethodID)(env_2, v32, "init", "(ILjava/security/Key;)V"); v40 = (env_2->field_0->ExceptionCheck)(env_2); v23 = env_2->field_0; if ( v40 ) goto LABEL_19; (v23->CallVoidMethod)(env_2, v37, v39, 1, v77); env_3 = env_2; (env_2->field_0->DeleteLocalRef)(env_2, v77); v41 = (env_2->field_0->ExceptionCheck)(env_2); v23 = env_2->field_0; if ( v41 ) goto LABEL_19; v42 = (v23->FindClass)(env_2, "java/io/ByteArrayOutputStream"); v43 = v42; v44 = (env_2->field_0->GetMethodID)(env_2, v42, "", "()V"); OutputStreamObject = (env_2->field_0->NewObject)(env_2, v43, v44); OutputStreamObject_wriTe = (env_2->field_0->GetMethodID)(env_2, v43, "write", "([B)V"); OutputStreamObject_toByteArray = (env_2->field_0->GetMethodID)(env_2, v43, "toByteArray", "()[B"); OutputStreamObject_close = (env_2->field_0->GetMethodID)(env_2, v43, "close", "()V"); (env_2->field_0->DeleteLocalRef)(env_2, v43); v45 = (env_2->field_0->GetObjectClass)(env_2, src_data); getBytes_function = (env_2->field_0->GetMethodID)(env_2, v45, "getBytes", "(Ljava/lang/String;)[B"); v47 = env_2->field_0->CallObjectMethod; v48 = (env_2->field_0->NewStringUTF)(env_2, "UTF-8"); src_data_bytes = (v47)(env_2, src_data, getBytes_function, v48); (env_2->field_0->DeleteLocalRef)(env_2, v45); src_data_bytes_array = src_data_bytes; src_data_bytes_length = (env_2->field_0->GetArrayLength)(env_2, src_data_bytes); env_1 = env_2; doFinal_function = (env_2->field_0->GetMethodID)(env_2, v68, "doFinal", "([BII)[B"); (env_2->field_0->DeleteLocalRef)(env_2, v68); src_data_bytes_length_1 = src_data_bytes_length; if ( src_data_bytes_length >= 1 ) // 开始循环遍历字节,将字节写入到OutPutStream中去 { v52 = default_1024_bit_rsa_max_size; if ( src_data_bytes_length <= default_1024_bit_rsa_max_size )// 如果长度小于默认的 则设置加密的长度为需要加密的长度 否则需要加密的长度就是最大的1024 v52 = src_data_bytes_length; // 第一次不走这里 v53 = 0; do { v54 = src_data_bytes_length; if ( v52 <= src_data_bytes_length ) // 第一次的时候 v52是默认的最大长度 1024位的长度,此时的if成立,因为src_data_bytes_length超出了1024位的长度 v54 = v52; // 所以此时加密的长度就先加密最大的1024位 后面多出来的先不加密 v55 = v54; // 这里的v55 就是 1024位的长度 v56 = env_1->field_0->CallObjectMethod(&env_1->field_0, v71, doFinal_function, src_data_bytes_array); (env_2->field_0->CallVoidMethod)(env_2, OutputStreamObject, OutputStreamObject_wriTe); env_1 = env_2; (env_2->field_0->DeleteLocalRef)(env_2, v56);// 释放资源 src_data_bytes_length += 11 - need_encode_bit_num;// 要被加密的长度 减去第一次加密过的长度1024位 v53 += default_1024_bit_rsa_max_size; // v53是一个计数器,记录着已经加密过的字节的长度 v52 = v55; } while ( src_data_bytes_length_1 > v53 ); // 需要加密的长度是大于的v53(默认的长度),所以还是第二次循环还是继续执行 } env_3 = env_2; v57 = (env_1->field_0->GetStaticMethodID)(env_2, b64_2, "encodeToString", "([BI)Ljava/lang/String;");// 最后调用静态方法encodeToString进行加密RSA加密过的数据 v58 = env_2->field_0->CallStaticObjectMethod; v59 = (env_2->field_0->CallObjectMethod)(env_2, OutputStreamObject, OutputStreamObject_toByteArray); v60 = (v58)(env_2, b64_2, v57, v59, 0); (env_3->field_0->CallVoidMethod)(env_3, OutputStreamObject, OutputStreamObject_close); v61 = (env_3->field_0->ExceptionCheck)(env_3); v23 = env_2->field_0; if ( v61 ) {LABEL_19: (v23->ExceptionClear)(env_3); result = 0; } else { (v23->DeleteLocalRef)(env_2, b64_2); (env_2->field_0->DeleteLocalRef)(env_2, v69); (env_2->field_0->DeleteLocalRef)(env_2, v71); (env_2->field_0->DeleteLocalRef)(env_2, src_data_bytes_array); (env_2->field_0->DeleteLocalRef)(env_2, OutputStreamObject); (env_2->field_0->DeleteLocalRef)(env_2, v79); result = v60; } } return result;}
所以解决方法,模拟Base64编码生成的RSA的密文的结果:
RSA弄完了可以继续看signature是如何生成的,来到如下的函数地方:
int __fastcall Java_com_ximalaya_ting_android_host_util_common_EncryptUtil_getPlaySignatureNative(_JNIEnv *env, JNINativeInterface *a2, int a3, int data_key, int data_value, int data_size){ _BYTE *v6; // r0 _BYTE *v7; // r4 int v8; // ST28_4 _BYTE *v9; // r4 size_t v10; // r5 size_t v11; // r0 void *a5; // r5 _JNIEnv *env_2; // r6 int v14; // r4 int data_key_1; // [sp+14h] [bp-20h] JNINativeInterface *v17; // [sp+18h] [bp-1Ch] _JNIEnv *env_1; // [sp+1Ch] [bp-18h] int v19; // [sp+20h] [bp-14h] data_key_1 = data_key; v17 = a2; env_1 = env; v19 = a3; if ( sub_1D00(env, a3) < 0 ) // 判断... j_exit(0); v6 = j_malloc(29); v7 = v6; v8 = v6; j___aeabi_memcpy(v6, "23627d1451047b8d257a96af5db32", 28); v7[28] = 0; v9 = j_malloc(37); j___aeabi_memcpy(v9, "59538f081d651df75b4aa1695085472081594", 36); v9[36] = 0; v10 = j_strlen(v8); v11 = v10 + j_strlen(v9); a5 = j_malloc(v11 + 1); // 接收上面来个字符串的拼接 j___aeabi_memclr(a5); // zeroMemory j_strcat(a5, v8); // 拼接 23627d1451047b8d257a96af5db32 j_strcat(a5, v9); // 拼接 59538f081d651df75b4aa1695085472081594 j_free(v8); j_free(v9); env_2 = env_1; v14 = i_find_encode_function(env_1, data_key_1, data_value, data_size, a5);// 加密函数 j_free(a5); (env_2->field_0->DeleteLocalRef)(env_2, v17); (env_2->field_0->DeleteLocalRef)(env_2, v19); return v14;}
重点函数i_find_encode_function(这里改过名了),默认是一个sub_xxxx的函数名,跟进去,注释都写好了,重点是sub_B814这个函数,它是最终加密MD5的结果,在这里进行断点然年后查看R0寄存器的内存值就可以知道最终的加密结果了,MD5类是C写的,有什么办法识别MD5的特征呢?初始化MD5加密的时候 肯定会有4个常数的出现,比如如下sub_B814跟进会发现:
int __fastcall i_find_encode_function(_JNIEnv *env, int data_key, int data_value, int data_size, int a5){ char *v5; // r5 int data_size_1; // r4 unsigned int v7; // r3 _JNIEnv *env_2; // r4 int data_key_a; // r0 _BYTE *v10; // r0 int v11; // r0 _BYTE *v12; // r0 signed int v13; // r5 int v14; // r4 char *v15; // r0 int *v16; // r4 int v17; // r0 size_t v18; // r0 int v19; // r4 bool v20; // nf unsigned __int8 v21; // vf int v22; // r5 char *v23; // r1 int *v24; // r4 int v25; // r5 size_t v26; // r0 size_t v27; // r0 char *v28; // r1 int v29; // r0 int *v30; // r4 int v31; // r1 int *v32; // r2 int v33; // r0 int *v34; // r4 _DWORD *v35; // r5 _JNIEnv *v36; // r4 char *v37; // r5 int v38; // r4 int v39; // r1 int result; // r0 _DWORD *v41; // [sp+4h] [bp-60h] int v42; // [sp+8h] [bp-5Ch] int *v43; // [sp+Ch] [bp-58h] _DWORD *data_value_array; // [sp+10h] [bp-54h] int data_key_1; // [sp+14h] [bp-50h] int v46; // [sp+18h] [bp-4Ch] int *v47; // [sp+1Ch] [bp-48h] int *v48; // [sp+20h] [bp-44h] char *data_key_a_1; // [sp+24h] [bp-40h] int *v50; // [sp+28h] [bp-3Ch] int *data_key_array; // [sp+2Ch] [bp-38h] int v52; // [sp+30h] [bp-34h] char *v53; // [sp+34h] [bp-30h] _JNIEnv *env_1; // [sp+38h] [bp-2Ch] int v55; // [sp+40h] [bp-24h] int v56; // [sp+44h] [bp-20h] int v57; // [sp+48h] [bp-1Ch] int v58; // [sp+4Ch] [bp-18h] int v59; // [sp+50h] [bp-14h] v46 = data_value; env_1 = env; v5 = 0; v58 = 0; v43 = &v58; v59 = 0; v57 = 0; v56 = 0; v50 = &v55; v55 = 0; data_size_1 = data_size; v7 = (4 * data_size + 7) & 0xFFFFFFF8; v41 = &v41; data_value_array = (&v41 - v7); v52 = -1; v42 = a5; // 23627d1451047b8d257a96af5db3259538f081d651df75b4aa1695085472081594 v53 = data_size_1; data_key_1 = data_key; data_key_array = (&v41 - v7); if ( data_size_1 <= 0 ) { v13 = data_size_1; j_qsort(&v41 - v7, data_size_1, 4u, sub_4D40);// 排序 v14 = 1; } else { v47 = &v59; env_2 = env_1; do { v48 = (&stru_2A8 + 12); data_key_a = (env_2->field_0->GetObjectArrayElement)(env_2, data_key_1, v5);// 遍历获取data_key中的每个元素 data_key_a_1 = data_key_a; v10 = sub_4F48(env_2, data_key_a); // getBytes获取每个元素data_key的字节 data_key_array[v5] = v10; // 存储data_key_a v11 = (*(&env_1->field_0->reserved0 + v48))(// // 遍历获取data_value中的每个元素 env_1, v46, v5); v48 = v11; v12 = sub_4F48(env_1, v11); // getBytes获取每个元素data_value的字节 data_value_array[v5] = v12; v50[4] = v12; // 存储 env_2 = env_1; sub_AAF0(&v55, data_key_array[v5], v47, 4); (env_2->field_0->DeleteLocalRef)(env_2, data_key_a_1); (env_2->field_0->DeleteLocalRef)(env_2, v48); ++v5; } while ( v53 != v5 ); v13 = v53; j_qsort(data_key_array, v53, 4u, sub_4D40); v14 = 1; if ( v13 >= 1 ) { v15 = 0; v16 = data_key_array; do { data_key_a_1 = v15; v17 = sub_AA68(&v55, *v16); v47 = v17; *v43 = v17; v48 = j_strlen(*v16); v18 = j_strlen(*v47); v15 = v48 + data_key_a_1 + v18 + 2; v13 += v52; ++v16; } while ( v13 ); v14 = (v15 + 1); v13 = v53; } } v19 = v14 + j_strlen(v42); v43 = j_malloc(v19); j___aeabi_memclr(v43); v21 = __OFSUB__(v13, 1); v20 = v13 - 1 < 0; v22 = v50; if ( !(v20 ^ v21) ) { v23 = v53; v24 = data_key_array; do { data_key_a_1 = v23; v48 = sub_AA68(&v55, *v24); *(v22 + 12) = v48; v25 = v43; j_strcat(v43, *v24); v26 = j_strlen(v25); *(v25 + v26) = 61; v47 = 0; *(v25 + v26 + 1) = 0; j_strcat(v25, *v48); v27 = j_strlen(v25); *(v25 + v27) = 38; v28 = data_key_a_1; v29 = v25 + v27; v22 = v50; *(v29 + 1) = v47; v23 = &v28[v52]; ++v24; } while ( v23 ); } v30 = v43; j_strcat(v43, v42); LOBYTE(v31) = *v30; if ( *v30 ) { v32 = v30; do { *v32 = *(tolower_tab_ + 2 * v31 + 2); v31 = *(v32 + 1); v32 = (v32 + 1); } while ( v31 ); } data_key_a_1 = sub_B814(v30); // C实现的MD5算法 加密完MD5然后作为sign值返回 j_free(v30); sub_AA30(&v55); v33 = v53; v34 = data_key_array; v35 = data_value_array; if ( v53 >= 1 ) { do { v53 = v33; j_free(*v34); j_free(*v35); v33 = &v53[v52]; ++v35; ++v34; } while ( &v53[v52] ); } v36 = env_1; (env_1->field_0->DeleteLocalRef)(env_1, data_key_1); (v36->field_0->DeleteLocalRef)(v36, v46); v37 = data_key_a_1; v38 = (v36->field_0->NewStringUTF)(v36, data_key_a_1);// 转换为java字符串 j_free(v37); v39 = v50[5]; result = _stack_chk_guard - v39; if ( _stack_chk_guard == v39 ) result = v38; return result;}
所以这里直接动态调试sub_B814这个函数
第一个函数执行过后返回值将会存储到R0中,所以内存窗口中就同步R0寄存器,R0寄存器中存储的是MD5,其值如下:
7219323f66f61ea591e0e733f8f835f3
被加密的值,如下两部分组成
构建的HTTP参数 + 23627d1451047b8d257a96af5db359538f081d651df75b4aa169508547208159
account=19817353426&channel=and-f5&device=android&deviceid=00000000-6415-8c0c-f875-98ae0037d7ef&impl=com.ximalaya.ting.android&nonce=5402247414770639301&password=e9bc0e13a8a16cbb07b175d92a113126&version=6.3.90&23627d1451047b8d257a96af5db359538f081d651df75b4aa169508547208159
如下验证:
所以最终的过程就是如下:
因为nonce是随机的,所以这里可以不必追究,直接让它为一个定值就好了,最终变动的就是account 和 password 这些也都是自己可控的!
Base64_encode(RSA_ENCODE({"account":"登陆账号","nonce":"4786336759083248576","password":"MD5(登陆密码)","signature":"MD5(account=登陆账号&channel=and-f5&device=android&deviceid=00000000-6415-8c0c-f875-98ae0037d7ef&impl=com.ximalaya.ting.android&nonce=4786336759083248576&password=MD5(登陆密码)&version=6.3.90&23627d1451047b8d257a96af5db359538f081d651df75b4aa169508547208159)"}))