当前位置: 首页 > 科技观察

另一种绕过AndroidP之上非公开API限制的方法

时间:2023-03-18 22:34:20 科技观察

去年发布的AndroidP引入了对非公开API的限制,对于开发者来说,这绝对是有史以来最重大的变化之一。前天,谷歌发布了AndroidQBeta版,越来越多的API被加入黑名单,谷歌要求下半年APP必须针对28个,也就是说目前的深灰名单也会生效;可以预见,在不久的将来,我们将告别大量的API。去年,我给出了一个简单的绕过AndroidP对非SDK接口限制的方法。经验证,该方法在AndroidQBeta版上依然可以正常使用。虽然该方法需要内存查找,理论上可能会失败,但在VirtualXposed和TaiChi中已经广泛验证,从未收到过关于反射失败引起的问题。而且据我所知,有几个用户量很大的APP是用我网上提供的FreeReflection库的,应该没有问题。但是今天,我打算给出另一种绕过限制的方法。这种方法是目前最好的解决方案。我亲自使用了一个多月没有任何问题。上次分析系统是如何施加这个限制的时候,我们提到了几种方式,最后给出了一种修改runtimeflag的方式;其中,我们提到系统有一个fn_caller_is_trusted条件:如果调用者是系统类,则允许被调用。这是显而易见的。毕竟这些私有API是给系统用的。如果系统本身被拒绝,那是在玩锤子吗?也就是说,如果我们能作为一个系统类来反映,那么我们就可以畅通无阻了。问题是,我们如何“反映为一个系统”?一种最常见的方法就是我们自己写一个类,然后通过一些手段把这个类的ClassLoader设置为系统的ClassLoader,然后用这个类来Reflect其他的类。但是这里的“通过某种方式”还是需要用到一些黑科技来实现的,和修改flags/inlinehook没有本质区别。反射为系统类有两层意思,1、直接把自己变成系统类;2.使用系统类调用反射。我们一一分析。“直接把自己变成系统类”是童鞋们可能觉得天方夜谭的一种方式。APP类如何成为系统类?但是,我们千万不要被自己固有的思维所局限,一切皆有可能!我们知道,对于APP来说,所谓的系统类就是BootstrapClassLoader加载的类。这个ClassLoader不是普通的DexClassLoader,所以我们不能通过插入dex路径来注入类。但是Android的ART在AndroidO上引入了JVMTI,而JVMTI提供了将某个类转换为BootstrapClassLoader中的类的方法!具体来说,我们写一个类来暴露反射相关的接口,然后通过JVMTI提供的AddToBootstrapClassLoaderSearch将这个类添加到BootstrapClassLoader中来达到目的。不过JVMTI在release版APP上运行还是需要Hack的,所以这种做法和其他黑科技没有本质区别。第二种方法,“借助系统类进行反射”的意思是,如果系统有一个方法systemMethod,这个systemMethod调用反射的对面方法,那么systemMethod无疑会反射成功。但是我们去哪里找这样的方法供我们使用呢?其实不光是我们可以找到这样一个方法,而且这个方法可以帮助我们调用任何函数,那就是反射本身!可能你已经晕了,我来解释一下:首先,我们通过反射API获取getDeclaredMethod方法。getDeclaredMethod是public的,所以没有问题;这种通过反射获得的方法称为元反射法。然后,我们通过刚才的反射得到元反射方法进行反射调用getDeclardMethod。到这里我们就达到了反射作为系统的目的——反射相关的API都是系统类,所以我们的元反射方法也是系统类加载的方法;因此我们的元反射方法调用的getDeclardMethod将被视为系统调用可以反映任意方法。伪代码如下:MethodmetaGetDeclaredMethod=Class.class.getDeclaredMethod("getDeclardMethod");//开放API,没问题MethodhiddenMethod=metaGetDeclaredMethod.invoke(hiddenClass,"hiddenMethod","hiddenMethod参数列表");//系统类是反射使用的API是隐藏的,检查直接通过。hiddenMethod.invoke//正确找到Method,直接在这里反射调用。我们已经可以通过“meta-reflection”方法获取hidden方法或者任意隐藏Field。但是,如果我们必须对我们使用的所有隐藏方法都这样做,那就有点麻烦了。在上面,我们后来发现隐藏API调用是有一个“豁免”条件的。具体代码如下:if(shouldWarn||action==kDeny){if(member_signature.IsExempted(runtime->GetHiddenApiExemptions())){action=kAllow;//Avoidre-examiningtheexemptionlistnexttime.//注意这个resultsinnowwarningforthemember,whichseemslikewhatonewouldexpect.//Exemptionseffectivelyaddsnewmemberstothewhitelist.MaybeWhitelistMember(runtime,member);returnAllow;}//很明显}只要释放黑名单中的IsExempted方法,即使这个方法返回true,也会调用allow。让我们再看看IsExempted方法:boolMemberSignature::IsExempted(conststd::vector&exemptions){for(conststd::string&exemption:exemptions){if(DoesPrefixMatch(exemption)){returntrue;}}returnfalse;}继续跟踪runtime->GetHiddenApiExemptions()中传递的参数,发现这个东西也是runtime中的参数。这种情况下,我们也无能为力,直接修改hidden_??api_exemptions_,和修改runtimeflag一样,也可以绕过。但是如果我们继续往下追查,我们会发现一个有趣的发现:这个API是暴露给Java层的,并且有一个对应的VMRuntime.setHiddenApiExemptionsJava方法;也就是说,只要我们通过VMRuntime.setHiddenApiExemptions设置豁免条件,我们就可以愉快的使用反射了。结合上面的方法,我们只需要通过“元反射”反射调用VMRuntime.setHiddenApiExemptions,就可以免除所有我们要使用的隐藏API。更进一步,如果我们观察上面IsExempted方法中调用的DoesPrefixMatch,我们发现这个东西是在对方法签名进行前缀匹配;童鞋们,我们Java方法类的签名都是L开头的!如果我们直接传入一个L,所有隐藏的API都会被原谅!详细代码在这里:https://github.com/tiann/FreeReflection理论上这个方案不存在兼容性问题。即使ROM删除了setHiddenApiExemptions方法,我们仍然可以使用“元反射”方法来反射隐藏的API,所有代码加起来不超过30行!当然,如果谷歌继续改进验证隐藏API调用的方法,这个方法可能会失败;但目前的机制没有问题。文章的底线,我想说的是,这篇文章的目的不是刻意绕过限制。不给自己的思想和人生设限,就会有更多的可能。