这是一很有想法的框架,基于核心原理我来学习一下。
Virtual app
基础理论的补充
代理hook
简单理解就是利用反射在运行的程序中
替换原有的字段或方法为自己改写为代理字段方法。
- 动态代理在调用过程中直接实现代理对象,而不用每个类都写一个代理对象。
由于JDK动态代理只支持接口,而这个Instrumentation是一个类,没办法,我们只有手动写静态代理类,覆盖掉原始的方法即可。
这里没理解。 只支持接口。解释方法函数了。那就懂了。
- 个人理解,生命周期,就是运行在程序过程中的各个实例在不同阶段的内部字段会有不同的状态。
而反射调用一般只能得到对象,如果没有获得生命周期,也就是不能获得相应的对象实例,除了静态方法,其他用处就不大了。
在Proxy.newProxyInstance方法中,共有三个参数:
1、targetObject.getClass().getClassLoader()目标对象通过getClass方法获取类的所有信息后,调用getClassLoader()
方法来获取类加载器。获取类加载器后,可以通过这个类型的加载器,在程序运行时,将生成的代理类加载到JVM即Java虚拟机中,以便运行时需要!
2、targetObject.getClass().getInterfaces()获取被代理类的所有接口信息,以便于生成的代理类可以具有代理类接口中的所有方法。
3、this:我们使用动态代理是为了更好的扩展,比如在方法之前做什么,之后做什么等操作。这个时候这些公共的操作可以统一交给代理类去做。
这个时候需要调用实现了InvocationHandler 类的一个回调方法。由于自身变实现了这个方法,所以将this传递过去。
这个函数实现代理的实现
public Object invoke(Object obj, Method method, Object[] args) throws Throwable {
//看了半天和hook框架基本一样,做一些hook后的事。但函数的接口过滤也是在这里实现的。
System.out.println("the fish price higher");
return (Integer)method.invoke(sell, args)+10;//这里调用了原函数
}
通过invoke实现接口的替换。Proxy.newProxyInstance获取被代理的对象。
代码例子
- 动态代理实现了拥有相同特征类的统一代理模式。
hook Binder
ServiceManager为了避免每次都进行跨进程通信,把这些Binder代理对象缓存在一张map里面。
public class BinderProxyHookHandler implements InvocationHandler {
private static final String TAG = "BinderProxyHookHandler";
// 绝大部分情况下,这是一个BinderProxy对象
// 只有当Service和我们在同一个进程的时候才是Binder本地对象
// 这个基本不可能
IBinder base;
Class<?> stub;
Class<?> iinterface;
public BinderProxyHookHandler(IBinder base) {
this.base = base;
try {
this.stub = Class.forName("android.content.IClipboard$Stub");//这一句和下一句不知道有啥区别
this.iinterface = Class.forName("android.content.IClipboard");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("queryLocalInterface".equals(method.getName())) {
Log.d(TAG, "hook queryLocalInterface");
// 这里直接返回真正被Hook掉的Service接口
// 这里的 queryLocalInterface 就不是原本的意思了
// 我们肯定不会真的返回一个本地接口, 因为我们接管了 asInterface方法的作用
// 因此必须是一个完整的 asInterface 过的 IInterface对象, 既要处理本地对象,也要处理代理对象
// 这只是一个Hook点而已, 它原始的含义已经被我们重定义了; 因为我们会永远确保这个方法不返回null
// 让 IClipboard.Stub.asInterface 永远走到if语句的else分支里面
return Proxy.newProxyInstance(proxy.getClass().getClassLoader(),
// asInterface 的时候会检测是否是特定类型的接口然后进行强制转换
// 因此这里的动态代理生成的类型信息的类型必须是正确的
new Class[] { IBinder.class, IInterface.class, this.iinterface },
new BinderHookHandler(base, stub));
}
Log.d(TAG, "method:" + method.getName());
return method.invoke(base, args);
}
}
ams和pms的hook
这一段作者分析了半天ASM 和PMS在调用过程中的获取过程,但最后的结论是只要我们把mInstanceField里面的字段替换成我们的代理服务,就实现了Hook。
插件化启动替换过程
先从App进程调用startActivity;然后通过IPC调用进入系统进程system_server,完成Activity管理以及一些校检工作,最后又回到了APP进程完成真正的Activity对象创建。
由于这个检验过程是在AMS进程完成的,我们对system_server进程里面的操作无能为力,只有在我们APP进程里面执行的过程才是有可能被Hook掉的,也就是第一步和第三步;
既然需要一个显式声明的Activity,那就声明一个!可以在第一步假装启动一个已经在AndroidManifest.xml里面声明过的替身Activity,让这个Activity进入AMS进程接受检验;最后在第三步的时候换成我们真正需要启动的Activity;这样就成功欺骗了AMS进程,瞒天过海!
Android 插件化原理解析——Activity生命周期管理
我们Hook掉ActivityManagerNative对于startActivity方法的调用,替换掉交给AMS的intent对象,将里面的TargetActivity的暂时替换成已经声明好的替身StubActivity;
系统加载Activity
ClassLoader机制
这一部分学习加壳脱壳的时候弄过,就不详细写了。
java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);
StrictMode.incrementExpectedActivityCount(activity.getClass());
r.intent.setExtrasClassLoader(cl);
这里利用两种方法:
激进方式:Hook掉ClassLoader,自己操刀
这个方法直接把mPackages (这个静态成员变量缓存了dex包的信息)的数组添加了自己插件的信息进去。可惜直接调用还是会出错。因为PMS会对安装过的插件做个检查,这里还是hook PMS ,欺骗系统验证。保守方案:委托系统,让系统帮忙加载
这个上一个区别好像是替换了宿主的加载器的 DexPathList内部有一个叫做dexElements的数组