现在回头看"限制原理"中论述的两个区分,其实只要我们能够混淆任何一个区分点都能够成功绕过此限制。混淆第一个区分点,会让系统错误地认为原本隐藏的API是公开的;混淆第二个区分点,会让系统错误地将用户代码调用识别为系统代码调用。方法二的核心思想就是混淆第二个区分点。
关注第二个区分点,可以发现,其实只要在BootStrapClassLoader加载的类中有任何一个帮助我们进行反射的类就能绕过这个问题,那么我们能否将我们apk中定义的类的ClassLoader改为BootStrapClassLoader呢?答案是肯定的!查看art/runtime/mirror/class.h可知SetClassLoader函数可以为一个类指定ClassLoader,用IDA查看/system/lib/libart.so确认此函数位于导出符号表中。SetClassLoader的第一个参数类型为ObjPtr<mirror::Class>,如何将jclass转化为此类型呢?通过在Android源码中查找,在art/runtime/well_known_classes.h中有一个非常合适的函数ToClass能够完成此任务,其声明如下
查看libart.so可知,ToClass函数也在其导出符号表中,因此ToClass函数是一个恰当的函数。方法二的具体实现代码见下图
其中,my_dlsym与dlsym类似,其功能是根据函数的导出符号寻找函数在进程中的地址。my_dlsym是我们自定义的一个函数。
makeHiddenApiAccessable调用成功之后,使用com.test.hidefix.ReflectionHelper类反射寻找隐藏API,不会再出现log警告,成功!
实际工程中使用时可以将ReflectionHelper类作为一个工具类,代码中所有反射寻找Method和Field的地方均使用ReflectionHelper处理。注意:ReflectionHelper类只能调用系统类,不能调用自己app代码中的任何类!否则会因为ClassLoader的全盘委托机制出现问题!
优点:能够调用所有隐藏API;仅需要寻找两个导出函数,适配性较好;没有使用Hook,稳定性好
缺点:JNI方式获取Method和Field时也需要转到ReflectionHelper工具类完成
方法三
方法三通过混淆第一个区分点突破限制。只要修改被隐藏的Method或Field对应的access_flags_,去掉其隐藏属性即可,下文为了论述方便,只以获取隐藏的Method为例进行说明,Field同理。
实际上,只要获取到一个jmethodID,将其强转为ArtMethod*类型,然后修改其access_flags_即可。但是后续版本中应用代码无法获取隐藏Method的jmethodID,貌似陷入一个死循环了。但是查看源码,我们是有方法获取ArtMethod*的:art/runtime/native/java_lang_Class.cc有以下函数:
此函数是Class.getDeclaredMethod方法在native的实现,注意到这里是先获取的result然后才判断ShouldBlockAccessToMember,因此我们可以hook获取result的mirror::Class::GetDeclaredMethodInternal这个函数,将得到的ObjPtr<mirror::Method>类型的result想办法转换为ArtMethod*类型即可。方法三具体实现代码如下:
应用到实际工程中时还需要Hook另外的类似函数,这里不再一一列举。
优点:原有代码无需修改,适用于原有代码量较多的情况。
缺点:需要使用Hook,实现难度较大