32

VirtualApp Hook 框架分析

 3 years ago
source link: http://solart.cc/2016/11/25/va_hook/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

VirtualApp Hook 框架分析

Nov 25, 2016

作者:blue

版权声明:本文图文为博主原创,转载请注明出处。

对于插件化框架 Hook 机制是一个核心,那到底 Hook 是什么呢?怎么去理解插件化中的 Hook 呢?在我看来插件化中的 Hook 机制就是通过反射注入动态代理来实现的。

先来说说何为反射注入,大家都知道依赖注入,其实反射注入算是依赖注入的一种,顾名思义,通过反射的方式将依赖对象注入目标对象。举个例子,想要替换掉 ActivityThread 中的 mInstrumentation

/*android.app.ActivityThread.java*/
public final class ActivityThread {
Instrumentation mInstrumentation;

public static ActivityThread currentActivityThread() {
return sCurrentActivityThread;
}
...
}

//Instrumentation代理类
public class InstrumentationDelegate extends Instrumentation {
private Instrumentation base;

public InstrumentationDelegate(Instrumentation base) {
this.base = base;
}
}

public class ReflectInject{

public void reflectInject() throws Exception {
// 根据全类名获取Class
Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
// 获取无参的currentActivityThread函数
Method currentActivityThreadMethod = activityThreadClass.getMethod("currentActivityThread");
// 调用currentActivityThread函数获取当前ActivityThread对象
currentActivityThreadMethod.setAccessible(true);
Object currentActivityThreadObject = currentActivityThreadMethod.invoke(null);
// 获取mInstrumentation字段
Field instrumentationField = activityThreadClass.getDeclaredField("mInstrumentation");
// 破坏封装获取对象
instrumentationField.setAccessible(true);
Object instrumentationObject = instrumentationField.get(currentActivityThreadObject);
// 注入Instrumentation代理对象
if(!(instrumentationObject instanceof InstrumentationDelegate)) {
instrumentationField.set(activityThreadClass, new InstrumentationDelegate((Instrumentation)instrumentationObject));
}
}
}

以上就是对于 mInstrumentation 的反射注入,当然凭借封装可以有更优雅的实现,这里为了方便展示过程粗暴直接。

关于动态代理大家可以参考彻底理解 Java 动态代理这篇文章,写得十分清晰。文章最后也提到了动态代理的局限性,动态代理无法支持对于非接口的类进行代理,所以在 Hook 时一般结合静态代理来特殊处理需要代理的类,比较典型的例子是 android.app.Instrumentation 的代理。好在 Android 系统服务大都通过 Binder 机制来实现的,而 Binder 机制的 C/S 架构对于接口的支持天然的好,这对于整个 Hook 框架中代理类实现的工作量来说就大大的减少了。

2、Hook 框架

我们知道 Hook 本身依赖反射机制,从上面示例上也可以看出,直接使用大量反射导致代码可读性、维护性变得非常差,从代码美观可读性、易维护性上来看,一个可读性强易维护的 Hook 框架显得尤为重要,目前众多开源的插件化框架中 VirtualApp 的 Hook 框架是最优秀的。为什么这么说呢,作者使用了基于注解的反射注入技术,合理的框架设计使得虽然 Hook 的对象非常多,代码却井井有条,不得不赞叹作者 lody 的巧妙构思,让人受益良多。

以下分析基于 master 分支 c493161 版本。

2.1 设计类图

又到了祭出法宝的时候了,废话不多说先看设计类图:

VA Diagram

点击放大查看高清无码大图

2.2 类图解析

首先作者设计了两个接口,一个是 Injectable ,这个接口比较简单,使实现这个接口的类都具备的注入的能力;

public interface Injectable {

void inject() throws Throwable;

boolean isEnvBad();

}

另一个是 IHookObject ,使实现这个接口的类具备管理代理类的 Hook 函数能力。

