用運(yùn)行時安全測試與逆向?qū)崙?zhàn)指南)
1. 為什么移動應(yīng)用測試?yán)@不開 objection做移動端安全測試和逆向分析的朋友大概率都遇到過這種情況拿到一個目標(biāo) App想看看它內(nèi)部有什么敏感操作、有沒有做證書校驗(yàn)、能不能動態(tài)調(diào)用某個類的方法結(jié)果抓包工具被 SSL Pinning 擋住反編譯出來的代碼又看得頭大直接上 Frida 寫腳本吧光調(diào)試 hook 點(diǎn)就得折騰半天。我第一次接觸 objection 就是在這種場景下。當(dāng)時要快速確認(rèn)一個支付類 App 是否在運(yùn)行時做了 root 檢測如果用 Frida 寫腳本得先定位檢測函數(shù)、再寫 hook 邏輯、還要處理線程問題前前后后一晚上沒了。而 objection 提供了一條現(xiàn)成的命令敲下去直接返回檢測結(jié)果還能一鍵繞過。那一刻我就意識到這類給 Frida 套一層現(xiàn)成工具箱的中間層工具才是實(shí)際干活時效率最高的方式。objection 不是新的 hook 引擎它本質(zhì)上是一套基于 Frida 的運(yùn)行時探測與動態(tài)調(diào)試工具包。你不需要寫一行業(yè)務(wù)代碼只需要通過命令行就能完成大量常見的移動應(yīng)用測試動作枚舉已加載的類、定位具體類的方法、內(nèi)存搜索、SSL Pinning 繞過、模擬運(yùn)行環(huán)境繞過 root 檢測、動態(tài)調(diào)用目標(biāo)方法、dump 敏感字符串等。它適用于 Android 和 iOS 兩大平臺底層對接 Frida 的注入機(jī)制但把我要寫腳本實(shí)現(xiàn)變成了我敲一條命令實(shí)現(xiàn)。這篇文章我會把 objection 的使用邏輯拆開講清楚包括環(huán)境搭建、核心命令背后的原理、實(shí)戰(zhàn)中常見的坑以及它與 Frida 原生腳本的協(xié)作關(guān)系。目標(biāo)是讓還沒用過的人能直接上手讓用過但只停留在會敲幾條命令階段的人能理解它每個功能模塊的底層機(jī)制從而在真實(shí)測試中選對工具、少走彎路。2. 部署與啟動先把 objection 和 Frida 的關(guān)系理順2.1 版本匹配是第一個容易踩的坑很多人裝 objection 的第一步就栽了原因不是安裝命令出錯而是沒有搞懂 objection、Frida 工具端、frida-server 三者的版本關(guān)系。objection 通過 Python 包管理工具安裝它依賴 Frida 的 Python 綁定而手機(jī)端需要運(yùn)行一個 frida-server 程序才能讓 PC 端的指令真正注入到應(yīng)用進(jìn)程里。我當(dāng)時在一個測試機(jī)上裝的 Frida 是 15.x 版本frida-server 也用的 15.x直接跑 objection 完全沒問題。后來另一臺測試機(jī)刷了一個老系統(tǒng)鏡像手頭只有 frida-server 12.8 的包結(jié)果 objection 怎么都連不上。排查半天才意識到Frida Python 包、frida-server、objection 三方之間必須保持一個大版本兼容否則會出現(xiàn)連接成功但命令超時報錯 device not foundRPC 調(diào)用失敗等一系列詭異現(xiàn)象。安裝順序建議如下先安裝指定版本的 Frida再安裝 objection最后在測試設(shè)備端 push 對應(yīng)版本的 frida-server。命令大致是這樣# 指定版本安裝 Frida避免裝到最新版造成和 frida-server 不匹配 pip install frida15.1.17 pip install objection安裝完成后用objection version查看當(dāng)前工具版本。接著把對應(yīng)版本的 frida-server 傳到設(shè)備的/data/local/tmp/目錄加執(zhí)行權(quán)限后手動啟動adb push frida-server /data/local/tmp/ adb shell chmod 755 /data/local/tmp/frida-server adb shell /data/local/tmp/frida-server 2.2 啟動方式和普通 Frida 腳本的差異objection 連接應(yīng)用有兩種常見方式一種是直接啟動一個新的應(yīng)用進(jìn)程另一種是附加到已經(jīng)運(yùn)行的進(jìn)程上。從命令上區(qū)分非常直觀# 啟動應(yīng)用并立即注入適合冷啟動分析 objection -g com.example.target explore # 附加到正在運(yùn)行的進(jìn)程適合動態(tài)調(diào)試場景 objection -p 12345 explore這里的explore是進(jìn)入交互式命令界面的關(guān)鍵字進(jìn)去之后你就面對一個類似終端的環(huán)境后續(xù)所有操作都在這套命令體系里完成。如果是 iOS 設(shè)備通常還需要加-N參數(shù)指定設(shè)備地址或者通過--host和--port指定遠(yuǎn)程連接參數(shù)。我建議新手在熟悉階段先不要急著連接真機(jī)而是在模擬器上演練。模擬器環(huán)境下 frida-server 的啟動相對簡單而且不用擔(dān)心設(shè)備變磚等命令都跑熟了再換到真機(jī)上做完整測試。注意-g參數(shù)后跟的是包名不是應(yīng)用名稱。很多人第一次運(yùn)行時把 App 的名字敲進(jìn)去了結(jié)果 tool 報錯找不到目標(biāo)進(jìn)程。3. 核心命令模塊從內(nèi)存搜索到方法鉤子進(jìn)入 explore 交互環(huán)境之后objection 能做的事情就非常多了。我按實(shí)際使用頻率把最核心的功能分成幾個模塊逐一說明。3.1 內(nèi)存搜索與字符串掃描拿到一個應(yīng)用我最先做的事永遠(yuǎn)是字符串掃描。應(yīng)用里的敏感信息往往直接以字符串形式存在內(nèi)存中比如加密密鑰、API Token、硬編碼的密碼等。memory search api_key這條命令會在整個進(jìn)程的堆內(nèi)存中搜索包含api_key的字符串。objection 返回的結(jié)果會帶上地址和對應(yīng)的內(nèi)存區(qū)域類型。搜索到目標(biāo)字符串之后可以用memory read來讀取對應(yīng)地址附近的內(nèi)容看看到底存了什么。內(nèi)存搜索模塊有一個細(xì)節(jié)經(jīng)常被忽略——默認(rèn)搜索的是當(dāng)前進(jìn)程的堆區(qū)也就是 Java 層和 Native 層動態(tài)分配的內(nèi)存。理論上你可以通過memory list modules先看進(jìn)程加載了哪些 so 庫再用memory search在特定模塊的內(nèi)存范圍內(nèi)搜索但這樣的操作對工具使用者來說要求比較高需要先搞清楚目標(biāo)數(shù)據(jù)可能存在于哪個區(qū)域。以我個人的實(shí)踐來看先全局搜一遍字符串再用結(jié)果反推位置比一開始就精確鎖定模塊更高效。3.2 類與方法的運(yùn)行時探測這是 objection 最常被使用的功能模塊也是它相對于手寫 Frida 腳本最大的優(yōu)勢。android hooking list classes這條命令會列出當(dāng)前進(jìn)程中所有已加載的類。如果你對包名有預(yù)期可以直接用搜索過濾android hooking list classes -s target.package.name拿到類名之后再列出該類下的所有方法android hooking list class_methods target.package.name.MainActivity這兩條命令的底層機(jī)制其實(shí)依賴于 Frida 對 Java 虛擬機(jī)運(yùn)行時ART的枚舉能力。objection 把 Frida 的枚舉 API 封裝成了一條條命令你不需要寫 JavaScript 代碼去遍歷只需要學(xué)會命令參數(shù)即可。這里有個比較關(guān)鍵的點(diǎn)list classes列出的類是當(dāng)前時刻已經(jīng)加載的類不是應(yīng)用 DEX 文件里的所有類。如果一個類還沒被創(chuàng)建實(shí)例或被反射調(diào)用它可能不在列表里。所以當(dāng)你發(fā)現(xiàn)某個目標(biāo)類搜不到時別急著懷疑命令寫錯了先在應(yīng)用里多點(diǎn)擊幾個功能讓更多代碼路徑被執(zhí)行然后再重新枚舉。3.3 動態(tài)調(diào)用與 hook 方法類和方法找出來之后下一步通常是 hook 它、觀察它的調(diào)用時機(jī)或者干脆直接調(diào)用它。android hooking watch class_method target.package.name.Utils.getKey --dump-args --dump-return這條命令會 hook 指定方法的進(jìn)入和返回打印參數(shù)和返回值。當(dāng)你覺得某個方法會在特定操作中被調(diào)用時這個方法非常直觀執(zhí)行一下目標(biāo)操作控制臺直接打印出入?yún)?、返回值和調(diào)用棧。還有android hooking set method系列命令用于替換方法實(shí)現(xiàn)。它的原理本質(zhì)上就是 Frida 的Java.perform配合overload和implementation屬性只是換成了命令行交互。android hooking set return_value true target.package.name.Utils.isRooted這條命令我?guī)缀趺看味紩玫接绕涫怯?root 檢測邏輯的應(yīng)用。它直接把方法的返回值改成 true 或者 false不需要寫任何 hook 代碼。提示如果要 hook 的方法存在多個重載版本objection 會提示你指定參數(shù)類型。你可以先用list class_methods查看方法簽名再按--class-loader參數(shù)配好重載類型。3.4 SSL Pinning 繞過移動應(yīng)用測試中被提到最多的需求應(yīng)該就是 SSL Pinning 繞過了。很多應(yīng)用使用了證書校驗(yàn)導(dǎo)致抓包工具看到的全是加密流量。objection 提供了一條相當(dāng)經(jīng)典的命令android sslpinning disable這條命令會嘗試?yán)@過應(yīng)用中常見的證書鎖定邏輯。它的實(shí)現(xiàn)原理是動態(tài)替換系統(tǒng)網(wǎng)絡(luò)棧中的驗(yàn)證邏輯比如把 OkHttp 的CertificatePinner、TrustManager 的驗(yàn)證方法等替換為不做校驗(yàn)的實(shí)現(xiàn)版本。但這里有兩點(diǎn)必須提醒第一sslpinning disable不是萬能的。它主要覆蓋常見的開源網(wǎng)絡(luò)庫和系統(tǒng)默認(rèn)的網(wǎng)絡(luò)棧如果應(yīng)用內(nèi)部實(shí)現(xiàn)了自定義的證書校驗(yàn)協(xié)議比如私有協(xié)議加密后做簽名校驗(yàn)這條命令就失效了還是得回到 Frida 腳本針對具體函數(shù)下手。第二如果應(yīng)用使用了 native 層的證書校驗(yàn)比如通過 JNI 調(diào)用 C/C 代碼做證書綁定Java 層的 hook 方式可能完全不起作用。遇到這種情況需要配合android hooking list activities等命令先定位到具體的網(wǎng)絡(luò)模塊再分析它的 native 校驗(yàn)邏輯。3.5 活動與服務(wù)組件操作在處理一些需要深鏈路跳轉(zhuǎn)的測試場景時直接調(diào)用 Activity 比手動操作 UI 高效得多。android activity start -c target.package.name.DeepLinkActivity這條命令可以啟動任意 Activity前提是它沒有被系統(tǒng)限制比如需要特殊權(quán)限才能啟動的組件。還有android activity hooking系列命令用于監(jiān)控 Activity 的生命周期回調(diào)。類似的還有android service系列和android broadcast系列可以枚舉和操作服務(wù)與廣播接收器。這些能力從底層看都是利用 Frida 對 Java 層組件的反射調(diào)用objection 只不過把常用的啟動、枚舉、監(jiān)控封裝成了命令行指令幫你省去了大量重復(fù)腳本編寫的時間。4. 運(yùn)行環(huán)境偽裝與安全檢測對抗4.1 root 檢測與模擬器檢測的繞過很多應(yīng)用會在啟動階段檢測運(yùn)行環(huán)境是否被 root 或者是否運(yùn)行在模擬器上一旦檢測到就直接退出或限制功能。做動態(tài)測試時這種檢測會極大阻礙分析進(jìn)度。objection 提供了環(huán)境偽裝相關(guān)的功能模塊核心命令是android root simulation這條命令的邏輯是對常見的 root 檢測點(diǎn)返回假數(shù)據(jù)讓應(yīng)用認(rèn)為當(dāng)前設(shè)備沒有 root。類似地android emulator detection相關(guān)命令用于模擬器檢測的繞過。但說實(shí)話這些統(tǒng)一條命令的實(shí)際效果取決于應(yīng)用時是怎么寫的檢測邏輯。如果只是檢查Build.TAGS字段或嘗試su文件的路徑這些模擬手段通常有效但如果應(yīng)用在 native 層自己做了文件枚舉或使用了一些比較偏門的檢測 API單純靠默認(rèn)的模擬命令是不夠的。我的建議是把 objection 自帶的偽裝命令當(dāng)成第一層防線跑完檢測看應(yīng)用反應(yīng)如果仍然被檢測到再用android hooking watch class_method去定位具體的檢測函數(shù)觀察調(diào)用時機(jī)然后針對性地做返回值替換。4.2 文件操作與二進(jìn)制導(dǎo)出運(yùn)行時測試經(jīng)常會遇到需要從應(yīng)用私有目錄中提取文件的情況。objection 提供了文件操作模塊file cd /data/data/target.package.name file cat shared_prefs/target.xml file download /data/data/target.package.name/files/test.db /tmp/test.db這三條命令分別對應(yīng)切換目錄、查看文件內(nèi)容、導(dǎo)出文件到本地。對測試最有用的是file download它能直接把應(yīng)用私有目錄下的數(shù)據(jù)庫、配置文件、緩存文件拉回 PC方便離線分析。這里有一個安全邊界的理解問題objection 之所以能讀取應(yīng)用私有目錄是因?yàn)?Frida 注入的進(jìn)程擁有應(yīng)用本身的 UID 權(quán)限。所以你能讀到的范圍和 App 自身擁有的權(quán)限一致不能越權(quán)訪問其他應(yīng)用的數(shù)據(jù)。這個概念想清楚就不會對著file cat /data/data/other.package/...的報錯一頭霧水了。4.3 敏感信息快速篩查排查 Logcat 日志和剪貼板數(shù)據(jù)是快速發(fā)現(xiàn)敏感泄露的手段之一。android heap search strings android keystore listheap search strings會遍歷 Java 堆上的字符串對象輸出所有可讀的字符串常量敏感信息很容易混在里面。比如某個應(yīng)用在內(nèi)存里拼接 Token 或銀行卡號這條命令往往能直接撈到。android keystore list則是查看應(yīng)用在 Android Keystore 里存儲的密鑰條目。它不能直接導(dǎo)出私鑰Android Keystore 在硬件級別保護(hù)私鑰但至少能確認(rèn)應(yīng)用使用了哪些別名和密鑰類型對于分析密鑰管理流程很有參考價值。5. 多包聯(lián)動與 Frida 腳本的協(xié)作模式5.1 在 objection 中加載自定義腳本objection 滿足不了 100% 的需求尤其是面對混淆嚴(yán)重的代碼或者私有協(xié)議時還是離不開自定義 Frida 腳本。好消息是 objection 支持加載額外的自定義腳本frida script load /tmp/my_custom_hook.js你可以在交互環(huán)境中動態(tài)加載一個寫好的 Frida 腳本它會在目標(biāo)進(jìn)程里立刻執(zhí)行后續(xù)輸出會與 objection 的命令行輸出混在一起方便你在同一會話里同時使用兩種工具。以我的經(jīng)驗(yàn)推薦的協(xié)作模式是先用 objection 的自動化命令做快速偵察明確目標(biāo)方法和調(diào)用邏輯再用 Frida 腳本做精細(xì)化 hook處理混淆函數(shù)、native 層邏輯等 objection 覆蓋不到的場景。兩者的關(guān)系是互補(bǔ)的不是替代的。5.2 多應(yīng)用同時調(diào)試日常工作中偶爾會遇到需要同時分析多個協(xié)作進(jìn)程的情況。objection 支持-p參數(shù)附加多個進(jìn)程但實(shí)際操作時要注意每個 objection 會話占用一個終端窗口否則輸出會互相干擾。多開時建議給每個會話打標(biāo)簽區(qū)分objection -p 10001 explore objection -p 10002 explore兩條命令分別開兩個終端即可。從底層來看每個會話都是獨(dú)立的 Frida RPC 通道互相之間沒有數(shù)據(jù)共享但如果你能理解應(yīng)用之間通過 Binder 或 Socket 的交互邏輯多開會話交叉觀察就能拼出完整的調(diào)用鏈條。6. 一個典型的實(shí)操排查過程從定位方法到繞過檢測為了把這些命令串起來我拿一個模擬的真實(shí)場景走一遍流程幫大家建立完整的操作手感。假設(shè)目標(biāo)應(yīng)用包名是com.example.demo它在啟動時檢測 root檢測到就退出。第一步附加或啟動進(jìn)程進(jìn)入 explore 環(huán)境objection -g com.example.demo explore第二步觀察應(yīng)用行為確認(rèn)它在啟動時直接退出了。此時我們不確定它用的是哪個檢測函數(shù)先枚舉一下與 root 相關(guān)的類android hooking list classes -s root返回結(jié)果里有幾個可疑的類包括一個名為com.example.demo.security.SecurityChecker的類。緊接著列出這個類的方法android hooking list class_methods com.example.demo.security.SecurityChecker可以看到它有一個isRooted()方法很好那就不用做粗粒度的全盤 hook 了直接針對這個方法的返回值下手android hooking set return_value false com.example.demo.security.SecurityChecker.isRooted重新觸發(fā)應(yīng)用的檢測邏輯觀察是否還能正常進(jìn)入主界面。如果仍然被攔截說明還有其他檢測點(diǎn)繼續(xù)用list classes -s root擴(kuò)大搜索范圍或者用android hooking watch class_method監(jiān)控整個包的類加載動向看它在啟動階段到底執(zhí)行了哪些方法。第三步解決完 root 檢測后用同樣的思路檢查它有沒有做 SSL Pinningandroid sslpinning disable然后開 Charles 或 Burp 抓包驗(yàn)證 HTTPS 流量是否可以正常解密查看。這個流程不算復(fù)雜但它是真實(shí)測試中最常見的一條主線。掌握這條思路之后很多問題都能用類似方法逐步分解掉而不是一頭扎進(jìn)源碼里從零開始逆向。7. 實(shí)戰(zhàn)中容易栽的六個具體問題7.1 注入后應(yīng)用崩潰多半是進(jìn)程啟動方式不對使用-g參數(shù)冷啟動注入時如果應(yīng)用自身具有反調(diào)試或 anti-Frida 機(jī)制注入過程可能被檢測到導(dǎo)致啟動即崩潰。優(yōu)先嘗試附加方式-p也就是先手動打開應(yīng)用再附加到進(jìn)程。另外檢查應(yīng)用是否有越獄檢測、Frida 檢測相關(guān)的代碼路徑如有需要先用環(huán)境偽裝命令處理再注入。7.2 命令執(zhí)行超時未必是命令錯了超時最常見的原因是進(jìn)程內(nèi)大量類和對象導(dǎo)致枚舉緩慢尤其是在復(fù)雜的商業(yè) App 中l(wèi)ist classes可能要好幾分鐘。這不算 bug耐心等即可。如果長時間無響應(yīng)還可以用android hooking list activities觀察是否只是某個功能模塊在阻塞主線程。7.3 抓包失敗確定是 Pinning 還是在 native 層加密sslpinning disable之后抓包還是亂碼先確認(rèn)流量是否走了 SO 層的自定義加密。最直接的驗(yàn)證方式是抓取明文特征比如搜索 HTTP 頭、JSON key 等關(guān)鍵字如果這些關(guān)鍵字也存在且加密說明應(yīng)用做了自定義加密封裝和 Pinning 就沒有關(guān)系了。7.4 文件傳輸失敗檢查寫權(quán)限file download導(dǎo)出文件時目標(biāo)路徑必須擁有當(dāng)前用戶寫權(quán)限。如果 PC 端路徑選了一個沒有權(quán)限創(chuàng)建的目錄命令會直接失敗。更常見的失敗原因是應(yīng)用目標(biāo)文件本身被占用或權(quán)限受限比如某些數(shù)據(jù)庫文件只能由應(yīng)用自身讀寫這時候先在應(yīng)用內(nèi)觸發(fā)一次關(guān)閉數(shù)據(jù)庫連接再導(dǎo)出。7.5 自動化命令覆蓋不了的高混淆代碼有些應(yīng)用做了字符串混淆和類名混淆list class_methods看到的方法名可能完全沒有意義。這種情況下objection 的自動化命令只是輔助定位的作用真正要突破的是通過 Frida 腳本精確指定內(nèi)存地址來 hook 函數(shù)或通過android hooking watch class_method觀察調(diào)用時附帶的參數(shù)特征反推邏輯。7.6 模擬器和真機(jī)的行為差異模擬器上的測試結(jié)果不能代表真機(jī)這個坑我反復(fù)踩過。很多應(yīng)用對模擬器的檢測比對 root 的檢測更嚴(yán)格特別是在系統(tǒng)屬性、硬件信息、傳感器數(shù)據(jù)等維度。如果你想在模擬器上跑 objection遇到環(huán)境檢測嚴(yán)格的應(yīng)用時優(yōu)先排查模擬器指紋其次才是 root 狀態(tài)。8. 進(jìn)階使用思路objection 在自動化巡檢里的位置objection 除了在單次測試中發(fā)揮作用也可以嵌入到自動化的安全巡檢流程里。比如在 CI 環(huán)境里準(zhǔn)備一個測試機(jī)每次構(gòu)建出新包后自動安裝、自動進(jìn)入 explore、自動執(zhí)行一組固定的檢查命令枚舉敏感字符串、列出允許導(dǎo)出的組件、檢測風(fēng)險方法等從而形成輕量級的基線巡檢。這個思路的核心不是讓它替代專業(yè)掃描器而是給測試團(tuán)隊提供一個可以快速執(zhí)行的運(yùn)行時檢查通道。巡檢結(jié)果輸出為文本日志再配合解析腳本可以自動把風(fēng)險項(xiàng)匯總到工單里。我實(shí)際落地過一套這樣的流程檢查項(xiàng)大約 20 條執(zhí)行時間約三分鐘對于發(fā)布前的快速反饋來說性價比很高。如果后續(xù)有興趣我還可以寫一篇專門講 objection 配合 CI 做自動化巡檢的文章把命令封裝、結(jié)果解析、風(fēng)險閾值判定這些細(xì)節(jié)都展開講清楚。