Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android逆向分析案例——某酒店APP的登陸請求分析

Android逆向分析案例——某酒店APP的登陸請求分析

編輯:關於Android編程

為了練練手,增長逆向分析的知識,本次博客打算分析一下某酒店APP的登陸請求。

這次的逆向分析還是以網絡請求為例,通過分析其登陸請求數據的加解密原理,將請求數據從密文轉換成明文,順便把返回的結果也轉成明文。

好了,既然明確了需求,那麼准備開始分析了,分析的步驟還是很簡單:反編譯-->找關鍵代碼-->分析請求代碼加解密原理-->驗證密文到明文准確性。

 

第一步,反編譯:

這一步中,一般來說我們可以結合smali文件和jd-gui查看器來分析,所以APKTool和dex2jar都可以用上。

因為APKTool和dex2jar的反編譯都有在前面的博客裡介紹過,所以在此就不詳細說明了,不明的可以回去看。

 

第二步,找關鍵代碼:

找關鍵代碼的方法有很多,我主要用到兩種方法,一是,利用wireshark抓包的關鍵字符串,結合sublime text工具在smali文件夾中使用“find in folder”功能找到關鍵字符串所在位置,然後再順籐摸瓜;二是直接通過jd-gui查看其代碼,並找到登陸界面的點擊事件,並定位到其關鍵代碼。

首先講第一種方法:

先抓包(打開wireshark監控,然後按下登陸按鈕進行登陸),並查看其TCP流,找出其關鍵字符串

\

可以看到圖中紅色方框中的關鍵詞"APPSIGN","sign","data"等,其中data的數據是我們想要知道的,但現在是密文。

接下來,我們利用找到的關鍵詞,利用sublime text工具的find in folder功能在smali文件夾中查找該關鍵詞出現的地方。

\

通過關鍵詞可以找到了一個HttpUtils的類,再看看其smali裡的表達式,可以看到com/loopj/android/http包,該包實際上就是第三方框架AsyncHttpClient的東西。

到這,我們基本上就可以定位到關鍵代碼位置了。

那麼,第二種方法又是怎樣的呢?

實際上第二種方法並不建議,這種方法就是直接找,因為fragment,Activity這些類是無法被混淆的,所以我們可以直接在jd-gui上浏覽一下目錄,找到其關鍵的地方,比如我們現在找登陸界面,那麼可以根據登陸的英文login去找,如下圖\

 

可以看到在com.htinns.UI.fragment.My包下可以找到了LoginFragment,沒錯,這就是登陸頁面,在文件數量很大的情況不建議用這種方法,費時。

在LoginFragment的代碼中可以看到有多個HttpUtils類的靜態方法被調用,基本上已經確定HttpUtils確實是登陸請求的一個工具類了。

 

第三步,分析加解密原理,實際上這跟第二步是緊密聯系的,因為要一邊順籐摸瓜,一邊推斷其加解密的原理,一步步地接近真相~

根據上面找到的關鍵代碼,我們可以在HttpUtils中找到一個靜態方法a(Context, RequestInfo),該方法中的參數RequestInfo實例是包含了賬號密碼的,就是該實例的字段c,所以我們可以根據RequestInfo.c這個內容去順籐摸瓜,找到其加密的位置。

\

可以看到,圖中紅色方框中的分別是加密的key和調用加密函數的入口a函數,那麼我們繼續順著這個a函數找:

 

  public static k a(Context paramContext, JSONObject paramJSONObject, String paramString)
  {
    k localk = new k();
    if (paramJSONObject == null);
    try
    {
      paramJSONObject = new JSONObject();
      if (TextUtils.isEmpty(b))
        b = av.e(paramContext);
      paramJSONObject.put("devNo", b);
      paramJSONObject.put("brand", Build.BRAND);
      paramJSONObject.put("manufacturer", Build.MANUFACTURER);
      paramJSONObject.put("model", Build.MODEL);
      if (TextUtils.isEmpty(g))
        g = av.f(paramContext);
      paramJSONObject.put("MAC", g);
      paramJSONObject.put("os", Build.VERSION.RELEASE);
      paramJSONObject.put("CHANNEL_ID", h.a("push_channelid", ""));
      paramJSONObject.put("PUSH_TOKEN", h.a("push_userid", ""));
      paramJSONObject.put("Jpush_CHANNEL_ID", h.a("jpush_channelid", ""));
      paramJSONObject.put("Jpush_PUSH_TOKEN", h.a("jpush_userid", ""));
      if (c == null)
        c = av.c(paramContext);
      paramJSONObject.put("access_mode", c);
      if (i == null)
        i = av.d(paramContext);
      paramJSONObject.put("ver", i);
      if (d == null)
        d = av.b(paramContext);
      paramJSONObject.put("channel", d);
      paramJSONObject.put("platform", ag.a());
      paramJSONObject.put("LATITUDE", e);
      paramJSONObject.put("LONGITUDE", f);
      GuestInfo localGuestInfo = GuestInfo.GetInstance();
      if (localGuestInfo != null)
        paramJSONObject.put("Token", localGuestInfo.TOKEN);
      Calendar localCalendar = Calendar.getInstance();
      String str1 = new SimpleDateFormat("yyyyMMddHHmmss").format(localCalendar.getTime());
      paramJSONObject.put("resultKey", paramString);
      String str2 = paramJSONObject.toString();
      localk.a("data", al.b(str2, "@!#$#%$%&^%&DFGFHF%&%&^%&%"));
      localk.a("time", str1);
      String str3 = Base64.encodeToString(av.c(str2 + str1 + paramString), 2);
      localk.a("sign", str3);
      localk.a("APPSIGN", a(str3));
      return localk;
    }
    catch (Exception localException)
    {
      localException.printStackTrace();
      MobclickAgent.onEvent(MyApplication.a(), "BUILD_PARAM_EXCEPTION", localException.getMessage());
    }
    return localk;
  }

