一次app抓包引發的Android分析記錄

wyzsk發表於2020-08-19
作者: 綠盟科技 · 2014/08/26 12:55

0×00 起因


最近想了解下移動端app是如何與服務端進行互動的,就順手下了一個某app抓下http包。誰知抓下來的http居然都長這個模樣:

POST /ca?qrt=***LoginHTTP/1.1
Content-Type:application/x-www-form-urlencoded
Content-Length:821
Host:client.XXX.com
Connection:Keep-Alive

c=B946D7CF7B9E5B589F8DE6BC9E5DACF08DA6B35DA7A899DCA5AD5A58ADDAD5E66363589EF2D385B1ACACABDAD5E65F63589EF2D3F5B43EA7ACE36DFCA5383EABE7D4F1403E379FF0DEFCAD9FABA7E6E1F243A7AAAC74E380A4A09E6593D3FEA4ABA8ACF0DDF0AEAAB3A7EAE9FBA4A09E5F97D3FEA49EA09E9B97A9A4B69EADE7E1F6B0AC9EA0DA94A95E57609EF2D3B55E659EA0DA94B55F9EB69EDAD5E668689EB6DA98AA6E576E62939DE6A69E616D868CB673636162DAEBE6AEA2ACA2E486F3AD9EA09EA898A0A4B69EABE8E1F3B29EA09EA998A0A4B69EADE3E385AE3DA7ABDB6FF4B93A9F3DEFE3FBA537ACAD77D486AD37ADABE77383B4B4ACB4DAD5E66E9EB69EA886AF634061599F97E6A69E676394D3FEA4ACACACE8E1F4B2ACACACE8E1F4B2AC9EA0DA9CAAA4B69E9EDCD3B269589EB6DADFF4B2ACABACE3E9E675&b=AD178E267CA8666F636D6C54ADC1B3AEAD736574696950776D71A9BEACADA7A8727762A8C0AA67656172736A6D676F706E6369837F726C71A5AAA4756C656963ADC1A6797870615E676E7063666C756CAC7E&ext=&v=alex

這是我在嘗試登陸時抓包獲取的唯一http包,顯而易見POST資料中的c引數是包含登入資訊的,但是為什麼長這個模樣?為了得到答案,我開啟了我一週的Android動態除錯和靜態分析學習之旅。

這篇文章將透過這段字串原文的過程,向各位介紹幾種非常好用的Android除錯工具以及它們的一些簡單用法。

0×01 分析過程


1.基本靜態分析過程

拿到一個apk最常規的做法應該是就是,反編譯檢視一下java原始碼了。

apktool反編譯得到smali(其實主要是為了看AndroidManifest.xml):

/*我用的apktool是2.0.0-Beta9,命令和常見的1.x版本的命令有所不同*/ 
apktool d XXX.apk -o ./doc
/*我用的apktool是2.0.0-Beta9,命令和常見的1.x版本的命令有所不同*/
apktooldXXX.apk-o./doc

enter image description here

然後用的dex2jar工具將apk反編譯為jar,並透過JD-GUI來檢視java原始碼:

dex2jar.shXXX.apk

enter image description here

在apktool反編譯的目錄中我們可以翻看AndroidManifest.xml來了解apk檔案的基本結構,我從中首先找到主Activity的所在:

<activityandroid:configChanges=”keyboardHidden|orientation”android:exported=”true”android:name=”com.XXX.NoteActivity”android:screenOrientation=”portrait”android:[email protected]:style/Theme.Black.NoTitleBar.Fullscreen”><intent-filter><actionandroid:name=”android.intent.action.MAIN”/><categoryandroid:name=”android.intent.category.LAUNCHER”/><categoryandroid:name=”android.intent.category.MULTIWINDOW_LAUNCHER”/></intent-filter>

可以從上面的內容看出主Activity是com.XXX.NoteActivity這個類所定義的,然後透過JD-GUI開啟dex2jar反編譯後的jar包,檢視NoteActivity。

enter image description here

先來看onCreate中的操作,發現使用produard對apk進行了混淆了。這種混淆雖然不會影響我們反編譯出來的程式碼內容,但是由於對類名、函式名、變數名進行了隨機命名,導致我們閱讀程式碼的過程比較痛苦。

我的目的很明確,就是定位到處理提交資料的程式碼,沒有必要去花大量的時間來閱讀被混淆的程式碼,所以我決定使用動態跟蹤程式執行的軌跡來定位我想要獲得的程式碼。

2.動態定位過程

