Xposed入門及免重啟系統(tǒng)和免重啟App探索

Xposed入門及免重啟系統(tǒng)和免重啟App探索

Xposed和Frida相比,F(xiàn)rida更加靈活還可以支持native所以目前我在使用Hook的時候也會優(yōu)先選擇Frida,但是根據(jù)實(shí)際使用體驗來看的話,會存在一些Xposed能用frida卻Hook失敗或者反過來Frida能用Xposed失敗的情況,所以二者目前也是結(jié)合使用。

之前使用Xposed或者EDXposed的時候Xposed模塊每次修改都需要修改代碼編譯和重啟設(shè)備,比較繁瑣,對于逆向過程中定位一些函數(shù)啥的不是很方便,所以就萌生了一些研究的小想法,就考慮做一個免重啟的模塊方便定位一些方法。

其實(shí)之前也有很多大佬做過類似的功能更多更強(qiáng)大的項目,如免重啟的Xposed框架,還有一些直接就可以枚舉方法動態(tài)hook的模塊。但是生命在于折騰,隨便玩玩也是可以的嘛??。

這個思路的核心在于getDeclaredMethodsXposedBridge.hookMethod(),通過getDeclaredMethods遍歷方法,然后用XposedBridge.hookMethod()進(jìn)行Hook和打印參數(shù)操作。在配置文件中配置需要Hook的包名、方法名。

1. 編寫入門

目前關(guān)于Xposed的入門教程不計其數(shù),在此就簡單寫一下,重點(diǎn)會放在免重啟

新建一個Android Project

修改AndroidManifest.xml文件

<!-- xposed識別用,表示此app是個xposed模塊 -->
<meta-data
    android:name="xposedmodule"
    android:value="true" />
<!-- 模塊描述 -->
<meta-data
    android:name="xposeddescription"
    android:value="劫持樣例"/>
<!--最低版本號 -->
<meta-data
    android:name="xposedminversion"
    android:value="30"/>

修改app/build.gradle導(dǎo)入xposed依賴

在android {}的同級別添加如下內(nèi)容,表示從jcenter倉庫中找包,也可以換成其他的鏡像站

repositories {
    jcenter()
}

在dependencies中添加如下內(nèi)容(必須是compileOnly )

compileOnly 'de.robv.android.xposed:api:82'
compileOnly 'de.robv.android.xposed:api:82:sources'

增加xposed_init

  • 在main目錄下新增assets文件夾
  • 在assets文件夾下新增文件xposed_init
    最后的目錄結(jié)構(gòu)為:app\src\main\assets\xposed_init
    xposed_init的作用為存放Xpose Hook代碼的入口類,格式大致如下,存放自己寫的Hook代碼的包名.類名即可
com.example.androidhookdemo.Main_JG

2. 代碼實(shí)現(xiàn)

新建一個類,實(shí)現(xiàn)接口IXposedHookLoadPackage的方法handleLoadPackage

一個簡單的算是固定寫法吧,一般開頭這么寫就可以滿足需求了

import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.callbacks.XC_LoadPackage;

import static de.robv.android.xposed.XposedHelpers.findAndHookMethod;


//新建一個類,實(shí)現(xiàn)接口IXposedHookLoadPackage
public class Main_JG implements IXposedHookLoadPackage {
    @Override
    //實(shí)現(xiàn)接口定義的方法handleLoadPackage
    public void handleLoadPackage(final XC_LoadPackage.
            LoadPackageParam lpparam) throws Throwable {
        .......//這里是具體的Hook代碼,下面寫
    }

使用findAndHookMethod來Hook目標(biāo)方法

先導(dǎo)入:import static de.robv.android.xposed.XposedHelpers.findAndHookMethod;

