先看結論與判斷條件

  • 先記錄每個 SO 的直接和傳遞依賴、裝載入口、初始化時機、目標 API 下限與呼叫方。
  • 靜態 JNI 名稱發現和 RegisterNatives 動態註冊的約束不同,名稱處理前必須確認真實繫結方式。
  • arm64-v8a、armeabi-v7a 與 x86_64 是不同產物和相容矩陣,一個架構透過不能外推到另一個架構。
  • 保護強度驗收和執行穩定性驗收必須繫結同一最終 APK、同一簽名與同一 SO 集合。

先畫出 ELF 依賴圖和真實裝載時間線

一個 Android 應用可能由主庫、業務庫、演算法庫、第三方 SDK 和系統庫共同組成。Java 或 Kotlin 程式碼可以直接呼叫 System.loadLibrary,主庫也可以繼續透過 ELF 依賴或執行時裝載其他庫。最終崩潰點可能出現在被保護庫之外,因為符號解析和初始化順序由整條依賴鏈共同決定。

依賴圖應分別記錄每個 ABI 的庫檔案、DT_NEEDED 關係、最低系統 API、裝載入口、初始化函式、匯出介面和呼叫方。還要標記庫來自自研、開源依賴還是閉源 SDK,因為能夠修改和迴歸的責任邊界不同。

Android NDK 文件指出,原生符號通常在庫裝載時解析。程式碼引用了目標系統不存在的 API,即使執行時分支看似不會執行,也可能在 dlopen 階段直接失敗。因此 NDK API 下限和裝置系統版本必須進入依賴圖。

Native 依賴圖的最小欄位
欄位要記錄什麼為什麼影響加固驗證方式
庫與來源主庫、傳遞依賴、第三方和系統庫決定可修改範圍和迴歸責任按最終 APK 的每個 ABI 解包核對
裝載入口靜態依賴、System.loadLibrary 或執行時 dlopen決定最早失敗階段和日誌位置記錄啟動時間線與裝載結果
API 下限構建 APP_PLATFORM 與使用的系統符號高於裝置 API 可能在裝載時缺符號目標系統真機啟動和符號核對
初始化順序JNI_OnLoad、建構函式與業務初始化程式碼佈局或時序變化可能放大隱式依賴比較未保護與保護候選時間線
異常與執行緒C++ 異常、執行緒歸屬、JNIEnv 使用跨庫和跨執行緒錯誤常直接崩潰CheckJNI、符號化棧和業務迴歸

靜態註冊和動態註冊決定符號處理邊界

JNI 可以透過名稱約定發現 Native 方法,也可以在初始化階段使用 RegisterNatives 把 Java 方法與函式地址建立對映。靜態註冊依賴可預測的匯出名稱;動態註冊通常只需要暴露少量初始化入口,但依賴類查詢、方法簽名、註冊時機和載入執行緒。

如果加固或符號處理改變了靜態註冊名稱,系統可能找不到 Native 實現。如果動態登錄檔中的方法簽名、類名保留規則或初始化順序變化,也可能在首次呼叫前就失敗。僅檢查匯出符號數量,不能證明 JNI 繫結仍然正確。

Android 官方 JNI 指南還提醒 JNIEnv 具有執行緒約束,pending exception、錯誤引用和跨執行緒使用都可能導致崩潰。保護後的迴歸應覆蓋真實執行緒和異常路徑,而不是隻呼叫一個無引數的演示方法。

兩種 JNI 註冊方式的檢查重點
註冊方式依賴加固敏感點最小驗證
靜態名稱發現Java 類名、方法名、簽名和匯出符號重新命名、隱藏符號和簽名不匹配逐個關鍵入口呼叫並檢查連結錯誤
RegisterNatives類查詢、方法簽名、登錄檔和初始化時機類名處理、註冊順序、函式地址變化確認註冊完成並執行真實輸入輸出
第三方封裝SDK 內部規則和閉源實現自校驗、隱式匯出和版本差異按供應商支援矩陣和真實場景驗證
  • 列出關鍵 Java 到 Native 對映
  • 確認靜態或動態註冊方式
  • 保留必要類名和簽名規則
  • 驗證異常、執行緒和引用生命週期