從這個a函數中可以知道,這個函數就是拼接JsonObject的地方,還可以看到抓包時的關鍵詞“APPSIGN”,“data”等。既然我們需要解密的是data的數據,那麼我們再看看data數據在這裡是怎樣被加密的。

 

\

從紅色方框中可以看到localk.a(String, String)就是一個拼接函數,裡面的 al.b(str2, "@!#$#%$%&^%&DFGFHF%&%&^%&%")就是一個加密方法。至於str2是不是data的明文信息,我們可以利用hook技術驗證一下。

 

    public void handleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable {
        if (loadPackageParam.packageName.equals("com.htinns")) {
            XposedBridge.log("Load Pakage:"+loadPackageParam.packageName);
            XposedHelpers.findAndHookMethod("com.htinns.Common.al",
                    loadPackageParam.classLoader,
                    "b",
                    String.class,
                    String.class,
                    new XC_MethodHook() {
                        @Override
                        protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                            //查看加密前的String
                            Log.d("zz", (String)param.args[0]);
                        }

                        @Override
                        protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                        }
                    });
        }
    }
上面是一段Xposed模塊中的hook函數,該函數暴露其明文,Xposed使用在前面有介紹過,所以在這就不說了,看看hook後的日志。
\

 

很好,就是明文,裡面還有密碼呢,那麼接下來看一下加密的地方,我把al類貼出來,該類就是加密類,因為混淆過,它很多函數都是調用a, 或b,順籐摸瓜的時候很煩~

 

public class al
{
  private static byte[] a(String paramString)
  {
    int i = 0;
    byte[] arrayOfByte1 = paramString.getBytes();
    byte[] arrayOfByte2 = new byte[256];
    for (int j = 0; j < 256; j++)
      arrayOfByte2[j] = ((byte)j);
    if ((arrayOfByte1 == null) || (arrayOfByte1.length == 0))
      arrayOfByte2 = null;
    while (true)
    {
      return arrayOfByte2;
      int k = 0;
      int m = 0;
      while (i < 256)
      {
        k = 0xFF & k + ((0xFF & arrayOfByte1[m]) + (0xFF & arrayOfByte2[i]));
        int n = arrayOfByte2[i];
        arrayOfByte2[i] = arrayOfByte2[k];
        arrayOfByte2[k] = n;
        m = (m + 1) % arrayOfByte1.length;
        i++;
      }
    }
  }

  public static byte[] a(String paramString1, String paramString2)
  {
    if ((paramString1 == null) || (paramString2 == null))
      return null;
    return b(paramString1.getBytes(), paramString2);
  }

  public static byte[] a(byte[] paramArrayOfByte, String paramString)
  {
    if ((paramArrayOfByte == null) || (paramString == null))
      return null;
    return b(paramArrayOfByte, paramString);
  }

  public static String b(String paramString1, String paramString2)
  {
    if ((paramString1 == null) || (paramString2 == null))
      return null;
    return e.b(a(paramString1, paramString2), false);
  }

  private static byte[] b(byte[] paramArrayOfByte, String paramString)
  {
    int i = 0;
    byte[] arrayOfByte1 = a(paramString);
    byte[] arrayOfByte2 = new byte[paramArrayOfByte.length];
    int j = 0;
    int k = 0;
    while (i < paramArrayOfByte.length)
    {
      k = 0xFF & k + 1;
      j = 0xFF & j + (0xFF & arrayOfByte1[k]);
      int m = arrayOfByte1[k];
      arrayOfByte1[k] = arrayOfByte1[j];
      arrayOfByte1[j] = m;
      int n = 0xFF & (0xFF & arrayOfByte1[k]) + (0xFF & arrayOfByte1[j]);
      arrayOfByte2[i] = ((byte)(paramArrayOfByte[i] ^ arrayOfByte1[n]));
      i++;
    }
    return arrayOfByte2;
  }
}

