【Xposed】hook接口分析

上篇 【Xposed】Android-Hook初探 留坑
想要hook资源所需实现的三个接口,分别为:

  • IXposedHookZygoteInit
  • IXposedHookLoadPackage
  • IXposedHookInitPackageResources

IXposedHookZygoteInit

接口源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package de.robv.android.xposed;

/**
* Hook the initialization of Zygote process(es), from which all the apps are forked.
*
* <p>Implement this interface in your module's main class in order to be notified when Android is
* starting up. In {@link IXposedHookZygoteInit}, you can modify objects and place hooks that should
* be applied for every app. Only the Android framework/system classes are available at that point
* in time. Use {@code null} as class loader for {@link XposedHelpers#findAndHookMethod(String, ClassLoader, String, Object...)}
* and its variants.
*
* <p>If you want to hook one/multiple specific apps, use {@link IXposedHookLoadPackage} instead.
*/
public interface IXposedHookZygoteInit extends IXposedMod {
/**
* Called very early during startup of Zygote.
* @param startupParam Details about the module itself and the started process.
* @throws Throwable everything is caught, but will prevent further initialization of the module.
*/
void initZygote(StartupParam startupParam) throws Throwable;

/** Data holder for {@link #initZygote}. */
final class StartupParam {
/*package*/ StartupParam() {}

/** The path to the module's APK. */
public String modulePath;

/**
* Always {@code true} on 32-bit ROMs. On 64-bit, it's only {@code true} for the primary
* process that starts the system_server.
*/
public boolean startsSystemServer;
}
}

实现IXposedHookZygoteInit接口,在Zygote进程启动之前执行initZygote方法,该方法只会执行两次次,在实际操作过程中可以用于工具类的初始化,避免在IXposedHookLoadPackage中会重复加载

1
2
3
4
@Override
public void initZygote(StartupParam startupParam) throws Throwable {

}

StartupParam有两个参数

1
2
3
4
5
6
7
8
9
10
11
12
final class StartupParam {
/*package*/ StartupParam() {}

/** The path to the module's APK. */
public String modulePath;

/**
* Always {@code true} on 32-bit ROMs. On 64-bit, it's only {@code true} for the primary
* process that starts the system_server.
*/
public boolean startsSystemServer;
}
  • modulePath:此软件的路径
  • startsSystemServer:在32位ROM上始终{代码真}。在64位上,它只对主代码是{真}。

重启手机

1
2
3
4
[ (Home_X.java:30)#initZygote ] /data/app/com.liompei.homex-1/base.apk
[ (Home_X.java:31)#initZygote ] true
[ (Home_X.java:30)#initZygote ] /data/app/com.liompei.homex-1/base.apk
[ (Home_X.java:31)#initZygote ] false

IXposedHookLoadPackage

加载包时开始hook

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/**
* Get notified when an app ("Android package") is loaded.
* This is especially useful to hook some app-specific methods.
*
* <p>This interface should be implemented by the module's main class. Xposed will take care of
* registering it as a callback automatically.
*/
public interface IXposedHookLoadPackage extends IXposedMod {
/**
* This method is called when an app is loaded. It's called very early, even before
* {@link Application#onCreate} is called.
* Modules can set up their app-specific hooks here.
*
* @param lpparam Information about the app.
* @throws Throwable Everything the callback throws is caught and logged.
*/
void handleLoadPackage(LoadPackageParam lpparam) throws Throwable;

/** @hide */
final class Wrapper extends XC_LoadPackage {
private final IXposedHookLoadPackage instance;
public Wrapper(IXposedHookLoadPackage instance) {
this.instance = instance;
}
@Override
public void handleLoadPackage(LoadPackageParam lpparam) throws Throwable {
instance.handleLoadPackage(lpparam);
}
}
}

接口IXposedHookLoadPackage实现handleLoadPackage方法.在加载app时调用,该方法会在Application.onCreate()前执行,并且携带一个XC_LoadPackage.LoadPackageParam lpparam返回过来,LoadPackageParam的详细信息包括

  • packageName 应用包名
  • processName 应用所在进程
  • classLoader 类加载器
  • appInfo 应用程序的ApplicationInfo
  • isFirstApplication 是否首次打开应用程序

举例说明,当我们要hook微信,则可以这样写

1
2
3
4
5
6
7
8
@Override
public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) throws Throwable {
if (lpparam.packageName.equals("com.tencent.mm")){
Zx.d("包名"+lpparam.packageName);
Zx.d("所在进程"+lpparam.processName);
Zx.d("是否首次启动应用"+lpparam.isFirstApplication);
}
}

这样安装之后,打钩重启,当微信的应用程序被启动时,就会调用这里的方法

(hook的大部分操作都会在handleLoadPackage方法中书写)

实例

在MainActivity中,有下面的一个方法

1
2
3
private void setData(String s){
Toast.makeText(this,s,Toast.LENGTH_SHORT).show();
}

我的测试应用,包名为com.liompei.homex

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Override
public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) throws Throwable {
if (lpparam.packageName.equals("com.liompei.homex")){
XposedHelpers.findAndHookMethod("com.liompei.homex.MainActivity", lpparam.classLoader, "setData", String.class, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
//在setData方法执行前调用
String s= (String) param.args[0];
//s即为传入的值
//修改传入的值
param.args[0]="你好";
}

@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
//在setData方法执行后调用
//这里不需要修改返回值,所以没有修改的意义
//修改返回值使用 param.setResult();
}
});
}
}

