<span id="7ztzv"></span>
<sub id="7ztzv"></sub>

<span id="7ztzv"></span><form id="7ztzv"></form>

<span id="7ztzv"></span>

        <address id="7ztzv"></address>

            原文地址:http://drops.wooyun.org/tips/2871

            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動態庫》

            <span id="7ztzv"></span>
            <sub id="7ztzv"></sub>

            <span id="7ztzv"></span><form id="7ztzv"></form>

            <span id="7ztzv"></span>

                  <address id="7ztzv"></address>

                      亚洲欧美在线