Android-Hook-机制-基础

前言

Linus Benedict Torvalds : RTFSC – Read The Funning Source Code

概述

Hook 技术无论对安全还是恶意软件都是十分重要,其本质就是劫持函数调用。通过Hook技术实现对目标程序的远程注入,也可以用来修改自身一些缺陷的代码。Hook的功能,使它能够将自身的代码“融入”被勾住(Hook)的程序的进程中,成为目标进程的一个部分。因为在Android系统中使用了沙箱机制,普通用户程序的进程空间都是独立的,程序的运行彼此间都不受干扰,这就使我们希望通过一个程序改变其他程序的某些行为的想法不能直接实现,但是Hook的出现给我们开拓了解决此类问题的道路。当然,根据Hook对象与Hook后处理的事件方式不同,Hook还分为不同的种类,如消息Hook、API Hook等。

例子

首先我们先来实现一个简单的例子,接着再来详细分析:
这个例子主要是hook startactivity的调用,让其在调用前输出一段日志。

Step 1 创建 Instrumentation 类

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
36
public class EvilInstrumentation extends Instrumentation {
private static final String TAG = "EvilInstrumentation";
// 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) {
// Hook之前, 可以输出你想要的!
System.out.println("xxxx: 执行了startActivity, 参数如下: " + "who = [" + who + "], " +
"contextThread = [" + contextThread + "], token = [" + token + "], " +
"target = [" + target + "], intent = [" + intent +
"], requestCode = [" + requestCode + "], options = [" + options + "]");
// 开始调用原始的方法, 调不调用随你,但是不调用的话, 所有的startActivity都失效了.
// 由于这个方法是隐藏的,因此需要使用反射调用;首先找到这个方法
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) {
// rom修改了 需要手动适配
throw new RuntimeException("do not support!!! pls adapt it");
}
}
}

Step 2 创建 HookHelper 类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class HookHelper {
public static void attachContext() throws Exception {
// 先获取到当前的ActivityThread对象
Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread");
currentActivityThreadMethod.setAccessible(true);
//currentActivityThread是一个static函数所以可以直接invoke,不需要带实例参数
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);
}
}

Step 3 找到适合实际调用

我们选择在程序一开始调用主Activity的时候就调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
}
@Override
protected void attachBaseContext(Context newBase) {
super.attachBaseContext(newBase);
try {
HookHelper.attachContext();
} catch (Exception e) {
e.printStackTrace();
}
}
}

ok, 当你再次调用新的activity的时候就会输出一句话了,接下来我们详细分析下它的工作原理。

详细解析

Instrumentation

Base class for implementing application instrumentation code. When running with instrumentation turned on, this class will be instantiated for you before any of the application code, allowing you to monitor all of the interaction the system has with the application. An Instrumentation implementation is described to the system through an AndroidManifest.xml <instrumentation> tag. 实施应用程序代码的基础类。 当开启instrumentation运行时,此类将在任何应用程序代码之前为您实例化,从而允许您监视系统与应用程序的所有交互。 通过AndroidManifest.xml的instrumentation向系统描述了Instrumentation实现。

使用Instrumentation,可以在主程序启动之前,创建模拟的系统对象,如Context;控制应用程序的多个生命周期;发送UI事件给应用程序;在执行期间检查程序状态。 Instrumentation框架通过将主程序和测试程序运行在同一个进程来实现这些功能。

Instrumentation 有很多方法可以归我们使用,例如 execStartActivity(…) 就是调用 Activity的方法。我们通过创建一个我们的Instrumentation类继承它,并且覆写父类的方法来实现我们想要做的事。

在 Step 1 的时候我们就生成了一个Instrumentation,并且让它覆写了父类的execStartActivity方法。

Field

A Field provides information about, and dynamic access to, a single field of a class or an interface. The reflected field may be a class (static) field or an instance field. A Field permits widening conversions to occur during a get or set access operation, but throws an IllegalArgumentException if a narrowing conversion would occur. 字段提供有关类或接口的单个字段的信息和动态访问。 反射的字段可以是类(静态)字段或实例字段。 A字段允许在获取或设置访问操作期间扩展转换,但如果发生缩小转换,则会引发IllegalArgumentException。

Field 是反射技术用到的一个类,通过它可以将反射过来的类的内部函数、变量随意使用。

Field 包含了四个常用方法:

  • getFields()
  • getField(String)
  • getDeclaredFields()
  • getDeclaredField(String)

在 Step 2 的时候我们通过反射技术拿到了 系统里进程的Instrumentation,通过替换成我们自己的Instrumentation来警醒hook注入。

Hook 关键点

Hook 的最关键的步骤其实是找到Hook点,只有找到这个点才能进行接下来的注入。所以细心找到这个关键点非常重要。
寻找Hook点,原则是静态变量或者单例对象,尽量Hook pulic的对象和方法,非public不保证每个版本都一样,需要适配。

总结

看了Hook 机制的基本使用和细节详解,差不多有个大致了解了,其实Hook 很简单,分为三步:

  1. 寻找 Hook点,原则是静态变量或者单例对象,尽量Hook pulic的对象和方法。
  2. 找到合理的代理方法,例如动态代理或者静态代理。
  3. 就是用代理的对象替换原有的对象进行工作。