SO逆向之x博國際版登陸分析

篇幅有限

完整內(nèi)容及源碼關注公眾號:ReverseCode,發(fā)送

抓包

Charles本地證書

安卓8

cd /data/misc/user/0/cacerts-added/
mount -o remount,rw /
chmod 777 *
cp * /etc/security/cacerts/
mount -o remount,ro /

安卓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

image-20211105204214989

xposed+justTrustMe.apk破解ssl pinning

抓包登錄http://api.weibo.cn/2/account/login庐舟,賬戶密碼為188888888/123456

參數(shù)
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搀突,多次抓包發(fā)現(xiàn)除了p其他值都不變沉桌,p看起來像是RSA加密,目標參數(shù)i和s

./fs1280arm64
frida -U com.weico.international -l hookEvent.js 事件hook,點擊登錄后觸發(fā)點擊事件
image-20211108155045044
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
image-20211108204950577
image-20211108205514055

打開jadx查看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);
}

以上說明在調(diào)用refreshSinaToken時加密參數(shù)有password,sValue第队。userName為登錄名哮塞,cpt為none,cptcode為none凳谦。對應請求參數(shù)中u對應userName忆畅,c=weicoabroadi=WeiboSecurityUtils.getIValue(WApplication.cContext),getuser=getoauth=getcookie=1家凯,lang=zh_CN_#Hans缓醋,p=password,s=sValue绊诲。

在doLogin中調(diào)用了refreshSinaToken遥诉,同時也生成了password和sValue的值。

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亏吝。

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);參數(shù)分別為context炉抒,賬戶+密碼,decode = WeicoSecurityUtils.decode(KeyUtil.WEICO_PIN)世舰,直接用上面扣出的加解密邏輯即可动雹。

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

static {
    System.loadLibrary("utility");
}
public native String calculateS(Context context, String str, String str2);

可以知道該方法定義在了libutility.so中,引出今天的分析so跟压,該方法中參數(shù)一是Context上下文胰蝠,參數(shù)二是傳入的明文,參數(shù)三是固定的值震蒋,返回值是8位的Sign茸塞,且輸入不變的情況下,輸出也固定不變查剖。

image-20211108161215489

靜態(tài)綁定钾虐,F(xiàn)5查看C偽代碼,y設置type為JNIEnv*

image-20211108161638232
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邏輯才是實現(xiàn)加密的流程无切。

Unidbg

搭建Unidbg框架荡短,不過沒有JNI OnLoad

public class sina extends AbstractJni{
    private final AndroidEmulator emulator;
    private final VM vm;
    private final Module module;
 
    sina() {
        // 創(chuàng)建模擬器實例,進程名建議依照實際進程名填寫,可以規(guī)避針對進程名的校驗
        emulator = AndroidEmulatorBuilder.for32Bit().setProcessName("com.sina.International").build();
        // 獲取模擬器的內(nèi)存操作接口
        final Memory memory = emulator.getMemory();
        // 設置系統(tǒng)類庫解析
        memory.setLibraryResolver(new AndroidResolver(23));
        // 創(chuàng)建Android虛擬機,傳入APK哆键,Unidbg可以替我們做部分簽名校驗的工作
        vm = emulator.createDalvikVM(new File("sinaInternational.apk"));
 
        // 加載目標SO
        DalvikModule dm = vm.loadLibrary(new File("libutility.so"), true); // 加載so到虛擬內(nèi)存
        //獲取本SO模塊的句柄,后續(xù)需要用它
        module = dm.getModule();
        vm.setJni(this); // 設置JNI
        vm.setVerbose(true); // 打印日志
        // 樣本連JNI OnLoad都沒有
        // dm.callJNI_OnLoad(emulator); // 調(diào)用JNI OnLoad
    };
 
    public static void main(String[] args) {
        sina test = new sina();
    }
}

alt+g 查看修改當前指令模式掘托,1是Thumb,0是Arm模式籍嘹,Thumb 指令看作ARM指令壓縮形式的子集闪盔,添加一個calculateS函數(shù),依然是地址方式調(diào)用辱士,ARM32有Thumb和ARM兩種指令模式泪掀,此處是thumb模式,所以hook的時候地址要在start基礎上+1颂碘。

image-20211108162945580

ARM模式指令總是4字節(jié)長度异赫,Thumb指令長度多數(shù)為2字節(jié),少部分指令是4字節(jié)。右鍵查看Text view塔拳,IDA-Options-General

image-20211108163426798

指令大多為兩個字節(jié)長度鼠证,那就是Thumb

除了基本類型,比如int靠抑,long等量九,其他的對象類型一律要手動 addLocalObject。