  • 使用findAndHookMethod方法Hook目標(biāo)方法,findAndHookMethod(目標(biāo)class名,lpparam.classLoader,方法名,參數(shù)1,參數(shù)2....,
  • new XC_MethodHook(){實(shí)現(xiàn)beforeHookedMethod和afterHookedMethod方法})
    XC_MethodHook:在目標(biāo)方法執(zhí)行前/后運(yùn)行相應(yīng)的替換函數(shù);
    beforeHookedMethod 主要用來讀取和修改參數(shù)
    afterHookedMethod 主要用來修改返回值以及查看修改是否生效
  • XC_MethodHook可以替換為XC_MethodReplace:完全替換目標(biāo)方法,執(zhí)行用戶自定義方法。
//過濾掉不符合要求的類,可以打一下日志,看看是否正常
if (lpparam.packageName.equals("com.example.xiaoming")){
  XposedBridge.log("Loaded app: " + lpparam.packageName);            
  findAndHookMethod("com.example.xiaoming.myapplication.MainActivity", lpparam.classLoader, "getString", String.class,new XC_MethodHook(){
    @Override
    protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
      XposedBridge.log("開始~~~~~");
      XposedBridge.log("參數(shù)1 = " + param.args[0]);
      //修改被hook方法的參數(shù)
      param.args[0] = "XXX";
    }

    @Override
    protected void afterHookedMethod(MethodHookParam param) throws Throwable {
      XposedBridge.log("參數(shù)1 = " + param.args[0]);
      XposedBridge.log("結(jié)束~~~");
    }
  });
}

3.Xposed模塊免重啟系統(tǒng)

現(xiàn)在進(jìn)入正題,介紹一下當(dāng)前的模塊實(shí)現(xiàn)免重啟系統(tǒng)的方案,流程大體如下:

  • 修改配置文件
  • 重啟App(強(qiáng)行停止并再次啟動)
  • 讀取配置
  • 根據(jù)配置中配置的類名遍歷其中的方法
  • 根據(jù)配置選擇是Hook所有方法還是Hook方法列表中的方法
  • 打印參數(shù)和返回值

配置文件

{
    "packageName":{
      "className1":["encrypt","decrypt"],
      "className2":["encryptCBC","decryptCBC"],
      "isHookAll":false,
      "isShelld":false,
      "shellClass":"s.h.e.l.l.S",
      "shellMethod":"attachBaseContext"
    },

    "com.xxx.xxx":{
        "className":["b","a"],
        "isHookAll":false,
        "isShelld":true,
        "shellClass":"com.secneo.apkwrapper.AW",
        "shellMethod":"attachBaseContext"
    }
}

讀取配置

public static String readJsonFile(String fileName) {
        String jsonStr = "";
        try {
            File jsonFile = new File(fileName);
            FileReader fileReader = new FileReader(jsonFile);
            Reader reader = new InputStreamReader(new FileInputStream(jsonFile),"utf-8");
            int ch = 0;
            StringBuffer sb = new StringBuffer();
            while ((ch = reader.read()) != -1) {
                sb.append((char) ch);
            }
            fileReader.close();
            reader.close();
            jsonStr = sb.toString();
            return jsonStr;
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }

關(guān)鍵方法

// 獲取所需的類對象
clazz = Class.forName(className, false, classLoader);

// 遍歷目標(biāo)類的方法
for (final Method method: clazz.getDeclaredMethods()) {
  // 處理邏輯
}

// Hook目標(biāo)方法
try {
  Class clazz = Class.forName(className, false, classLoader);
  // 遍歷方法
  for (final Method method : clazz.getDeclaredMethods()) {
    StringBuffer s = new StringBuffer();
    int num = 0;
    for (Class c : method.getParameterTypes()) {
      s.append(c.getName()).append(",");
      num = num + 1;
    }
    // Hook目標(biāo)方法
    if (hookMethods.contains(method.getName())) {// Hook符合條件的方法
      XposedBridge.log("進(jìn)入" + className + "\\" + method + "分支");
      XposedBridge.hookMethod(method, new XC_MethodHook() {
        @Override
        protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
          super.beforeHookedMethod(param);
        }

        @Override
        protected void afterHookedMethod(MethodHookParam param) throws Throwable {
          int a = method.getParameterTypes().length;
          if (a > 0) {
            for (int i = 0; i < a; i++) {
              XposedBridge.log(methodName + ",參數(shù) [" + i + "] = " + param.args[i]);
            }
            XposedBridge.log( methodName + ",返回值" + param.getResult().toString());
          } else {
            XposedBridge.log( methodName + ",返回值" + param.getResult().toString());
          }
        }
      });
    }
  }
} catch (ClassNotFoundException e) {
  e.printStackTrace();
}

