limited space
Complete content and source code pay attention to the public number: ReverseCode, send red
packet capture
Charles local certificate
Android 8
cd /data/misc/user/0/cacerts-added/ mount -o remount,rw / chmod 777 * cp * /etc/security/cacerts/ mount -o remount,ro /
Android 7
cd /data/misc/user/0/cacerts-added/ mount -o rw,remount /system chmod 777 * cp * /etc/security/cacerts/ mount -o ro,remount /system
ssl pinning
xposed+justTrustMe.apk crack ssl pinning
Log in to http://api.weibo.cn/2/account/login by capturing packets, the account password is 188888888/123456
parameter | value |
---|---|
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 |
analyze
Weibo 1.7.1.apk, captured many times and found that all other values were unchanged except p, p looks like RSA encryption, the target parameters i and s
./fs1280arm64 frida -U com.weico.international -l hookEvent.js event hook,Trigger click event after click login
android hooking watch class com.weico.international.activity.SinaLoginMainActivity --dump-args --dump-backtrace --dump-return do this class hook,Click again to log in android hooking watch class_method com.weico.international.activity.SinaLoginMainActivity.refreshSinaToken --dump-args --dump-backtrace --dump-return right refreshSinaToken conduct hook
Open jadx to view refreshSinaToken
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); }
The above description shows that when calling refreshSinaToken, the encrypted parameters include password and sValue. userName is the login name, cpt is none, and cptcode is none. In the corresponding request parameters, u corresponds to userName, c=weicoabroad, i=WeiboSecurityUtils.getIValue(WApplication.cContext), getuser=getoauth=getcookie=1, lang=zh_CN_#Hans, p=password, s=sValue.
The refreshSinaToken is called in doLogin, and the values of password and sValue are also generated.
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
Copy the code in WeicoSecurityUtils.securityPsd(password) and cooperate with android.util.Base64 to complete the encryption and get the password.
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
Follow up the final String sValue = WeiboSecurityUtils.calculateSInJava(getApplicationContext(), userName + password, decode); the parameters are context, account + password, decode = WeicoSecurityUtils.decode(KeyUtil.WEICO_PIN), directly use the encryption and decryption logic deducted above That's it.
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; }
Follow up getInstance().calculateS
static { System.loadLibrary("utility"); } public native String calculateS(Context context, String str, String str2);
It can be known that this method is defined in libutility.so, which leads to today's analysis so. In this method, the first parameter is the Context context, the second parameter is the incoming plaintext, the third parameter is a fixed value, and the return value is 8 bits. Sign, and the input remains unchanged, the output is also fixed.
Static binding, F5 to view C pseudo code, y set type to JNIEnv*
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;
If sub_1C60 returns 0, it returns 0 directly and hangs up, presumably the entire if logic is the process of implementing encryption.
Unidbg
Build the Unidbg framework, but without JNI OnLoad
public class sina extends AbstractJni{ private final AndroidEmulator emulator; private final VM vm; private final Module module; sina() { // Create a simulator instance. It is recommended to fill in the process name according to the actual process name, which can avoid the verification of the process name. emulator = AndroidEmulatorBuilder.for32Bit().setProcessName("com.sina.International").build(); // Get the memory operation interface of the simulator final Memory memory = emulator.getMemory(); // Set system class library resolution memory.setLibraryResolver(new AndroidResolver(23)); // Create an Android virtual machine, pass in the APK, and Unidbg can do part of the signature verification for us vm = emulator.createDalvikVM(new File("sinaInternational.apk")); // Load target SO DalvikModule dm = vm.loadLibrary(new File("libutility.so"), true); // load so into virtual memory //Get the handle of this SO module, you need to use it later module = dm.getModule(); vm.setJni(this); // Set up JNI vm.setVerbose(true); // print log // The sample doesn't even have JNI OnLoad // dm.callJNI_OnLoad(emulator); // call JNI OnLoad }; public static void main(String[] args) { sina test = new sina(); } }
alt+g View and modify the current instruction mode, 1 is Thumb, 0 is Arm mode, Thumb instruction is regarded as a subset of ARM instruction compressed form, add a calculateS function, it is still called by address, ARM32 has Thumb and ARM two instruction modes , here is thumb mode, so the address should be +1 based on start when hook ing.
ARM mode instructions are always 4 bytes in length, Thumb instructions are mostly 2 bytes in length, and a few instructions are 4 bytes in length. Right click to view Text view, IDA-Options-General
Most of the instructions are two bytes long, that is Thumb
Except for basic types, such as int, long, etc., other object types must be manually addLocalObject.
public String calculateS() throws Exception { List<Object> list = new ArrayList<>(10); list.add(vm.getJNIEnv()); // The first parameter is env list.add(0); // The second parameter, the instance method is jobject, the static method is jclazz, directly fill in 0, generally not used. 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()); }
The running error is as follows, the address of the displayed error is 0x2c8d
g jumps to 0x2c8d, F5 checks the C pseudocode, converts a1 to JNI Env using the shortcut key y, and belongs to the function jbyte *__fastcall sub_2C3C(JNIEnv *a1, int a2)
The Signature in this place must be the signature verification.
x cross reference
After entering the first article, I found the previous function sub_1C60. Once the function returns 0, go directly to gg, and the verification will return 1 successfully, and continue to x cross reference
Jump to the function Java_com_sina_weibo_security_WeiboSecurityUtils_calculateS at the beginning
Tab to view Text View, sub_1C60 address is FF F7 EB FE
ARM parameter passing rules
- r0: parameter 1, used as return value 1 when returning, general register 1
- r1: parameter 2, return value, general register 2
- r2: parameter 3, general register
- r3: parameter 4, general register
- r4 ~ r8: variable registers 1, 2, 3, 4, 5
- r9: platform register, the meaning of this register is defined by the platform standard
- r10,r11: variable registers
- r12: Internal procedure call register
- r13: stack register SP
- r14:link register
- r15:PC
We can implement mov r0,1 to not execute this function and give the correct return value. And this function doesn't generate some values or intermediate variables that need to be used later, so this lets us not need to worry about other registers.
arm to hex , it can be said that hex and arm are converted to each other
Change the sub_1C60 address FF F7 EB FE to 4F F0 01 00, we can call Unicorn to patch the virtual memory, the +1 of Thumb only needs to be considered when running and hooking, and patching is not needed.
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 }
When dynamic patch is needed, you can't come to the website to convert arm to get hex, you can use the Patch method encapsulated by Unidbg for us. Find FF F7 EB FE, and then use Keystone to convert the patch code "mov r0,1" into machine code, fill it in, and check whether the lengths are equal.
public void patchVerifyS(){ // 0x1E86 is the address of 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); } }
According to the pseudo-C code analysis, using Unidbg to implement the algorithm, splicing the text and key together, and then putting it into the MDStringOld function, the results are extracted from the 1st, 5th, 2nd, 10th, 17th, 9th, 25th, and 27th bits respectively. Results.
Double click to enter MDStringOld,tab to enter Text View, hook address is 0x1BD0+1
Unidbg has a variety of Hook tools embedded, currently there are four main ones, Dobby, HookZz, xHook, Whale
- xHook is an open source PLT HOOK-based Hook framework from iQIYI. It cannot hook functions that are not in the symbol table, nor does it support inline hook s. This is unbearable in our reverse analysis, so I will ignore it here.
- Whale only has Hooks for symbol table functions in Unidbg test cases, and does not see Inline Hook s or Hooks for non-exported functions, so it is not considered.
- HookZz is the predecessor of Dobby. Both can Hook the functions in the non-export table, that is, the functions displayed as sub_xxx in IDA, and can also perform inline hook s, so you can choose one of the two. I like the name HookZz, so HookZz. Use HookZz hook MDStringOld function, MDStringOld is an export function, you can pass in the symbol name, parse the address, but no matter what findsymbol, findExport, I will look for the address, address, yyds.
public void HookMDStringold(){ // Load HookZz IHookZz hookZz = HookZz.getInstance(emulator); hookZz.wrap(module.base + 0x1BD0 + 1, new WrapCallback<HookZzArm32RegisterContext>() { // inline wrap export function @Override // Similar to frida onEnter public void preCall(Emulator<?> emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info) { // Similar to Frida args[0] Pointer input = ctx.getPointerArg(0); System.out.println("input:" + input.getString(0)); }; @Override // Similar to 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
Print the parameters and return value of MDStringOld, where 0x1BD0 is the starting address of MDStringOld.
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
Follow up Object iValue = WeiboSecurityUtils.getIValue(WApplication.cContext);
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);
In the above logic, the parameter deviceSerial is obtained through getWifiMac or getImei, and is actively called by Frida
function getDeviceSerial(){ Java.perform(function(){ Java.choose("com.sina.weibo.security.WeiboSecurityUtils",{ onMatch:function(ins){ // get context var current_application = Java.use('android.app.ActivityThread').currentApplication(); var context = current_application.getApplicationContext(); // The dynamic method choose onMatch finds the instance to call console.log("found ins => ",ins); // smali or objection to see the real method name 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)
Get imei as deviceSerial, search getIValue in IDA, 1EF4 is the starting address
Set type to JNIEnv*
public String calculateI(){ List<Object> list = new ArrayList<>(10); list.add(vm.getJNIEnv()); // The first parameter is env list.add(0); // The second parameter, the instance method is jobject, the static method is jclazz, directly fill in 0, generally not used. 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()); }
The error location is at 0x2c8d
g jump over
F5 to view the source code in the method jbyte *__fastcall sub_2C3C(JNIEnv *a1, int a2), x cross reference
See the familiar sub_1C60, continue x cross-reference, find sub_1C60 in getIValue
tab to enter assembly mode. text:00001FFE FF F7 2F FE BL sub_1C60, change sub_1C60 to 1,
public void patchVerifyI(){ // sub_1C60 in Java_com_sina_weibo_security_WeiboSecurityUtils_getIValue // 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); } }
Next, double-click dword_7068 to find the address 00007068 and modify it to 0x0
public void patchVerifyI(){ // sub_1C60 in Java_com_sina_weibo_security_WeiboSecurityUtils_getIValue // 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); }
Summarize
In this case, xposed is used to crack the ssl pinning anti-capture, combined with objection, frida and unidbg to modify the opcode for the so layer, and complete the reverse analysis and active call of the parameters.
For the full content, see the Weixin public account: ReverseCode