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

近日市場投放部門的同事找我說,在應(yīng)用商店輸入關(guān)鍵字查看我們APP的排名,這個(gè)能不能通過技術(shù)自動(dòng)化實(shí)現(xiàn)?本以為是件很簡單的事情,結(jié)果做的時(shí)候遇到了重重阻礙,于是就有了這次逆向之旅。

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

本次用到的工具主要有:apk查殼神器、apktool.jar、signapk.jar、jeb2、IDA、Xposed,和一臺(tái)夜神模擬器。相關(guān)資源文末有下載鏈接

這個(gè)事情的核心其實(shí)就是抓下接口,偽造一個(gè)相同的請(qǐng)求,然后就可以用腳本刷接口了。抓包工具一般就是Fiddler或者Charles,然后在手機(jī)安裝一個(gè)證書,就可以抓取HTTPS請(qǐng)求(如果事情這么簡單,就不會(huì)有接下來的事了)。安裝好證書后開始抓包,發(fā)現(xiàn)還是失敗,并且APP檢測(cè)到了抓包行為,提示當(dāng)前網(wǎng)絡(luò)存在安全隱患(WTF?)??雌饋磉@個(gè)APP在本地應(yīng)該有證書,就是那個(gè)X509什么什么的。沒辦法,正式進(jìn)入本文的主題:逆向。

APP防逆向目前的手段主要還是混淆+加固?;煜脑挍]辦法還原,只能靠自己去理解,而加固倒是有現(xiàn)成的手段,先去找一個(gè)apk查殼神器看看目標(biāo)APK使用了什么加固手段,再去找找對(duì)應(yīng)的脫殼方法,幸運(yùn)的是我這次破解的商店沒有用到任何加固(也挺不幸運(yùn),脫殼的技術(shù)暫時(shí)了解不到了)。

Step1 解讀Smali

apktool.jar 應(yīng)該廣為人知,使用它可以得到apk包內(nèi)的smali文件和資源文件。使用如下命令即可:

java -jar apktool.jar xxx.apk

執(zhí)行完之后可以得到一個(gè)下圖結(jié)構(gòu)的文件夾:

我們這次分析的目標(biāo)就是smali_xxx文件夾,有多個(gè)文件夾是因?yàn)楫?dāng)前APP大多使用了MultiDex(PS:這里我不是沒試過用dex2jar轉(zhuǎn)成可讀性更強(qiáng)的Java Class,但是dex2jar只能解析一個(gè)dex,其他dex的文件都丟失了,后邊使用jeb2搞定了這個(gè)事,但是我們待會(huì)再說)。smali文件浩如煙海,如果沒有一定的目的性是肯定辦不了事的,但我們的目標(biāo)很明確就是X509Certificate,所以全局搜索它:

看起來有400多個(gè)結(jié)果分布在50多個(gè)文件中,但是這次搜索并不是一無所獲,其中 CertificateUtil 以及 checkClientTrusted、checkServerTrusted 引起了我的注意。前者不知道干嘛的,但是后者都是X509TrustManager定義的接口,我們定位到這個(gè)文件開頭,可以看到確實(shí)實(shí)現(xiàn)了X509TrustManager:

不知道是否運(yùn)氣太好,一次就破案了,只要我們刪除 checkServerTrusted 里的校驗(yàn),就可以讓客戶端信任所有的服務(wù)端證書,也就可以實(shí)現(xiàn)抓包操作了。

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

雖然不了解smali語法,但是還是能大致看懂一些的,隨便截個(gè)方法體會(huì)一下:

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

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

java -jar apktool.jar b {剛剛反編譯出來的路徑} 

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

// 下載signapk.jar 會(huì)同時(shí)得到 testkey.x509.pem testkey.pk8
java -jar signapk.jar testkey.x509.pem testkey.pk8 xxx.apk out.apk

但是APP肯定會(huì)對(duì)簽名進(jìn)行校驗(yàn)的,我們這個(gè)方式雖然能正常安裝,但依然無法達(dá)到抓包的目的,具體原因請(qǐng)繼續(xù)往下看。這說明通過修改smali文件回編apk的方式比較難搞,也可能做不了,所以我們有了方式2:Xposed。

Step2 Xposed Hook Java

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

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

接下來就可以編寫代碼,新建一個(gè)APP工程,加入以下內(nèi)容:

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

// AndroidManifest.xml
<!-- 表明這是一個(gè)xposed插件 -->
<!-- 表明這是一個(gè)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插件開發(fā)測(cè)試" />

這樣配置一下,Xposed就可以識(shí)別我們的工程是一個(gè)插件,在那個(gè)安裝器里選擇模塊,就可以激活我們的插件。

接下來就可以寫我們的hook方法了,在工程中隨便建一個(gè)類,實(shí)現(xiàn) IXposedHookLoadPackage 接口,在 assets 目錄下新建 xposed_init 文件,然后把這個(gè)類的class路徑加入其中。IXposedHookLoadPackage 接口只有一個(gè)方法 handleLoadPackage,這里就是我們功能實(shí)現(xiàn)的地方。就我們的需求而言,這個(gè)非常簡單:

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;
                }
            });
        }
    }
}