public interface IHookObject {

void copyHooks(IHookObject from);

Map<String, Hook> getAllHooks();

Hook addHook(Hook hook);

Hook removeHook(String hookName);

void removeHook(Hook hook);

void removeAllHook();

<H extends Hook> H getHook(String name);

Object getProxyInterface();

Object getBaseInterface();

int getHookCount();

}

上面我们提到了 Hook 函数这个概念,怎么理解这个概念呢,因为动态代理的调用是函数级别的,所以 Hook 相当于替换函数实现。再来看 Hook 这个抽象类,这个类定义了 Hook 的处理时机,以及提供一些 Hook 环境的依赖,实现类通过指定代理函数名,可以根据需要在 beforeCallcallafterCall 执行逻辑处理。所以, Hook 的实现类可以理解为代理函数的类象化。

public abstract class Hook {

private boolean enable = true;

public abstract String getName();

public boolean beforeCall(Object who, Method method, Object... args) {
return true;
}

public Object call(Object who, Method method, Object... args) throws Throwable {
return method.invoke(who, args);
}

public Object afterCall(Object who, Method method, Object[] args, Object result) throws Throwable {
return result;
}

public boolean isEnable() {
return enable;
}

public void setEnable(boolean enable) {
this.enable = enable;
}

...

@Override
public String toString() {
return "Hook${ " + getName() + " }";
}
}

来看看抽象类 HookDelegate ,它是 IHookObject 的接口实现,在构造中通过 HookHandler 完成了动态代理,内部维护了 Hook 集合,代码如下。

public abstract class HookDelegate<T> implements IHookObject {

private static final String TAG = HookDelegate.class.getSimpleName();
private T mBaseInterface;
private T mProxyInterface;
/**
* 内部维护的Hook集合
*/
private Map<String, Hook> internalHookMapping = new HashMap<String, Hook>();

@Override
public Map<String, Hook> getAllHooks() {
return internalHookMapping;
}

public HookDelegate(Class<?>... proxyInterfaces) {
// 获取接口,完成动态代理
mBaseInterface = createInterface();
if (mBaseInterface != null) {
if (proxyInterfaces == null) {
proxyInterfaces = HookUtils.getAllInterface(mBaseInterface.getClass());
}
mProxyInterface = (T) Proxy.newProxyInstance(mBaseInterface.getClass().getClassLoader(), proxyInterfaces, new HookHandler());
} else {
VLog.d(TAG, "Unable to build HookDelegate: %s.", getClass().getName());
}
}

public HookDelegate() {
this((Class[]) null);
}

protected abstract T createInterface();

@Override
public void copyHooks(IHookObject from) {
this.internalHookMapping.putAll(from.getAllHooks());
}

// 添加 Hook 函数
@Override
public Hook addHook(Hook hook) {
if (hook != null && !TextUtils.isEmpty(hook.getName())) {
if (internalHookMapping.containsKey(hook.getName())) {
VLog.w(TAG, "Hook(%s) from class(%s) have been added, can't add again.", hook.getName(),
hook.getClass().getName());
return hook;
}
internalHookMapping.put(hook.getName(), hook);
}
return hook;
}

...

/**
* @return 包装后的代理对象
*/
@Override
public T getProxyInterface() {
return mProxyInterface;
}

/**
* @return 原对象
*/
@Override
public T getBaseInterface() {
return mBaseInterface;
}

...

private class HookHandler implements InvocationHandler {
// 动态代理,通过函数名找到对应的 Hook 函数,完成 beforeCall、call、afterCall 的调用
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Hook hook = getHook(method.getName());
try {
if (hook != null && hook.isEnable()) {
if (hook.beforeCall(mBaseInterface, method, args)) {
Object res = hook.call(mBaseInterface, method, args);
res = hook.afterCall(mBaseInterface, method, args, res);
return res;
}
}
return method.invoke(mBaseInterface, args);
} catch (InvocationTargetException e) {
Throwable cause = e.getTargetException();
if (cause != null) {
throw cause;
}
throw e;
}
}
}

}

对于 HookBinderDelegate 这个类,继承自 HookDelegate 扩展了 IBinder 接口,借此方便处理系统 Binder 服务的代理。

