0x01 閃退
開始在接到任務(wù)的時(shí)候,由于是第一次接觸APP測試,俺興高采烈的將其裝進(jìn)咱的夜神模擬器里面,準(zhǔn)備學(xué)習(xí)一番。結(jié)果,輕觸APP,它輕輕地來了,卻又輕輕地走了(對沒錯(cuò),它無腦閃退。。。),我當(dāng)時(shí)的心情是這樣的:!@#¥@%¥#@%&#%&????。
隨后各位大佬告訴我,可以更換真機(jī)、模擬器、安卓版本各種嘗試。
在經(jīng)過一系列的測試后,將夜神模擬器的版本換為 Android 7 成功解決閃退問題????。
0x02 抓包
為了抓取APP的數(shù)據(jù)包,我將burp的證書裝入我的模擬器中。但是,卻一直抓不到,在做了一定功課了解后,知道了可以采用 Xposed + JustTrustMe 來突破 SSL PINNING 。

但是!但是!但是!模擬器在未裝 JustTrustMe 時(shí)候無法抓包,而在裝上以后,整個(gè)網(wǎng)絡(luò)環(huán)境,不,可,用。打擾了~

還好,感謝雄哥給的 JustTrustMePlus (只令某一個(gè)APP強(qiáng)制信任證書)。


再用 Burp 進(jìn)行抓包的時(shí)候,就能成功抓取到APP的數(shù)據(jù)包了。
嗯~ o( ̄▽ ̄)o,舒服了...
0x03 傳輸加解密
再次抓包時(shí),發(fā)現(xiàn)傳輸過程存在加解密。

起初,使用阿雄教的方法,用 Xserver 去動態(tài) hook 加解密(函數(shù)這個(gè)模塊需要知道其加解密函數(shù)),可是卻沒有任何反應(yīng)。

無能為力的我,便去求同事幫忙逆向一下,卻是無果 /(ㄒoㄒ)/~~。
最后,在老大的指導(dǎo)下,請出了 Inspecakge 。
Inspeckage:是一個(gè)用于提供幫助Android應(yīng)用程序動態(tài)分析的工具。通過對Android API的函數(shù)使用hook技術(shù),幫助用戶了解應(yīng)用程序在運(yùn)行時(shí)的行為。Gayhub地址:https://github.com/ac-pm/Inspeckage
操作如下:
1、勾選需要調(diào)試的APP

2、本機(jī)進(jìn)行端口轉(zhuǎn)發(fā)
adb connect 127.0.0.1:62001
adb forward tcp:8008 tcp:8008

Tips:
62001是夜神模擬器在本機(jī)的默認(rèn)端口
而關(guān)于模擬器多開的問題,端口計(jì)算公式如下:
nox_i = 62024+i
例如:
nox_1 = 62025
nox_2 = 62026
nox_3 = 62027
當(dāng)然,也可以通過命令行查看端口

3、打開Inspeckage

將上方的 OFF 勾選為 ON 就能成功 hook 函數(shù)了
4、配合Burp分析加解密
在抓取數(shù)據(jù)包后,我發(fā)現(xiàn),POST的數(shù)據(jù)使用 0x1d 作為分隔符將其分為三段

多抓了幾個(gè)數(shù)據(jù)包后,綜合分析出,BurpSuite截獲的POST數(shù)據(jù)包被 0x1d 字符分成了三段M1,M2M,M3,其中:
M2的動態(tài)生成過程對應(yīng) Inspeckage 中的 3,其使用了 AES CBC 模式進(jìn)行加解密,且 IV 初始偏移量 為一個(gè)固定值(Inspecakge中能看到)。
M3可以看到通過X算法加密前,恰好為第二段的密鑰(第三段的密鑰是每次請求都會動態(tài)生成的,我并沒有解密出第三段的加密方法,只能每次使用Inspeckage查看密鑰,如果有大佬了解的,請不吝賜教)

而M1,使用了密鑰+明文再經(jīng)過一次 MD5 散列的處理,在后端做篡改的校驗(yàn)

流程圖如下:


5、編寫小腳本
分析出結(jié)果后,便能根據(jù)思路寫一個(gè)腳本出來簡化操作了
解密測試:

修改參數(shù)再加密的測試(此處進(jìn)行越權(quán)測試):

