*本文原創做者:L5,本文屬FreeBuf原創獎勵計劃,未經許可禁止轉載php
Android是一種基於Linux的自由及開放源代碼的操做系統,由Google公司和開放手機聯盟領導及開發。因爲其開放的特質,吸引了一大批硬件廠商和軟件開發者。第三方的統計數據顯示,2016年Android佔有的市場份額高達76.4%,遠遠超過其餘智能手機廠商。java
大量的Android os裝機量,在豐富安卓系統使用場景的同時,也催生出了許多安全問題。xposed框架提供了不須要修改系統源碼就能靈活定製系統功能的能力,極大的方便了安全研究人員的工做。且xposed利用了JNI機制修改Java framework的功能實現,沒有涉及arm本地指令的適配工做,因此不多出現兼容性問題。本文是爲了帶領你們瞭解下此框架的能力以及實現方式,最終打造一款屬於本身的「神器」。android
本文總計5個章節,以xposed的使用需求做爲切入點,由淺入深地介紹了模塊的編寫實戰、模塊編寫進階篇和經常使用模塊編寫以及異常狀況&後續展望。本文來源於平時的實踐,用做你們互相交流與學習。git
咱們在選擇使用xposed功能模塊的時候,可能基於如下需求之一:github
[1]監控app行爲:查看關鍵api 的調用日誌,用於特定目標的行爲分析。apache
[2]定製系統功能:改變原先函數的處理邏輯,自定義api行爲。編程
[3]沙箱功能定製:主要關注反環境檢測(上述兩點關注app自己),如:惡意樣本分析,模擬器須要儘量的「真實」以便觸發樣本行爲。api
固然,實際的需求並不會僅僅侷限於此,可能會更多。這裏列出的需求點也只是我的的總結,若有遺漏,敬請告知。畢竟需求驅動學習,文章的出發點也是但願彙集有着共同需求點的小夥伴,你們有個討論地方,共同窗習和進步。好,其它話很少說,接下來進入xposed模塊的編寫實戰。數組
xposed 模塊的能力包括如下幾個方面:安全
[1] 對普通函數或者構造函數有做用(針對具體實現類,不包括接口,抽象類的實現函數也能夠hook)。
[2] 對目標函數進行 before、after 代碼插樁,多用於操做(查看或修改)api的入參以及返回值。
[3] 目標函數替換,多用於功能變動、版本升級。
接下來,咱們列舉下 xposed 模塊編寫可能遇到的實際場景(假設閱讀本文以前,讀者擁有基本的模塊編寫經驗)。在這個demo中,我將盡量全面的再現須要hook操做的場景。好比:函數體、構造函數、匿名類、匿名內部類以及類的值域。
上述demo中存在
(1)靜態field變量sMoney
(2)隱藏函數hidden_fun(觸發的條件相對苛刻)
(3)內部類inner_class
(4)匿名內部類Animal animal = new Animal(){}
(5)構造函數demo()
咱們逐個回顧下相應問題的解決方法:
(1)靜態field變量sMoney的值的修改和獲取,能夠直接使用xposed提供的XposedHelpers類相關功能函數。具體操做能夠類比如下示例代碼片斷:
/* * Hook field * class: com.example.inner_class_demo.demo * field: sMoney */ Class clazz = XposedHelpers.findClass("com.example.inner_class_demo.demo",lpparam.classLoader); XposedHelpers.setStaticObjectField(clazz,"sMoney",110); Field sMoney = clazz.getDeclaredField("sMoney"); sMoney.setAccessible(true); System.out.println(sMoney.get(null));
(2)主動調用隱藏函數hidden_fun(這一類函數是指觸發條件比較苛刻的函數,可是咱們又須要瞭解它的輸入、輸出的大體關係),須要經過clazz來新建實例,最後將此實例與函數名組裝成XposedHelpers.callMethod() 的實參需求形式。具體操做能夠類比如下示例代碼片斷:
/* * Call hidden function * class : com.example.inner_class_demo.demo * function: hidden_fun() */ Class clazz = XposedHelpers.findClass("com.example.inner_class_demo.demo",lpparam.classLoader); XposedHelpers.callMethod(constructor.newInstance(),"hidden_fun");
以上代碼僅適用於存在無參構造函數的類,若是目標類沒有無參構造函數,那就麻煩一點了,須要根據構造函數參數類型,反射尋找構造函數,接着才能相似上述操做。具體操做能夠類比如下示例代碼片斷:
假設此時的構造函數僅有如下函數,即public demo(){}不存在的情形: public demo(String str){...} /* * Call hidden function * class : com.example.inner_class_demo.demo * function: hidden_fun() */ Class clazz = XposedHelpers.findClass("com.example.inner_class_demo.demo",lpparam.classLoader); Constructor constructor = clazz.getConstructor(String.class); XposedHelpers.callMethod(constructor.newInstance("..."),"hidden_fun");
(3)內部類inner_class做爲Android編程過程常見的一種編程方式,這裏爲了demo的全面,也將其列出。其實內部類整個處理過程與普通類極其類似,具體操做能夠類比如下示例代碼片斷:
/* * Hook the function of inner class * class : com.example.inner_class_demo.demo$inner_class * function: secret(String , boolean) */ XposedHelpers.findAndHookMethod("com.example.inner_class_demo.demo$inner_class", lpparam.classLoader, "secret", String.class, boolean.class, new XC_MethodHook() { protected void beforeHookedMethod(MethodHookParam param) throws Throwable { for (int i = 0; i < param.args.length; i++) { XposedBridge.log(" argument is:" + param.args[i]); } int field_result = (int) XposedHelpers.getObjectField(param.thisObject,"pMoney"); XposedBridge.log(String.valueOf(field_result)); } });
須要注意的是,這裏打印目標函數參數列表的時候,用了XposedBridge.log()。這樣的輸出方式對日誌的長度有限制,即長度不超過1024。特殊場合(好比:文件讀取時,須要查看文件的內容)須要注意處理下,否則會出現截斷的現象。
(4)匿名內部類Animal animal = new Animal(){}的處理
同內部類,通常是class_name$1之類,具體能夠反編譯目標程序查看下。常見的反編譯工具,好比:apktool、jeb、baksmali 都可以方便達到目的。
(5)構造函數demo()的處理,可使用xposed提供的XposedHelpers類,具體操做能夠類比如下示例代碼片斷:
/* * Hook the constructor of class * class : om.example.inner_class_demo.demo * function: demo() */ Class clazz = XposedHelpers.findClass("com.example.inner_class_demo.demo",lpparam.classLoader); XposedHelpers.findAndHookConstructor(clazz, new XC_MethodHook() { ... });
須要注意的是,因爲構造函數不一樣於普通函數,函數名不須要提供(由於與類名相同,xposed框架處理函數名問題)。
在實際的模塊編寫時候,咱們都或多或少地遇到一些問題。接下來我將列出一些我在實踐過程當中遇到的一些問題和解決思路,期待幫助有一樣困惑的小夥伴。若是你有問題,這裏很不幸的又沒有列出,那你能夠拿出來你們一塊兒討論下。
經過上一小節模塊的編寫,咱們如今已經能夠順利hook某一特定目標函數了。若是遇到某一類函數須要「批量」hook操做的時候,好比:須要同時監控多個構造函數、多個重載函數,咱們此時不可能去挨個hook每一個具體目標,那麼應該怎麼操做呢?咱們能夠這樣來實現:
/* * Hook all constructors of class * class : om.example.inner_class_demo.demo * function: demo()、demo(String) */ hookAllConstructors(clazz, new XC_MethodHook() { ... }); /* * Call all methods of class * class : om.example.inner_class_demo.demo * function: method()、method(String) */ hookAllMethods(clazz, new XC_MethodHook() { ... });
咱們能夠總結一個規律:hook重載函數時候,只須要忽略參數的具體類型便可。這種方式其實能夠達到兩種效果:1. 高效的處理函數重載問題 2.目標函數參數類型太複雜,自定義的類型太多。忽略參數類型,能夠簡化咱們的hook工做。
因爲dalvik環境下xposed對multidex的支持沒有很好的通用解決方案,尋找目標函數會發生ClassNotFoundError,因此處理multidex須要一些技巧(Tips): 此問題由於classloader出錯引發的,因此要尋找attachBaseContext 的classloader,而非lpparam.classLoader(此思路來自非蟲前輩)。
下圖即爲xposed做者對不支持multidex的解釋,詳細的內容能夠去github上查看對應的issue。
在這一章節,我將列出一些常見的功能模塊。但願發散下你們的思路、節約模塊開發的時間成本(畢竟重複勞動會消耗些時間、精力,聚沙成塔嘛)。
Hook org.apache.http 包中的網絡請求,忽略參數而後使用hookAllMethods就能夠同時攔截HttpPost、HttpGet、HttpUriRequest類型的網絡請求參數。
/* * Hook net access * abstract class: org.apache.http.impl.client.AbstractHttpClient * function : execute(HttpHost target, HttpRequest request,HttpContext context) * :execute(HttpUriRequest request, HttpContext context) */ hookAllMethods("org.apache.http.impl.client.AbstractHttpClient", lpparam.classLoader, "execute", new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { print_args(param); } });
須要注意點有二:
1. 這裏就是0×02章節裏面第[1]點提到的,xposed針對抽象類中的具體實現函數的hook。
2. 這裏參數特殊,若是直接強轉成String類型而後輸出,將會獲得無心義的輸出,形如:org.apache.http.client.methods.HttpPost@41d45200。因此輸出以前能夠判斷下,具體操做能夠類比如下示例代碼片斷:
Object arg = param.args[i]; String argValue = "null"; if(arg instanceof HttpPost){ URI uri = ((HttpPost)arg).getURI(); argValue = String.format("uri=%s ", uri.toString()); }else if(arg instanceof HttpGet){ URI uri = ((HttpGet)arg).getURI(); argValue = uri.toString(); }else if(arg instanceof HttpUriRequest){ URI uri = ((HttpUriRequest)arg).getURI(); argValue = uri.toString(); }else argValue = arg.toString();
這裏須要注意的是,HttpPost 之類的包在高版本的sdk中已經不存在了,順利經過編譯須要進行如下操做:1. Android studio中修改編譯文件,添加
android { useLibrary 'org.apache.http.legacy' }
3. ADT中能夠按照如下教程,The import org.apache.http.HttpResponce cannot resolved error solution
網絡重定向,修改網絡請求地址,「模擬「」中間人攻擊效果,具體操做能夠類比如下示例代碼片斷:
/* * Redirect net access * class: java.net.URL * */ XposedHelpers.findAndHookConstructor("java.net.URL", lpparam.classLoader, String.class, new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { String url = (String) param.args[0]; param.args[0] = "http://www.baidu.com/"; XposedBridge.log("new URL to " + param.args[0]); } });
[3] IO異常監控,這裏的IO異常包括全部網絡IO異常和本地異常,具體操做能夠類比如下示例代碼片斷:
/* * Monitor IO Exception * class: java.io.IOException * */ XposedBridge.hookAllConstructors(IOException.class, new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { XposedBridge.log((Throwable) param.thisObject); } });
經常使用的hook操做總結以下,Object…表明着變參,實際編程過程當中,須要保證提供的參數列表中最後一個參數必定是hook操做的回調,前面是函數參數class類型。
/* * Hook any method (or constructor) with the specified callback * * @param targetclass The method in which * @param hookMethod The method to be hooked. * @param callback The callback to be executed when the hooked method is called. * @return An object that can be used to remove the hook. */ XposedHelpers#findAndHookMethod(String, ClassLoader, String, Object...) XposedHelpers#findAndHookMethod(Class, String, Object...) XposedBridge#hookAllMethods XposedHelpers#findAndHookConstructor(String, ClassLoader, Object...) XposedHelpers#findAndHookConstructor(Class, Object...) XposedBridge#hookAllConstructors
模塊編程過程當中,若是不但願直接提供變參列表,能夠提供Object數組,這樣能夠保證上述接口的「穩定」。具體操做能夠類比如下示例代碼片斷:
Object[] args_obj = new Object[2] ; XC_MethodHook callback_fun = new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { XposedBridge.log("..."); } @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { XposedBridge.log("...); } }; args_obj[0] = String.class; args_obj[1] = callback_fun; findAndHookMethod(class_name, lpparam.classLoader, function_name,args_obj);
以上demo爲debug版本,沒有混淆。在實踐過程當中,可能遇到混淆甚至加固的產品。更有甚者,目標app即便沒有混淆、加固,可是咱們仍是不能很快定位目標函數,難道此時只能大海撈針般靜態尋找target?這時候須要經過一些輔助工具幫助咱們定位api位置。
此文章可能考慮續篇,內容根據上述異常狀況或者我能想到的新的出發點與思路。好比:反模擬器檢測、反調試和加密庫操做監控,每一點都是一個小的工程,須要考慮周全些纔能有實用價值,你們一塊兒努力。
最後,感謝非蟲對文章難點提供的解決思路,以及最終文章質量的把關。
致謝非蟲(非蟲看雪ID)