雖然要用動態的方法來定位,但是還是需要簡單的閱讀java原始碼來確定提交資料的大概處理方式。

我的運氣還是不錯,網路傳輸部分的程式碼並沒有被混淆。大體看了一下這些程式碼,發現和一般的app一樣,客戶端和服務端的資料互動也是使用json的格式進行的,並且使用了阿里開源的fastjson類來處理json內容。

enter image description here

enter image description here

瞭解了以上的這些情況,我決定透過跟蹤JSONObject這個類來定位處理提交資料的位置。

這裡推薦一個分析app的神器——Andbug,雖然不能用來單步除錯,但是動態跟蹤app中各種執行緒呼叫棧、類呼叫棧、方法呼叫棧,斷點獲取當前記憶體中變數內容等功能還是非常實用的。

廢話不多說了,來看操作吧,先獲取要動態分析的app程式ID:

adb shell ps

enter image description here

程式ID 445,使用Andbug掛載該程式,並使用classes命令查詢fastjson類的全路徑:

andbug shell -p 435 classes JSONObject andbug shell -p435 classes JSONObject

enter image description here

這裡提示一個使用classes和method命令查詢的小技巧。我們在Andbug的shell環境下使用classes時很容易由於class過多而導致沒辦法看到所有的class。這是我們可以在終端環境下使用classes命令配合more來一點點的檢視,就像這樣: andbug classes -p 435|more

然後我們使用class-trace命令來對這個類進行跟蹤:

class-trace com.alibaba.fastjson.JSONObject

enter image description here

在app中隨便觸發一個會提交請求的事件,呼叫過程在終端中完美的呈現了出來:

enter image description here

可以看到呼叫過程都用到了com.XXX.net.task.CommonTask中的方法,開啟這個類的java原始碼第一眼就看到這段程式碼:

#!java
protectedHttpEntity buildHttpEntity()
  {
    if(this.hostUrl.indexOf(“?”)>0)
      this.hostUrl=(this.hostUrl+“&qrt=”+this.networkTask.param.key.getDesc());
    while(true)
    {
      Stringstr=String.valueOf(this.networkTask.param.ke);
      StringBuilderlocalStringBuilder1=newStringBuilder();
      localStringBuilder1.append(“c=”+chrome(str));
      localStringBuilder1.append(“&”);
      StringBuilderlocalStringBuilder2=newStringBuilder(“b=”);
      BaseParamlocalBaseParam=this.networkTask.param.param;
      SerializerFeature[]arrayOfSerializerFeature=newSerializerFeature[1];
      arrayOfSerializerFeature[0]=SerializerFeature.WriteTabAsSpecial;
      localStringBuilder1.append(NetworkParam.convertValue(JSON.toJSONString(localBaseParam,arrayOfSerializerFeature),str));
      if((this.networkTask.param.param instanceofHotelBookParam))
      {
        HotelBookParamlocalHotelBookParam=(HotelBookParam)this.networkTask.param.param;
        if(localHotelBookParam.vouchParam!=null)
          dealVouchRequest(localStringBuilder1,localHotelBookParam.vouchParam);
      }
      localStringBuilder1.append(“&”);
      localStringBuilder1.append(“ext=”+NetworkParam.convertValue(XXXApp.getContext().ext,str));
      localStringBuilder1.append(“&v=alex”);
      this.networkTask.param.url=localStringBuilder1.toString();
      try
      {
        StringEntitylocalStringEntity=newStringEntity(this.networkTask.param.url);
        returnlocalStringEntity;
        this.hostUrl=(this.hostUrl+“?qrt=”+this.networkTask.param.key.getDesc());
      }
      catch(UnsupportedEncodingExceptionlocalUnsupportedEncodingException)
      {
        cl.m();
      }
    }
    returnnull;
  }

結合之前的抓包,這應該就是我要找的地方了。從中找到處理c引數的程式碼,看到呼叫了com.XXX.net.task.AbstractHttpTask.chrome對引數值進行了處理,跟進chrome方法:

#!java
protectedStringchrome(StringparamString)
  {
    JSONObjectlocalJSONObject=gcc(paramString);
    Stringstr=“60001058″.substring(0,4)+“lex”;
    returnNetworkParam.convertValue(localJSONObject.toString(),str);
  }

繼續趕進到convertValue方法:

#!java
publicstaticStringconvertValue(StringparamString1,StringparamString2)
  {
    if(TextUtils.isEmpty(paramString1))
      return "";
    if(paramString2==null)
      paramString2=“”;
    try
    {
      Stringstr=URLEncoder.encode(Goblin.e(paramString1,paramString2),“utf-8″);
      returnstr;
    }
    catch(ThrowablelocalThrowable)
    {
      localThrowable.printStackTrace();
    }
    return "";
  }

