0x01 閃退
開始在接到任務的時候蓬网,由于是第一次接觸APP測試,俺興高采烈的將其裝進咱的夜神模擬器里面鹉勒,準備學習一番帆锋。結果,輕觸APP禽额,它輕輕地來了锯厢,卻又輕輕地走了(對沒錯,它無腦閃退脯倒。实辑。。)藻丢,我當時的心情是這樣的:!@#¥@%¥#@%&#%&????剪撬。
隨后各位大佬告訴我,可以更換真機悠反、模擬器残黑、安卓版本各種嘗試馍佑。
在經過一系列的測試后,將夜神模擬器的版本換為 Android 7
成功解決閃退問題????梨水。
0x02 抓包
為了抓取APP的數據包拭荤,我將burp的證書裝入我的模擬器中。但是疫诽,卻一直抓不到穷劈,在做了一定功課了解后,知道了可以采用 Xposed
+ JustTrustMe
來突破 SSL PINNING
踊沸。
但是!但是社证!但是逼龟!模擬器在未裝 JustTrustMe
時候無法抓包,而在裝上以后追葡,整個網絡環(huán)境腺律,不,可宜肉,用匀钧。打擾了~
還好,感謝雄哥給的 JustTrustMePlus
(只令某一個APP強制信任證書)谬返。
再用 Burp
進行抓包的時候之斯,就能成功抓取到APP的數據包了。
嗯~ o( ̄▽ ̄)o遣铝,舒服了...
0x03 傳輸加解密
再次抓包時佑刷,發(fā)現傳輸過程存在加解密。
起初酿炸,使用阿雄教的方法瘫絮,用 Xserver
去動態(tài) hook
加解密(函數這個模塊需要知道其加解密函數),可是卻沒有任何反應填硕。
無能為力的我麦萤,便去求同事幫忙逆向一下,卻是無果 /(ㄒoㄒ)/~~扁眯。
最后壮莹,在老大的指導下,請出了 Inspecakge
姻檀。
Inspeckage:是一個用于提供幫助Android應用程序動態(tài)分析的工具垛孔。通過對Android API的函數使用hook技術,幫助用戶了解應用程序在運行時的行為施敢。Gayhub地址:https://github.com/ac-pm/Inspeckage
操作如下:
1周荐、勾選需要調試的APP
2狭莱、本機進行端口轉發(fā)
adb connect 127.0.0.1:62001
adb forward tcp:8008 tcp:8008
Tips:
62001是夜神模擬器在本機的默認端口
而關于模擬器多開的問題,端口計算公式如下:
nox_i = 62024+i
例如:
nox_1 = 62025
nox_2 = 62026
nox_3 = 62027
當然概作,也可以通過命令行查看端口
3腋妙、打開Inspeckage
將上方的 OFF
勾選為 ON
就能成功 hook
函數了
4、配合Burp分析加解密
在抓取數據包后讯榕,我發(fā)現骤素,POST的數據使用 0x1d
作為分隔符將其分為三段
多抓了幾個數據包后,綜合分析出愚屁,BurpSuite截獲的POST數據包被 0x1d
字符分成了三段M1,M2M,M3济竹,其中:
M2的動態(tài)生成過程對應 Inspeckage
中的 3,其使用了 AES CBC
模式進行加解密霎槐,且 IV 初始偏移量
為一個固定值(Inspecakge中能看到)送浊。
M3可以看到通過X算法加密前,恰好為第二段的密鑰(第三段的密鑰是每次請求都會動態(tài)生成的丘跌,我并沒有解密出第三段的加密方法袭景,只能每次使用Inspeckage查看密鑰,如果有大佬了解的闭树,請不吝賜教)
而M1耸棒,使用了密鑰+明文再經過一次 MD5
散列的處理,在后端做篡改的校驗
流程圖如下:
5报辱、編寫小腳本
分析出結果后与殃,便能根據思路寫一個腳本出來簡化操作了
解密測試:
修改參數再加密的測試(此處進行越權測試):
可以順利開始測試了,舒服了~~~~
0x04 后續(xù)思考
在后續(xù)進行測試的時候碍现,由于每次請求都需要把密鑰和密鑰解密前的密文輸入程序奈籽,導致整個測試流程較為繁瑣,進度較慢鸵赫。(由于不會寫B(tài)urp插件衣屏,手法比較笨)
于是回想了下整個加解密流程,發(fā)現關鍵是:
1辩棒、密鑰 Key1
是本地通過代碼隨機生成的狼忱,經過加密流程生成 PostData
。
2一睁、服務器端經過代碼解密出Key2
钻弄,不和客戶端生成的 Key1
做校驗,只要能成功解密出數據就行者吁。
也就是說窘俺,我們可以將 Key2
寫為固定值,然后每次只需要輸入需要加密的數據就行了复凳。當然瘤泪,解密過程還是需要我們輸入 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 總結
通過本次APP的測試实檀,總的來說是艱辛卻收獲了很多惶洲,但最后就結果來講有兩點不太完美的地方:
1、對于M3并未成功破解膳犹,否則可以寫出更加精簡的代碼
2恬吕、由于不會寫B(tài)urp插件,所以操作過程還是比較繁瑣(下次一定學著寫????)