記一次Android逆向之旅(入門向)

大大紙飛機發表於2020-04-07

近日市場投放部門的同事找我說,在應用商店輸入關鍵字檢視我們APP的排名,這個能不能通過技術自動化實現?本以為是件很簡單的事情,結果做的時候遇到了重重阻礙,於是就有了這次逆向之旅。

逆向,聽起來就很黑客的詞,好像比開發高大上好多倍啊。真正接觸到才明白,逆向其實是一個很需要耐心,會查閱資料,又比較依賴運氣的一件事。我的這次逆向之旅,雖然沒有觸及到很深刻的主題,但是如果你是一個新手又對逆向感興趣,至少可以幫助你儘快進入狀態,少走一些彎路。

本次用到的工具主要有:apk查殼神器、apktool.jar、signapk.jar、jeb2、IDA、Xposed,和一臺夜神模擬器。相關資源文末有下載連結

這個事情的核心其實就是抓下介面,偽造一個相同的請求,然後就可以用指令碼刷介面了。抓包工具一般就是Fiddler或者Charles,然後在手機安裝一個證照,就可以抓取HTTPS請求(如果事情這麼簡單,就不會有接下來的事了)。安裝好證照後開始抓包,發現還是失敗,並且APP檢測到了抓包行為,提示當前網路存在安全隱患(WTF?)。看起來這個APP在本地應該有證照,就是那個X509什麼什麼的。沒辦法,正式進入本文的主題:逆向。

APP防逆向目前的手段主要還是混淆+加固。混淆的話沒辦法還原,只能靠自己去理解,而加固倒是有現成的手段,先去找一個apk查殼神器看看目標APK使用了什麼加固手段,再去找找對應的脫殼方法,幸運的是我這次破解的商店沒有用到任何加固(也挺不幸運,脫殼的技術暫時瞭解不到了)。

Step1 解讀Smali

apktool.jar 應該廣為人知,使用它可以得到apk包內的smali檔案和資原始檔。使用如下命令即可:

java -jar apktool.jar xxx.apk
複製程式碼

執行完之後可以得到一個下圖結構的資料夾:

記一次Android逆向之旅(入門向)

我們這次分析的目標就是smali_xxx資料夾,有多個資料夾是因為當前APP大多使用了MultiDex(PS:這裡我不是沒試過用dex2jar轉成可讀性更強的Java Class,但是dex2jar只能解析一個dex,其他dex的檔案都丟失了,後邊使用jeb2搞定了這個事,但是我們待會再說)。smali檔案浩如煙海,如果沒有一定的目的性是肯定辦不了事的,但我們的目標很明確就是X509Certificate,所以全域性搜尋它:

記一次Android逆向之旅(入門向)

看起來有400多個結果分佈在50多個檔案中,但是這次搜尋並不是一無所獲,其中 CertificateUtil 以及 checkClientTrusted、checkServerTrusted 引起了我的注意。前者不知道幹嘛的,但是後者都是X509TrustManager定義的介面,我們定位到這個檔案開頭,可以看到確實實現了X509TrustManager:

記一次Android逆向之旅(入門向)

不知道是否運氣太好,一次就破案了,只要我們刪除 checkServerTrusted 裡的校驗,就可以讓客戶端信任所有的服務端證照,也就可以實現抓包操作了。

一次失敗的嘗試:修改Smali,回編APK

雖然不瞭解smali語法,但是還是能大致看懂一些的,隨便截個方法體會一下:

記一次Android逆向之旅(入門向)

大體上沒那麼難懂吧?每一步在幹什麼寫的很清楚,讓我們往裡面加程式碼可能比較難,但是刪除一些還是比較簡單的,我們就想讓checkServerTrusted直接返回null,直接改成下面這樣:

記一次Android逆向之旅(入門向)

smali修改完成後,得重新打包成apk,不然怎麼安裝呢?打包的方法如下:

java -jar apktool.jar b {剛剛反編譯出來的路徑} 
複製程式碼

這個時候得到的apk是沒有簽名的,以前可以在回編命令加入 -c/--copy-original 保持原有的簽名,只要沒有修改Manifest檔案即可。但是現在這個辦法不可用了,在官方issues找到了解釋:apktool issue。所以我們可以通過 signapk.jar 來進行重簽名。