ABI 必須按獨立釋出產物驗收

ABI 不只是目錄標籤。Android NDK 官方說明 ABI 約定了指令集、位元組序、呼叫約定、棧和暫存器使用、可執行檔案格式及 C++ 名稱改編。arm64-v8a 與 armeabi-v7a 的機器碼和依賴不同,x86_64 模擬器透過更不能代表 ARM 真機。

每個 ABI 都要確認所有直接和傳遞依賴是否存在,庫的 API 下限是否相容目標系統,打包工具是否誤刪某個架構的檔案。只有主庫包含 arm64-v8a,而第三方依賴缺少對應產物時,應用仍會在裝載階段失敗。

如果產品計劃只支援部分 ABI,應在釋出記錄、應用商店配置和測試範圍中明確寫出。未構建、未安裝或未執行關鍵 JNI 路徑的架構必須標記未覆蓋,不能用編譯透過替代執行證據。

ABI 驗收矩陣示例
檢查項arm64-v8aarmeabi-v7ax86_64判定規則
所有依賴存在逐庫核對按釋出範圍核對僅在需要時核對任一必需依賴缺失即阻斷
最低系統可裝載目標 API 真機目標 API 真機模擬器或裝置不得只在最新系統驗證
JNI 關鍵路徑真實輸入輸出真實輸入輸出不得替代 ARM 結果每個釋出 ABI 獨立記錄
崩潰可歸因保留匹配符號保留匹配符號保留匹配符號符號必須對應同一候選包

保護動作會觸碰哪些 Native 執行條件

符號隱藏減少靜態線索,字串處理降低明文暴露,控制流或函式虛擬化改變選中程式碼的執行形式。它們對匯出符號、函式邊界、異常展開、間接呼叫、對齊和效能的影響不同。不能用處理後符號更少這一項代表完整 Native 保護,也不能用庫仍能被讀取直接否定全部保護效果。

啟動初始化、訊號處理、C++ 異常、回撥、執行緒區域性狀態、反射式符號查詢和第三方 SDK 自校驗屬於高敏感區域。選擇範圍時,應優先從輸入輸出明確、呼叫頻率可測、失敗能隔離的業務函式開始,避免第一次 PoC 就處理整條初始化鏈。

保護配置應對每個函式或函式組記錄選擇原因、呼叫階段、ABI、依賴、效能預算和回滾組。下面只展示公開安全的驗證清單形態,不包含內部符號或產品配置語法。

Native 保護組的公開安全記錄形態
native_group: protocol-core-v1
abis: [arm64-v8a, armeabi-v7a]
load_phase: post-authentication
jni_registration: dynamic
execution:
  frequency: bounded
  main_thread: false
dependencies:
  - business-runtime
  - system-crypto
acceptance:
  - dependency-resolution
  - jni-registration
  - exception-path
  - output-parity
  - latency-budget
rollback: native-group-v0

用最早差異定位 SO 加固後的閃退

排查時先固定未保護基線和保護候選包的檔案身份、簽名、裝置、系統、安裝方式與業務輸入,然後按時間線尋找最早差異:程序建立、Application、庫裝載、JNI_OnLoad、註冊完成、首次 Native 呼叫、關鍵業務返回。最後一條崩潰日誌可能只是連鎖反應。

UnsatisfiedLinkError 通常指向庫缺失、ABI 不匹配、依賴或符號解析問題;註冊錯誤可能表現為找不到 Native 方法;錯誤的 JNIEnv、引用或 pending exception 可能在業務呼叫中崩潰。應保留與候選包匹配的 Native 符號用於內部歸因,但公開報告不暴露真實符號和地址。

CheckJNI 可以幫助發現部分 JNI 誤用,但它是診斷工具,不等於生產執行狀態,也不能覆蓋所有 Native 記憶體和併發問題。修復後必須回到目標釋出配置重跑關鍵路徑。

