android P对非SDK API调用限制
自google在android 7.0增加了链接器命名空间限制对私有系统so库的调用限制后,android 9.0又增加了非SDK API的调用限制。这样无论是native层还是framework层应用程序都无法访问系统私有的接口,有效防止应用在调用这些经常会变更的私有API后造成的崩溃现象。一般想在framework层调用私有API是通过反射的方法进行调用,所以限制也是在反射的基础上进行的。
分析调用限制源码
一般通过反射调用getDeclaredMethod
获取私有API接口。(源码基于android_10.0.0_r7,与android 9略有差异)
getDeclaredMethod
getDeclaredMethod
会调用native函数getDeclaredMethodInternal
Class_getDeclaredMethodInternal
getDeclaredMethodInternal
对应的native函数为Class_getDeclaredMethodInternal
,此函数最后会调用ShouldDenyAccessToMember
进行权限检查。
ShouldDenyAccessToMember
ShouldDenyAccessToMember
会调用一个重载的ShouldDenyAccessToMember
。每一个类对应一个Domain
,此domain
是在类第一个被加载的时候初始化的DexFile_defineClassNative--->InitializeDexFileDomain
,初始化的标准是看此类对应的dex文件所在的目录。应用程序的话默认就是kApplication
。
ShouldDenyAccessToMember
先调用fn_get_access_context(GetHiddenapiAccessContextFunction函数)获取调用者的上下文和被调用者的上下文信息,接着调用CanAlwaysAccess比较二者domain大小,如果调用者的domain小于被调用者的domain就允许访问(domain约小权限越高)。- 调用
GetHiddenApiEnforcementPolicy
检查全局策略标志hidden_api_policy_
,如果hidden_api_policy_
为kDisabled(0)
则允许访问此隐藏api - 调用
ShouldDenyAccessToMemberImpl
,此函数内部会调用GetHiddenApiExemptions
检查豁免条件,如果检查通过就返回false运行访问。
enum class Domain : char {
kCorePlatform = 0, // 0
kPlatform, // 1
kApplication, // 2
};
template<typename T>
inline bool ShouldDenyAccessToMember(T* member,
const std::function<AccessContext()>& fn_get_access_context,
AccessMethod access_method)
REQUIRES_SHARED(Locks::mutator_lock_) {
DCHECK(member != nullptr);
// Get the runtime flags encoded in member's access flags.
// Note: this works for proxy methods because they inherit access flags from their
// respective interface methods.
const uint32_t runtime_flags = GetRuntimeFlags(member);
// Exit early if member is public API. This flag is also set for non-boot class
// path fields/methods.
// 如果获取的是公共的API则返回false不进行限制
if ((runtime_flags & kAccPublicApi) != 0) {
return false;
}
// Determine which domain the caller and callee belong to.
// This can be *very* expensive. This is why ShouldDenyAccessToMember
// should not be called on every individual access.
// 获取调用者caller上下文和被调用者callee上下文
const AccessContext caller_context = fn_get_access_context();
const AccessContext callee_context(member->GetDeclaringClass());
// Non-boot classpath callers should have exited early.
DCHECK(!callee_context.IsApplicationDomain());
// if 调用者caller的domain < 被调用者callee的domain则返回false允许访问 (domain越小权限越大)
// Check if the caller is always allowed to access members in the callee context.
if (caller_context.CanAlwaysAccess(callee_context)) {
return false;
}
// Check if this is platform accessing core platform. We may warn if `member` is
// not part of core platform API.
switch (caller_context.GetDomain()) {
//对于应用程序dex文件来说其默认domain应该是kApplication
case Domain::kApplication: {
DCHECK(!callee_context.IsApplicationDomain());
// Exit early if access checks are completely disabled.
// 检查全局策略标志hidden_api_policy_,如果 hidden_api_policy_为 kDisabled(false)则允许访问此隐藏api
EnforcementPolicy policy = Runtime::Current()->GetHiddenApiEnforcementPolicy();
if (policy == EnforcementPolicy::kDisabled) {
return false;
}
// If this is a proxy method, look at the interface method instead.
member = detail::GetInterfaceMemberIfProxy(member);
// Decode hidden API access flags from the dex file.
// This is an O(N) operation scaling with the number of fields/methods
// in the class. Only do this on slow path and only do it once.
ApiList api_list(detail::GetDexFlags(member));
DCHECK(api_list.IsValid());
// Member is hidden and caller is not exempted. Enter slow path.
//内部会调用GetHiddenApiExemptions检查豁免条件,如果检查通过就返回false运行访问。
return detail::ShouldDenyAccessToMemberImpl(member, api_list, access_method);
}
case Domain::kPlatform: {
DCHECK(callee_context.GetDomain() == Domain::kCorePlatform);
// Member is part of core platform API. Accessing it is allowed.
if ((runtime_flags & kAccCorePlatformApi) != 0) {
return false;
}
// Allow access if access checks are disabled.
EnforcementPolicy policy = Runtime::Current()->GetCorePlatformApiEnforcementPolicy();
if (policy == EnforcementPolicy::kDisabled) {
return false;
}
// If this is a proxy method, look at the interface method instead.
member = detail::GetInterfaceMemberIfProxy(member);
// Access checks are not disabled, report the violation.
// This may also add kAccCorePlatformApi to the access flags of `member`
// so as to not warn again on next access.
return detail::HandleCorePlatformApiViolation(member,
caller_context,
access_method,
policy);
}
case Domain::kCorePlatform: {
LOG(FATAL) << "CorePlatform domain should be allowed to access all domains";
UNREACHABLE();
}
}
}
看一下fn_get_access_context也就是GetHiddenapiAccessContextFunction函数获取调用者上下文的过程,GetHiddenapiAccessContextFunction会去调用GetReflectionCaller。
- 调用VisitFrame如果返回true,则调用
hiddenapi::AccessContext(true
)返回此类的domain为kCorePlatform,这里想到了之前分析的lsposed
会调用setTrusted
设置自身加载的dex文件domain为kCorePlatform
。 - 调用VisitFrame如果返回false,则正常返回类的
domain
(初始化的时候设置的domain)。
VisitFrame
判断类的classloader
为BootStrapClassLoader
的话会进一步判断调用者类是否为java.lang.Class
或者java.lang.invoke.*,如果是就返回true,然后此类的domain就会被设置为kCorePlatform权限最高可以访问隐藏的api。
// art/runtime/native/java_lang_Class.cc
static hiddenapi::AccessContext GetReflectionCaller(Thread* self)
REQUIRES_SHARED(Locks::mutator_lock_) {
// Walk the stack and find the first frame not from java.lang.Class and not
// from java.lang.invoke. This is very expensive. Save this till the last.
struct FirstExternalCallerVisitor : public StackVisitor {
explicit FirstExternalCallerVisitor(Thread* thread)
: StackVisitor(thread, nullptr, StackVisitor::StackWalkKind::kIncludeInlinedFrames),
caller(nullptr) {
}
bool VisitFrame() override REQUIRES_SHARED(Locks::mutator_lock_) {
ArtMethod *m = GetMethod();
if (m == nullptr) {
// Attached native thread. Assume this is *not* boot class path.
caller = nullptr;
return false;
} else if (m->IsRuntimeMethod()) {
// Internal runtime method, continue walking the stack.
return true;
}
//如果调用者的类加载器为BootStrapClassLoader
ObjPtr<mirror::Class> declaring_class = m->GetDeclaringClass();
if (declaring_class->IsBootStrapClassLoaded()) {
//如果调用者类为java.lang.Class则return true,此类的domain最后会等于kCorePlatform
if (declaring_class->IsClassClass()) {
return true;
}
//如果调用者类为java.lang.invoke.*则return true,此类的domain最后会等于kCorePlatform
// Check classes in the java.lang.invoke package. At the time of writing, the
// classes of interest are MethodHandles and MethodHandles.Lookup, but this
// is subject to change so conservatively cover the entire package.
// NB Static initializers within java.lang.invoke are permitted and do not
// need further stack inspection.
ObjPtr<mirror::Class> lookup_class = GetClassRoot<mirror::MethodHandlesLookup>();
if ((declaring_class == lookup_class || declaring_class->IsInSamePackage(lookup_class))
&& !m->IsClassInitializer()) {
return true;
}
}
caller = m;
return false;
}
ArtMethod* caller;
};
FirstExternalCallerVisitor visitor(self);
visitor.WalkStack();
// Construct AccessContext from the calling class found on the stack.
// If the calling class cannot be determined, e.g. unattached threads,
// we conservatively assume the caller is trusted.
ObjPtr<mirror::Class> caller = (visitor.caller == nullptr)
? nullptr : visitor.caller->GetDeclaringClass();
return caller.IsNull() ? hiddenapi::AccessContext(/* is_trusted= */ true)
: hiddenapi::AccessContext(caller);
}
绕过非SDK API访问限制
降低调用类的domain
因为domain是在类第一次被加载的时候设置的,除非能在类一开始加载的时候就改变其domain(native层直接修改内存应该也可行)。或者在较低的domain堆栈中调用,如System.LoadLibrary函数,java.lang.System
类是系统加载的而且对应的jar包肯定位于系统framework目录下,那么其domain肯定是kCorePlatform
,而此函数在native层会调用调用Jni_OnLoad
函数,如果在Jni_OnLoad
中通过反射是调用函数是不受限制的,这样可以进一步通过反射调用Java层的SetHiddenApiExemptions
设置豁免条件绕过隐藏的api限制。Java层的SetHiddenApiExemptions
函数位于VMRuntime
类中,对应的native层函数是VMRuntime_setHiddenApiExemptions
,最后会调用libart.so
中的setHiddenApiExemptions
函数设置豁免条件。
JNI_Onload中调用SetHiddenApiExemptions
函数即可绕过sdk私有api限制
jclass VMRuntimeClass = env->FindClass("dalvik/system/VMRuntime");
jmethodID setHiddenApiExemptionsMethodID = env->GetMethodID(VMRuntimeClass, "setHiddenApiExemptions", "([Ljava/lang/String;)V");
jmethodID VMRuntimeConstructionMethodID = env->GetMethodID(VMRuntimeClass,"<init>", "()V");
jobject VMRuntime_obj = env->NewObject(VMRuntimeClass, VMRuntimeConstructionMethodID);
//jstring tmp = env->NewStringUTF("");也可以
jstring tmp = env->NewStringUTF("L");
jobjectArray signaturePrefixes = env->NewObjectArray(1, env->FindClass("java/lang/String"), tmp);
env->CallVoidMethod(VMRuntime_obj, setHiddenApiExemptionsMethodID, signaturePrefixes);
java层有setHiddenApiEnforcementPolicy
接口可以设置hidden_api_policy_
,但是在运行之后设置hidden_api_policy_
并不会同步到native层,需要在更早的时机设置,所以这种JNI_Onload
中反射调用setHiddenApiEnforcementPolicy
解除sdk限制并不可行。
设置hidden_api_policy_= kDisabled
通过在native层调用libart.so的SetHiddenApiEnforcementPolicy
函数设置隐藏api访问策略标志为kDisabled(0)
,因为此函数并未导出,所以需要针对不同的版本进行适配,不是很稳定。
设置hidden_api_exemptions_
通过在native层调用libart.so的VMRuntime_setHiddenApiExemptions
(内部会调用SetHiddenApiExemptions)函数设置豁免条件,此函数通过动态注册与java层的SetHiddenApiExemptions
绑定,并未导出,调用的话也需要对各种版本进行适配不稳定。
其他方法
纯java层实现请参考:https://lovesykun.cn/archives/android-hidden-api-bypass.html
旧的失效的方法
android 10.0 之前使用元反射的方法和修改调用者类classloader
为null(BootStrapClassLoader)已经失效。(因为android 10之后会判断调用者上下文的domain等级)
以上仅为个人研究观点。
参考:https://bbs.kanxue.com/thread-268936.htm#msg_header_h3_15