概述
在学习Android插件化的过程中有用到Hook相关技术,本篇文章对Hook相关技术做也给简单的介绍,并写两个小Demo,当你了解了Hook之后可能会对你以后的碰到问题时多了一个解题思路
定义
Hook单词的意思就是钩子,那我们在什么时候用到这个钩子呢,如上图所示,在一个事件或者动作执行的过程中,截获相关事件或者动作,加入自己的代码或者替换装自己的代理对象,这就叫Hook
Hook的原理
本文主要是采用java反射机制拿到要执行的对象或者方法就行修改或者替换
**关注点:**在hook的时候我们首先需要找到要Hook的对象,什么样的对象比较好Hook呢,那就是单例和静态变量,单例和静态变量在进程中不容易发生变化,相对容易被定位到,二普通象则比价容易发生变化(随时有可能被销毁),。我们根据这个原则找到所谓的Hook点
以上就是我对Hook的理解,且是还挺简单的,但实践是检验真理的唯一标准,下面我会写两个小Demo
Demo1
本例子Hook的是一个工具类
/**
* 打印机工具类,提供黑白打印和彩色打印
*/
public class PrintUtil {
private static IPrint colorPrint = new ColorPrint(); //彩色打印机
private static IPrint blackWhitePrint = new BlackWhitePrint(); //黑白打印机
public static void colorPrint(String content){
colorPrint.print(content);
}
public static void blackWhitePrint(String content){
blackWhitePrint.print(content);
}
}
工具类如上
private void operate4(){
// HookHelper.hookPrint();
PrintUtil.blackWhitePrint("黑白内容");
PrintUtil.colorPrint("彩色内容");
}
正常结果如上 ,下面我们对PrintUtil进行hook ,首先我们先找Hook点,在PrintUtil中有两个静态变量,这就是我们要找的Hook点 具体代码如下
/**
* 对printUtil进行hook处理
*/
public static void hookPrint(){
try {
Class<?> printClass = Class.forName("com.example.shiyagang.myapplication.util.PrintUtil");
Field colorPrintField= printClass.getDeclaredField("colorPrint");
Field blackWhitePrintField = printClass.getDeclaredField("blackWhitePrint");
colorPrintField.setAccessible(true);
blackWhitePrintField.setAccessible(true);
colorPrintField.set(null,new BlackWhitePrint());
blackWhitePrintField.set(null,new ColorPrint());
}catch (Exception e){
e.printStackTrace();
}
}
我们通过反射对PrintUtil的两个静态变量进行替换
替换完执行结果如下
彩色打印机打出了黑白内容,我们成功了,嘿嘿
Demo2
这个例子我们在context.startActivity的调用链,找到相关的hook点进行替换。我们首先分下context.startActivity的流程,Context.startActivity其实走到了ContextImpl的startActivity
如上图所示最终调用了ActivityThread类的mInstrumentation成员的execStartActivity方法;注意到,ActivityThread 实际上是主线程,而主线程一个进程只有一个,因此这里是一个良好的Hook点
- 我们要拿到ActivityThread的mInstrumentation ,首先得拿到ActivityThread的实例
- ActivityThread类里面有一个静态方法currentActivityThread可以帮助我们拿到ActivityThread的实例
通过以上步骤我们就能进行相关hook了
/**
* 对activityThread进行Hook
*
*/
public static void attachContext() throws Exception{
// 先获取到当前的ActivityThread对象
Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread");
currentActivityThreadMethod.setAccessible(true);
Object currentActivityThread = currentActivityThreadMethod.invoke(null);
// 拿到mInstrumentation 字段
Field mInstrumentationField = activityThreadClass.getDeclaredField("mInstrumentation");
mInstrumentationField.setAccessible(true);
Instrumentation mInstrumentation = (Instrumentation) mInstrumentationField.get(currentActivityThread);
// 创建代理对象
Instrumentation evilInstrumentation = new EvilInstrumentation(mInstrumentation);
// 偷梁换柱
mInstrumentationField.set(currentActivityThread, evilInstrumentation);
}
EvilInstrumentation的代理对象如下:
/**
* Instrumentation 的静态代理类
*/
public class EvilInstrumentation extends Instrumentation {
private static final String TAG = EvilInstrumentation.class.getSimpleName();
// ActivityThread中原始的对象, 保存起来
Instrumentation mBase;
public EvilInstrumentation(Instrumentation base) {
mBase = base;
}
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
Log.e(TAG, "我们Hook了 Activity的启动流程");
try {
Method execStartActivity = Instrumentation.class.getDeclaredMethod(
"execStartActivity",
Context.class, IBinder.class, IBinder.class, Activity.class,
Intent.class, int.class, Bundle.class);
execStartActivity.setAccessible(true);
return (ActivityResult) execStartActivity.invoke(mBase, who,
contextThread, token, target, intent, requestCode, options);
} catch (Exception e) {
throw new RuntimeException("出问题了,去适配吧");
}
}
}
下面我们看下Activity的代码 ,我们在attachBaseContext中进行Hook
private void operate3(){
Intent intent = new Intent(getApplicationContext(),SecondActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
getApplicationContext().startActivity(intent);
}
@Override
protected void attachBaseContext(Context newBase) {
super.attachBaseContext(newBase);
try {
// 在这里进行Hook
HookHelper.attachContext();
} catch (Exception e) {
e.printStackTrace();
}
}
入上图所示我们已经成功了,我们在这只是打印了一个日志,当然你可以干任何事情
总结
到此已经对Hook做了简单的介绍
- 我们需要先找到Hook点 ,静态变量和单例比较好Hook
- 植入我们的代码,可以采用代理的方式进行植入
- 进行偷梁换柱
今年年初我花一个月的时间收录整理了一套知识体系,如果有想法深入的系统化的去学习的,可以点击传送门,我会把我收录整理的资料都送给大家,帮助大家更快的进阶。