IXposedHookInitPackageResources

资源布局初始化时进行hook,如果要使用此类,必须在xposed->设置中,将[禁用资源勾子]一项取消打钩

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/**
* Get notified when the resources for an app are initialized.
* In {@link #handleInitPackageResources}, resource replacements can be created.
*
* <p>This interface should be implemented by the module's main class. Xposed will take care of
* registering it as a callback automatically.
*/
public interface IXposedHookInitPackageResources extends IXposedMod {
/**
* This method is called when resources for an app are being initialized.
* Modules can call special methods of the {@link XResources} class in order to replace resources.
*
* @param resparam Information about the resources.
* @throws Throwable Everything the callback throws is caught and logged.
*/
void handleInitPackageResources(InitPackageResourcesParam resparam) throws Throwable;

/** @hide */
final class Wrapper extends XC_InitPackageResources {
private final IXposedHookInitPackageResources instance;
public Wrapper(IXposedHookInitPackageResources instance) {
this.instance = instance;
}
@Override
public void handleInitPackageResources(InitPackageResourcesParam resparam) throws Throwable {
instance.handleInitPackageResources(resparam);
}
}
}

实现接口重写handleInitPackageResources方法

1
2
3
4
@Override
public void handleInitPackageResources(XC_InitPackageResources.InitPackageResourcesParam resparam) throws Throwable {

}

resparam中包含了packageNameXResources,通过XResources拿到资源文件即可进行修改view参数的操作

实例

在MainActivity的资源文件activity_main.xml,打印并拿到view对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@Override
public void handleInitPackageResources(XC_InitPackageResources.InitPackageResourcesParam resparam) throws Throwable {
if (resparam.packageName.equals("com.liompei.homex")){
resparam.res.hookLayout(resparam.packageName, "layout", "activity_main", new XC_LayoutInflated() {
@Override
public void handleLayoutInflated(LayoutInflatedParam liparam) throws Throwable {
printView((ViewGroup) liparam.view, 1);
}
});
}
}

//遍历资源布局树,并打印出来
private void printView(ViewGroup view, int deep) {
String viewgroupDeepFormat = "";
String viewDeepFormat = "";
for (int i = 0; i < deep - 1; i++) {
viewgroupDeepFormat += "\t";
}
viewDeepFormat = viewgroupDeepFormat + "\t";
XposedBridge.log(viewgroupDeepFormat + view.toString());
Zx.d(viewgroupDeepFormat + view.toString());
int count = view.getChildCount();
for (int i = 0; i < count; i++) {
if (view.getChildAt(i) instanceof ViewGroup) {
printView((ViewGroup) view.getChildAt(i), deep + 1);
} else {
XposedBridge.log(viewDeepFormat + view.getChildAt(i).toString());
Zx.d(viewDeepFormat + view.getChildAt(i).toString());
}
}
}

需要注意的是,如果手机的xposed框架不支持资源勾子,会在Xposed->日志中显示

1
This class requires resource-related hooks(which are disabled),skipping it.