public String calculateS() throws Exception {
    List<Object> list = new ArrayList<>(10);
    list.add(vm.getJNIEnv()); // 第一個參數(shù)是env
    list.add(0); // 第二個參數(shù)颂碧,實例方法是jobject荠列,靜態(tài)方法是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

image-20211108163602626

g跳轉(zhuǎn)到0x2c8d个曙,F(xiàn)5查看C偽代碼,將a1使用快捷鍵y轉(zhuǎn)成JNI Env受楼,所屬函數(shù)jbyte *__fastcall sub_2C3C(JNIEnv *a1, int a2)

image-20211108164550912

這地方出現(xiàn)Signature一定是簽名校驗了

image-20211108165757993

x交叉引用

image-20211108170909203

進入第一條后發(fā)現(xiàn)之前的函數(shù)sub_1C60垦搬,該函數(shù)一旦返回0,直接gg艳汽,校驗成功返回1猴贰,繼續(xù)x交叉引用

image-20211108171144032

跳轉(zhuǎn)到了一開始的函數(shù)Java_com_sina_weibo_security_WeiboSecurityUtils_calculateS

image-20211108171251713

Tab查看Text View,sub_1C60地址為FF F7 EB FE

image-20211108171340198

ARM參數(shù)傳遞規(guī)則

  • r0:參數(shù)1河狐,返回時作為返回值1用米绕,通用寄存器1
  • r1:參數(shù)2,返回值馋艺,通用寄存器2
  • r2:參數(shù)3栅干,通用寄存器
  • r3:參數(shù)4,通用寄存器
  • r4 ~ r8:變量寄存器1捐祠,2碱鳞,3,4踱蛀,5
  • r9:平臺寄存器窿给,該寄存器的意義由平臺標準定義
  • r10,r11:變量寄存器
  • r12:內(nèi)部過程調(diào)用寄存器
  • r13:棧寄存器SP
  • r14:link寄存器
  • r15:PC

我們可以通過mov r0,1實現(xiàn)不執(zhí)行這個函數(shù),并給出正確的返回值率拒。且這個函數(shù)并沒有產(chǎn)生一些之后需要使用的值或者中間變量崩泡,所以這讓我們不需要管別的寄存器。

arm轉(zhuǎn)hex猬膨,可以講hex和arm互相轉(zhuǎn)換

image-20211108172439486

sub_1C60地址FF F7 EB FE改為4F F0 01 00角撞,我們可以調(diào)用Unicorn對虛擬內(nèi)存進行patch,Thumb的+1只在運行和Hook時需要考慮,patch不用靴寂。

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
}
image-20211109202049242

當需要動態(tài)patch的時候就不能以來網(wǎng)站轉(zhuǎn)換arm來拿到hex了磷蜀,可以使用Unidbg給我們封裝的Patch方法。找到FF F7 EB FE百炬,再用Keystone 把patch代碼"mov r0,1"轉(zhuǎn)成機器碼褐隆,填進去,校驗一下長度是否相等即可剖踊。

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);
    }
}

根據(jù)偽C代碼分析庶弃,利用Unidbg實現(xiàn)算法,將text和key拼接起來德澈,然后放到MDStringOld函數(shù)中歇攻,出來的結果,從中分別抽出第1梆造,5缴守,2,10镇辉,17屡穗,9,25忽肛,27位就是結果了村砂。

image-20211108174300477

雙擊進入MDStringOld,tab進入Text View,hook地址為0x1BD0+1

image-20211108174846870

Unidbg內(nèi)嵌了多種Hook工具屹逛,目前主要是四種础废,Dobby,HookZz罕模,xHook评腺,Whale

  • xHook 是愛奇藝開源的基于PLT HOOK的Hook框架,它無法Hook不在符號表里的函數(shù)手销,也不支持inline hook歇僧,這在我們的逆向分析中是無法忍受的,所以在這里不去理會它锋拖。
  • Whale 在Unidbg的測試用例中只有對符號表函數(shù)的Hook诈悍,沒看到Inline Hook 或者 非導出函數(shù)的Hook,所以也不去考慮兽埃。
  • HookZz是Dobby的前身侥钳,兩者都可以Hook 非導出表中的函數(shù),即IDA中顯示為sub_xxx的函數(shù)柄错,也都可以進行inline hook舷夺,所以二選一就行了苦酱。我喜歡HookZz這個名字,所以就HookZz了给猾。使用HookZz hook MDStringOld函數(shù)疫萤,MDStringOld是導出函數(shù),可以傳入符號名敢伸,解析地址扯饶,但管他什么findsymbol,findExport呢池颈,我就認準地址尾序,地址,yyds躯砰。
    public void HookMDStringold(){
        // 加載HookZz
        IHookZz hookZz = HookZz.getInstance(emulator);
 
        hookZz.wrap(module.base + 0x1BD0 + 1, new WrapCallback<HookZzArm32RegisterContext>() { // inline wrap導出函數(shù)
            @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());
    }    
image-20211109202250718

Frida

打印MDStringOld的參數(shù)和返回值每币,其中0x1BD0為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

跟進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);