// 下載signapk.jar 會同時得到 testkey.x509.pem testkey.pk8
java -jar signapk.jar testkey.x509.pem testkey.pk8 xxx.apk out.apk
複製程式碼

但是APP肯定會對簽名進行校驗的,我們這個方式雖然能正常安裝,但依然無法達到抓包的目的,具體原因請繼續往下看。這說明通過修改smali檔案回編apk的方式比較難搞,也可能做不了,所以我們有了方式2:Xposed。

Step2 Xposed Hook Java

回編譯行不通,我們就要找找其他辦法,Xposed可以在不修改apk程式碼的情況下,改變apk的行為,雖然它只能hook java層程式碼,但就目前而言足夠了。首先我們要有一臺root過的手機,然後安裝Xposed,文末我會分享此次使用的夜神模擬器和對應的Xposed安裝器,如果你有Pixel或者Nexus手機那就更好了。

安裝的過程很簡單,只要提示以下的狀態就是安裝成功了:

記一次Android逆向之旅(入門向)

接下來就可以編寫程式碼,新建一個APP工程,加入以下內容:

// app build.gradle
compileOnly 'de.robv.android.xposed:api:82'

// AndroidManifest.xml
<!-- 表明這是一個xposed外掛 -->
<!-- 表明這是一個xposed外掛 -->
<meta-data
    android:name="xposedmodule"
    android:value="true" /> <!-- 指定xposed的最小版本 -->
<meta-data
    android:name="xposedminversion"
    android:value="30+" /> <!-- 外掛的描述 -->
<meta-data
    android:name="xposeddescription"
    android:value="xposed外掛開發測試" />
複製程式碼

這樣配置一下,Xposed就可以識別我們的工程是一個外掛,在那個安裝器裡選擇模組,就可以啟用我們的外掛。

接下來就可以寫我們的hook方法了,在工程中隨便建一個類,實現 IXposedHookLoadPackage 介面,在 assets 目錄下新建 xposed_init 檔案,然後把這個類的class路徑加入其中。IXposedHookLoadPackage 介面只有一個方法 handleLoadPackage,這裡就是我們功能實現的地方。就我們的需求而言,這個非常簡單:

public class HookXXXStore implements IXposedHookLoadPackage {
    @Override
    public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) throws Throwable {
        XposedBridge.log("HookLogic >> current package:" + lpparam.packageName);
        if (lpparam.packageName.equals("com.xxx.market")) {
            XposedHelpers.findAndHookMethod("a.a.a.cis", lpparam.classLoader, "checkServerTrusted", X509Certificate[].class, String.class, new XC_MethodReplacement() {
                @Override
                protected Object replaceHookedMethod(MethodHookParam param) throws Throwable {
                    XposedBridge.log("直接替換checkServerTrusted方法,返回null");
                    return null;
                }
            });
        }
    }
}
複製程式碼

簡單明瞭不需要解釋吧?傳進去class、方法名、引數,再加一個回撥,注意是 XC_MethodReplacement,直接替換原方法的實現,class名、方法名剛剛截圖裡都有。把這個指令碼跑起來,重啟一下就可以生效了。然後把抓包工具跑起來,原APP跑起來,就可以看到抓包的資料了。

Step3 破解sign驗籤

抓包看資料可不是我們的目的,我們不僅想看到,還希望能自己寫指令碼呼叫介面。但是介面一般都會有個驗籤步驟,就是把Header中的引數拼接起來,再通過一定的加密演算法生成一個字串,客戶端和服務端採用相同的手段,比對一下生成的字串是否一致。所以我們必須知道這個sign的規則才能“偽裝”自己就是APP,來刷介面資料。

從smali尋找蛛絲馬跡的過程就不說了,搜尋sign然後找可疑目標就好。不過這次找到之後smali檔案讀起來就比較費勁了,剛剛是因為我們知道那個方法的作用,不需要了解實現就能改,現在我們必須知道它取了哪些Header,又怎麼排序怎麼加密的,smali讀起來像看天書,你們感受一下:

記一次Android逆向之旅(入門向)

