背景闡述
Android是一種基於Linux的自由及開放源代碼的操作系統,由Google公司和開放手機聯盟領導及開發。由於其開放的特質,吸引了一大批硬件廠商和軟件開發者。第三方的統計數據顯示,2016年Android占有的市場份額高達76.4%,遠遠超過其他智能手機廠商。
大量的Android os裝機量,在豐富安卓系統使用場景的同時,也催生出了許多安全問題。xposed框架提供了不需要修改系統源碼就能靈活定制系統功能的能力,極大的方便了安全研究人員的工作。且xposed利用了JNI機制修改Java framework的功能實現,沒有涉及arm本地指令的適配工作,所以很少出現兼容性問題。本文是為了帶領大家了解下此框架的能力以及實現方式,最終打造一款屬於自己的“神器”。
本文總計5個章節,以xposed的使用需求作為切入點,由淺入深地介紹了模塊的編寫實戰、模塊編寫進階篇和常用模塊編寫以及異常情況&后續展望。本文來源於平時的實踐,用作大家互相交流與學習。
xposed 使用需求
我們在選擇使用xposed功能模塊的時候,可能基於以下需求之一:
[1]監控app行為:查看關鍵api 的調用日志,用於特定目標的行為分析。
[2]定制系統功能:改變原先函數的處理邏輯,自定義api行為。
[3]沙箱功能定制:主要關注反環境檢測(上述兩點關注app本身),如:惡意樣本分析,模擬器需要盡可能的“真實”以便觸發樣本行為。
當然,實際的需求並不會僅僅局限於此,可能會更多。這里列出的需求點也只是個人的總結,如有遺漏,敬請告知。畢竟需求驅動學習,文章的出發點也是希望聚集有着共同需求點的小伙伴,大家有個討論地方,共同學習和進步。好,其它話不多說,接下來進入xposed模塊的編寫實戰。
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工作。
二:目標app功能豐富,用到multidex加載技術,我們又該怎么辦?
由於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位置。
此文章可能考慮續篇,內容根據上述異常情況或者我能想到的新的出發點與思路。比如:反模擬器檢測、反調試和加密庫操作監控,每一點都是一個小的工程,需要考慮周全些才能有實用價值,大家一起努力。
最后,感謝非蟲對文章難點提供的解決思路,以及最終文章質量的把關。