吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 5523|回复: 17
收起左侧

[Android 原创] Xposed入门及模块级免重启系统和免重启App

[复制链接]
谶焮 发表于 2022-7-4 09:37

Xposed入门及免重启系统和免重启App探索

Xposed和Frida相比,Frida更加灵活还可以支持native所以目前我在使用Hook的时候也会优先选择Frida,但是根据实际使用体验来看的话,会存在一些Xposed能用frida却Hook失败或者反过来Frida能用Xposed失败的情况,所以二者目前也是结合使用。

之前使用Xposed或者EDXposed的时候Xposed模块每次修改都需要修改代码编译和重启设备,比较繁琐,对于逆向过程中定位一些函数啥的不是很方便,所以就萌生了一些研究的小想法,就考虑做一个免重启的模块方便定位一些方法。

其实之前也有很多大佬做过类似的功能更多更强大的项目,如免重启的Xposed框架,还有一些直接就可以枚举方法动态hook的模块。但是生命在于折腾,随便玩玩也是可以的嘛😜。

这个思路的核心在于getDeclaredMethodsXposedBridge.hookMethod(),通过getDeclaredMethods遍历方法,然后用XposedBridge.hookMethod()进行Hook和打印参数操作。在配置文件中配置需要Hook的包名、方法名。

1. 编写入门

目前关于Xposed的入门教程不计其数,在此就简单写一下,重点会放在免重启

新建一个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导入xposed依赖

在android {}的同级别添加如下内容,表示从jcenter仓库中找包,也可以换成其他的镜像站

repositories {
    jcenter()
}

在dependencies中添加如下内容(必须是compileOnly )

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

增加xposed_init

  • 在main目录下新增assets文件夹
  • 在assets文件夹下新增文件xposed_init
    最后的目录结构为:app\src\main\assets\xposed_init
    xposed_init的作用为存放Xpose Hook代码的入口类,格式大致如下,存放自己写的Hook代码的包名.类名即可
com.example.androidhookdemo.Main_JG

2. 代码实现

新建一个类,实现接口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;

//新建一个类,实现接口IXposedHookLoadPackage
public class Main_JG implements IXposedHookLoadPackage {
    @Override
    //实现接口定义的方法handleLoadPackage
    public void handleLoadPackage(final XC_LoadPackage.
            LoadPackageParam lpparam) throws Throwable {
        .......//这里是具体的Hook代码,下面写
    }

使用findAndHookMethod来Hook目标方法

先导入:import static de.robv.android.xposed.XposedHelpers.findAndHookMethod;

  • 使用findAndHookMethod方法Hook目标方法,findAndHookMethod(目标class名,lpparam.classLoader,方法名,参数1,参数2....,
  • new XC_MethodHook(){实现beforeHookedMethod和afterHookedMethod方法})
    XC_MethodHook:在目标方法执行前/后运行相应的替换函数;
    beforeHookedMethod 主要用来读取和修改参数
    afterHookedMethod 主要用来修改返回值以及查看修改是否生效
  • XC_MethodHook可以替换为XC_MethodReplace:完全替换目标方法,执行用户自定义方法。
//过滤掉不符合要求的类,可以打一下日志,看看是否正常
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("参数1 = " + param.args[0]);
      //修改被hook方法的参数
      param.args[0] = "XXX";
    }

    @Override
    protected void afterHookedMethod(MethodHookParam param) throws Throwable {
      XposedBridge.log("参数1 = " + param.args[0]);
      XposedBridge.log("结束~~~");
    }
  });
}

3.Xposed模块免重启系统

现在进入正题,介绍一下当前的模块实现免重启系统的方案,流程大体如下:

  • 修改配置文件
  • 重启App(强行停止并再次启动)
  • 读取配置
  • 根据配置中配置的类名遍历其中的方法
  • 根据配置选择是Hook所有方法还是Hook方法列表中的方法
  • 打印参数和返回值

配置文件

{
    "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;
        }
    }

关键方法