簡單明了不需要解釋吧?傳進(jìn)去class、方法名、參數(shù),再加一個(gè)回調(diào),注意是 XC_MethodReplacement,直接替換原方法的實(shí)現(xiàn),class名、方法名剛剛截圖里都有。把這個(gè)腳本跑起來,重啟一下就可以生效了。然后把抓包工具跑起來,原APP跑起來,就可以看到抓包的數(shù)據(jù)了。

Step3 破解sign驗(yàn)簽

抓包看數(shù)據(jù)可不是我們的目的,我們不僅想看到,還希望能自己寫腳本調(diào)用接口。但是接口一般都會(huì)有個(gè)驗(yàn)簽步驟,就是把Header中的參數(shù)拼接起來,再通過一定的加密算法生成一個(gè)字符串,客戶端和服務(wù)端采用相同的手段,比對(duì)一下生成的字符串是否一致。所以我們必須知道這個(gè)sign的規(guī)則才能“偽裝”自己就是APP,來刷接口數(shù)據(jù)。

從smali尋找蛛絲馬跡的過程就不說了,搜索sign然后找可疑目標(biāo)就好。不過這次找到之后smali文件讀起來就比較費(fèi)勁了,剛剛是因?yàn)槲覀冎滥莻€(gè)方法的作用,不需要了解實(shí)現(xiàn)就能改,現(xiàn)在我們必須知道它取了哪些Header,又怎么排序怎么加密的,smali讀起來像看天書,你們感受一下:

滿眼都是goto goto,這不是考驗(yàn)?zāi)托牧耍侵苯訐艨迥托?。所以我選擇使用jeb2直接看對(duì)應(yīng)的java代碼。jeb2使用更簡單,直接把源apk丟進(jìn)去就可以了。這里就是關(guān)鍵代碼:

要搞清楚里面的arg8、arg10、v1、v0比較容易,重點(diǎn)是關(guān)鍵參數(shù)arg7是另一個(gè)類處理的,而具體實(shí)現(xiàn)是在native完成的:

就好像眼看著就得到答案了,答案又埋在了更深的地方。關(guān)鍵是Xposed面對(duì)native是無力的,c語言本來就已經(jīng)擱置很久,而且so庫還會(huì)做混淆,為破解增加了無限壓力。最后我采用了一種共生的方式完成了這個(gè)任務(wù),那就是在安裝了這個(gè)軟件的手機(jī)上,調(diào)用它的so庫來實(shí)現(xiàn)功能,好處是不需要了解它具體的實(shí)現(xiàn),缺點(diǎn)是離不開Android平臺(tái)了。

找到so是一回事,確定一個(gè)方案卻是另一回事。如果不先看一下so,沒準(zhǔn)還以為自己可以讀懂呢?這時(shí)候IDA就派上用場了,它可以打開so,還可以轉(zhuǎn)成c語言代碼,甚至可以動(dòng)態(tài)調(diào)試(如果不是IDA和我的模擬器連接比較困難,總是卡在某個(gè)錯(cuò)誤中,也許能動(dòng)態(tài)調(diào)試出結(jié)果呢)。

Step4 IDA分析so庫

分析so庫不僅為了找尋答案,也是來解釋一下為什么我說換了簽名之后就不能抓包了(其實(shí)也不是不能抓包,主要是拿不到需要的數(shù)據(jù))。我們使用IDA打開這個(gè)so,主要關(guān)注簽名用到的 .c 方法,還有傳入了Context的 .a 方法。我們先看 .a 方法:

這里先是拿到了包名信息,這個(gè)“偽裝”起來很容易,接下來還根據(jù)Signature做了簽名的校驗(yàn):

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

接下來 .c 方法就很簡單了,有了加密的key,調(diào)用加密算法進(jìn)行加密即可。

Step5 共生的方式調(diào)用so庫

我們的探索就先到此,解釋一下怎么通過共生的方式達(dá)到我們的目的。Context有個(gè)createPackageContext方法,可以創(chuàng)建另外一個(gè)包的上下文,它有兩個(gè)標(biāo)記位,CONTEXT_INCLUDE_CODE和CONTEXT_IGNORE_SECURITY,表示可以執(zhí)行對(duì)方的代碼以及忽略安全警告。我們只要把這個(gè)context給到so,就可以實(shí)現(xiàn)我們的功能了:

Context context = mContext.createPackageContext("com.xxx.market", Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY);
XxxTools.a(context);

接下來就可以正常地進(jìn)行抓包、解析數(shù)據(jù),實(shí)現(xiàn)我們需要的功能了。

總結(jié)和展望

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

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

相關(guān)的工具下載地址已經(jīng)放在下方公眾號(hào),您可以回復(fù) 逆向 獲取哦。


我是飛機(jī)醬,如果您喜歡我的文章,可以關(guān)注我~

編程之路,道阻且長。唯,路漫漫其修遠(yuǎn)兮,吾將上下而求索。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容