[关闭]
@TryLoveCatch 2022-05-09T01:25:33.000000Z 字数 3200 阅读 454

Android知识体系之隐藏API调用

Android知识体系


针对@hide方法,如果调用者是系统类,那么就允许被调用。

也就是说,如果我们能以系统类的身份去反射,那么就能畅通无阻。

以系统类的身份去反射 有两个意思,
1. 直接把我们自己变成系统类;
2. 借助系统类去调用反射。

第二种方法,「借助系统的类去反射」也就是说,如果系统有一个方法systemMethod,这个systemMethod 去调用反射相反的方法,那么systemMethod毋庸置疑会反射成功。但是,我们从哪去找到这么一个方法给我们用?事实上,我们不仅能找到这样的方法,而且这个方法能帮助我们调用任意的函数,那就是反射本身!可能你已经绕晕了,我解释一下:

上次分析系统是如何施加这个限制 的时候,我们提到了几种方式,最终给出了一种修改 runtime flag 的办法;其中我们提到,系统有一个 fn_caller_is_trusted 条件:如果调用者是系统类,那么就允许被调用。这是显而易见的,毕竟这些私有 API 就是给系统用的,如果系统自己都被拒绝了,这是在玩锤子呢?

也就是说,如果我们能以系统类的身份去反射,那么就能畅通无阻。问题是,我们如何以「系统的身份去反射」呢?一种最常见的办法是,我们自己写一个类,然后通过某种途径把这个类的 ClassLoader 设置为系统的 ClassLoader,再借助这个类去反射其他类。但是这里的「通过某种途径」依然要使用一些黑科技才能实现,与修改 flags / inline hook 无本质区别。

以系统类的身份去反射 有两个意思,1. 直接把我们自己变成系统类;2. 借助系统类去调用反射。我们一个个分析。

第二种方法,「借助系统的类去反射」也就是说,如果系统有一个方法systemMethod,这个systemMethod 去调用反射相反的方法,那么systemMethod毋庸置疑会反射成功。但是,我们从哪去找到这么一个方法给我们用?事实上,我们不仅能找到这样的方法,而且这个方法能帮助我们调用任意的函数,那就是反射本身!可能你已经绕晕了,我解释一下:

  1. 首先,我们通过反射 API 拿到 getDeclaredMethod 方法。getDeclaredMethod 是 public 的,不存在问题;这个通过反射拿到的方法我们称之为元反射方法
  2. 然后,我们通过刚刚反射拿到元反射方法去反射调用 getDeclaredMethod。这里我们就实现了以系统身份去反射的目的——反射相关的 API 都是系统类,因此我们的元反射方法也是被系统类加载的方法;所以我们的元反射方法调用的 getDeclaredMethod 会被认为是系统调用的,可以反射任意的方法。
  1. // 公开API,无问题
  2. Method metaGetDeclaredMethod =
  3. Class.class.getDeclaredMethod("getDeclaredMethod");
  4. // 系统类通过反射使用隐藏 API,检查直接通过。
  5. Method hiddenMethod = metaGetDeclaredMethod.invoke(hiddenClass,
  6. "hiddenMethod", "hiddenMethod参数列表");
  7. // 正确找到 Method 直接反射调用
  8. hiddenMethod.invoke

到这里,我们已经能通过「元反射」的方式去任意获取隐藏方法或者隐藏 Field 了。但是,如果我们所有使用的隐藏方法都要这么干,那还有点小麻烦。

我们后来发现,隐藏 API 调用还有「豁免」条件,具体代码如下:

  1. if (shouldWarn || action == kDeny) {
  2. if (member_signature.IsExempted(runtime->GetHiddenApiExemptions())) {
  3. action = kAllow;
  4. // Avoid re-examining the exemption list next time.
  5. // Note this results in no warning for the member, which seems like what one would expect.
  6. // Exemptions effectively adds new members to the whitelist.
  7. MaybeWhitelistMember(runtime, member);
  8. return kAllow;
  9. }
  10. // 略
  11. }

只要 IsExempted 方法返回 true,就算这个方法在黑名单中,依然会被放行然后允许被调用。

runtime->GetHiddenApiExemptions(),这个API 竟然是暴露到 Java 层的,有一个对应的 VMRuntime.setHiddenApiExemptions(String[]) Java方法;
也就是说,只要我们通过 VMRuntime.setHiddenApiExemptions 设置下豁免条件,我们就能愉快滴使用反射了。

所以我们只需要通过元反射来反射调用 VMRuntime.setHiddenApiExemptions 就能将我们自己要使用的隐藏 API 全部都豁免掉了。
但是隐藏API有很多,一个个豁免过于麻烦了,我们再观察一下IsExempted方法:

  1. bool MemberSignature::IsExempted(const std::vector<std::string>& exemptions) {
  2. for (const std::string& exemption : exemptions) {
  3. if (DoesPrefixMatch(exemption)) {
  4. return true;
  5. }
  6. }
  7. return false;
  8. }

IsExempted 方法里面调用的 DoesPrefixMatch,这个方法的作用是在对方法签名进行前缀匹配
那么,所有Java方法类的签名都是以 L开头,如果我们直接传个 L进去,所有的隐藏API全部被赦免了!

  1. Method forName = Class.class.getDeclaredMethod("forName", String.class);
  2. Method getDeclaredMethod = Class.class.getDeclaredMethod("getDeclaredMethod", String.class, Class[].class);
  3. Class<?> vmRuntimeClass = (Class<?>) forName.invoke(null, "dalvik.system.VMRuntime");
  4. Method getRuntime = (Method) getDeclaredMethod.invoke(vmRuntimeClass, "getRuntime", null);
  5. Method setHiddenApiExemptions = (Method) getDeclaredMethod.invoke(vmRuntimeClass, "setHiddenApiExemptions", new Class[]{String[].class});
  6. sVmRuntime sVmRuntime = getRuntime.invoke(null);
  7. setHiddenApiExemptions.invoke(sVmRuntime, new Object[]{new String[]{"L"}});

理论上讲,这个方案不存在兼容性问题。即使 ROM 删掉了 setHiddenApiExemptions 方法,我们依然可以用「元反射」的方式去反射隐藏API,并且所有的代码加起来不超过30行!当然,如果 Google 继续改进验证隐藏API调用的方法,这个方式可能会失效;但是目前的机制没有问题。

参考

另一种绕过 Android P以上非公开API限制的办法
https://lovesykun.cn/archives/android-hidden-api-bypass.html

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注