篇幅有限
完整內(nèi)容及源碼關(guān)注公眾號(hào):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
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加密,目標(biāo)參數(shù)i和s
./fs1280arm64
frida -U com.weico.international -l hookEvent.js 事件hook,點(diǎn)擊登錄后觸發(fā)點(diǎn)擊事件
android hooking watch class com.weico.international.activity.SinaLoginMainActivity --dump-args --dump-backtrace --dump-return 對(duì)該類進(jìn)行hook,重新點(diǎn)擊登錄
android hooking watch class_method com.weico.international.activity.SinaLoginMainActivity.refreshSinaToken --dump-args --dump-backtrace --dump-return 對(duì)refreshSinaToken進(jìn)行hook
打開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í)加密參數(shù)有password,sValue。userName為登錄名,cpt為none,cptcode為none。對(duì)應(yīng)請(qǐng)求參數(shù)中u對(duì)應(yīng)userName,c=weicoabroad
,i=WeiboSecurityUtils.getIValue(WApplication.cContext)
,getuser=getoauth=getcookie=1,lang=zh_CN_#Hans
,p=password,s=sValue。
在doLogin中調(diào)用了refreshSinaToken,同時(shí)也生成了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
跟進(jìn)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;
}
跟進(jìn)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,且輸入不變的情況下,輸出也固定不變。
靜態(tài)綁定,F(xiàn)5查看C偽代碼,y設(shè)置type為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;
sub_1C60如果返回0,直接返回0,掛了,想必整個(gè)if邏輯才是實(shí)現(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)建模擬器實(shí)例,進(jìn)程名建議依照實(shí)際進(jìn)程名填寫,可以規(guī)避針對(duì)進(jìn)程名的校驗(yàn)
emulator = AndroidEmulatorBuilder.for32Bit().setProcessName("com.sina.International").build();
// 獲取模擬器的內(nèi)存操作接口
final Memory memory = emulator.getMemory();
// 設(shè)置系統(tǒng)類庫解析
memory.setLibraryResolver(new AndroidResolver(23));
// 創(chuàng)建Android虛擬機(jī),傳入APK,Unidbg可以替我們做部分簽名校驗(yàn)的工作
vm = emulator.createDalvikVM(new File("sinaInternational.apk"));
// 加載目標(biāo)SO
DalvikModule dm = vm.loadLibrary(new File("libutility.so"), true); // 加載so到虛擬內(nèi)存
//獲取本SO模塊的句柄,后續(xù)需要用它
module = dm.getModule();
vm.setJni(this); // 設(shè)置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 查看修改當(dāng)前指令模式,1是Thumb,0是Arm模式,Thumb 指令看作ARM指令壓縮形式的子集,添加一個(gè)calculateS函數(shù),依然是地址方式調(diào)用,ARM32有Thumb和ARM兩種指令模式,此處是thumb模式,所以hook的時(shí)候地址要在start基礎(chǔ)上+1。
ARM模式指令總是4字節(jié)長度,Thumb指令長度多數(shù)為2字節(jié),少部分指令是4字節(jié)。右鍵查看Text view,IDA-Options-General
image-20211108163426798指令大多為兩個(gè)字節(jié)長度,那就是Thumb
除了基本類型,比如int,long等,其他的對(duì)象類型一律要手動(dòng) addLocalObject。
public String calculateS() throws Exception {
List<Object> list = new ArrayList<>(10);
list.add(vm.getJNIEnv()); // 第一個(gè)參數(shù)是env
list.add(0); // 第二個(gè)參數(shù),實(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());
}
運(yùn)行報(bào)錯(cuò)如下,顯示的報(bào)錯(cuò)所處地址0x2c8d
g跳轉(zhuǎn)到0x2c8d,F(xiàn)5查看C偽代碼,將a1使用快捷鍵y轉(zhuǎn)成JNI Env,所屬函數(shù)jbyte *__fastcall sub_2C3C(JNIEnv *a1, int a2)
這地方出現(xiàn)Signature一定是簽名校驗(yàn)了
x交叉引用
進(jìn)入第一條后發(fā)現(xiàn)之前的函數(shù)sub_1C60,該函數(shù)一旦返回0,直接gg,校驗(yàn)成功返回1,繼續(xù)x交叉引用
跳轉(zhuǎn)到了一開始的函數(shù)Java_com_sina_weibo_security_WeiboSecurityUtils_calculateS
Tab查看Text View,sub_1C60地址為FF F7 EB FE
ARM參數(shù)傳遞規(guī)則
- r0:參數(shù)1,返回時(shí)作為返回值1用,通用寄存器1
- r1:參數(shù)2,返回值,通用寄存器2
- r2:參數(shù)3,通用寄存器
- r3:參數(shù)4,通用寄存器
- r4 ~ r8:變量寄存器1,2,3,4,5
- r9:平臺(tái)寄存器,該寄存器的意義由平臺(tái)標(biāo)準(zhǔn)定義
- r10,r11:變量寄存器
- r12:內(nèi)部過程調(diào)用寄存器
- r13:棧寄存器SP
- r14:link寄存器
- r15:PC
我們可以通過mov r0,1
實(shí)現(xiàn)不執(zhí)行這個(gè)函數(shù),并給出正確的返回值。且這個(gè)函數(shù)并沒有產(chǎn)生一些之后需要使用的值或者中間變量,所以這讓我們不需要管別的寄存器。
arm轉(zhuǎn)hex,可以講hex和arm互相轉(zhuǎn)換
將sub_1C60
地址FF F7 EB FE
改為4F F0 01 00
,我們可以調(diào)用Unicorn對(duì)虛擬內(nèi)存進(jìn)行patch,Thumb的+1只在運(yùn)行和Hook時(shí)需要考慮,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
}
當(dāng)需要?jiǎng)討B(tài)patch的時(shí)候就不能以來網(wǎng)站轉(zhuǎn)換arm來拿到hex了,可以使用Unidbg給我們封裝的Patch方法。找到FF F7 EB FE
,再用Keystone 把patch代碼"mov r0,1"轉(zhuǎn)成機(jī)器碼,填進(jìn)去,校驗(yà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實(shí)現(xiàn)算法,將text和key拼接起來,然后放到MDStringOld
函數(shù)中,出來的結(jié)果,從中分別抽出第1,5,2,10,17,9,25,27位就是結(jié)果了。
雙擊進(jìn)入MDStringOld
,tab進(jìn)入Text View,hook地址為0x1BD0+1
Unidbg內(nèi)嵌了多種Hook工具,目前主要是四種,Dobby,HookZz,xHook,Whale
- xHook 是愛奇藝開源的基于PLT HOOK的Hook框架,它無法Hook不在符號(hào)表里的函數(shù),也不支持inline hook,這在我們的逆向分析中是無法忍受的,所以在這里不去理會(huì)它。
- Whale 在Unidbg的測(cè)試用例中只有對(duì)符號(hào)表函數(shù)的Hook,沒看到Inline Hook 或者 非導(dǎo)出函數(shù)的Hook,所以也不去考慮。
- HookZz是Dobby的前身,兩者都可以Hook 非導(dǎo)出表中的函數(shù),即IDA中顯示為sub_xxx的函數(shù),也都可以進(jìn)行inline hook,所以二選一就行了。我喜歡HookZz這個(gè)名字,所以就HookZz了。使用HookZz hook MDStringOld函數(shù),MDStringOld是導(dǎo)出函數(shù),可以傳入符號(hào)名,解析地址,但管他什么findsymbol,findExport呢,我就認(rèn)準(zhǔn)地址,地址,yyds。
public void HookMDStringold(){
// 加載HookZz
IHookZz hookZz = HookZz.getInstance(emulator);
hookZz.wrap(module.base + 0x1BD0 + 1, new WrapCallback<HookZzArm32RegisterContext>() { // inline wrap導(dǎo)出函數(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());
}
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
跟進(jìn)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主動(dòng)調(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();
// 動(dòng)態(tài)方法choose onMatch找到實(shí)例進(jìn)行調(diào)用
console.log("found ins => ",ins);
// smali或objection看真實(shí)方法名
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為起始地址
設(shè)置type為JNIEnv*
public String calculateI(){
List<Object> list = new ArrayList<>(10);
list.add(vm.getJNIEnv()); // 第一個(gè)參數(shù)是env
list.add(0); // 第二個(gè)參數(shù),實(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());
}
報(bào)錯(cuò)位置在0x2c8d
g跳轉(zhuǎn)過去
F5查看源碼在方法jbyte *__fastcall sub_2C3C(JNIEnv *a1, int a2)
中,x交叉引用
看到熟悉的sub_1C60,繼續(xù)x交叉引用,找到getIValue中的sub_1C60

tab進(jìn)入?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);
}
}
接下來雙擊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);
}
總結(jié)
本次案例中使用xposed破解ssl pinning反抓包,結(jié)合objection,frida和unidbg針對(duì)so層修改opcode,完成參數(shù)的逆向分析和主動(dòng)調(diào)用。
本文由博客群發(fā)一文多發(fā)等運(yùn)營工具平臺(tái) OpenWrite 發(fā)布