Xposed模塊編寫的那些事

*本文原創做者:L5,本文屬FreeBuf原創獎勵計劃,未經許可禁止轉載php

背景闡述

Android是一種基於Linux的自由及開放源代碼的操做系統,由Google公司和開放手機聯盟領導及開發。因爲其開放的特質,吸引了一大批硬件廠商和軟件開發者。第三方的統計數據顯示,2016年Android佔有的市場份額高達76.4%,遠遠超過其餘智能手機廠商。java

大量的Android os裝機量,在豐富安卓系統使用場景的同時,也催生出了許多安全問題。xposed框架提供了不須要修改系統源碼就能靈活定製系統功能的能力,極大的方便了安全研究人員的工做。且xposed利用了JNI機制修改Java framework的功能實現,沒有涉及arm本地指令的適配工做,因此不多出現兼容性問題。本文是爲了帶領你們瞭解下此框架的能力以及實現方式,最終打造一款屬於本身的「神器」。android

本文總計5個章節,以xposed的使用需求做爲切入點,由淺入深地介紹了模塊的編寫實戰、模塊編寫進階篇和經常使用模塊編寫以及異常狀況&後續展望。本文來源於平時的實踐,用做你們互相交流與學習。git

xposed 使用需求

咱們在選擇使用xposed功能模塊的時候,可能基於如下需求之一:github

[1]監控app行爲:查看關鍵api 的調用日誌,用於特定目標的行爲分析。apache

[2]定製系統功能:改變原先函數的處理邏輯,自定義api行爲。編程

[3]沙箱功能定製:主要關注反環境檢測(上述兩點關注app自己),如:惡意樣本分析,模擬器須要儘量的「真實」以便觸發樣本行爲。api

固然,實際的需求並不會僅僅侷限於此,可能會更多。這裏列出的需求點也只是我的的總結,若有遺漏,敬請告知。畢竟需求驅動學習,文章的出發點也是但願彙集有着共同需求點的小夥伴,你們有個討論地方,共同窗習和進步。好,其它話很少說,接下來進入xposed模塊的編寫實戰。數組

xposed 模塊編寫實戰

xposed 模塊的能力包括如下幾個方面:安全

[1] 對普通函數或者構造函數有做用(針對具體實現類,不包括接口,抽象類的實現函數也能夠hook)。

[2] 對目標函數進行 before、after 代碼插樁,多用於操做(查看或修改)api的入參以及返回值。

[3] 目標函數替換,多用於功能變動、版本升級。

接下來,咱們列舉下 xposed 模塊編寫可能遇到的實際場景(假設閱讀本文以前,讀者擁有基本的模塊編寫經驗)。在這個demo中,我將盡量全面的再現須要hook操做的場景。好比:函數體、構造函數、匿名類、匿名內部類以及類的值域。

1.png

上述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。

圖片5.png

經常使用模塊編寫

在這一章節,我將列出一些常見的功能模塊。但願發散下你們的思路、節約模塊開發的時間成本(畢竟重複勞動會消耗些時間、精力,聚沙成塔嘛)。

第一部分

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

相關文章
相關標籤/搜索