以上邏輯中參數(shù)deviceSerial通過getWifiMac或者getImei獲取琢歇,使用Frida主動調(diào)用

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();
                // 動態(tài)方法choose onMatch找到實例進行調(diào)用
                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)
image-20211110101058565

拿到了imei作為deviceSerial兰怠,IDA中該搜索getIValue,1EF4為起始地址

image-20211110101308515

設置type為JNIEnv*

image-20211109204732446
public String calculateI(){
    List<Object> list = new ArrayList<>(10);
    list.add(vm.getJNIEnv()); // 第一個參數(shù)是env
    list.add(0); // 第二個參數(shù)矿微,實例方法是jobject痕慢,靜態(tài)方法是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

image-20211110101527159

g跳轉(zhuǎn)過去

image-20211110101647241

F5查看源碼在方法jbyte *__fastcall sub_2C3C(JNIEnv *a1, int a2)中快骗,x交叉引用

image-20211110101730772

看到熟悉的sub_1C60娜庇,繼續(xù)x交叉引用,找到getIValue中的sub_1C60

image-20211110101758622
![image-20211110101902313](https://upload-images.jianshu.io/upload_images/17440265-3a49511d34083621.png)

tab進入?yún)R編模式.text:00001FFE FF F7 2F FE BL sub_1C60方篮,降sub_1C60改為1即可名秀,

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);
    }
}
image-20211110103843824

接下來雙擊dword_7068找到地址00007068,修改為0x0

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);
}
image-20211110105140167

總結

本次案例中使用xposed破解ssl pinning反抓包藕溅,結合objection匕得,frida和unidbg針對so層修改opcode,完成參數(shù)的逆向分析和主動調(diào)用巾表。

本文由博客群發(fā)一文多發(fā)等運營工具平臺 OpenWrite 發(fā)布

?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末汁掠,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子集币,更是在濱河造成了極大的恐慌考阱,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,204評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件鞠苟,死亡現(xiàn)場離奇詭異乞榨,居然都是意外死亡秽之,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評論 3 395
  • 文/潘曉璐 我一進店門吃既,熙熙樓的掌柜王于貴愁眉苦臉地迎上來考榨,“玉大人,你說我怎么就攤上這事鹦倚『又剩” “怎么了?”我有些...
    開封第一講書人閱讀 164,548評論 0 354
  • 文/不壞的土叔 我叫張陵申鱼,是天一觀的道長愤诱。 經(jīng)常有香客問我,道長捐友,這世上最難降的妖魔是什么淫半? 我笑而不...
    開封第一講書人閱讀 58,657評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮匣砖,結果婚禮上科吭,老公的妹妹穿的比我還像新娘。我一直安慰自己猴鲫,他們只是感情好对人,可當我...
    茶點故事閱讀 67,689評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著拂共,像睡著了一般牺弄。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上宜狐,一...
    開封第一講書人閱讀 51,554評論 1 305
  • 那天势告,我揣著相機與錄音,去河邊找鬼抚恒。 笑死咱台,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的俭驮。 我是一名探鬼主播回溺,決...
    沈念sama閱讀 40,302評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼混萝!你這毒婦竟也來了遗遵?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,216評論 0 276
  • 序言:老撾萬榮一對情侶失蹤譬圣,失蹤者是張志新(化名)和其女友劉穎瓮恭,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體厘熟,經(jīng)...
    沈念sama閱讀 45,661評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡屯蹦,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,851評論 3 336
  • 正文 我和宋清朗相戀三年维哈,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片登澜。...
    茶點故事閱讀 39,977評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡阔挠,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出脑蠕,到底是詐尸還是另有隱情购撼,我是刑警寧澤,帶...
    沈念sama閱讀 35,697評論 5 347
  • 正文 年R本政府宣布谴仙,位于F島的核電站迂求,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏晃跺。R本人自食惡果不足惜揩局,卻給世界環(huán)境...
    茶點故事閱讀 41,306評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望掀虎。 院中可真熱鬧凌盯,春花似錦、人聲如沸烹玉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,898評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽二打。三九已至县忌,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間继效,已是汗流浹背芹枷。 一陣腳步聲響...
    開封第一講書人閱讀 33,019評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留莲趣,地道東北人。 一個月前我還...
    沈念sama閱讀 48,138評論 3 370
  • 正文 我出身青樓饱溢,卻偏偏與公主長得像喧伞,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子绩郎,可洞房花燭夜當晚...
    茶點故事閱讀 44,927評論 2 355

推薦閱讀更多精彩內(nèi)容