wystudio 发表于 2023-7-1 16:22

[Xposed模块] Xposed Hook实用方法详谈(7月3日更新)

本帖最后由 wystudio 于 2023-7-4 15:58 编辑


# 缘起
正如各位坛友所见,目前论坛中已经有许多Xposed模块编写教程,但都是基础教程或针对于某一应用。仍需根据所Hook的应用来实际调整。

本文将列举并谈及一些常用Hook方法(**适用于大部分应用**),以方便新人的学习。

PS:本文为**基础进阶教程**,需要有一定的Xposed Hook,Java基础。若您为新手建议先观看一下教程:

[@正己](https://www.52pojie.cn/home.php?mod=space&uid=1109458):
[七、Sorry,会Hook真的可以为所欲为-Xposed快速上手(上)模块编写](https://www.52pojie.cn/thread-1740944-1-1.html)

[八、Sorry,会Hook真的可以为所欲为-xposed快速上手(下)快速hook](https://www.52pojie.cn/thread-1748081-1-1.html)

# 实用方法列表
## 1.获取Activity对象(Android世界的大门)
#### ①介绍:
众所周知,Android中的四大组件为:
* Activity
* Service
* Content Provider
* Broadcast Provider

Activity处于第一个可见其十分重要
而在Java中有了Activity对象,便可对Activity进行各种操作,如:打开另一个Activity,关闭当前Activity,获取Activity中的布局等。
同时Activity继承于Context,所以有了Activity方法也可以使用Context的方法。并且一些需要传入Context作为参数的方法也可以传入Activity对象,比如 new各种View和new一个Toast等。
***总之得到了Activity对象,就犹如打开了Android世界的大门,世界之大任你遨游!***

***下面介绍两种不同的获取方法,请根据实际情况选择使用***
### 法一:获取指定的Activity对象
该方法用于在知道类名和`onCreate`实现方法名的前提下,获取指定的Activity对象

#### ②步骤:
##### (1)首先要确定获取哪一个Activity对象。
这一步可以使用MT管理器中的Activity记录捕捉或直接打开清单文件查找。这里不再过多赘述。记下这个Activity类的包名路径,这里以 `com.example.app.MainActivity` 为例
!(https://blog.wystudio.xyz/wp-content/uploads/2023/07/IMG_20230701_113447_602.jpg)

##### (2)找到这个Activity的 onCreate 方法的实现方法名。
如果被Hook的应用没有被混淆,那名称就为 `onCreate` 。如果被混淆了,那就把应用拖入jsdx或用MT管理器等工具,去这个Activity的smail代码中找到名称。并记下这个名称。
这里假设没有被混淆,方法名为 `onCreate`:
!(https://blog.wystudio.xyz/wp-content/uploads/2023/07/IMG_20230701_125322_215.jpg)

##### (3)Hook这个方法即可
现在我们得到了指定Activity的类名和 `onCreate` 实现方法名。
最后我们只需要Hook此类中的 `onCreate` 实现方法即可拿到Activity对象,具体代码如下:
```java
XposedHelpers.findAndHookMethod("com.example.app.mainActivity", lpparam.classLoader, "onCreate",new XC_MethodHook() {
    @Override
    protected void afterHookedMethod(MethodHookParam param) throws Throwable {
       Activity mainActivity = (Activity) param.thisObject; //mainActivity就是最终得到的Activity对象
    }
});
```
最后的 `mainActivity` 就是我们获取到的Activity对象
***现在你可以像编写一个APP一样,去使用它做各种事情***

### 法二:获取当前Activity对象
此方法用于获取当前正在显示的Activity对象!
> !注意:因为此方法属于非公开的API使用,可能因为Android版本的更迭而发生变化!

> 该方法在一些系统被魔改的有些过分的系统中,可能出现未知错误。但是大部分系统都没问题

#### ②步骤:
##### (1)Hook `android.app.ActivityThread` 中的 `currentActivityThread` 方法
通过Hook此方法获取到当前ActivityThread对象

##### (2)通过反射获取到当前Activity对象
先通过反射获取到Activity集合,再获取当前Activity
代码如下:
```java
XposedHelpers.findAndHookMethod("android.app.ActivityThread", lpparam.classLoader, "currentActivityThread", new XC_MethodHook() {
    @Override
    protected void afterHookedMethod(MethodHookParam param) throws Throwable {
      super.afterHookedMethod(param);
      Object currentActivityThread = param.getResult(); // 获取当前ActivityThread对象
      
      Field activityField = currentActivityThread.getClass().getDeclaredField("mActivities");
      activityField.setAccessible(true);

      // 获取Activity集合
      Map<?, ?> activities = (Map<?, ?>) activityField.get(currentActivityThread);
      for (Object activityRecord : activities.values()) {
            Class<?> activityRecordClass = activityRecord.getClass();
            Field activityField2 = activityRecordClass.getDeclaredField("activity");
            activityField2.setAccessible(true);
            
            // 获取Activity对象
            Activity currentActivity = (Activity)activityField2.get(activityRecord);
            // currentActivity就为当前正在运行的Acticity对象
      }
    }
});
```
最后的`currentActivity`就为当前正在运行的Acticity对象
> 使用此方法,每当***打开另一个Activity***或***当前Activity被改变***,都会自动重新获取当前Activity

***这里只介绍了两种获取Activity对象的方法,但其实不止这两种。请根据实际情况选择使用!***

## 2.获取Activity的根布局
#### ①介绍:
前一个方法介绍了如何获取Activity对象,现在有了对象,就可以进行各种操作,这里说说获取布局

#### ②步骤:
##### (1)拿到Activity对象
通过方法1拿到Activity对象

##### (2)通过findViewById拿到布局
> 有些新手可能会问为什么 Activity 中有 `setContentView(int id)` 这个方法却没有 `getContentView` 这个方法呢?
> 这个我真没办法回答,但是我能告诉你怎么去通过Activity对象拿到根布局

其实通过Activity对象来获取根布局的方法有很多种,比如:
* getRootView()
但是通常拿不到id,这是为什么呢?这是因为拿到的getRootView 是在ContentView外的DecorView。
      > 在《Android群英传》中说到:Android的Window对象由PhoneWindow实现, DecorView将显示内容呈现在PhoneWindow上,所有的View监听由 WindowManagerService接收,通过Activity对象回调onClickListener; DecorView包含TitleView 和ContentView。
这就是为什么 requestWindowFeature(Window.FEATURE_NO_TITLE)要放在setContent之前。
---来自(https://baike.baidu.com/item/Android%E7%BE%A4%E8%8B%B1%E4%BC%A0/18525972?fr=ge_ala "Android群英传")

* getWindow().getDecorView().getRootView()
此方法高版本安卓获取不到
* 等

这些方法要么高版本获取不到,要么耗时巨大。下面作者介绍的这个方法则将完美避开这些问题。
具体代码如下:
```java
//通过方法1拿到mainActivity
ViewGroup rootLayout = (ViewGroup)mainActivity.findViewById(android.R.id.content);
```
这个rootLayout就是根布局,为ViewGroup类型。
有了根布局就可以对Activity中的组件进行操作,例如:
```java
//获取根布局下的第一个View
View view = rootLayout.getChildAt(0);

//删除根布局下的第一个View
rootLayout.removeViewAt(0);

//向根布局中添加View
TextView textView = new TextView(mainActivity);
textView.setText("我是新添加的TextView");
rootLayout.addView(textView);

//......
```

#### ③情景模拟:
当我们在Hook某个应用时,发现`mainActivity`中有一个广告一直阻挡我们的视线,十分苦恼。但又不想去分析广告代码,怎么才能去除呢?
其实我们可以通过Hook得到这个广告所在页面的根布局,然后通过布局助手等软件,找到这个广告组件的id,然后进行去除。如果没有id,那么就只有分析布局结构,然后拿到这个广告组件的对象进行去除。
这里我们假设拿不到这个id。
通过布局结构分析发现这个广告组件布局结构如下:
* 根布局
    * RelativeLayout
      * ADView (广告组件)
      * xxx
    * LinearLayout
    * xxx

##### (1)先通过根布局拿到RelativeLayout
根据布局结构分析,RelativeLayout为根布局下第一个组件
使用 `getChildAt(int id)` 方法获取RelativeLayout:
```java
//通过方法2拿到根布局 rootLayout
RelativeLayout relativeLayout = (RelativeLayout) rootLayout.getChildAt(0);
```

##### (2)再通过RelativeLayout删除广告组件
根据布局结构分析,广告组件为 RelativeLayout 下的第一个组件
使用 `removeViewAt(int id)` 方法删除广告组件:
```java
relativeLayout.removeViewAt(0);
```
至此,这个广告组件已经被我们成功去除!

## 3.获取ApplicationContext
#### ①介绍:
实用方法1讲述了如何获取Activity对象,但是有些时候我们并不需要Activity对象也懒得去分析Activity所在的类名。我们只需要一个ApplicationContext(应用上下文)
并且我们只需要在应用启动时Hook得到它,并把它存在变量中,后续就可以直接拿来使用
并且ApplicationContext不会因为当前Activity的改变而无法使用。它随应用启动而创建,随应用终止而销毁。在应用的任何一个地方都可以使用它。

***下面介绍两种不同的获取方法,请根据实际情况选择使用***
### 法一:通过Application获取Context
该方法通过Hook应用启动时的`Application`类中的`attach`方法来拿到Context

#### ②步骤:
##### (1)Hook `Application` 类中的 `attach` 方法
`attach`方法中的参数就是我们需要的Context
代码如下:
```java
XposedHelpers.findAndHookMethod(Application.class,"attach",new XC_MethodHook(){
    @Override
    protected void afterHookedMethod(MethodHookParam param) throws Throwable {
      super.afterHookedMethod(param);
      Context context= (Context) param.args;
      //这个context就为我们需要的ApplicationContext
    }
});
```
最后`context`就为我们需要的ApplicationContext

> ***此方法可用于一些动态加载dex的APP,如各种商业级APP***

#### ③原理分析:
> 为什么`Application`类中的`attach`方法参数是一个Context呢?这个参数又是谁传给它的呢?

由于Android是开源的,我们查阅Android的源代码得到:
##### (1)先由 `ActivityThread.` 中的 `handleBindApplication` 方法创建`Application`
```java
private void handleBindApplication(AppBindData data) {
    ...
    // If the app is Honeycomb MR1 or earlier, switch its AsyncTask
    // implementation to use the pool executor.Normally, we use the
    // serialized executor as the default. This has to happen in the
    // main thread so the main looper is set right.
    if (data.appInfo.targetSdkVersion <= android.os.Build.VERSION_CODES.HONEYCOMB_MR1) {
      AsyncTask.setDefaultExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
    }

    ...
    final ContextImpl appContext = ContextImpl.createAppContext(this, data.info);
    ...
      app = data.info.makeApplication(data.restrictedBackupMode, null);

      ...

      mInitialApplication = app;

      ...
      try {
            mInstrumentation.callApplicationOnCreate(app);
      ...
}
```
可以看到在`handleBindApplication`又调用了`makeApplication`方法构造`Application`

##### (2)再通过 `LoadedApk` 中的 `makeApplication` 构造`Application`
```java
public Application makeApplication(boolean forceDefaultAppClass,
      Instrumentation instrumentation) {
    ...
      ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
      app = mActivityThread.mInstrumentation.newApplication(
                cl, appClass, appContext);
      appContext.setOuterContext(app);
    ...
    return app;
}
```
可以看到`makeApplication`通过`ContextImpl.createAppContext`构造了`contextImpl`,然后用`newApplication`构造了`Application`

##### (3)然后调用`Instrumentation` 的 `newApplication` 方法
```java
public Application newApplication(ClassLoader cl, String className, Context context)
      throws InstantiationException, IllegalAccessException,
      ClassNotFoundException {
    Application app = getFactory(context.getPackageName())
            .instantiateApplication(cl, className);
    app.attach(context);
    return app;
}
```
可以看出`newApplication`先构造了`Application`,最后再调用`attach`方法并传入由`makeApplication`中创建的`context`作为参数

##### (4)最后调用 `Application` 中的 `attach` 方法
```java
/* package */ final void attach(Context context) {
    attachBaseContext(context);
    mLoadedApk = ContextImpl.getImpl(context).mPackageInfo;
}
```
可以看到先调用了`attachBaseContext`,然后给`mLoadedApk`赋值

##### 总结:
`Application`中的`attach`方法是由`Instrumentation`中的`newApplication`方法在Application创建时调用的
传入的参数`Context`是在`LoadedApk`中的`makeApplication`方法创建的

### 法二:通过ActivityThread获取Context
该方法通过Hook`ActivityThread`中的`getSystemContext`方法来获取Context

#### ②步骤:
##### (1)获取当前ActivityThread对象
通过Hook`ActivityThread`中的`currentActivityThread`方法来获取当前ActivityThread对象

##### (2)调用getSystemContext方法
调用当前ActivityThread对象中的`getSystemContext`方法
代码如下:
```java
XposedHelpers.findAndHookMethod("android.app.ActivityThread", lpparam.classLoader, "currentActivityThread",new XC_MethodHook() {
    @Override
    protected void afterHookedMethod(MethodHookParam param) throws Throwable {
      super.afterHookedMethod(param);
      Object currentActivityThread = param.getResult(); // 获取当前ActivityThread对象
      Context context= (Context)XposedHelpers.callMethod(currentActivityThread,"getSystemContext");
      //context即为我们要的ApplicationContext
    }   
});
```
context即为我们要获取的ApplicationContext

## 4.未完待续。。。

# 写在最后
本文为作者学习Xposed Hook时,发现的一些实用,常用方法记录以及一些思路记录,供大家学习!
***本文如有错误,请大佬指正!***

本文已同步发至作者博客:[立即查看](https://blog.wystudio.xyz/index.php/2023/07/01/xposed%e6%a8%a1%e5%9d%97-xposed-hook%e5%ae%9e%e7%94%a8%e6%96%b9%e6%b3%95%e8%af%a6%e8%b0%88/)(无联系方式)

# 参考文档
[《Android群英传》 徐宜生 电子工业出版社](https://baike.baidu.com/item/Android%E7%BE%A4%E8%8B%B1%E4%BC%A0/18525972?fr=ge_ala)
(https://www.cnblogs.com/gordon0918/p/6732100.html)

wystudio 发表于 2023-7-1 21:46

本帖最后由 wystudio 于 2023-7-2 13:08 编辑

小k666 发表于 2023-7-1 21:23
每个版本更新,混淆后类名都不一样,有没有好的自适应方法
其实还可以通过Hook系统中的API来拿到Activity对象,但是不好判断获取的对象为哪一个Activity的。
等我看看,把这个方法也加上

tyc600 发表于 2024-11-13 21:43

如何将hook到的activity对象保存为全局变量,供其他类调用?楼主能不能详细说一下。谢谢&#128522;

klxn0-0 发表于 2023-7-1 17:38

onCreat 应为onCreate吧

wystudio 发表于 2023-7-1 19:39

klxn0-0 发表于 2023-7-1 17:38
onCreat 应为onCreate吧

对,我的问题{:1_937:}

OliverHarrison 发表于 2023-7-1 19:39

谢谢分享!!!!

fnckyon2014 发表于 2023-7-1 20:33

6,刚好最近在学习

正己 发表于 2023-7-1 21:21

内容可以再完善些哦,先提前一威望,期待后续教程内容

小k666 发表于 2023-7-1 21:23

每个版本更新,混淆后类名都不一样,有没有好的自适应方法

wystudio 发表于 2023-7-1 21:39

正己 发表于 2023-7-1 21:21
内容可以再完善些哦,先提前一威望,期待后续教程内容

好的,谢谢版主

wertop 发表于 2023-7-1 21:54

页: [1] 2 3 4 5
查看完整版本: [Xposed模块] Xposed Hook实用方法详谈(7月3日更新)