public abstract class HookBinderDelegate extends HookDelegate<IInterface> implements IBinder {

private IBinder mBaseBinder;
public HookBinderDelegate(Class<?>... proxyInterfaces) {
super(proxyInterfaces);
init();
}

public HookBinderDelegate() {
super();
init();
}

private void init() {
mBaseBinder = getBaseInterface() != null ? getBaseInterface().asBinder() : null;
addHook(new AsBinder());
}

//此处通过反射替换系统服务
public void replaceService(String name) {
if (mBaseBinder != null) {
ServiceManager.sCache.get().put(name, this);
}
}

//这里Hook asBinder函数,使该函数调用后返回代理Binder对象。
private final class AsBinder extends Hook {

@Override
public String getName() {
return "asBinder";
}

@Override
public Object call(Object who, Method method, Object... args) throws Throwable {
return HookBinderDelegate.this;
}
}

...

public Context getContext() {
return VirtualCore.get().getContext();
}

...

@Override
public IInterface queryLocalInterface(String descriptor) {
return getProxyInterface();
}

...

public IBinder getBaseBinder() {
return mBaseBinder;
}

}

接在在来看 PatchDelegate 这个抽象类,它是 Injectable 的接口实现,依赖 @Patch@ApiLimit 注解将 Hook 类的添加进 Hook 集合;它的泛型为 IHookObject ,这就意味着 HookDelegate HookBinderDelegate 的实现类很容易通过泛型约束,并通过 inject 接口完成注入。

public abstract class PatchDelegate<T extends IHookObject> implements Injectable {

protected T hookDelegate;

protected Object baseObject;

public PatchDelegate() {
this(null);
}

public PatchDelegate(Object baseObject) {
attachInterface(baseObject);
}

protected void attachInterface(Object baseObject) {
this.baseObject = baseObject;
this.hookDelegate = createHookDelegate();
onBindHooks();
afterHookApply(hookDelegate);
}

protected abstract T createHookDelegate();

protected void onBindHooks() {
if (hookDelegate == null) {
return;
}
// 通过 @Patch、@ApiLimit 注解将 Hook 函数添加至代理类的 Hook 集合
Class<? extends PatchDelegate> clazz = getClass();
Patch patch = clazz.getAnnotation(Patch.class);
int version = Build.VERSION.SDK_INT;
if (patch != null) {
Class<?>[] hookTypes = patch.value();
for (Class<?> hookType : hookTypes) {
ApiLimit apiLimit = hookType.getAnnotation(ApiLimit.class);
boolean needToAddHook = true;
if (apiLimit != null) {
int apiStart = apiLimit.start();
int apiEnd = apiLimit.end();
boolean highThanStart = apiStart == -1 || version > apiStart;
boolean lowThanEnd = apiEnd == -1 || version < apiEnd;
if (!highThanStart || !lowThanEnd) {
needToAddHook = false;
}
}
if (needToAddHook) {
addHook(hookType);
}
}

}
}

private void addHook(Class<?> hookType) {
try {
Constructor<?> constructor = hookType.getDeclaredConstructors()[0];
if (!constructor.isAccessible()) {
constructor.setAccessible(true);
}
Hook hook;
if (constructor.getParameterTypes().length == 0) {
hook = (Hook) constructor.newInstance();
} else {
hook = (Hook) constructor.newInstance(this);
}
hookDelegate.addHook(hook);
} catch (Throwable e) {
throw new RuntimeException("Unable to instance Hook : " + hookType + " : " + e.getMessage());
}
}

public Hook addHook(Hook hook) {
return hookDelegate.addHook(hook);
}

protected void afterHookApply(T delegate) {
}

@Override
public abstract void inject() throws Throwable;

public Context getContext() {
return VirtualCore.get().getContext();
}

public T getHookDelegate() {
return hookDelegate;
}
}

最后再来说说 PatchManager ,这个类顾名思义就知道是补丁的管理类,在这里将各个 Patch 完成注入。

