SO reverse engineering analysis

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

parametervalue
cweicoabroad
i3655223
s7c5edcf8
u188888888
pbFbQbLlD4PMcp8gOTSxh3NFS4g2VJIh5Vw6k62wAq49BLlQaeeVDAYBL4iqwY7AHup8LZRGrfHsf+/zP246oBg+LV3UqK+3IpZ6qP654NkEUH/YNzg+JP8WbMmxTE4mZsddMReBquawLm1WwN86m7WRiVO0GBxznHvyK/h5uhmk=
getuser1
getoauth1
getcookie1
langzh_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

Tags: frida

Posted by brandtj on Mon, 17 Oct 2022 07:07:08 +1030