感覺的勝利的曙光越來越近了,這個Goblin.e應該就是最後的加密方法了吧,誰知開啟這個檔案(內心一萬隻草泥馬在狂奔):

#!java
packageXXX.lego.utils;
importcom.XXX.XXXApp;

publicclassGoblin
{
  static
  {
    try
    {
      System.loadLibrary(“goblin_2_5″);
      return;
    }
    catch(UnsatisfiedLinkErrorlocalUnsatisfiedLinkError1)
    {
      try
      {
        System.load(“/data/data/”+XXXApp.getContext().getPackageName()+“/lib/lib”+“goblin_2_5″+“.so”);
        return;
      }
      catch(UnsatisfiedLinkErrorlocalUnsatisfiedLinkError2)
      {
      }
    }
  }

  publicstaticnativeStringSHR();

  publicstaticnativeStringd(StringparamString1,StringparamString2);

  publicstaticnativeStringdPoll(StringparamString);

  publicstaticnativeStringda(StringparamString);

  publicstaticnativeStringdn(byte[]paramArrayOfByte,StringparamString);

  publicstaticnativebyte[]dn1(byte[]paramArrayOfByte,StringparamString);

  publicstaticnativeStringduch(StringparamString);

  publicstaticnativeStringe(StringparamString1,StringparamString2);

  publicstaticnativeStringePoll(StringparamString);

  publicstaticnativeStringea(StringparamString);

  publicstaticnativebyte[]eg(byte[]paramArrayOfByte);

  publicstaticnativeStringes(StringparamString);

  publicstaticnativeintgetCrc32(StringparamString);

  publicstaticnativeStringgetPayKey();

  publicstaticnativeStringve(StringparamString);
}

3.呼叫so檔案函式

居然把加密方法寫到了so檔案中!難道要去看ARM彙編?

既然這個so檔案中有加密函式,那是不是就應該有解密函式,那我應該還是可以偷懶的吧。

我們在上面看到的e函式肯定是用來加密的,那那個d函式是不是用來解密的(encode和decode)?

自己本地建立一個app,並且建立一個XXX.lego.utils包,新增一個Goblin.java檔案,把我們剛剛看到的Goblin原始碼貼上進去。然後在app的一個Activity中匯入Goblin,並在OnCreate中呼叫d函式來嘗試解密。部分程式碼如下:

#!java
Stringc=“B946D7CF7B9E5B589F8DE6BC9E5DACF08DA6B35DA7A899DCA5AD5A58ADDAD5E66363589EF2D385B1ACACABDAD5E65F63589EF2D3F5B43EA7ACE36DFCA5383EABE7D4F1403E379FF0DEFCAD9FABA7E6E1F243A7AAAC74E380A4A09E6593D3FEA4ABA8ACF0DDF0AEAAB3A7EAE9FBA4A09E5F97D3FEA49EA09E9B97A9A4B69EADE7E1F6B0AC9EA0DA94A95E57609EF2D3B55E659EA0DA94B55F9EB69EDAD5E668689EB6DA98AA6E576E62939DE6A69E616D868CB673636162DAEBE6AEA2ACA2E486F3AD9EA09EA898A0A4B69EABE8E1F3B29EA09EA998A0A4B69EADE3E385AE3DA7ABDB6FF4B93A9F3DEFE3FBA537ACAD77D486AD37ADABE77383B4B4ACB4DAD5E66E9EB69EA886AF634061599F97E6A69E676394D3FEA4ACACACE8E1F4B2ACACACE8E1F4B2AC9EA0DA9CAAA4B69E9EDCD3B269589EB6DADFF4B2ACABACE3E9E675″;
Stringp2=“6000lex”;
Stringtest=Goblin.d(c,p2);
System.out.println(test);

enter image description here

天不遂人願啊!解密出錯,看來真的要去看ARM彙編了。。。。。。

4.動態除錯so檔案

由於app自帶的加密資料,我們不知道原來的樣子,所以要自己構造一個字串加密,來除錯。修改上面的app程式碼如下:

#!java
Stringjson=“json{/”test/”:/”test1/”,/”test4/”:/”test1/”,/”test5/”:/”test1/”,/”test6/”:/”test1/”,/”test3/”:/”test1/”,/”test2/”:/”test1/”,/”test1/”:/”test1/”}”;
Stringp2=“6000lex”;
Stringtest=Goblin.e(c,p2);
System.out.println(test);

