Hook是一種在特定事件或操作發生時插入自定義代碼的編程技術。在前端開發中,例如Vue和Angular的生命週期鈎子,體現了Hook的機制,允許開發者在組件的不同階段執行代碼,提升代碼的模塊化和可重用性。
Android Hook與此類似,允許開發者在Android應用程序運行時修改或擴展現有功能。通過攔截方法調用、修改參數或返回值,Hook可用於調試、測試和逆向工程,例如監控應用程序行為、捕獲API調用,或在不修改源代碼的情況下添加新功能,以及替換so模塊以實現獲取驗證碼或廣告攔截等功能。
在快寫完這篇博客的時候,我才發現我想要實現的功能不需要Android Hook即可實現…不過不耽誤我對這方面做一些瞭解,所以接着寫完了。
補充知識
前幾天刷機的時候基本算是悶頭跟着教程走,中間還險些變磚。雖然過程中我邊操作邊瞭解,但也是比較粗糙,這裏再補充點知識並做做記錄。
Magisk工具
現在的刷機教程一般都會引導讀者在root後安裝Magisk,乍看上去是個工具包,集合了很多功能,那這個“面具”具體是什麼,有什麼用呢?
Magisk是一款流行的Android系統級root工具,由XDA開發者topjohnwu創建和維護。與傳統的root方法不同,Magisk採用了一種“無系統”的方式,這意味着它不會直接修改系統分區中的文件,而是通過修改boot鏡像來實現root權限。這種方法的好處在於它能夠更好地兼容系統更新,避免root權限導致的系統不穩定或應用崩潰。Magisk的“無系統”特性也使得它能夠更好地隱藏root權限,從而繞過一些應用的root檢測機制,例如Google的SafetyNet。
Magisk的核心功能是提供root權限,允許用户訪問和修改系統文件,以及安裝各種Magisk模塊來擴展系統功能。這些模塊可以實現各種各樣的功能,例如自定義系統UI、增強系統性能、攔截廣告、修改系統設置等等。Magisk還提供了一個Magisk Manager應用,方便用户管理root權限和Magisk模塊。Magisk的持續更新和強大的社區支持,使其成為Android用户中非常受歡迎的root工具。 Magisk的出現,很大程度上簡化了root流程,並提升了root後的系統穩定性和安全性。
Xposed框架
Xposed框架是一個Android模塊化框架,允許開發者通過模塊修改系統和應用行為,無需修改APK文件。它由rovo89開發,最初用於調試和測試,通過替換系統核心文件實現功能,並允許開發者使用Java代碼hook方法。Xposed功能強大,可修改系統UI、性能、廣告等,但安裝複雜,存在風險,需要root權限。
Magisk和Xposed都是Android root工具,但Magisk採用“無系統”方式,更安全穩定,兼容性更好;Xposed功能更強大,但修改系統文件,風險更高。一些用户會結合使用兩者,先用Magisk獲得root權限,再安裝Xposed,以兼顧安全性和功能性,但需要注意模塊兼容性。
與之相對的還有CydiaSubstrate框架,不過xposed是開源項目,所以培養了更加龐大的開發者社區,網上基於Xposed框架的模塊插件非常多。
Xposed Hook實現原理及缺陷
Xposed雖好,但也存在缺陷,比較致命的就是兼容性差。
Xposed框架的運作依賴於Android系統的Zygote進程和app_process,但其機制並非簡單的替換。準確地説,Xposed通過替換系統默認的/system/bin/app_process為一個修改過的版本來實現其功能,這個修改後的app_process包含了Xposed框架的核心代碼,以及方法Hook機制。
系統啓動時,init.rc腳本啓動Zygote進程,使用的是這個被Xposed替換後的/system/bin/app_process。因此,Zygote進程本身就加載了Xposed框架。所有應用進程都是Zygote的子進程,因此它們繼承了Xposed框架的代碼。
開發者編寫的Xposed模塊定義了Hook規則,這些模塊會被Xposed框架加載,並根據規則執行Hook操作。Xposed框架會管理這些模塊,並確保它們能夠正確地與Zygote進程和應用進程交互。Xposed的Hook操作並非在應用運行時動態進行,而是在Zygote進程啓動的早期階段就完成了,這使得Hook操作能夠影響所有從Zygote fork出的應用進程。這與在應用運行時動態Hook方法相比,效率更高,也更穩定。
聽起來很不錯,但問題也就出在這裏。Android系統的更新迭代頻繁,且不説Android本身的各種大小版本,各家手機廠商也會在原本的基礎上進行魔改,這就導致Android系統版本繁多。每次系統更新都可能改變app_process的結構或行為。Xposed框架需要針對每次系統更新進行適配,才能保證其正常工作。如果Xposed框架沒有及時更新以適應新的app_process,那麼它就可能無法正常工作,甚至導致系統崩潰或bootloop(無限重啓)。知名項目可以依賴龐大的貢獻者羣體及時更新,小項目跟不上版本就會慢慢廢棄。
EdXposed框架
GitHub - ElderDrivers/EdXposed: Elder driver Xposed Framework.
EdXposed是Xposed框架的改進版,主要解決了Xposed在高版本Android系統上的兼容性問題。Xposed通過替換核心文件app_process工作,這種方式在新的Android系統中容易衝突,導致不穩定甚至崩潰。而EdXposed則基於Riru項目,通過注入代碼到zygote進程,避免了直接修改系統文件,從而提升了兼容性和穩定性。 兩者功能相似,都允許安裝模塊擴展系統功能,但EdXposed在兼容性和穩定性方面顯著優於Xposed。
LSPosed框架
LSPosed是EdXposed的進一步發展,旨在提供更高的兼容性和用户體驗。與EdXposed類似,LSPosed也基於Riru項目,通過注入代碼到Zygote進程來實現功能擴展,避免了直接修改系統文件的問題,從而提升了穩定性。
LSPosed在模塊管理方面進行了優化,提供了更直觀的用户界面,使得用户可以更方便地安裝和管理模塊。此外,LSPosed支持無root模式,降低了使用門檻,這個特點降低了用户的使用門檻,可以拉更多人入坑。
LSPosed仍然需要針對不同Android版本進行適配。
Zygote進程
上文中反覆提到Zygote進程,這個進程是什麼呢?
Zygote進程是Android系統中至關重要的一個進程,它是所有Android應用程序的父進程。它在系統啓動時被初始化,預加載所有應用可能用到的核心類和資源到內存中,從而在需要啓動新應用時,通過fork自身快速創建新進程,繼承Zygote進程的內存空間,實現高效的應用啓動。這利用了Linux內核的寫時複製技術,提升效率並節省內存。
Zygote進程還會顯式啓動System Server進程,後者負責初始化和管理各種系統服務。 由於Android系統版本差異和廠商定製,app_process的結構和行為可能變化,這影響了需要修改app_process的框架(如Xposed)的穩定性。EdXposed通過Riru項目注入zygote進程,避免直接修改app_process,從而提升了兼容性和穩定性。
Riru
先貼倉庫鏈接:GitHub - RikkaApps/Riru: Inject into zygote process,不過兩年前就歸檔了。
Riru是一個Magisk模塊,它允許將代碼注入zygote進程,而不會直接修改系統文件。這使得它在不同Android版本和廠商定製系統上具有更好的兼容性。
Riru與Android的關係在於它利用Android系統的zygote進程機制運行代碼,通過系統接口或漏洞注入代碼,影響zygote進程及其子進程(所有應用)的行為。Riru和Xposed都可修改應用行為,但實現方式不同:Xposed替換app_process,而Riru注入zygote進程,後者更安全、兼容性更好。
前面説到的EdXposed框架則依賴Riru實現功能,利用Riru提供的機制注入zygote進程,避免直接修改系統文件,從而提高穩定性和兼容性。EdXposed可視為基於Riru構建的Xposed框架替代方案,Riru提供底層機制,EdXposed在其上構建更易用的框架。
ok,先介紹到這裏,更進一步的實現原理可以看這篇文檔,想必會收穫頗豐:riru-docs/riru模塊解析.md at main · AlienwareHe/riru-docs · GitHub。
安裝LSPosed
LSPosed倉庫release地址:Releases · LSPosed/LSPosed,安裝LSPosed-Zygisk模塊即可,下載zip推送到設備,然後在Magisk中作為模塊安裝。安裝後重啓。
我前面已經配了基於Zygisk的Shamiko模塊,如果切換為Riru會與Zygisk衝突,所以這裏直接選擇LSPosed-Zygisk模塊。
重啓後,通知欄會彈出“LsPosed已加載”,點擊即可進入管理界面(如果沒顯示,可以通過撥號鍵輸入 *#*#5776733#*#* 進入LSPosed)。
進入LSPosed App,設置 – 創建快捷方式 – 關閉 狀態通知 – 顯示已激活,代表已成功刷入LSPosed框架。
編寫Xposed模塊
- 編寫一個簡單的xposed模塊 - yanq的個人博客
- Xposed 框架的使用 - l0neman 的博客
- 從零開始編寫Xposed模塊
剛開始找到的教程很老,各種奇奇怪怪問題一堆,後面邊翻文檔邊修,也算是磕磕絆絆寫出來第一個cposed模塊,實現的功能很簡單,就是監測應用的啓動,檢測到應用啓動就打印一條日誌。
晚上的很多類似的教程,但有一點不好,就是不解釋為什麼要怎麼寫,我比較呆,寫完之後會搜搜為什麼要這麼做,這裏也一併附上,所以後面的內容會有點囉嗦。
AndroidStudio版本為2024.2,設備版本為Oneplus Ace2(Android14),項目選擇的是Kotlin DSL
開發xposed模塊,本質上和開發android模塊是一樣的,區別在於:
- 讓LSPosed知道我們安裝的這個程序是個xposed模塊;
- 模塊裏要包含有xposed的API的jar包,以實現下一步的hook操作;
- 這個模塊裏面要有對目標程序進行hook操作的方法;
- 要讓手機上的xposed框架知道,我們編寫的xposed模塊中,哪一個方法是實現hook操作的,也就是hook類的入口。
Android Studio新建項目
Android Studio新建空項目,操作流程見我的上一篇文章:淺試Android開發,不同的是開發app可以選擇empty activity,開發xposed模塊推薦選擇no activity(至少這個例子是這樣的)。
選擇 "Empty Activity" 模板時,Android Studio 會為你創建一個包含基本活動(Activity)和佈局文件的項目,而對下面這個xposed模塊來説,這些都是不必要的,所以此處選擇no activity即可,乾淨的項目結構也方便操作。
settings.gradle添加xposed框架依賴
項目根目錄下的settings.gradle中添加一行
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
maven { url 'https://api.xposed.info/' } // 添加這一行即可
}
}
Xposed模塊需要依賴Xposed框架的庫,而這些庫不在默認的Maven倉庫,通過添加 Xposed 的Maven倉庫,確保Gradle能夠找到並下載所需的Xposed相關依賴。
模塊級build.gradle添加Xposed Framework API
將Xposed框架所需的API庫添加到項目中,以便你的模塊能夠使用Xposed提供的功能,例如hook。在app/build.gradle文件的dependencies段中添加以下代碼:
dependencies {
compileOnly("de.robv.android.xposed:api:82")
compileOnly("de.robv.android.xposed:api:82:sources")
}
其中complieOnly表示只在編譯時使用這些庫,最終生成的APK文件中不會包含這些庫,從而減小APK大小;de.robv.android.xposed:api:82是Xposed API庫,82代表版本號,你需要根據你使用的EdXposed版本選擇合適的版本號,如果版本號不匹配,模塊可能無法正常工作;de.robv.android.xposed:api:82:sources包含Xposed API庫的源代碼,方便調試和理解API的實現細節。
這裏可以不必理會版本號,直接cv即可。
arrays.xml添加模塊作用域
路徑為app/src/main/res/values/,在該目錄下創建arrays.xml文件,內容如下:
<resources>
<string-array name="xposedscope" >
<!-- 這裏填寫模塊的作用域應用的包名,可以填多個。 -->
<item>com.xposed.demo</item>
</string-array>
</resources>
模塊作用域的主要目的是告訴Xposed框架,哪些應用程序是該模塊可以影響的目標。通過在arrays.xml中指定應用的包名,Xposed框架能夠在這些應用啓動時加載你的模塊,從而實現對這些應用的hook操作。
AndroidManifest.xml中添加meta信息
目的是告訴LSPosed框架,這個應用程序是一個Xposed模塊,並提供模塊的描述信息和最低API版本要求。在AndroidManifest.xml文件的<application>標籤內添加以下元數據:
<!-- 是否是xposed模塊 -->
<meta-data android:name="xposedmodule" android:value="true" />
<!-- 模塊描述 -->
<meta-data android:name="xposeddescription" android:value="這是一個lsxposed demo" />
<!-- 最低xposed版本號 -->
<meta-data android:name="xposedminversion" android:value="82" />
<!-- 模塊作用域 -->
<meta-data android:name="xposedscope" android:resource="@array/xposedscope"/>
xposedmodule="true"聲明這是一個Xposed模塊。xposeddescription模塊的描述,會在Xposed Installer中顯示。xposedminversion模塊所需的最低Xposed API版本。 這個版本號應該與你設備上安裝的EdXposed版本兼容,或者低於該版本。- 作用域在上一小標題解釋過了。
編寫Hook代碼
在MainActivity同級目錄創建一個名為HookDemo.kt的類,實現IXposedHookLoadPackage接口:
package com.xposed.demo.hookdemo;
import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.callbacks.XC_LoadPackage.LoadPackageParam;
public class MainHook implements IXposedHookLoadPackage {
public void handleLoadPackage(LoadPackageParam lpparam) throws Throwable {
XposedBridge.log("Loaded app: " + lpparam.packageName);
}
}
默認讀者有kotlin基礎,薄弱的話可以看看官方文檔:基本語法 · Kotlin 官方文檔 中文版,這裏主要介紹引用的模塊,首次接觸會比較陌生。
IXposedHookLoadPackage:一個接口,允許開發者實現應用加載時的hook邏輯。通過實現這個接口,模塊可以在特定應用被加載時執行自定義代碼。XposedBridge:Xposed框架的核心類,提供了多種方法來進行hook和日誌記錄等操作。XC_LoadPackage.LoadPackageParam:一個類,包含了關於加載的應用程序的信息,例如包名、類加載器等。
然後是MainHook類的介紹,其實有上文的包介紹就差不多清楚了:
handleLoadPackage是接口IXposedHookLoadPackage中定義的方法。當一個應用被加載時,Xposed框架會調用這個方法。LoadPackageParam lpparam參數包含了被加載應用的相關信息。XposedBridge.log(...)方法用於記錄日誌,這裏記錄了加載的應用的包名。通過查看Xposed的日誌,開發者可以看到哪些應用被加載了。
指定Hook入口
告訴Xposed框架,你的hook代碼的入口點在哪裏,以便框架能夠在合適的時機調用你的hook代碼。在app/src/main目錄下創建一個名為assets的文件夾,並在其中創建一個名為xposed_init的文件(沒有擴展名)。 在這個文件中,寫入你的hook類的全限定名:
com.xposed.demo.hookdemo.MainHook
安裝並測試
以上內容全部搞定之後就可以build項目為apk安裝包了,打包出來後可以先使用debug版本的安裝包,可以使用adb install 絕對路徑直接安裝到設備上
adb install C:\Users\{YOUR_USERNAME}\AndroidStudioProjects\HookDemo\app\build\outputs\apk\debug\app-debug.apk
安裝好之後在LSPosed中啓用該HookDemo模塊,勾選應用的app,然後重啓手機應用個更改,然後使用數據線連接設備和電腦後,在電腦命令行中監聽帶有特徵值的日誌信息(其實就是拼接的特殊字符串),我們的Hook代碼中是Loaded app:
adb logcat | Select-String "Loaded app"
開始監聽後在移動設備上啓動對應app,即可觀察到日誌信息。
我本來是想做個移動端智能助手,讀系統消息以及檢測部分應用的使用情況,餵給預設好的AI再給出一些反饋什麼的,結果寫xposed模塊的時候發現普通android app就可以實現讀取系統通知欄裏通知的功能,hook反而沒必要了😭。
不過也好,有個契機瞭解了點Android Hook的知識。如果智能助手的項目能接着往下推的話,Hook相關知識也不是完全用不上,比如當我點開某些應用時可以給出反饋,也不錯~