滿眼都是goto goto,這不是考驗耐心了,是直接擊垮耐心。所以我選擇使用jeb2直接看對應的java程式碼。jeb2使用更簡單,直接把源apk丟進去就可以了。這裡就是關鍵程式碼:

記一次Android逆向之旅(入門向)

要搞清楚裡面的arg8、arg10、v1、v0比較容易,重點是關鍵引數arg7是另一個類處理的,而具體實現是在native完成的:

記一次Android逆向之旅(入門向)

就好像眼看著就得到答案了,答案又埋在了更深的地方。關鍵是Xposed面對native是無力的,c語言本來就已經擱置很久,而且so庫還會做混淆,為破解增加了無限壓力。最後我採用了一種共生的方式完成了這個任務,那就是在安裝了這個軟體的手機上,呼叫它的so庫來實現功能,好處是不需要了解它具體的實現,缺點是離不開Android平臺了。

找到so是一回事,確定一個方案卻是另一回事。如果不先看一下so,沒準還以為自己可以讀懂呢?這時候IDA就派上用場了,它可以開啟so,還可以轉成c語言程式碼,甚至可以動態除錯(如果不是IDA和我的模擬器連線比較困難,總是卡在某個錯誤中,也許能動態除錯出結果呢)。

Step4 IDA分析so庫

分析so庫不僅為了找尋答案,也是來解釋一下為什麼我說換了簽名之後就不能抓包了(其實也不是不能抓包,主要是拿不到需要的資料)。我們使用IDA開啟這個so,主要關注簽名用到的 .c 方法,還有傳入了Context的 .a 方法。我們先看 .a 方法:

記一次Android逆向之旅(入門向)

這裡先是拿到了包名資訊,這個“偽裝”起來很容易,接下來還根據Signature做了簽名的校驗:

記一次Android逆向之旅(入門向)

雖然不大明白,但大致就是對Signature簡單處理一下,再md5加密,和一個特定的字串對比,通過之後才能走下面的初始化流程,得到加密需要用到的key等欄位。

接下來 .c 方法就很簡單了,有了加密的key,呼叫加密演算法進行加密即可。

Step5 共生的方式呼叫so庫

我們的探索就先到此,解釋一下怎麼通過共生的方式達到我們的目的。Context有個createPackageContext方法,可以建立另外一個包的上下文,它有兩個標記位,CONTEXT_INCLUDE_CODE和CONTEXT_IGNORE_SECURITY,表示可以執行對方的程式碼以及忽略安全警告。我們只要把這個context給到so,就可以實現我們的功能了:

Context context = mContext.createPackageContext("com.xxx.market", Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY);
XxxTools.a(context);
複製程式碼

接下來就可以正常地進行抓包、解析資料,實現我們需要的功能了。

總結和展望

之所以中斷探索的過程,不是說到了so就不能破解了,通過強大的frida工具可以同時 hook java 和 native 的程式碼,而且native的校驗也都是比較容易繞過去的。難點在於使用frida要懂一些python指令碼,還要掌握一點JS的知識,理解native的程式碼還需要有一些c語言基礎,雖然深度要求不高,但是廣度上需要掌握很多東西,這些都需要長時間的接觸,而不是突然遇到就能突然掌握的。frida的配置也比較繁瑣,整體而言對我們這個簡單的需求有點殺雞焉用牛刀的感覺。

因此本文僅僅是一個引子,告訴你有哪些工具,每個工具的作用,如果你對逆向有興趣,可以從使用這些工具入手,慢慢深入。將這些工具集中在一起介紹一下,如果能減少你入門探索的時間,讓你感受到逆向的門檻好像也沒那麼高,而由此帶來一點點信心上的提升,那這篇文章的價值就實現了。

相關的工具下載地址已經放在下方公眾號,您可以回覆 逆向 獲取哦。


我是飛機醬,如果您喜歡我的文章,可以關注我的微信公眾號: 大大紙飛機

或者掃描下方二維碼直接新增:

公眾號

您也可以關注我的github:github.com/LtLei/artic…

程式設計之路,道阻且長。唯,路漫漫其修遠兮,吾將上下而求索。

相關文章