缘起
正如各位坛友所见,目前论坛中已经有许多Xposed模块编写教程,但都是基础教程或针对于某一应用。仍需根据所Hook的应用来实际调整。
本文将列举并谈及一些常用Hook方法(适用于大部分应用),以方便新人的学习。
PS:本文为基础进阶教程,需要有一定的Xposed Hook,Java基础。若您为新手建议先观看一下教程:
@正己:
七、Sorry,会Hook真的可以为所欲为-Xposed快速上手(上)模块编写
八、Sorry,会Hook真的可以为所欲为-xposed快速上手(下)快速hook
实用方法列表
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
为例
(2)找到这个Activity的 onCreate 方法的实现方法名。
如果被Hook的应用没有被混淆,那名称就为 onCreate
。如果被混淆了,那就把应用拖入jsdx或用MT管理器等工具,去这个Activity的smail代码中找到名称。并记下这个名称。
这里假设没有被混淆,方法名为 onCreate
:
(3)Hook这个方法即可
现在我们得到了指定Activity的类名和 onCreate
实现方法名。
最后我们只需要Hook此类中的 onCreate
实现方法即可拿到Activity对象,具体代码如下:
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
代码如下:
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对象来获取根布局的方法有很多种,比如:
这些方法要么高版本获取不到,要么耗时巨大。下面作者介绍的这个方法则将完美避开这些问题。
具体代码如下:
//通过方法1拿到mainActivity
ViewGroup rootLayout = (ViewGroup)mainActivity.findViewById(android.R.id.content);
这个rootLayout就是根布局,为ViewGroup类型。
有了根布局就可以对Activity中的组件进行操作,例如:
//获取根布局下的第一个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
- LinearLayout
- xxx
(1)先通过根布局拿到RelativeLayout
根据布局结构分析,RelativeLayout为根布局下第一个组件
使用 getChildAt(int id)
方法获取RelativeLayout:
//通过方法2拿到根布局 rootLayout
RelativeLayout relativeLayout = (RelativeLayout) rootLayout.getChildAt(0);
(2)再通过RelativeLayout删除广告组件
根据布局结构分析,广告组件为 RelativeLayout 下的第一个组件
使用 removeViewAt(int id)
方法删除广告组件:
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
代码如下:
XposedHelpers.findAndHookMethod(Application.class,"attach",new XC_MethodHook(){
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
super.afterHookedMethod(param);
Context context= (Context) param.args[0];
//这个context就为我们需要的ApplicationContext
}
});
最后context
就为我们需要的ApplicationContext
此方法可用于一些动态加载dex的APP,如各种商业级APP
③原理分析:
为什么Application
类中的attach
方法参数是一个Context呢?这个参数又是谁传给它的呢?
由于Android是开源的,我们查阅Android的源代码得到:
(1)先由 ActivityThread.
中的 handleBindApplication
方法创建Application
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
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
方法
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
方法
/* 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
该方法通过HookActivityThread
中的getSystemContext
方法来获取Context
②步骤:
(1)获取当前ActivityThread对象
通过HookActivityThread
中的currentActivityThread
方法来获取当前ActivityThread对象
(2)调用getSystemContext方法
调用当前ActivityThread对象中的getSystemContext
方法
代码如下:
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时,发现的一些实用,常用方法记录以及一些思路记录,供大家学习!
本文如有错误,请大佬指正!
本文已同步发至作者博客:立即查看(无联系方式)
参考文档
《Android群英传》 徐宜生 电子工业出版社
Android Studio Xposed模块编写(二) -Gordon0918 - 博客园