編輯:關於Android編程
Xposed,大名鼎鼎得Xposed,是Android平台上最負盛名的一個框架。在這個框架下,我們可以加載很多插件App,這些插件App可以直接或間接操縱系統層面的東西,比如操縱一些本來只對系統廠商才open的功能(實際上是因為Android系統很多API是不公開的,而第三方APP又沒有權限)。有了Xposed後,理論上我們的插件APP可以hook到系統任意一個Java進程(zygote,systemserver,systemui好不啦!)。
功能太強大,自然也有缺點。Xposed不僅僅是一個插件加載功能,而是它從根上Hook了Android Java虛擬機,所以它需要root,所以每次為它啟用新插件APP都需要重新啟動。而如果僅是一個插件加載模塊的話,當前有很多開源的插件加載模塊,就沒這麼復雜了。
Anyway,Xposed強大,我們可以學習其中的精髓,並且可以把它的思想和技術用到自己的插件加載模塊裡。這就是我們要學習Xposed的意義。
Xposed支持32位和64位的dalvik以及ART,同時支持selinux。我仔細看了下,如果拓展把這些東西都講的話,一個是很枯燥,另外一個是背離了我們本章講解插件的主線。所以,本章將圍繞下面幾個點開展介紹:
l 32位dalvik上非selinux模式下的xposed實現原理
64位、selinux模式其實難度不在虛擬機上,而是在selinux上。
本章先來介紹下Xposed,這是一個大工程,包含多個項目,我也是花了不少時間才把它整個玩轉起來的。Xposed包含如下幾個工程:
提示,提示,提示:
我把所有相關代碼都下載並放到下面的地址了:
https://code.csdn.net/Innost/xposed-learning 裡邊包含:
1 xposed所有代碼庫的內容
2 XposedDemo:Xposed插件APP Demo,非常簡單
3 XposedDemoTarget:XposedDemo將hook上的目標App
下面介紹下如何編譯Xposed,這裡以Android 4.4.4為例。做Android開發最好配一個Nexus手機或Pad。我用得是Nexus 7 2013Wi-Fi版。Anyway,Xposed的編譯和具體機器無關,不過下面的前提條件需要滿足:
好,馬上開始我們的步驟:
根據XposedTools的說明,我們先要修改下AOSP源碼裡的.repo,具體步驟如下:
這個文件是什麼內容呢?來看圖1:
這個文件內容是啥意思呢?
配置好後,請在AOSP目錄下執行repo sync。這樣它會根據manifests更新AOSP源碼。當然,也可以只是下載frameworks/base/cmds/xposed工程和更新build工程。
注意,repo sync是一個重型操作,會導致所有工程都進行一次同步。我建議的方法是直接下載和更新對應的工程
比如,下載xposed工程,用repo sync frameworks/base/cmds/xposed即可。對於build目錄,先把原來的build挪到其他地方。然後repo sync build即可。
好了,到此,所有源碼都已經ready了。
下面我們進入XposedTools目錄,然後修改其中的build.conf文件。該文件用於指示AOSP源碼等參數。如圖2所示:
XposdTools提供了一個build.conf.sample模板,圖2中的build.conf文件是在這個模板基礎上修改而來。紅框中是我修改的結果。其他選項沒有變化。
到XposedTools目錄下,執行:./build.pl -t arm:19命令,這表明我要編譯arm平台上SDK=19版本的xposed框架。注意,./build.pl --help會打印出使用方法。build.pl是一個perl腳本。圖3是編譯過程截圖:
圖3中,你可以發現build.pl跑到AOSP源碼目錄下,執行了:
在使用build.pl時,它還依賴一些Perl的類庫,請童鞋們按照下面步驟下載這些依賴庫:
sudo apt-get install libconfig-inifiles-perl
sudo apt-get install libio-all-perl
sudo apt-get install libfile-readbackwards-perl
sudo apt-get install libfile-tail-perl
sudo apt-get install libtie-ixhash-perl
build.pl執行過程中,如果報還有其他依賴庫未找到,請通過下面命令
apt-cache search perl XXX 來查找需要apt-get install哪個目標庫。XXX是build.pl執行過程中報錯時提供的庫信息
編譯完成後,將產生一個zip包到AOSP/out/sdk19/arm下。AOSP/out是我在build.conf中指定的目錄。如圖4所示:
編譯結果是一個xposed-v65-arm-custom-build-xyz-20151030.zip包,這個包可以通過recovery刷到手機上。包的內容就是files文件夾下的內容,包含:
XposedInstaller是Xposed的App,用於管理Xposed框架和插件App。本節我們主要討論它是如何安裝Xposed框架和插件App的。
XposedInstaller啟動界面如圖5所示:
圖5中可知XposedInstaller提供好幾項子頁面。第一個“框架”用來安裝或卸載Xposed框架的。我們來看它。
如圖6所示:
注意,圖6右上角的“程序自帶”兩個版本號,分別是app_process版本號為58,XposedBridge.jar版本號是54.
而“激活”這兩項為空,因為我們的系統還沒有安裝Xposed框架。
“程序自帶”是什麼意思?原來,XposedInstaller在自己的assets目錄下攜帶了所需要的xposed框架程序和模塊,如圖7所示:
從圖7可知,XposdInstaller自帶了xposed版zyote(比如app_process_xposed_sdk16),為了更好得支持不同版本的Android,它還區分了SDK版本。另外,XposedInstaller也支持刷機包把xposed框架模塊刷入系統,比如Xposed-Installer-Recovery.zip,裡邊包含的主要內容就是一個腳本,在recovery模式下運行,其內部也是把assets裡的文件拷貝到/system相關目錄中。這一塊我們後續看代碼就知道怎麼玩兒了。
安裝Xposed框架的主要功能由InstallerFragment.java提供,我們看看相關代碼。
onCreateView函數是Fragment裡初始化UI的核心回調,其代碼如圖8所示:
圖8代碼中最後的refreshVersions用於獲取版本號,也就是圖6中右上角“程序自帶”要顯示的信息,它包括兩個東西:
檢查版本主要是為了兼容性考慮。代碼中的refreshVersions用於獲取他們的版本號,代碼如圖9所示:
圖9中:
想知道怎麼獲取版本系想你嗎?來看圖10:
太簡單了,不惜得說。
注意XposedBridge.jar包安裝版的位置,它在/data/data/de.robv.android.xposed.installer/bin/XposedBridge.jar裡
InstallerFragment的install函數用於安裝xposed框架相關的模塊。這個函數一點也不復雜,不過還是給大家看看。如圖11所示:
install函數沒什麼難度,但是我們要總結下xposed框架安裝到底做了什麼手腳:
嗯嗯,也沒什麼太多可說的。
xposed插件,在xposed世界裡我們說它是插件,但是放到Android世界裡它就是一種特殊的APP。這種類型的APP由xposed框架識別並加載,然後hook到其他的App進程。
當然,這裡既然提到進程,那麼這些APP就必須要被先啟動起來才是。
XposedInstaller的插件管控由ModulesFragment界面來處理。本節主要想介紹下XposedInstaller是怎麼對待Xposed插件APP的。
來看代碼,如圖12所示:
我們重點來看看Xposed插件APP是怎麼被load的,代碼其實在ModuleUtil的reloadInstalledModules函數中。代碼很簡單如圖13所示:
圖13左下角已經告訴各位Xposed插件APP該是什麼樣的了,右下角是XposedDemo示例。
在AndroidManifest.xml裡定義這些東西只是告訴Xposed自己是一個插件APP。但是作為一個掛鉤插件,Xposed還需要知道這個APP裡哪個類是用來掛鉤的。這句話的意思是:
1 這個APP是一個插件APP。該APP包含很多功能。
2 這個APP包含的眾多功能中,有一個功能是給目標進程掛鉤。掛鉤操作是Xposed框架來做的,所以它需要知道該APP中哪個類是繼承了Xposed鉤子接口。這樣,Xposed框架找到插件APP之後會觸發這個app的鉤子接口進行掛鉤。
要做到第二步就得在assets/下放一個名叫xposed_init文件,裡邊指明插件APP的掛鉤類名。XposedDemo的
assets/xposed_init的內容就是:com.xposed.demo.MyXposedModule。當然,這個插件類必須實現Xposed的IXposedHookLoadPackage接口類。這個我們以後再討論。
Xposed框架分為xposed版app_process和XposedBridge.jar兩部分。app_process就是zygote,我們先看看xposed版的zygote干了些什麼。
注意,本章只分析32位,dalvik版的xposed app_process,其入口main函數位於app_main.cpp裡。
圖14所示的代碼展示了Xposed版zygote與眾不同之處。
圖14中,左上角的框是app_main的main函數,裡邊有兩處不同之處:
圖9展示了initialize函數的內容,主要是最後一個把/system/bin/XposedBridge.jar(這個jar包的位置和我們在XposedInstaller那裡看到得不同,原因前面解釋過了)加到CLASSPATH比較重要。
注意,我們這裡雖然對initialize介紹的內容很少,但實際上這個函數要真正看明白還是很需要技術實力的:
1 logcat:start:裡邊fork了logcat進程用來存儲xposed自己的log
2 service:startAll:為了完美支持selinux,這裡的處理更是很有技巧。selinux是一個完整的知識體系,想徹底掌握它的童鞋請參考我的三部曲文章《深入理解SELinux SEAndroid》
1&2其實很真實得反映出xposed的作者在Android、Linux上水平很高,經驗很豐富。
最後,如果xposed框架啟用成功,那麼zygote的入口類將由以前的com.android.internal.os.ZygoteInit變成de.robv.android.xposed.XposedBridge。
下面我們按照執行流程,把相關函數分析一遍:
圖16為代碼:
onVmCreated比較簡單了:
xposed官方說MIUI大量用了xposed的東西,並且不共享,以後碰到MIUI的問題他們不再支持。Don't know what to say....
main函數代碼如圖17所示:
main函數裡有三個重要函數:
initNative很重要,來看代碼,如圖18所示:
圖18中,我們重點看一下callback_XposedBridge_initNative和register_natives_XResources這兩個函數。這兩個函數比較簡單,我們統一放到圖19中:
不多說了,沒什麼難度。
從這個函數開始,xposed就開始給系統一些關鍵函數掛鉤子了。我們看看它怎麼玩兒的。代碼如圖20所示:
圖20中我在eclipse裡用了代碼縮略顯示的方法,可知一共有五個框,分別hook了一些關鍵內容。我們從上到下一次分析。先來分析下Xposed框架提供的掛鉤函數findAndHookMethod。
findAndHookMethod用來對指定類的指定函數進行掛鉤。這個函數很重要,開發插件APP時用得最多。來看它的代碼,如圖21所示:
findAndHookMethod代碼Java層面的邏輯還是比較好理解的:
來看hookMethod,如圖22所示:
由hookMethod可知,一個目標函數可以掛多個鉤子,這些鉤子由一個集合來存儲。然後我們將轉到JNI層去看看hookMethodNative干了什麼事情。這才是hook的核心。代碼如圖23所示:
hookMethodNative完成了真正的掛鉤處理,其思想很簡單:
我們在《深入理解Android之dalvik》一文中介紹過,JVM調用java函數時候,發現這個函數為native的話,就調用它的nativeFunc。下面我們看看鉤子函數的調用。
hook鉤子函數後我們就要調用它。上一節我們發現xposed在掛鉤子的時候會把原函數改造成native屬性(即Dalvik會按native函數的方式調用它),對應的nativeFunc是hookedMethodCallback,其代碼如圖24所示:
注意喔,我們現在已經在處理被掛鉤函數的調用了喔....從JNI會進入到java層的鉤子函數dispatch總入口,及handleHookedMethod,這個函數比較復雜,我們一段一段來看它。
很簡單。接著看第二段,如圖26所示:
貌似也很簡單喔...
現在來看原目標函數的調用,即invokeOriginalMethodNative。代碼如圖27所示:
同樣很容易,不多說了。到此,我們已經看到了Xposed掛鉤的所有過程。好像也沒什麼復雜的,只要對dalvik稍微屬性點,應該是比較容易做的。
當然,本文只是給大家show了主要流程,真要自己動手做,我覺得難點在於參數傳遞等方面。anyway,了解了大體流程,後面的事情也好辦,多嘗試幾次就好。
下面我們回過頭來看initForZygote裡加的幾個鉤子都干了什麼。
圖20的initForZygote代碼示意中可知xposed對下面幾個函數進行hook(此處先不討論鉤子函數干了什麼)
main函數在initForZygote之後的下一個動作就是loadModules,這就是加載所有的插件APP。我們來看看這個函數,代碼如圖28所示:
我們來看下loadModules的處理:
圖30展示了hookLoadPackage和hookInitPackageResource兩個函數的內容,特別簡單。
嗯嗯,就是把對應的鉤子函數保存起來先。
好了,下面我們就來開始分析initForZygote裡掛上的幾個鉤子分別有什麼用。
initForZygote為hanldeBindApplication設置了前處理鉤子,代碼如圖31所示:
前面說過,在APP生命周期內,handleBindApplication是APP剛准備好相關信息的一個重要點,在這個點去進行掛鉤處理簡直是最好不過了。當然,此處的掛鉤處理也就是准備好這個APP的相關信息然後調用所有的IXposedHookLoadPackage類型的鉤子。
注意,除了handleBindApplication之外,由於一個APP進程事實上可以加載多個APK(比如那些申明同樣的uid和運行在同一進程的APP),在LoadedApk的構造函數中也做了類似的處理
IXposedHookLoadPackage鉤子一般會干些什麼呢?圖31對XposedInstaller的處理就很明顯了。一般而言,這種鉤子會對目標APP中感興趣的函數進行掛鉤(調用findAndHookMethod),比如XposedInstaller對getActiveXposedVersion進行了掛鉤,用於返回系統裡正在使用的Xposed框架版本。
我們應該在鉤子函數裡干些什麼?這是一個重要問題。我也不廢話了,直接上XposedDemo的源碼,如圖32所示:
圖32是我在xposed-learning項目中提供的XposedDemo示例,可知:
簡單點說,我們在IXposedHookLoadPackage的handleLoadPackage中把該掛的鉤子都掛上就好。
initAndLoop是system_server進程的關鍵函數,在這個函數裡Android Framework的絕大部分Service都將被創建。真是藝高人膽大,這個進程居然都提供了掛鉤處理。其代碼如圖33所示:
代碼倒是很簡單,無非是針對system_server進行hook。插件函數如果想區分被hook的進程是否為system_server的話,只需要判斷packageName是否為"android"即可。
再次強調,system_server是Android Java層Framework的核心,要hook它需要萬分小心,否則或導致手機系統出現會各種不穩定,崩潰,重啟等情況。
下面我們介紹下Xposed框架對資源是怎麼Hook的。
前面章節介紹了Xposed框架如何對代碼調用邏輯進行hook。在Android APP中,除了代碼邏輯外,Xposed還支持對資源進行Hook。對資源Hook的原理其實和對代碼調用進行Hook的原理類似。這裡我們簡單介紹下Xposed框架如何對資源進行Hook。
在Android APP中,資源有三個重要類:
就這麼簡單。我們一步一步來看。
注意,XposedDemo並沒有hook資源,請感興趣的童鞋們自行加上該功能進行測試。
hookResource對ResourceManager等進行了掛鉤處理。來看代碼,如圖34所示:
hookResources主要是對ResourcesManager的getTopLevelResources進行了Hook。APP中原來使用的是Resources代表資源,Hook之後,Xposed用XResources代替了Resources。圖35展示了XResources的派生關系。
接著看hookResources第二段代碼,如圖36所示:
第二段代碼中,Xposed資源Hook框架調用了資源hook類型的鉤子。同樣,我們需要關注在這種類型的鉤子裡插件APP要干得事情,那就是:
上面替換的還是APP自己的資源,除此之外,Xposed還能替換系統資源(即framework-res.apk裡聲明的資源),這部分代碼也在hookResources裡,來看圖37:
圖37中,Xposed將Resources裡代表系統資源的mSystem對象也進行了替換。不過這裡沒有獨立調用回調。沒關系,因為在第二部分的回調中,插件APP通過XResources的setSystemWideReplacement可以對系統資源進行替換。
注意,hookResources之後,APP裡調用的Resources相關的函數就全部轉到XResources來處理了。比如獲取字符串的getString函數,其最終會調用到XResources的getText函數,代碼如圖38所示:
到此,我們對Xposed框架如何hook資源進行了介紹。不過,layout資源的hook我這裡並沒有介紹,請童鞋們閱讀XResources的getLayout函數和init函數。
到此Xposed 32位dalvik版框架基本介紹完了。這一趟絕對不是本篇這20多頁文章這麼輕松的事情。正如我在《深入理解Android之Dalvik》一文寫得那樣,我是在研究xposed的時候,發現必須要搞清楚dalvik,所以才先寫了dalvik的文章,然後才能走到今天這一步。
Xposed是一個成熟框架,高度體現了開發者在Java虛擬機這塊有著非常深厚的知識積累。同時,如果加上selinux的話,那開發者對linux系統也是相當相當熟悉。另外,貌似開發者是用業余時間搞出來的,這在當下上班時間強制為996的碼農而言幾乎是不可能的事情。
再次向開發者致敬,也同時呼吁基於xposed框架的派生框架開發者遵守相關開源協議。
Android 5.0 Lollipop 推出了 Material Design 安卓界面風格的設計理念並且給出了一個 Material Design 的兼容庫,庫中包含
AndroidN 除了提供諸多新特性和功能外,還對系統和 API 行為做出了各種變更。本文重點介紹您應該了解並在開發應用時加以考慮的一些重要變更。如果您之前發布過 And
先看看效果圖:源碼:package com.zihao.radar; import android.app.Activity; import android.os.Bu
基本概念:安卓平台提供對藍牙的通訊棧的支持,允許設別和其他的設備進行無線傳輸數據。應用程序層通過安卓API來調用藍牙的相關功能,這些API使程序無線連接到藍牙設備,並擁有