残页的小博客

残页的小博客

马上订阅 残页的小博客 RSS 更新: https://blog.canyie.top/atom.xml

Android R上的隐藏API限制学习笔记

2020年6月10日 10:00

2018年发布的Android 9中引入了对隐藏API的限制,这对整个Android生态来说当然是一件好事,但也严重限制了以往我们通过反射等手段实现的“黑科技”(如插件化等),所以开发者们纷纷寻找手段绕过这个限制,比如我曾经提出了两个绕过方法,其中一个便是几乎完美的双重反射(即“元反射”,现在来看叫“套娃反射”比较好);而在即将发布的Android R中把这个方法封杀了(谷歌:禁止套娃!),因此我重新研究了Android R中的限制策略。

2021/4/10 更新:
本文存在纰漏,请看一个通用的纯 Java 安卓隐藏 API 限制绕过方案

上有政策

常言道,知己知彼,百战百胜。要想破解这个限制,就必须去搞懂系统是怎么施加的限制;ok,废话不多说,let’s go!
以我们在Java层通过反射获取一个Method为例,Class.getMethod/getDeclaredMethod最终都会进入一个native方法getDeclaredMethodInternal,这个方法的实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
static jobject Class_getDeclaredMethodInternal(JNIEnv* env, jobject javaThis, jstring name, jobjectArray args) {
// 省略无关代码……
Handle<mirror::Method> result = hs.NewHandle(
mirror::Class::GetDeclaredMethodInternal<kRuntimePointerSize>(
soa.Self(),
klass,
soa.Decode<mirror::String>(name),
soa.Decode<mirror::ObjectArray<mirror::Class>>(args),
GetHiddenapiAccessContextFunction(soa.Self())));
if (result == nullptr || ShouldDenyAccessToMember(result->GetArtMethod(), soa.Self())) {
return nullptr;
}
return soa.AddLocalReference<jobject>(result.Get());
}

我们可以发现,如果ShouldDenyAccessToMember返回true,那么就会返回null,上层就会抛出方法找不到的异常。这里和Android P没什么不同,只是把ShouldBlockAccessToMember改了个名而已。
ShouldDenyAccessToMember会调用到hiddenapi::ShouldDenyAccessToMember,该函数是这样实现的:

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
template<typename T>
inline bool ShouldDenyAccessToMember(T* member,
const std::function<AccessContext()>& fn_get_access_context,
AccessMethod access_method)
REQUIRES_SHARED(Locks::mutator_lock_) {

const uint32_t runtime_flags = GetRuntimeFlags(member);

// 1:如果该成员是公开API,直接通过
if ((runtime_flags & kAccPublicApi) != 0) {
return false;
}

// 2:不是公开API(即为隐藏API),获取调用者和被访问成员的Domain
// 因为获取调用者需要回溯调用栈,性能非常差,所以尽量避免这个消耗
const AccessContext caller_context = fn_get_access_context();
const AccessContext callee_context(member->GetDeclaringClass());

// 3:如果调用者是可信的,直接返回
if (caller_context.CanAlwaysAccess(callee_context)) {
return false;
}

// 4:非可信调用者尝试访问隐藏API,根据调用者的Domain决定行为
switch (caller_context.GetDomain()) {
case Domain::kApplication: {
DCHECK(!callee_context.IsApplicationDomain());

// 如果访问检查被完全禁用,那么直接返回
EnforcementPolicy policy = Runtime::Current()->GetHiddenApiEnforcementPolicy();
if (policy...

剩余内容已隐藏

查看完整文章以阅读更多