Hook的核心理论无非是两个:反射和动态代理。简而言之,就是要把目标对象替换成我们想要的东西。
通过反射可以获取一个类中的方法和变量。获取到的方法可以执行,并且得到返回值;获取到的变量可以对其赋值。如果方法或者变量被设置为private,在处理前需要先修改其访问权限。下面为个人理解的反射代码实现:
// 获取指定类中的方法
Method method = 类.class.getDeclaredMethod("方法名");
// 打开访问权限
method.setAccessible(true);
// 执行此方法,并且将对象传入;因为方法需要被对象调用
Object object = method.invoke(类对象);
// 获取类中的变量
Class<?> class = Class.forName("包名+类名");
// 根据变量名取到变量
Field field = class.getDeclaredField("变量名");
// 打开访问权限
field.setAccessible(true);
所以,Hook的步骤如下:
- 确定Hook对象
- 找到Hook对象的持有者
- 定义代理类
- 替换掉要hook的对象
先举个没有啥用的🌰。在MainActivity中有一个imageView,虽然可以直接修改,但是现在我们就是要用Hook的方式修改它,看看接下来怎么做。
- 确定Hook对象
对象自然是imageView - 找到Hook对象的持有者
MainActivity就是持有者 - 定义代理类
代理类的作用根据实际情况去写,现在就是要在设置Bitmap时输出Bitmap的大小,实现如下。
public class HookedImageView extends ImageView {
public HookedImageView(Context context) {
super(context);
}
public HookedImageView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public HookedImageView(Context context, @Nullable AttributeSet attrs,
int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public void setImageBitmap(Bitmap bm) {
super.setImageBitmap(bm);
if (bm != null) {
Log.d(TAG, "setImageBitmap: " + bm.getByteCount());
}
}
}
- 替换掉要hook的对象
现在需要写一个hook函数替换对象,为了方便直接就把这个方法写在MainActivity中了,这时是可以直接拿到持有者即此Activity,所以不需要通过反射的方式获取。所以方法实现中只通过反射拿到了类中的变量并进行了替换。
private void hook(Activity activity) {
try {
Class<?> listnerInfoClz = Class.forName("com.ke.demo.MainActivity");
Field field = listnerInfoClz.getDeclaredField("imageView");
field.setAccessible(true);
HookedImageView proxy = new HookedImageView(activity);
field.set(activity, proxy);
} catch (ClassNotFoundException | NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
}
最后打开这个页面,使用 setImageBitmap
设置图片后就看到了如下日志。
需要注意的是替换对象一定要在对象初始化之后,并且在自定义方法前。