可以順利開始測試了,舒服了~~~~
0x04 后續(xù)思考
在后續(xù)進(jìn)行測試的時(shí)候,由于每次請求都需要把密鑰和密鑰解密前的密文輸入程序,導(dǎo)致整個(gè)測試流程較為繁瑣,進(jìn)度較慢。(由于不會寫B(tài)urp插件,手法比較笨)
于是回想了下整個(gè)加解密流程,發(fā)現(xiàn)關(guān)鍵是:
1、密鑰 Key1 是本地通過代碼隨機(jī)生成的,經(jīng)過加密流程生成 PostData 。
2、服務(wù)器端經(jīng)過代碼解密出Key2 ,不和客戶端生成的 Key1 做校驗(yàn),只要能成功解密出數(shù)據(jù)就行。
也就是說,我們可以將 Key2 寫為固定值,然后每次只需要輸入需要加密的數(shù)據(jù)就行了。當(dāng)然,解密過程還是需要我們輸入 Key1 的。
于是我取了某次在 Inspeckage 中取到的 Key 和 X(Key) 寫為定值,修改了原本的代碼,簡化了操作。
package com.encrypt;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import sun.misc.BASE64Encoder;
import sun.misc.BASE64Decoder;
import org.apache.commons.codec.digest.DigestUtils;
import java.util.Arrays;
import java.util.List;
import java.util.Scanner;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
class AesCBC {
//IV
private static String InitV="XXXXXXXXXXXXXXXX";
public void setKey(String key) {
this.key = key;
}
public void setE(String e) {
E = e;
}
public void setD(String d) {
D = d;
}
public String getKey() {
return key;
}
public String getE() {
return E;
}
public String getD() {
return D;
}
private String key="";
private String E="";
private String D="";
/**
* FUNCTION: ENCRYPT
* */
public String encrypt() throws Exception{
Cipher cipher=Cipher.getInstance("AES/CBC/PKCS5Padding");
SecretKeySpec sks=new SecretKeySpec(getKey().getBytes(),"AES");
IvParameterSpec iv=new IvParameterSpec(InitV.getBytes());
cipher.init(Cipher.ENCRYPT_MODE,sks,iv);
byte[] ctext=cipher.doFinal(getE().getBytes());
return new BASE64Encoder().encode(ctext);
}
/**
* FUNCTION: DECRYPT
* */
public String decrypt() throws Exception{
Cipher cipher=Cipher.getInstance("AES/CBC/PKCS5Padding");
SecretKeySpec sks=new SecretKeySpec(getKey().getBytes(),"AES");
IvParameterSpec iv=new IvParameterSpec(InitV.getBytes());
cipher.init(Cipher.DECRYPT_MODE,sks,iv);
byte[] ptext=cipher.doFinal(new BASE64Decoder().decodeBuffer(getD()));
return new String(ptext);
}
public static void main(String[] args) throws Exception {
//開始
System.out.println("--------------------~Strat~--------------------");
System.out.println("***********************************************");
AesCBC ac=new AesCBC();
Scanner sc = new Scanner(System.in);
while(true){
System.out.print("Please Choice Mode【D(ecrypt)/E(ncrpty)】: ");
String mode=sc.nextLine().toUpperCase().trim();
if(mode.equals("D")){
//-------解密-------
//輸入key
System.out.println("--------------------AESKey~--------------------");
System.out.print("Key: ");
String key1=sc.nextLine().trim();
ac.setKey(key1);
System.out.print("CipherText: ");
String D=sc.nextLine().trim();
ac.setD(D);
System.out.println("\n--------------------Decrypt~--------------------");
String ptext=ac.decrypt();
System.out.println("Decrypt Result: "+ptext);
System.out.println("\n--------------------Finished--------------------\n\n");
}else if(mode.equals("E")){
//-------加密-------
//key為定值
System.out.println("--------------------AESKey~--------------------");
String K="(HqttsSdHpJHTwkF7 , WUy7AghAGVmCHcdC78jW+wTbCi2SvJ7n3Ig6Mmbi+Qdkn5TL79ISZ8XnIA03KRDhtZmPltbJSCQaIw8TbkkzY7wK/SpUmK0+wZV4feYDf+4RIAHCQyA+bWXx1dvdJT00toNyrQSCuORsCh2VMusmJ6XhyI1MrYNDY3m+1poNqes=)";
System.out.println("HqttsSdHpJHTwkF7\n");
String str="";
//正則匹配括號里的,并用逗號分割
Pattern pattern = Pattern.compile("(?<=\\()[^\\)]+");
Matcher matcher = pattern.matcher(K);
while(matcher.find()){
str=matcher.group();
}
List<String> keys = Arrays.asList(str.split(" , "));
String key1=keys.get(0);
String key2=keys.get(1);
ac.setKey(key1);
System.out.print("PlainText: ");
String E=sc.nextLine().trim();
ac.setE(E);
System.out.println("\n--------------------Encrypt~--------------------");
String ctext=ac.encrypt();
String hashStr=ac.getKey()+ac.getE();
String hashedStr=DigestUtils.md5Hex(hashStr);
char sp=29;
System.out.println("BURP Result: "+ hashedStr+sp+ctext+sp+key2);
System.out.println("\n--------------------Finished--------------------\n\n");
}else if(mode.equals("0")){
System.out.println("\n***********************************************");
System.out.println("--------------------the End--------------------");
System.exit(0);
}
}
}
}
0x05 總結(jié)
通過本次APP的測試,總的來說是艱辛卻收獲了很多,但最后就結(jié)果來講有兩點(diǎn)不太完美的地方:
1、對于M3并未成功破解,否則可以寫出更加精簡的代碼
2、由于不會寫B(tài)urp插件,所以操作過程還是比較繁瑣(下次一定學(xué)著寫????)