篇幅有限 完整内容及源码关注公众号:ReverseCode,发送 冲 
抓包 Charles本地证书 
系统证书目录:/system/etc/security/cacerts/
其中的每个证书的命名规则如下:. 文件名是一个Hash值,而后缀是一个数字。 
文件名可以用下面的命令计算出来: openssl x509 -subject_hash_old -in
后缀名的数字是为了防止文件名冲突的,比如如果两个证书算出的Hash值是一样的话,那么一个证书的后缀名数字可以设置成0,而另一个证书的后缀名数字可以设置成1
安卓8
1 2 3 4 5 6 7 cd /data/misc/user/0/cacerts-added/ mount -o remount,rw / mount -o remount,rw /system chmod 777 * cp * /etc/security/cacerts/ mount -o remount,ro / mount -o remount,ro /system 
安卓7
1 2 3 4 5 6 7 cd /data/misc/user/0/cacerts-added/ mount -o rw,remount /system mount -o rw,remount / chmod 777 * cp * /etc/security/cacerts/ mount -o ro,remount /system mount -o ro,remount / 
安卓10
Move_Certificates-v1.9 直接面具插件安排,重启即可
重启
ssl pinning 
xposed+justTrustMe.apk破解ssl pinning
抓包登录http://api.weibo.cn/2/account/login,账户密码为188888888/123456
参数 值 c weicoabroad i 3655223 s 7c5edcf8 u 188888888 p bFbQbLlD4PMcp8gOTSxh3NFS4g2VJIh5Vw6k62wAq49BLlQaeeVDAYBL4iqwY7AHup8LZRGrfHsf+/zP246oBg+LV3UqK+3IpZ6qP654NkEUH/YNzg+JP8WbMmxTE4mZsddMReBquawLm1WwN86m7WRiVO0GBxznHvyK/h5uhmk= getuser 1 getoauth 1 getcookie 1 lang zh_CN_#Hans 
分析 微博1.7.1.apk,多次抓包发现除了p其他值都不变,p看起来像是RSA加密,目标参数i和s
1 2 ./fs1280arm64 frida -U com.weico.international -l hookEvent.js 事件hook,点击登录后触发点击事件 
1 2 android hooking watch class com.weico.international.activity.SinaLoginMainActivity --dump-args --dump-backtrace --dump-return  对该类进行hook,重新点击登录 android hooking watch class_method com.weico.international.activity.SinaLoginMainActivity.refreshSinaToken --dump-args --dump-backtrace --dump-return  对refreshSinaToken进行hook 
打开jadx查看refreshSinaToken
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 private static void refreshSinaToken(String userName, String password, String sValue, String cpt, String cptcode, WeicoCallbackString callback) {     Object iValue = WeiboSecurityUtils.getIValue(WApplication.cContext);     Map<String, Object> maps = new LinkedHashMap<>();     maps.put(SinaRetrofitAPI.ParamsKey.c, KeyUtil.WEICO_C_VALUE);     maps.put(SinaRetrofitAPI.ParamsKey.i, iValue);     maps.put(SinaRetrofitAPI.ParamsKey.s, sValue);     maps.put("u", userName);     maps.put("p", password);     maps.put("getuser", 1);     maps.put("getoauth", 1);     maps.put("getcookie", 1);     maps.put("lang", Utils.getLocalLanguage());     if (!TextUtils.isEmpty(cpt)) {         maps.put("cpt", cpt);     }     if (!TextUtils.isEmpty(cptcode)) {         maps.put("cptcode", cptcode);     }     SinaRetrofitAPI.getWeiboSinaService().login(maps, callback); } 
以上说明在调用refreshSinaToken时加密参数有password,sValue。userName为登录名,cpt为none,cptcode为none。对应请求参数中u对应userName,c=weicoabroad,i=WeiboSecurityUtils.getIValue(WApplication.cContext),getuser=getoauth=getcookie=1,lang=zh_CN_#Hans,p=password,s=sValue。
在doLogin中调用了refreshSinaToken,同时也生成了password和sValue的值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 private void doLogin(String cpt, String cptcode) {     this.loadingDialog = new EasyDialog.Builder(this.me).progress(true, 0).canceledOnTouchOutside(false).progressColor(Res.getColor(R.color.card_content_text)).show();     final String userName = this.loginNameEditText.getText().toString();     String password = this.loginPasswordEditText.getText().toString();     final String psd = WeicoSecurityUtils.securityPsd(password);     try {         String decode = WeicoSecurityUtils.decode(KeyUtil.WEICO_PIN);         LogUtil.d("decode " + decode + decode.equals("CypCHG2kSlRkdvr2RG1QF8b2lCWXl7k7"));         final String sValue = WeiboSecurityUtils.calculateSInJava(getApplicationContext(), userName + password, decode);         refreshSinaToken(userName, psd, sValue, cpt, cptcode, new WeicoCallbackString() {             /* class com.weico.international.activity.SinaLoginMainActivity.AnonymousClass10 */             @Override // com.weibo.sdk.android.api.WeicoCallbackString             public void onSuccess(String str, Object bak) {                 try {                     SinaLoginMainActivity.this.loadingDialog.dismiss();                     SinaLoginMainActivity.this.parseAccount(SinaLoginMainActivity.this.checkLoginResponseForWeibo(str), userName, psd, sValue);                 } catch (Exception e) {                     SinaLoginMainActivity.this.weibofail();                     UIManager.showSystemToast(e.getMessage());                 }             }             @Override // com.weibo.sdk.android.api.WeicoCallbackString             public void onFail(Exception e, Object bak) {                 LogUtil.e(e);                 SinaLoginMainActivity.this.loadingDialog.dismiss();                 SinaLoginMainActivity.this.weibofail();                 UIManager.showSystemToast((int) R.string.Login_failed);             }         });     } catch (Exception e) {         UIManager.showSystemToast((int) R.string.process_fail);     } } 
password 在WeicoSecurityUtils.securityPsd(password)中将代码拷出来配合android.util.Base64即可完成加密拿到password。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 public class WeiboSecurityUtils {     // password     private static final String KEY_ALGORITHM = "RSA";     private static final String KEY_CIPHER_ALGORITHM = "RSA/ECB/PKCS1Padding";     private static final int MAX_DECRYPT_BLOCK = 128;     private static final int MAX_ENCRYPT_BLOCK = 117;     private static String publicKeyInner = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDWcQcgj60fU8fFev9RlvFPg0GcRgHyGFTN9ytE\nLujvfCwGt7n54Q9+k1rDDo+sRQeYdTwA7ZMS8n1RHjZATgmHw9rBBzk/cHXAVIgrJrZ5txDdW1i4\n8ZxEarcdSrmlk9ZFSsvGXE8/0fZYHM0mr4WaIh2y9E0CNkd0rU9VKAR9RQIDAQAB";     private static final String publicKeyString = "iMxVDGf9f5Z3P3NsFac7tM7SC6DZDJY+H/vXc+xv3HlT2E/LUzWf5fct2P0VauekLzNAaNsH93SZ\n2Z3jUc/0x81FLThPwI8cexCuRT7P1bdnmcwhjZmW3Lc1FCu2K6iBuVQ9I51TR9eTU2lNcq4AW8WV\nEWtwIj6EpLFzQ3qOm3AY4UNgcGrNYYBbF+SiUkchdXbxYRBNFkguDiayaJzMC/5WmTrEnQ0xXwmy\nA2lWpZ6+sUlyDRU/HvPh5Oto0xpuLc6bIjfl0b+PSjxh5e/7/4jXoYoUfdm3r2FtPKJtQ2NeKnsp\nOCdk6HNULtk5WSnkBKjufQqoZblvdrEiixnogQ";     public static final String WEICO_PIN = "Fp1vyiH7EkHmHl6ixX9RmVYy5ynZDnmDZZgp7s7vNq2wfV5aLrM4dPCQiI6jboMS4zu19F66OucE\n9HTRWsC9ksQxuhhsBeBUWJTNeojX076C9gmOGESKJczQPFx1RxJfUfTGeGYAvoTSExo1wVa98v3z\nE5gl/uaAdduDI59yOZI";     final static BASE64Encoder encoder = new BASE64Encoder();     final static BASE64Decoder decoder = new BASE64Decoder();     public static String securityPsd(String password) {         try {             return new String(Base64.encode(encryptByPublicKey(password.getBytes(), decode(publicKeyString)), 2));         } catch (Exception e) {             e.printStackTrace();             return null;         }     }     private static byte[] encryptByPublicKey(byte[] data, String publicKey) throws Exception {         byte[] cache;         PublicKey publicK = KeyFactory.getInstance(KEY_ALGORITHM).generatePublic(new X509EncodedKeySpec(Base64.decode(publicKey.getBytes(), 2)));         Cipher cipher = Cipher.getInstance(KEY_CIPHER_ALGORITHM);         cipher.init(1, publicK);         int inputLen = data.length;         ByteArrayOutputStream out = new ByteArrayOutputStream();         int offSet = 0;         int i = 0;         while (inputLen - offSet > 0) {             if (inputLen - offSet > MAX_ENCRYPT_BLOCK) {                 cache = cipher.doFinal(data, offSet, MAX_ENCRYPT_BLOCK);             } else {                 cache = cipher.doFinal(data, offSet, inputLen - offSet);             }             out.write(cache, 0, cache.length);             i++;             offSet = i * MAX_ENCRYPT_BLOCK;         }         byte[] encryptedData = out.toByteArray();         out.close();         return encryptedData;     }     public static String decode(String encryptedStr) throws Exception {         return new String(decryptByPublicKey(Base64.decode(encryptedStr, 1), publicKeyInner));     }     public static byte[] decryptByPublicKey(byte[] encryptedData, String publicKey) throws Exception {         byte[] cache;         Key publicK = KeyFactory.getInstance(KEY_ALGORITHM).generatePublic(new X509EncodedKeySpec(Base64.decode(publicKey, 1)));         Cipher cipher = Cipher.getInstance(KEY_CIPHER_ALGORITHM);         cipher.init(2, publicK);         int inputLen = encryptedData.length;         ByteArrayOutputStream out = new ByteArrayOutputStream();         int offSet = 0;         int i = 0;         while (inputLen - offSet > 0) {             if (inputLen - offSet > 128) {                 cache = cipher.doFinal(encryptedData, offSet, 128);             } else {                 cache = cipher.doFinal(encryptedData, offSet, inputLen - offSet);             }             out.write(cache, 0, cache.length);             i++;             offSet = i * 128;         }         byte[] decryptedData = out.toByteArray();         out.close();         return decryptedData;     } } 
sValue 跟进final String sValue = WeiboSecurityUtils.calculateSInJava(getApplicationContext(), userName + password, decode);参数分别为context,账户+密码,decode = WeicoSecurityUtils.decode(KeyUtil.WEICO_PIN),直接用上面扣出的加解密逻辑即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public static String calculateSInJava(Context context, String srcArray, String pin) {     String str;     synchronized (mCalculateSLock) {         if (srcArray.equals(sSeed) && !TextUtils.isEmpty(sValue)) {             str = sValue;         } else if (context != null) {             sSeed = srcArray;             sValue = getInstance().calculateS(context.getApplicationContext(), srcArray, pin);             str = sValue;         } else {             str = "";         }     }     return str; } 
跟进getInstance().calculateS
1 2 3 4 static {     System.loadLibrary("utility"); } public native String calculateS(Context context, String str, String str2); 
可以知道该方法定义在了libutility.so中,引出今天的分析so,该方法中参数一是Context上下文,参数二是传入的明文,参数三是固定的值,返回值是8位的Sign,且输入不变的情况下,输出也固定不变。
静态绑定,F5查看C伪代码,y设置type为JNIEnv*
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 if ( sub_1C60(a1, a3) ) {   if ( (*a1)->PushLocalFrame(a1, 16) >= 0 )   {     v6 = (*a1)->GetStringUTFChars(a1, a5, 0);     v18 = (char *)(*a1)->GetStringUTFChars(a1, a4, 0);     v7 = j_strlen(v18);     v8 = v7 + j_strlen(v6) + 1;     v9 = j_malloc(v8);     j_memset(v9, 0, v8);     j_strcpy((char *)v9, v18);     j_strcat((char *)v9, v6);     v10 = (_BYTE *)MDStringOld(v9);     v11 = (char *)j_malloc(9u);     *v11 = v10[1];     v11[1] = v10[5];     v11[2] = v10[2];     v11[3] = v10[10];     v11[4] = v10[17];     v11[5] = v10[9];     v11[6] = v10[25];     v12 = v10[27];     v11[8] = 0;     v11[7] = v12;     v21 = (*a1)->FindClass(a1, "java/lang/String");     v22 = (*a1)->GetMethodID(a1, v21, "<init>", "([BLjava/lang/String;)V");     v13 = j_strlen(v11);     v19 = (*a1)->NewByteArray(a1, v13);     v14 = j_strlen(v11);     (*a1)->SetByteArrayRegion(a1, v19, 0, v14, v11);     v15 = (*a1)->NewStringUTF(a1, "utf-8");     v16 = (*a1)->NewObject(a1, v21, v22, v19, v15);     j_free(v11);     j_free(v9);     (*a1)->ReleaseStringUTFChars(a1, (jstring)a4, v18);     a4 = (int)(*a1)->PopLocalFrame(a1, v16);   }   else   {     a4 = 0;   } } return a4; 
sub_1C60如果返回0,直接返回0,挂了,想必整个if逻辑才是实现加密的流程。
Unidbg 搭建Unidbg 框架,不过没有JNI OnLoad
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 public class sina extends AbstractJni{     private final AndroidEmulator emulator;     private final VM vm;     private final Module module;       sina() {         // 创建模拟器实例,进程名建议依照实际进程名填写,可以规避针对进程名的校验         emulator = AndroidEmulatorBuilder.for32Bit().setProcessName("com.sina.International").build();         // 获取模拟器的内存操作接口         final Memory memory = emulator.getMemory();         // 设置系统类库解析         memory.setLibraryResolver(new AndroidResolver(23));         // 创建Android虚拟机,传入APK,Unidbg可以替我们做部分签名校验的工作         vm = emulator.createDalvikVM(new File("sinaInternational.apk"));           // 加载目标SO         DalvikModule dm = vm.loadLibrary(new File("libutility.so"), true); // 加载so到虚拟内存         //获取本SO模块的句柄,后续需要用它         module = dm.getModule();         vm.setJni(this); // 设置JNI         vm.setVerbose(true); // 打印日志         // 样本连JNI OnLoad都没有         // dm.callJNI_OnLoad(emulator); // 调用JNI OnLoad     };       public static void main(String[] args) {         sina test = new sina();     } } 
alt+g 查看修改当前指令模式,1是Thumb,0是Arm模式,Thumb 指令看作ARM指令压缩形式的子集,添加一个calculateS函数,依然是地址方式调用,ARM32有Thumb和ARM两种指令模式,此处是thumb模式,所以hook的时候地址要在start基础上+1。
ARM模式指令总是4字节长度,Thumb指令长度多数为2字节,少部分指令是4字节。右键查看Text view,IDA-Options-General
指令大多为两个字节长度,那就是Thumb
除了基本类型,比如int,long等,其他的对象类型一律要手动 addLocalObject。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public String calculateS() throws Exception {     List<Object> list = new ArrayList<>(10);     list.add(vm.getJNIEnv()); // 第一个参数是env     list.add(0); // 第二个参数,实例方法是jobject,静态方法是jclazz,直接填0,一般用不到。     DvmObject<?> context = vm.resolveClass("android/content/Context").newObject(null);// context     list.add(vm.addLocalObject(context));     list.add(vm.addLocalObject(new StringObject(vm, "188888888123456")));     list.add(vm.addLocalObject(new StringObject(vm, WeiboSecurityUtils.decode(WeiboSecurityUtils.WEICO_PIN))));     Number number = module.callFunction(emulator, 0x1E7C + 1, list.toArray())[0];     String result = vm.getObject(number.intValue()).getValue().toString();     return result; } public static void main(String[] args) {     sina test = new sina();     System.out.println(test.calculateS()); } 
运行报错如下,显示的报错所处地址0x2c8d
g跳转到0x2c8d,F5查看C伪代码,将a1使用快捷键y转成JNI Env,所属函数jbyte *__fastcall sub_2C3C(JNIEnv *a1, int a2)
这地方出现Signature一定是签名校验了
x交叉引用
进入第一条后发现之前的函数sub_1C60,该函数一旦返回0,直接gg,校验成功返回1,继续x交叉引用
跳转到了一开始的函数Java_com_sina_weibo_security_WeiboSecurityUtils_calculateS
Tab查看Text View,sub_1C60地址为FF F7 EB FE
ARM参数传递规则
r0:参数1,返回时作为返回值1用,通用寄存器1 r1:参数2,返回值,通用寄存器2 r2:参数3,通用寄存器 r3:参数4,通用寄存器 r4 ~ r8:变量寄存器1,2,3,4,5 r9:平台寄存器,该寄存器的意义由平台标准定义 r10,r11:变量寄存器 r12:内部过程调用寄存器 r13:栈寄存器SP r14:link寄存器 r15:PC 我们可以通过mov r0,1实现不执行这个函数,并给出正确的返回值。且这个函数并没有产生一些之后需要使用的值或者中间变量,所以这让我们不需要管别的寄存器。
arm转hex ,可以讲hex和arm互相转换
将sub_1C60地址FF F7 EB FE改为4F F0 01 00,我们可以调用Unicorn对虚拟内存进行patch,Thumb的+1只在运行和Hook时需要考虑,patch不用。
1 2 3 4 5 6 7 8 9 10 public void patchVerify(){     int patchCode = 0x4FF00100;     emulator.getMemory().pointer(module.base + 0x1E86).setInt(0,patchCode); } public static void main(String[] args) {     sina test = new sina();     test.patchVerify();     System.out.println(test.calculateS()); // 7c5edcf8 } 
当需要动态patch的时候就不能以来网站转换arm来拿到hex了,可以使用Unidbg给我们封装的Patch方法。找到FF F7 EB FE,再用Keystone 把patch代码”mov r0,1”转成机器码,填进去,校验一下长度是否相等即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public void patchVerifyS(){     // 0x1E86为sub_1C60的地址     Pointer pointer = UnidbgPointer.pointer(emulator, module.base + 0x1E86);     assert pointer != null;     byte[] code = pointer.getByteArray(0, 4);     if (!Arrays.equals(code, new byte[]{ (byte)0xFF, (byte) 0xF7, (byte) 0xEB, (byte) 0xFE })) { // FF F7 EB FE  BL sub_1C60         throw new IllegalStateException(Inspector.inspectString(code, "patch32 code=" + Arrays.toString(code)));     }     try (Keystone keystone = new Keystone(KeystoneArchitecture.Arm, KeystoneMode.ArmThumb)) {         KeystoneEncoded encoded = keystone.assemble("mov r0,1");         byte[] patch = encoded.getMachineCode();         if (patch.length != code.length) {             throw new IllegalStateException(Inspector.inspectString(patch, "patch32 length=" + patch.length));         }         pointer.write(0, patch, 0, patch.length);     } } 
根据伪C代码分析,利用Unidbg实现算法,将text和key拼接起来,然后放到MDStringOld函数中,出来的结果,从中分别抽出第1,5,2,10,17,9,25,27位就是结果了。
双击进入MDStringOld,tab进入Text View,hook地址为0x1BD0+1
Unidbg内嵌了多种Hook工具,目前主要是四种,Dobby,HookZz,xHook,Whale
xHook 是爱奇艺开源的基于PLT HOOK的Hook框架,它无法Hook不在符号表里的函数,也不支持inline hook,这在我们的逆向分析中是无法忍受的,所以在这里不去理会它。 Whale 在Unidbg的测试用例中只有对符号表函数的Hook,没看到Inline Hook 或者 非导出函数的Hook,所以也不去考虑。 HookZz是Dobby的前身,两者都可以Hook 非导出表中的函数,即IDA中显示为sub_xxx的函数,也都可以进行inline hook,所以二选一就行了。我喜欢HookZz这个名字,所以就HookZz了。使用HookZz hook MDStringOld函数,MDStringOld是导出函数,可以传入符号名,解析地址,但管他什么findsymbol,findExport呢,我就认准地址,地址,yyds。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public void HookMDStringold(){     // 加载HookZz     IHookZz hookZz = HookZz.getInstance(emulator);       hookZz.wrap(module.base + 0x1BD0 + 1, new WrapCallback<HookZzArm32RegisterContext>() { // inline wrap导出函数         @Override         // 类似于 frida onEnter         public void preCall(Emulator<?> emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info) {             // 类似于Frida args[0]             Pointer input = ctx.getPointerArg(0);             System.out.println("input:" + input.getString(0));         };           @Override         // 类似于 frida onLeave         public void postCall(Emulator<?> emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info) {             Pointer result = ctx.getPointerArg(0);             System.out.println("input:" + result.getString(0));         }     }); } public static void main(String[] args) {     sina test = new sina();     test.patchVerify1();     test.HookMDStringold();     System.out.println(test.calculateS()); } 
Frida 打印MDStringOld的参数和返回值,其中0x1BD0为MDStringOld起始地址。
1 2 3 4 5 6 7 8 9 10 11 12 function hookMDStringOld() {     var baseAddr = Module.findBaseAddress("libutility.so")     var MDStringOld = baseAddr.add(0x1BD0).add(0x1)     Interceptor.attach(MDStringOld, {         onEnter: function (args) {             console.log("input:\n", hexdump(this.arg0))         },         onLeave: function (retval) {             console.log("result:\n", hexdump(retval))         }     }) } 
iValue 跟进Object iValue = WeiboSecurityUtils.getIValue(WApplication.cContext);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public static String getIValue(Context context) {     if (!TextUtils.isEmpty(sIValue)) {         return sIValue;     }     String deviceSerial = getImei(context);     if (TextUtils.isEmpty(deviceSerial)) {         deviceSerial = getWifiMac(context);     }     if (TextUtils.isEmpty(deviceSerial)) {         deviceSerial = "000000000000000";     }     if (context == null || TextUtils.isEmpty(deviceSerial)) {         return "";     }     String iValue = getInstance().getIValue(context.getApplicationContext(), deviceSerial);     sIValue = iValue;     return iValue; } public native String getIValue(Context context, String str); 
以上逻辑中参数deviceSerial通过getWifiMac或者getImei获取,使用Frida主动调用 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 function getDeviceSerial(){     Java.perform(function(){         Java.choose("com.sina.weibo.security.WeiboSecurityUtils",{             onMatch:function(ins){                 // 获取context                 var current_application = Java.use('android.app.ActivityThread').currentApplication();                 var context = current_application.getApplicationContext(); 				// 动态方法choose onMatch找到实例进行调用                 console.log("found ins => ",ins); 				// smali或objection看真实方法名                 console.log("imei",ins.getImei(context))                 console.log("getWifiMac",ins.getWifiMac(context))             },             onComplete:function(){                 console.log("Search completed!")             }         })     }) } function main(){     console.log("Start hook")     getDeviceSerial() } setImmediate(main) 
拿到了imei作为deviceSerial,IDA中该搜索getIValue,1EF4为起始地址
设置type为JNIEnv*
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public String calculateI(){     List<Object> list = new ArrayList<>(10);     list.add(vm.getJNIEnv()); // 第一个参数是env     list.add(0); // 第二个参数,实例方法是jobject,静态方法是jclazz,直接填0,一般用不到。     DvmObject<?> context = vm.resolveClass("android/content/Context").newObject(null);// context     list.add(vm.addLocalObject(context));     // imei     list.add(vm.addLocalObject(new StringObject(vm, "352530084364850")));     Number number = module.callFunction(emulator, 0x1FE4 + 1, list.toArray())[0];     String result = vm.getObject(number.intValue()).getValue().toString();     return result; } public static void main(String[] args) throws Exception {     sina test = new sina();     System.out.println(test.calculateI()); } 
报错位置在0x2c8d
g跳转过去
F5查看源码在方法jbyte *__fastcall sub_2C3C(JNIEnv *a1, int a2)中,x交叉引用
看到熟悉的sub_1C60,继续x交叉引用,找到getIValue中的sub_1C60
 
tab进入汇编模式.text:00001FFE FF F7 2F FE BL sub_1C60,降sub_1C60改为1即可,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public void patchVerifyI(){     // Java_com_sina_weibo_security_WeiboSecurityUtils_getIValue中的sub_1C60     // 00001FFE FF F7 2F FE                 BL      sub_1C60     Pointer pointer = UnidbgPointer.pointer(emulator, module.base + 0x1FFE);     assert pointer != null;     byte[] code = pointer.getByteArray(0, 4);     if (!Arrays.equals(code, new byte[]{ (byte)0xFF, (byte) 0xF7, (byte) 0x2F, (byte) 0xFE })) {         throw new IllegalStateException(Inspector.inspectString(code, "patch32 code=" + Arrays.toString(code)));     }     try (Keystone keystone = new Keystone(KeystoneArchitecture.Arm, KeystoneMode.ArmThumb)) {         KeystoneEncoded encoded = keystone.assemble("mov r0,1");         byte[] patch = encoded.getMachineCode();         if (patch.length != code.length) {             throw new IllegalStateException(Inspector.inspectString(patch, "patch32 length=" + patch.length));         }         pointer.write(0, patch, 0, patch.length);     } } 
接下来双击dword_7068找到地址00007068,修改为0x0
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 public void patchVerifyI(){     // Java_com_sina_weibo_security_WeiboSecurityUtils_getIValue中的sub_1C60     // 00001FFE FF F7 2F FE                 BL      sub_1C60     Pointer pointer = UnidbgPointer.pointer(emulator, module.base + 0x1FFE);     assert pointer != null;     byte[] code = pointer.getByteArray(0, 4);     if (!Arrays.equals(code, new byte[]{ (byte)0xFF, (byte) 0xF7, (byte) 0x2F, (byte) 0xFE })) {         throw new IllegalStateException(Inspector.inspectString(code, "patch32 code=" + Arrays.toString(code)));     }     try (Keystone keystone = new Keystone(KeystoneArchitecture.Arm, KeystoneMode.ArmThumb)) {         KeystoneEncoded encoded = keystone.assemble("mov r0,1");         byte[] patch = encoded.getMachineCode();         if (patch.length != code.length) {             throw new IllegalStateException(Inspector.inspectString(patch, "patch32 length=" + patch.length));         }         pointer.write(0, patch, 0, patch.length);     }     UnidbgPointer basePoint = new UnidbgPointer(emulator,(module.base)+0x7068,4);     int[] javmarr = {(int)(0x0)};     basePoint.write(0,javmarr,0,1); } public static void main(String[] args) throws Exception {     Map<String, Object> param = new HashMap<>();     sina test = new sina();     test.patchVerifyI();     test.HookMDStringold();     test.patchVerifyS();     param.put("c", "weicoabroad");     param.put("i", test.calculateI());     param.put("s", test.calculateS());     param.put("u", "188888888");     param.put("p", WeiboSecurityUtils.securityPsd("123456"));     param.put("getuser", "1");     param.put("getoauth", "1");     param.put("getcookie", "1");     param.put("lang", "zh_CN_#Hans");     for (Map.Entry<String, Object> entry : param.entrySet()) {         System.out.println(entry.getKey() + "--->" + entry.getValue());     }     String result = HttpUtils.postRequest("http://api.weibo.cn/2/account/login", param);     System.out.println(result); } 
总结 本次案例中使用xposed破解ssl pinning反抓包,结合objection,frida和unidbg针对so层修改opcode,完成参数的逆向分析和主动调用。