生成程式碼如下:

C0990850B69C969E92C19C9DC5CDD9F0FAB99AD3960CE3F6D2CFB0AA9AE904F6C0A099A68DFFD0C1EE978CA985CAD2FCF6CD92A7C4FCE106CCC99CC39C11F7F3D0A9BDAD8AE3E0D9C3BC898EB8DCD3F2BB8B8BB3C010D9DAFAB99AD3960FE30CD2CFB0AA9AEC04E0C0A099A68DFFD0D7EE978CA985CED2B5

好了準備活動完成了,下面我們開始動態跟蹤之旅吧。在《Android軟體安全與逆向分析》中提供的動態分析工具是IDA pro 6.1以上版本,這個我在除錯過程中發現載入很慢。雖然載入完成後,能夠跟著IDA生成的流程圖來除錯很爽,但是載入成功率實在是太低了。所以,我放棄了用IDA進行動態除錯,而是選擇了 這個號稱移動端Onllydbg的gikdbg來進行除錯,同時配合IDA的流程圖。

gikdbg使用參考《gikdbg.art系列教程2.1-除錯so動態庫》這篇blog很容易上手,這裡也就不多說了。

除錯跟蹤過程很枯燥,也沒什麼可以說的,我們直接看結過吧。

透過反覆的動態跟蹤,確定下面這個迴圈是加密的關鍵:

enter image description here

可以看出加密方法比較簡單,對於源資料的每一個字元與0×45進行異或,然後jia0x24,最後再加上硬編碼在so檔案的一串key中的一個字元。根據彙編逆向出來的python程式碼如下:

result=""
i=0
while(i<len(json)):
    char=json[i]^69
    j=i
    ifj>len(key):
        j=j%len(key)
    encode=int(ord(char))+36+key[i]
    result+=encode
    i+=1

在加密完資料後,會在資料頭部新增一個8個字元(32位)的校驗資料,校驗演算法使用的是adler32。由此可以推出解密演算法,程式碼如下:

defdecode():

ejson=‘B69C969E92C19C9DC5CDD9F0FAB99AD3960CE3F6D2CFB0AA9AE904F6C0A099A68DFFD0C1EE978CA985CAD2FCF6CD92A7C4FCE106CCC99CC39C11F7F3D0A9BDAD8AE3E0D9C3BC898EB8DCD3F2BB8B8BB3C010D9DAFAB99AD3960FE30CD2CFB0AA9AEC04E0C0A099A68DFFD0D7EE978CA985CED2B5′

key=‘cBHO06GYkxNModVyAtXiGzlPETyS5KUL8gE4′
i=0
result=”
while(i<len(ejson)):
    j=i/2
    char=ejson[i:i+2]
    ifj<len(key):
        k=key[j]
    else:
        k=key[j%len(key)]
    i=i+2
    c=int(char,16)-int(ord(k))
    ifc<0:
        c+=128
    c=c-36
    ifc<0:
        c+=128
    c=c^69
    dchar=chr(c)
    result+=dchar
printresult
decode()

其中ejson中的內容為,我們使用e函式加密後獲得的內容剔除前八位,解密效果如下:

0×02 最終結果&分析總結


不過悲劇的是用這個解密方法沒辦法解密前面我抓包獲取的資料。。。。。。

鬱悶之心無以言表啊!!!

不過這個過程還是很有意義的,瞭解了Android各種姿勢的動態除錯方法。這裡再次回顧一些這個過程。

首先透過反編譯獲取smali和java程式碼進行靜態分析,發現程式碼被混淆後,明確自己的最終目標——找到處理提交請求的方法,然後進行動態跟蹤。動態跟蹤和靜態分析結合定位出處理提交請求的幾個類,翻看這些類的程式碼,來找到最終我們想找的方法。

在發現處理方法使用了so檔案中的函式,透過自己構造app來分別呼叫so中的各個函式,試圖從中找到直接的解密函式。

在so中沒有找到解密函式的情況下,透過動態除錯與靜態檢視彙編,分析出加密演算法,並寫出解密工具。

0×03 參考文章


【1】《Assembly Programming Principles》 【2】《Android動態逆向分析工具(一)——Andbug之基本操作》 【3】《Android動態逆向分析工具(四)—— Andbug補充除錯功能》 【4】《gikdbg.art系列教程2.1-除錯so動態庫》

本文章來源於烏雲知識庫,此映象為了方便大家學習研究,文章版權歸烏雲知識庫!

相關文章