0%

Android如何绕过反射黑名单

限制原理

Google 从 Android P 开始引入了针对非公开 API 的限制,这一点可以从 Native 相关的源码中找到限制的原理,从而从中找到解决办法,不过非必要原因不太建议去挑战这种限制,毕竟不清楚在后续的版本中会不会做限制,维护起来挺麻烦的。

在 Native 层有几个访问级别:

1
2
3
4
5
6
7
8
9
class HiddenApiAccessFlags {
public:
enum ApiList {
kWhitelist = 0,
kLightGreylist,
kDarkGreylist,
kBlacklist,
};
}

另外还有几个对应的响应级别:

1
2
3
4
5
6
enum Action {
kAllow, //通过
kAllowButWarn, //通过,但日志警告
kAllowButWarnAndToast, //通过,且日志警告和弹窗
kDeny //拒绝访问
};

这里介绍一下网上的一些解决方式,此外,还可以把我们调用了反射方法的类的类加载器设置为系统类加载器,这样就可以绕过 Native 层的限制了。

系统类伪装

黑名单在系统中有一个 fn_caller_is_trusted 的条件:如果调用者是系统类,那么就允许被调用。即如果我们能以系统类的身份去反射,那么就能畅通无阻:

  1. 首先通过反射 API 拿到 getDeclaredMethod 方法。getDeclaredMethod 是 public 的,不存在问题;这个通过反射拿到的方法网上称之为元反射方法。
  2. 然后通过刚刚的元反射方法去反射调用 getDeclardMethod。这里我们就实现了以系统身份去反射的目的——反射相关的 API 都是系统类,因此我们的元反射方法也是被系统类加载的方法;所以我们的元反射方法调用的 getDeclardMethod 会被认为是系统调用的,可以反射任意的方法。

伪代码如下:

1
2
3
4
5
6
// 公开API,无问题
Method metaGetDeclaredMethod = Class.class.getDeclaredMethod("getDeclardMethod");
// 系统类通过反射使用隐藏 API,检查直接通过。
Method hiddenMethod = metaGetDeclaredMethod.invoke(hiddenClass, "hiddenMethod", "hiddenMethod参数列表");
// 正确找到 Method 直接反射调用
hiddenMethod.invoke

豁免条件

隐藏 API 的调用有「豁免」条件,即只要它是豁免的,则即使它在黑名单中,也会被放行。这种方式暴露给了 Java 层,因此可以通过 VMRuntime.setHiddenApiExemptions 方法来实现。再结合上面这个方法,我们只需要通过 「元反射」 来反射调用 VMRuntime.setHiddenApiExemptions 就能将我们自己要使用的隐藏 API 全部都豁免掉了。另外系统在检查豁免时是通过方法签名进行前缀匹配的,而 Java 方法签名都是 L 开头的,因此我们可以把直接传个 L 进去,那么所有的隐藏API全部被赦免了!

源码直接参考网上大佬的开源项目: FreeReflection

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
public final class BootstrapClass {

private static final String TAG = "BootstrapClass";

private static Object sVmRuntime;
private static Method setHiddenApiExemptions;

static {
if (SDK_INT >= Build.VERSION_CODES.P) {
try {
Method forName = Class.class.getDeclaredMethod("forName", String.class);
Method getDeclaredMethod = Class.class.getDeclaredMethod("getDeclaredMethod", String.class, Class[].class);

Class<?> vmRuntimeClass = (Class<?>) forName.invoke(null, "dalvik.system.VMRuntime");
Method getRuntime = (Method) getDeclaredMethod.invoke(vmRuntimeClass, "getRuntime", null);
setHiddenApiExemptions = (Method) getDeclaredMethod.invoke(vmRuntimeClass, "setHiddenApiExemptions", new Class[]{String[].class});
sVmRuntime = getRuntime.invoke(null);
} catch (Throwable e) {
Log.w(TAG, "reflect bootstrap failed:", e);
}
}
}

/**
* make the method exempted from hidden API check.
*
* @param method the method signature prefix.
* @return true if success.
*/
public static boolean exempt(String method) {
return exempt(new String[]{method});
}

/**
* make specific methods exempted from hidden API check.
*
* @param methods the method signature prefix, such as "Ldalvik/system", "Landroid" or even "L"
* @return true if success
*/
public static boolean exempt(String... methods) {
if (sVmRuntime == null || setHiddenApiExemptions == null) {
return false;
}

try {
setHiddenApiExemptions.invoke(sVmRuntime, new Object[]{methods});
return true;
} catch (Throwable e) {
return false;
}
}

/**
* Make all hidden API exempted.
*
* @return true if success.
*/
public static boolean exemptAll() {
return exempt(new String[]{"L"});
}
}