4.Xposed模塊免重啟App替換參數(shù)和返回值

解決了每次都要編寫模塊、編譯、安裝、重啟這樣繁瑣的流程后,在后續(xù)的逆向中又遇到了另一個問題:有時需要修改一些方法的參數(shù)和返回值,但是如果使用Xposed每次重啟App也是個很麻煩的事(有時候Frida修改腳本后也會出現(xiàn)無法生效的情況),所以又搞了一下免重啟App來實(shí)現(xiàn)替換參數(shù)的Xposed模塊,方便在分析階段替換報文。

這個模塊目前實(shí)現(xiàn)比較簡單,即在Hook過程中每次執(zhí)行到被Hook的方法時都讀取配置文件進(jìn)行判斷

目前僅能替換一些字符串,如果需要替換復(fù)雜參數(shù)還需要做對應(yīng)的修改,后續(xù)如果遇到復(fù)雜變量的替換再增加和更新??

配置文件

{ 
  "Encrypt.decrypt":{ 
    // oldString根據(jù)需要匹配的字符串
    "\"STATUS\":\"B6047\"":{
      "isReplaceAll":false,
      "newString":"\"STATUS\":\"1\""
    }
  },
  "Encrypt.decrypt999":{
    "\"STATUS\":\"00002\"":{
      "isReplaceAll":false,
      "newString":"\"STATUS\":\"1\""
    },
    "\"STATUS\":\"00003\"":{
      "isReplaceAll":false,
      "newString":"\"STATUS\":\"1\""
    }
  }
}

關(guān)鍵方法

//讀取json文件
    public static JSONObject readJsonFile(String fileName) {
        String jsonStr = "";
        try {
            File jsonFile = new File(fileName);
            FileReader fileReader = new FileReader(jsonFile);
            Reader reader = new InputStreamReader(new FileInputStream(jsonFile),"utf-8");
            int ch = 0;
            StringBuffer sb = new StringBuffer();
            while ((ch = reader.read()) != -1) {
                sb.append((char) ch);
            }
            fileReader.close();
            reader.close();
            jsonStr = sb.toString();
            return JSONObject.parseObject(jsonStr);
//            return jsonStr;
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }
// 替換
public static String MyReplace(String value,String methodName){
        final JSONObject jsonObject = JsonUtil.readJsonFile("/data/local/tmp/replace.json");
        if(jsonObject != null && jsonObject.containsKey(methodName)){
            JSONObject jso = jsonObject.getJSONObject(methodName);
            for (Map.Entry<String, Object> entry : jso.entrySet()) {
                String oldStr = entry.getKey(); 
                JSONObject jsobj = jso.getJSONObject(oldStr); // 目標(biāo)json的key對應(yīng)的值為新字符串和替換模式
                String newStr = jsobj.getString("newString");
                if(value.contains(oldStr)){ 
                    if(jsobj.getBoolean("isReplaceAll")){ // 判斷替換模式
                        MLogUtil.d("Ming-HookED","新數(shù)據(jù):" + newStr +"替代包含目標(biāo)字符串:" + oldStr +"的數(shù)據(jù)" );
                        value = newStr;
                    }else{
                        MLogUtil.d("Ming-HookED","使用新字符串:" + newStr + "替換目標(biāo)字符串:" + oldStr);
                        value = value.replace(oldStr,newStr);
                    }
                }
            }
        }
        // 返回處理后的字符串
        return value;
    }

使用以上方法的返回的值替換目標(biāo)

String value = JsonUtil.MyReplace((String) param.getResult(),methodName);
param.setResult(value);

免重啟App的如果有需要使用的直接復(fù)制文章中的代碼編譯到自己的模塊即可

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容