public final class PatchManager {

private static final String TAG = PatchManager.class.getSimpleName();

private Map<Class<?>, Injectable> injectTable = new HashMap<>(12);

private PatchManager() {
}

public static PatchManager getInstance() {
return PatchManagerHolder.sPatchManager;
}

void injectAll() throws Throwable {
for (Injectable injectable : injectTable.values()) {
injectable.inject();
}
// XXX: Lazy inject the Instrumentation,
// It is important in many cases.
addPatch(AppInstrumentation.getDefault());
}

public boolean isInit() {
return PatchManagerHolder.sInit;
}

public void init() throws Throwable {
if (PatchManagerHolder.sInit) {
throw new IllegalStateException("PatchManager Has been initialized.");
}
injectInternal();
PatchManagerHolder.sInit = true;

}

private void injectInternal() throws Throwable {
if (VirtualCore.get().isMainProcess()) {
return;
}
if (VirtualCore.get().isServerProcess()) {
addPatch(new ActivityManagerPatch());
addPatch(new PackageManagerPatch());
return;
}
if (VirtualCore.get().isVAppProcess()) {
addPatch(new LibCorePatch());
addPatch(new ActivityManagerPatch());
addPatch(new PackageManagerPatch());
addPatch(HCallbackHook.getDefault());
//以下省略诸多Path
...
}
}

private void addPatch(Injectable injectable) {
injectTable.put(injectable.getClass(), injectable);
}

public <T extends Injectable> T findPatch(Class<T> clazz) {
// noinspection unchecked
return (T) injectTable.get(clazz);
}

public <T extends Injectable> void checkEnv(Class<T> clazz) {
Injectable injectable = findPatch(clazz);
if (injectable != null && injectable.isEnvBad()) {
try {
injectable.inject();
} catch (Throwable e) {
e.printStackTrace();
}
}
}

public <T extends Injectable, H extends IHookObject> H getHookObject(Class<T> patchClass) {
T patch = findPatch(patchClass);
if (patch != null && patch instanceof PatchDelegate) {
// noinspection unchecked
return (H) ((PatchDelegate) patch).getHookDelegate();
}
return null;
}

private static final class PatchManagerHolder {
private static PatchManager sPatchManager = new PatchManager();
private static boolean sInit;
}

}

2.3 过程梳理

如果感觉以上内容不好理解的话,下面的这幅图,可能会缓解这种不适感。

va hook

AccountManagerService Hook 为例,①、② 两步将 AccountBinderDelegate 等 Binder 代理注入至 ServiceManager ,假设触发 ③ getService 获取 AccountManagerService 后,调用 ④ getPwd ,这时 ⑤ getPwd 将通过 HookHandler 动态代理调用到代理类 ⑥ getHook 查找到函数代理对象,然后 ⑦ invoke 完成 Hook 函数 即 getPassword 代理调用。

到这里,整个 Hook 框架大致上就说完了。当然诸多版本的 Rom(官方、第三方)适配还是一个庞大的工作量,这就体现了作者对整个 Android Framework 掌握的功力了,这里额外提一下,作者对于Framework 镜像的处理,也是相当的精妙,这也为整个 Hook 框架的代码可读性贡献了相当一部分的力量,详见项目的 mirror 包 。

3、最后的闲扯

在阅读源码以及优秀开源项目的时候,大多数人都会感到很难读的通,我的一个看法和切身体会是,就像你第一眼看到一个人,肯定感觉十分陌生,而相处过一段时间后,这种陌生感就会慢慢消失,进而你反而会很了解她,知道她的爱好,知道她喜欢吃什么。阅读源码也是这样,短时间内如果无法拿下,又很想理解它,那么就要多花些时间,去读,不断的读和理解,了解源码所涉及的知识,尝试去用自己的理解去揣摩作者的思路,这个期间你需要用适合自己学习的方式(比如画类图,流程图、时序图,只要这种方式对你是有效的不必拘泥于形式)去记录修正你的理解,不断的去逼近作者的想法和思路,这也是一个学习成长的过程。

道阻且长,行则将至。that’s all.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK