目的
这篇文章是让大家体会hook技术大概是什么样子,在那些地方可以应用到。后面文章将深入hook技术,以及解析市面上现有的hook开源框架原理。
在前面的文章中我们已经说过什么是代理模式,那我们就先使用最简单的静态代理模式应用在我们的hook技术中。我们先复习一下静态代理模式。
/**
* 定义Demo接口
*/
public interface Demo {
public void save();
}
/**
* DemoImpl实现Demo接口并覆写save()方法
* 真实主题,执行具体业务
*/
public class DemoImpl implements Demo {
public void save() {
System.out.println("调用save()方法");
}
}
/**
* DemoImplProxy 也实现了Demo接口,并覆写了save()方法,增加了自己的业务
* 代理主题,负责其他业务的处理
*/
public class DemoImplProxy implements Demo {
Demo demoImpl = new DemoImpl();
public void save() {
System.out.println("开始记录日志");
demoImpl.save();
System.out.println("开始结束日志");
}
}
我们看出来,代理是对原有功能的拓展,而且很多时候我们在开发的过程中并不能改变我们原有的功能,比如系统打电话,startActivity等等。这时候我们就想,如果我们对他们进行拓展,让他们添加我们自己的一些元素,是不是就可以达到意想不到的效果。所以我们就引出了Hook的概念。其实Hook就是干偷梁换柱之事。
项目代码
++分析在下面模块++
我先把项目写上去,然后我们在分析,这样我觉得比先一大堆理论要好。我们目的很简单就是让我们在startActivity的时候我们拓展一下我们的功能(打印一些信息)
首先实现自己的Application子类,原因是这里执行比较早
public class APP extends Application {
@Override
public void onCreate() {
super.onCreate();
setEvilInstrumentation();
}
public void setEvilInstrumentation() {
try {
//先获取到当前的ActivityThread对象
Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread");//得到这个方法
currentActivityThreadMethod.setAccessible(true);
Object currentActivityThread = currentActivityThreadMethod.invoke(null);//此时得到的就是ActivityThread(因为在attach的时候调用赋值)
Field mInstrumentationField = activityThreadClass.getDeclaredField("mInstrumentation");//得到ActvitiyThread中的mInstrumentation字段
mInstrumentationField.setAccessible(true);
Instrumentation mInstrumentation = (Instrumentation) mInstrumentationField.get(currentActivityThread);
//创建代理对象
EvilInstrumentation evilInstrumentation = new EvilInstrumentation(mInstrumentation,APP.this);
mInstrumentationField.set(currentActivityThread, evilInstrumentation);//将系统中mInstrumentation换成自己的EvilInstrumentation
} catch (Exception e) {
e.printStackTrace();
}
}
}
我们要替换的类EvilInstrumentation
public class EvilInstrumentation extends Instrumentation {
private static final String TAG = "EvilInstrumentation";
// ActivityThread中原始的对象, 保存起来
Instrumentation mBase;
private Context mContext = null;
public EvilInstrumentation(Instrumentation base) {
mBase = base;
}
public EvilInstrumentation(Instrumentation mInstrumentation, APP app) {
this(mInstrumentation);
mContext = app.getApplicationContext();
}
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
Toast.makeText(mContext, "\n执行了startActivity, 参数如下: \n" + "who = [" + who + "], " +
"\ncontextThread = [" + contextThread + "], \ntoken = [" + token + "], " +
"\ntarget = [" + target + "], \nintent = [" + intent +
"], \nrequestCode = [" + requestCode + "], \noptions = [" + options + "]", Toast.LENGTH_LONG).show();
// 由于这个方法是隐藏的,因此需要使用反射调用;首先找到这个方法
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("需要重新适配了,framework层代码被谷歌改了");
}
}
}
MainActivity
public class MainActivity extends AppCompatActivity {
private Button btn ;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btn = findViewById(R.id.btn);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startActivity(new Intent(MainActivity.this,Activity2.class));
}
});
}
}
public class Activity2 extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_2);
}
}
代码分析
原本如果我们要处理一件事物,我们就需要如下使用
graph LR
A-->B.Method
A直接调用B的方法,当我们Hook的时候就需要多一层调用。
将B完全封装到C中,A直接和C进行通信即可,在我们项目中如何体现呢?
我们项目中,就是通过Hook将原本Android framework中startActivity过程的一个处理方法拦截下来,从而执行我们的方法。具体做法如下:
这个就是我们的核心方法
public void setEvilInstrumentation() {
try {
//先获取到当前的ActivityThread对象
Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread");//得到这个方法
currentActivityThreadMethod.setAccessible(true);
Object currentActivityThread = currentActivityThreadMethod.invoke(null);//此时得到的就是ActivityThread(因为在attach的时候调用赋值)
Field mInstrumentationField = activityThreadClass.getDeclaredField("mInstrumentation");//得到ActvitiyThread中的mInstrumentation字段
mInstrumentationField.setAccessible(true);
Instrumentation mInstrumentation = (Instrumentation) mInstrumentationField.get(currentActivityThread);
//创建代理对象
EvilInstrumentation evilInstrumentation = new EvilInstrumentation(mInstrumentation,APP.this);
mInstrumentationField.set(currentActivityThread, evilInstrumentation);//将系统中mInstrumentation换成自己的EvilInstrumentation
} catch (Exception e) {
e.printStackTrace();
}
}
我们首先要熟悉startActivity的大概流程,要知道从哪里拦截比较好,这样我们心中就有一个目的,从而通过反射达到那个目的。
小结过程
- 获取ActivityThread类的Class(这样就能得到其中所有的方法和变量)
- 获取ActivityThread的currentActivityThread方法
- 执行currentActivityThread方法就会返回ActivityThread实例
- 通过ActivityThread实例我们拿到mInstrumentation成员变量
- 通过mInstrumentation成员变量我们就可以对这个变量进行代理,因为他是启动Activity的核心
- 将mInstrumentation对象包装到EvilInstrumentation实例中
- 将我们的系统中启动Activity的核心类mInstrumentation替换成EvilInstrumentation
- 然后startActivity就会执行我们代理类的逻辑也就是(execStartActivity)方法中的内容
但是有坑,有的委托类不让继承,我们就没办法控制其他类调用,怎么办?
- 第一种办法就是将Instrumentation类中所有方法进行代理(好处就是对于每个方法有不同的处理逻辑)
- 使用动态代理(不管执行什么方法都会执行我们动态代理里面写的逻辑)
下一节,我们继续拓展Hook技术