常見現象與下一步證據
最早現象優先檢查需要的證據不要先做什麼
dlopen 失敗ABI、DT_NEEDED、API 下限和符號同候選包的庫清單與裝載錯誤反覆切換無關保護開關
找不到 Native 方法註冊方式、類名、方法簽名和時機登錄檔、保留規則和呼叫入口只看匯出符號數量
首次呼叫崩潰引數、引用、執行緒、異常和函式處理符號化棧、輸入和基線對照用另一版本符號檔案解釋
部分裝置失敗系統 API、廠商差異、ABI 和第三方庫裝置系統矩陣與最早差異用模擬器透過替代真機結論

釋出結論要同時覆蓋強度、相容和可維護性

靜態強度觀察可以記錄符號、字串、程式碼結構和關鍵入口暴露面的變化;執行驗收則驗證裝載、JNI、關鍵業務、異常和目標 ABI。兩類證據互相補充,不能用其中一類替代另一類。

最終候選包還要完成安裝升級、簽名、渠道、啟動、崩潰監控、Native 符號歸檔和回滾演練。符號歸檔必須能用檔案身份找到匹配版本,否則線上 Native 崩潰無法正確符號化。

本文提供的是工程檢查框架,不是某個 SO 或某個保護配置的測試結論。沒有目標包、目標 ABI 和真實呼叫路徑時,只能判斷準備工作是否完整,不能承諾相容性或效能。

  • 每個釋出 ABI 都有執行證據
  • 關鍵 JNI 入口覆蓋正常和異常路徑
  • Native 符號與最終候選身份匹配
  • 裝載、業務和回滾結果可複核
  • 未覆蓋的裝置與系統被明確限制

事實依據與適用邊界

以下內容區分官方事實、本文工程判斷和不能外推的範圍,避免把設計建議寫成未經驗證的產品結論。

本文判斷事實或工程依據適用限制
ABI 必須作為獨立產物維度驗收。Android NDK 官方定義 ABI 覆蓋指令集、呼叫約定、暫存器、棧、ELF 格式和名稱改編。文件定義不證明應用已經包含完整依賴或在目標裝置執行成功。
高於裝置系統的 Native API 引用可能在裝載時失敗。NDK 常見問題文件說明符號通常在庫裝載時解析,引用不存在的 API 不能依靠執行時分支規避。具體失敗仍需結合構建引數、依賴和裝置日誌確認。
JNI 錯誤需要執行緒、引用和異常級別的驗證。Android JNI 指南列出錯誤 JNIEnv、引用、pending exception 和方法註冊等可導致崩潰的問題。CheckJNI 只輔助發現一部分問題,不能替代完整業務和記憶體安全測試。
單一 x86_64 模擬器結果不能外推 ARM 真機。不同 ABI 使用不同指令集和呼叫約定,最終庫和依賴組合也可能不同。如果產品明確不釋出某個 ABI,可以將其標記不適用而不是強行測試。
保護強度與相容性應在同一候選包上閉合。重新構建或替換 SO 會改變程式碼、符號、依賴和可能的執行行為。這是產物治理原則,不代表任何特定保護級別已經透過。

工程常見問題

只有一個主 SO,需要畫依賴圖嗎?

需要。主 SO 仍可能依賴系統庫、C++ 執行時或第三方庫,並透過 JNI 與 Java 層互動。依賴圖用於確認最早裝載點和責任邊界。

arm64-v8a 透過後還要測 armeabi-v7a 嗎?

如果釋出包包含並支援 armeabi-v7a,就需要獨立驗證。兩個 ABI 的指令、呼叫約定和依賴產物不同,不能互相替代。

隱藏所有匯出符號是否更安全?

不一定。必要的靜態 JNI 入口、第三方呼叫和系統約定可能依賴匯出符號。應先確認註冊和呼叫方式,再最小化暴露。

CheckJNI 沒有報錯是否可以釋出?

不能單獨據此釋出。還要驗證實際目標配置、業務輸入、異常路徑、ABI、系統範圍、安裝升級和回滾。

SO 加固後崩潰應該先關閉全部保護嗎?

先固定候選身份並找最早差異。可以按保護組二分或回退,但每次只改變一個可記錄變數,避免得到無法歸因的新產物。

想用自己的 App 驗證?

提交候選包、目標系統和關鍵業務路徑,申請御盾 PoC 與相容性評估。

繼續閱讀: SO 加固與 Native 相容性檢查清單