前面插件化一和二说了下插桩式加载未安装的APK,主要是重写了getResource和getClassloader两个方法来实现的。以及每个组件要实现一个接口,通过接口注入上下文来达到它的生命周期。
那么插桩式和hook式的实现方式有什么不同呢?
插桩式是怎么加载到插件中的class文件呢,是通过将将APK转化成插件的Classloader,然后想要加载插件的class文件,我们的去拿这个插件的classloader去loadClass。所以是有一个中间者的。
hook式呢是将插件apk融入到了我们的宿主apk,那直接在里面就可以直接loadClass了,在不用这个插件的ClassLoader了,这样的话对于插件和宿主就没什么区别了,不像插桩式有一个中间者。
那么要实现hook式 就要知道android中一个class文件式怎样被加载到内存中去的。其实就是通过PathClassLoader来加载的。
那么我们先看下ClassLoader
任何一个java程序都是由一个或者多个class组成的,在程序运行时,需要将class文件加载到JVM中才可以使用,负责加载这些class文件的就是java的类加载机制。CLassLoader的作用就是加载class文件提供给程序运行时使用,每个Class对象内部都有一个ClassLoader来标示自己是有那个classLoade加载的。
Android app的所有的java文件都是通过PathClassLoader来加载的,那么它的父类是BaseDexClassLoader,还有一个兄弟类是DexClassLoader,那么他们有什么区别呢。
从上面可以看出这两个类的构造函数不同。(在26的源码中DexClassLoader中的optimizedDirectory也废弃了)
PathClassLoader:用于Android应用程序类加载器。可以加载指定的dex,以及jar、zip、apk中的classes.dex
DexClassLoader:加载指定的dex以及jar、zip、apk中的classes.dex。
可以看到创建ClassLoader的时候需要接收一个CLassLoader parent的参数,这个parent的目的就在于实现类加载的委托。
某个类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,一次递归,如果父加载器可以完成加载任务,那么就返回,只有当父加载器无法完成加载任务时,才自己去加载。
因此我们自己创建的ClassLoader:newPathClassLoader("/sdcard/xx.dex",getClassLoader()),并不仅仅只能加载我们的xx.dex中的class。
需要注意的是,findBootstrapClassOrNull 这个方法,当parent为null的时候,去这个BootCLassLoader进行加载,
但是在Android当中的实现:
所以new PathClassLoader("/sdcard/xx.dex",null),是不能加载Activity.class的。
上面分析了加载了一个class,是利用了双亲委托机制,那么要是都找不到那就开始调用自己的findCLass方法
在ClassLoader类中findClass:
任何ClassLoader的子类,都可以重写loadClass和findClass。如果你不想使用双亲委托,就重写loadClas修改实现,重写findClass则表示在双亲委托机制下,父ClassLoader都找不到class的情况下,定义自己去查找一个class。
而我们的PathClassLoader会自己负责加载Activity这样的类,利用双亲委托父类去加载activity,而我们的PathClassLoader没有重写findClass,是在它的父类里面。因此我们可以看看父类的findClass是如何实现的。
可以看到加载PathClassLoader加载class,转化为从DexPathList中加载class了,那么我们看看DexPathList中的findClass
那么从上面分析得到
到这里我们想要加载一个插件的apk ,其实最终加载的是一个dex文件(先说class文件,加载资源后面说),有没有办法吧这个dex文件给转化成一个 Element 对象,给放到 Elemeng数组 当中,这样直接就可以加载我们插件中的类了。
1、首先我们肯定是要得到插件APK的的中DexPathList对象中的dexElement数组
2、插件的dexElements数组我们拿到了,那么是不是要开始拿我们系统里面的 ,我们反射获取,和上面的一样。
3、上面我们获取到了系统和我们插件的dexElement数组,然后我们将这个数组合并到一个新的数组里面去,并且给注入到系统里面
至此,加载插件的一个流程基本就完成了。但是上面只是处理了class文件,没有处理资源。资源的话我们也是采用hook的方式去实现
在宿主的Application中hook这个方法,然后去重写getAsserts和getResources两个方法:
然后在插件的BaseActivity中继续重写getAssets和getResources两个方法
这样就可以完成hook式加载一个未安装的APK了。至此基本就完成了插桩式和Hook式插件化的基本实现。(后面几篇是优化)。
源码的执行是按照一定流程思路进行的,hook就是在源码的执行流程之间插入一步 *** 作,起到拦截,替换的作用;被改变的对象称为hook点,一般将不易发生变化的类作为hook点;
学习hook必须了解代理模式,可以参考我这篇文章: 反射和动态代理
首先需要知道startactivity的流程: Android进阶解密①——activity的启动过程
我们知道startActivity会通过mInstrumentation这个类,我们可以将这个类作为hook点;
自定义一个Instrumentation,在activity的工作过程中通过反射替换原来的Instrumentation,将原来的Instrumentation传到代理类里面,通过method invoke保证原来的功能不变,然后可以添加自己的自定义 *** 作;
首先拿到activity原来的Instrumentation对象,通过原来的Instrumentation构建出一个InstrumentationProxy对象,将Proxy设置给activity,然后只要在startActivity()之前调用这个方法替换就可以了;
Binder Hook 可以 Hook 掉当前进程用到的系统 Service 服务。
以 LocationManager 为例,在获取一个 LocationManager 时分为两步:
(1) 获取 IBinder 对象;
(2) 通过 IBinder 的 asInterface() 方法转化为 LocationMangerService 对象,接着初始化 LocationManager 。
application 层用到的都是 LocationManager 对象。
原理:
整个过程需要利用反射设置一个自定义的 Binder 对象和一个自定义的 Service 对象。由于我们只 Hook 其中一部分的功能,其他功能还需要保留,所以要用动态代理的方式创建自定义对象。
在理解后面的内容前你需要了解这些知识点:
Activity 等类在获取系统 Service 时,都是调用 getSystemService(serviceName) 方法获取的。
Context # getSystemService() 方法最终会调用到 ServiceManager # getService() 方法中。以 LocationManager 对应的 ServiceFetcher 为例,它的 createService() 方法源码如下:
假如我们要 Hook 掉 LocationManager # getLastKnownLocation() 方法(下文都是)。我们要做的就是让
ServiceManager.getService(Context.LOCATION_SERVICE) 返回我们自定义的 Binder 对象。
先看一下这个方法的源码:
sCache 是一个 Map,缓存了已经向系统请求过的 Binder。如果需要让这个方法返回我们自己的 binder 对象,只需要事先往 sCache 中 put 一个自定义的 Binder 对象就行了。
在 put 之前,需要先创建出一个自定义的 Binder。这个 Binder 在被 ILocationManager.Stub.asInterface 处理后,可以返回一个自定义的 LocationManagerService 对象。
先看一下 Binder 的 asInterface() 的实现:
如果把 queryLocalInterface()方法返回一个自定义的Service,使得走 if 语句内部,不走 else,那就算是Hook 成功了。
假设我们想让系统的 LocationManager 返回的位置信息全是在天安门(116.23, 39.54)。那我们需要使得 LocatitionManagerService 的 getLastLocation() 方法 返回的全是 (116.23, 39.54)。
由于我们不能直接拿到系统的这个Service对象,可以先用反射的方式拿到系统的LocationManagerService。然后拦截 getLastLocation() 方法。
原生的Binder对象在调用 queryLocalInterface() 方法时会返回原生的Service对象。我们希望返回3.1中的自定义Service。所以这里拦截 queryLocalInterface() 方法。
有了自定义的 Binder 后,将它注入到 ServiceManger 的 sCache 变量中就完成 Hook 了~
当onClick被调用的时候,Toast和Log都会显示天安门的坐标(116.23, 39.54)。证明Hook成功!
你甚至可以用Binder Hook的方式Hook掉 ActivityManager 。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)