// 获取所需的类对象
clazz = Class.forName(className, false, classLoader);

// 遍历目标类的方法
for (final Method method: clazz.getDeclaredMethods()) {
  // 处理逻辑
}

// Hook目标方法
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目标方法
    if (hookMethods.contains(method.getName())) {// Hook符合条件的方法
      XposedBridge.log("进入" + 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 + ",参数 [" + 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替换参数和返回值

解决了每次都要编写模块、编译、安装、重启这样繁琐的流程后,在后续的逆向中又遇到了另一个问题:有时需要修改一些方法的参数和返回值,但是如果使用Xposed每次重启App也是个很麻烦的事(有时候Frida修改脚本后也会出现无法生效的情况),所以又搞了一下免重启App来实现替换参数的Xposed模块,方便在分析阶段替换报文。

这个模块目前实现比较简单,即在Hook过程中每次执行到被Hook的方法时都读取配置文件进行判断

目前仅能替换一些字符串,如果需要替换复杂参数还需要做对应的修改,后续如果遇到复杂变量的替换再增加和更新☺️

配置文件

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

关键方法

//读取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); // 目标json的key对应的值为新字符串和替换模式
                String newStr = jsobj.getString("newString");
                if(value.contains(oldStr)){ 
                    if(jsobj.getBoolean("isReplaceAll")){ // 判断替换模式
                        MLogUtil.d("Ming-HookED","新数据:" + newStr +"替代包含目标字符串:" + oldStr +"的数据" );
                        value = newStr;
                    }else{
                        MLogUtil.d("Ming-HookED","使用新字符串:" + newStr + "替换目标字符串:" + oldStr);
                        value = value.replace(oldStr,newStr);
                    }
                }
            }
        }
        // 返回处理后的字符串
        return value;
    }

使用以上方法的返回的值替换目标

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

到此结束

免费评分

参与人数 7威望 +1 吾爱币 +25 热心值 +6 收起 理由
codezm + 1 用心讨论,共获提升!
lingyun011 + 1 + 1 热心回复!
Event + 1 谢谢@Thanks!
qtfreet00 + 1 + 20 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
笙若 + 1 + 1 谢谢@Thanks!
Devil丶绝翼 + 1 + 1 Lsp不用重启 单个作用域也很方便 支持开发者
Andy0214 + 1 + 1 我很赞同!

查看全部评分

本帖被以下淘专辑推荐:

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

 楼主| 谶焮 发表于 2022-7-7 11:30
源码,直接放附件好了,就俩文件

源码.zip

3.72 KB, 下载次数: 39, 下载积分: 吾爱币 -1 CB

 楼主| 谶焮 发表于 2022-7-7 11:10
Piz.liu 发表于 2022-7-5 09:16
有没有完整的代码参考下

我看看传到github试试,原来的代码写的比较乱,中途修改了很多次有很多遗留的代码,不过关键代码都在文章里了
怜渠客 发表于 2022-7-4 14:51
看了一下这位大佬的方式,是不是和市面上那些自定义hook一样,自定义类名,返回值等配置,然后进行模块加载,本质是模块没有变化,所以就不需要重启
慵懒丶L先森 发表于 2022-7-4 16:54
感谢分享,之前写模块的时候一直不知道怎么搞加载配置文件的功能,这次学到了
青春莫相随 发表于 2022-7-5 02:20
学习了,感谢分享
sornian 发表于 2022-7-5 07:00
没有那么复杂,用lsposed就行
Piz.liu 发表于 2022-7-5 09:16
有没有完整的代码参考下
lws0318 发表于 2022-7-5 09:25
学习了,感谢分享
小k666 发表于 2022-7-5 16:11
学习了!下次找个软件试一下
只是来打酱油 发表于 2022-7-6 09:53
这样的思路 也很赞
bjznhxy 发表于 2022-7-6 22:35
学习,感谢分享!
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则 警告:本版块禁止灌水或回复与主题无关内容,违者重罚!

快速回复 收藏帖子 返回列表 搜索

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-4-25 10:12

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表