經過跟蹤,最後來看看梳理後的加密流程圖:

 

\

由上圖可以看到,加密的過程關鍵有3處:

一是,對key字符串“@!#$#%$%&^%&DFGFHF%&%&^%&%”進行特殊的String轉byte[]處理,這裡我根據smali文件將其算法還原出來,並用java實現:

	public static byte[] getKeyBytes(String key){
		//73
		byte[] keyBytes=key.getBytes();
		//74
		byte[] resultBytes=new byte[256];
		//76
		for(int i=0; i<256; i++){
			//77
			resultBytes[i]=(byte)i;
		}
		//81
		if(keyBytes==null || keyBytes.length==0){
			//91
			return null;
		}else{
			int j=0, k=0;
			//84
			for(int i=0; i<256; i++){
				//85
				int m=(keyBytes[k]&0xff)+(resultBytes[i]&0xff);
				j=j+m & 0xff;
				
				//86
				int x=resultBytes[i];
				//87, 88
				resultBytes[i]=resultBytes[j];
				resultBytes[j]=(byte) x;
				k=k+1;
				k=k%keyBytes.length;
			}
			return resultBytes;
		}
代碼中的注釋是smali上面顯示的函數,不用鳥它。

二是,對明文的byte[]和特殊處理key後的byte[]進行異或:

public static byte[] xorEncode(byte[] data, byte[] key){

byte[] encodeBytes=new byte[data.length];

int j=0, k=0;

for(int i=0; i

三是,對異或後的byte[]進行Base64編碼,java中已自帶該編碼,所以我就不貼出來了。到此為止,我們已經將明文到密文的加密原理給分析清楚了,現在我們就驗證一下是否正確。第四步,也是最後一步,我們可以正向驗證,也可以逆向驗證,這裡的話我們采取逆向驗證,即從抓包的數據中還原出明文。打開wireshark,將過濾器設置為frame contain "APPSIGN",然後在手機上登陸一下這APP,該過濾條件能很容易地將包區別出來:

\

然後隨便選擇一個打開,這裡我們可以打開第一個包的TCP流,並將data後的數據(藍色部分)復制出來作為String驗證:

\

這裡藍色選定的數據實際上已被wireshark通過URLEncode過,所以在逆向驗證時,需要將數據URLDecode一下才進行下一步。 public static void main(String[] args) throws IOException {

// TODO Auto-generated method stub

String str="vr%2BCsMVfUcBzHDadnFFNAjdhONP0Aivd9j3BdYn35E3EkHG2bOmXSnhH%2FlqJmnDw%2FpVJpr%2B9sSHI8WoV5deh51b9tiltTcFjQWWeSPhIQg1tgLD51hfaX9IkwIiYRAocGrBzsCuKotTJA25RWcFwyyR48pV%2BiJt2abLRLIEsaA%2FaM541c4FREfRUC2qDqw8SJnGVPIKiAS6iHGaHW%2BoCfFGhUzyNcsMarcxEmK9RBbSTscsGcCivhJ%2F1RBAjW%2BndZQHLFeIm0jJWQdlpCpKZHgTiUCq%2BzL1UGM8Iqv41xHRq%2Bf0yimbAzPW%2B%2Ft6LrRKYcA9VpmARgQTvjUmgZsQ8j%2F65EAvzYuCeeO80T017rJHmOeV17DX0IKRgWdhg3LomDnyMqsv%2BQA8I755162jgEcxplO1lgaTvRpNMORlwgByUUXh%2BbJqmvyjDq1%2FXYQBgD%2F9J1WyWW3OTnxdwmuYsrOBIBBNgMYYCELGqW2IknlSwkzfZEBUjWBpYHgrc9DIOzv8cVaMCQxCb1VV7E8MToa0fmI7FcvsJIM2J";

String key="@!#$#%$%&^%&DFGFHF%&%&^%&%";

String data=new String(xorEncode(Base64.getDecoder().decode(URLDecoder.decode(str, "utf8")), getKeyBytes(key)));

System.out.println(data);

} 

在static main中,分幾步來逆向,其中異或的異或等於本身,所以只需要再執行一次異或就能過掉哪個異或加密算法:

\

最後在後台輸出字符串:

\

很顯然,這就是明文,也就是說驗證成功!至於Response的數據解密在這裡就不說了,其原理也差不多,只是比Request的數據加密逆向驗證多了一層gzip格式的解壓編碼,其他Base64和異或都和Request共用一套加解密算法。

 

 

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved