深入源码分析non-sdk并绕过Android(安卓)9.0反射限制
Android 9.0终于来了,non-sdk或许是我们最大的适配点。本文将分析non-sdk的原理以及如何绕过它继续反射调用系统私有API。
先看一段简单的反射代码:
Class<?> activityThreadClass = Class.forName("android.app.ActivityThread"); Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread");
运行这段代码便会出现""Accessing hidden ....."警告。
先看一下getDeclaredMethod()实现:
public Method getDeclaredMethod(String name, Class<?>... parameterTypes) throws NoSuchMethodException, SecurityException { return getMethod(name, parameterTypes, false); }
接下来看下getMethod()函数:
private Method getMethod(String name, Class<?>[] parameterTypes, boolean recursivePublicMethods) throws NoSuchMethodException { if (name == null) { throw new NullPointerException("name == null"); } if (parameterTypes == null) { parameterTypes = EmptyArray.CLASS; } for (Class<?> c : parameterTypes) { if (c == null) { throw new NoSuchMethodException("parameter type is null"); } } Method result = recursivePublicMethods ? getPublicMethodRecursive(name, parameterTypes) : getDeclaredMethodInternal(name, parameterTypes); // Fail if we didn't find the method or it was expected to be public. if (result == null || (recursivePublicMethods && !Modifier.isPublic(result.getAccessFlags()))) { throw new NoSuchMethodException(name + " " + Arrays.toString(parameterTypes)); } return result; }
因为它的参数固定为recursivePublicMethods为false,所以最后会调用getDeclaredMethodInternal(),而这是一个natie函数,定义如下。
/** * Returns the method if it is defined by this class; {@code null} otherwise. This may return a * non-public member. * * @param name the method name * @param args the method's parameter types */ @FastNative private native Method getDeclaredMethodInternal(String name, Class<?>[] args);
getDeclaredMethodInternal()的native实现在art/runtime/native/java_lang_Class.cc中,为了看得更直观的,我们来看一下9.0和8.1相比做了哪些修改。下图中左边是9.0,右边是8.1:
一下就看出来了,9.1的代码在反射时候会增加一个ShouldBlockAccessToMember()判断,如果返回true,那么你在getDeclaredMethod()时候就会得到null。
顺着往下看ShouldBlockAccessToMember()是怎么判断的:
// Returns true if the first non-ClassClass caller up the stack should not be// allowed access to `member`.templateALWAYS_INLINE static bool ShouldBlockAccessToMember(T* member, Thread* self) REQUIRES_SHARED(Locks::mutator_lock_) { hiddenapi::Action action = hiddenapi::GetMemberAction( member, self, IsCallerTrusted, hiddenapi::kReflection); if (action != hiddenapi::kAllow) { hiddenapi::NotifyHiddenApiListener(member); } return action == hiddenapi::kDeny;}
它会调用hiddenapi::GetMemberAction(),如果返回hiddenapi::kDeny,则会block反射调用。
hiddenapi主要会涉及到art/runtime/hidden_api.h和art/runtime/hidden_api.cc两个文件。
继续往下看GetMemberAction()的实现:
template inline Action GetMemberAction(T* member,Thread* self,std::function fn_caller_is_trusted,AccessMethod access_method) REQUIRES_SHARED(Locks::mutator_lock_) { DCHECK(member != nullptr); HiddenApiAccessFlags::ApiList api_list = member->GetHiddenApiAccessFlags(); Action action = GetActionFromAccessFlags(member->GetHiddenApiAccessFlags()); if (action == kAllow) { // Nothing to do. return action; } if (fn_caller_is_trusted(self)) { // Caller is trusted. Exit. return kAllow; } return detail::GetMemberActionImpl(member, api_list, action, access_method);}
这个函数会先通过GetHiddenApiAccessFlags()获取到API的hidden访问级别,再根据它用GetActionFromAccessFlags()来拿到对应的Action。
如果Action为kAllow,则不做任何处理。
如果Action不为kAllow再通过fn_caller_is_trusted看当前是否是系统调用的,如果是系统调用则返回kAllow。
如果Action不为kAllow且不是系统调用则继续通过GetMemberActionImpl()做进一步判断,看是否需要block。
(by the way,如果我们要绕过反射限制,就可以在这个函数中做处理,后面会讲到)
GetHiddenApiAccessFlags()的实现在runtime/art_method-inl.h中:
inline HiddenApiAccessFlags::ApiList ArtMethod::GetHiddenApiAccessFlags() REQUIRES_SHARED(Locks::mutator_lock_) { if (UNLIKELY(IsIntrinsic())) { switch (static_cast(GetIntrinsic())) { case Intrinsics::kSystemArrayCopyChar: case Intrinsics::kStringGetCharsNoCheck: case Intrinsics::kReferenceGetReferent: // These intrinsics are on the light greylist and will fail a DCHECK in // SetIntrinsic() if their flags change on the respective dex methods. // Note that the DCHECK currently won't fail if the dex methods are // whitelisted, e.g. in the core image (b/77733081). As a result, we // might print warnings but we won't change the semantics. return HiddenApiAccessFlags::kLightGreylist; case Intrinsics::kVarHandleFullFence: case Intrinsics::kVarHandleAcquireFence: case Intrinsics::kVarHandleReleaseFence: case Intrinsics::kVarHandleLoadLoadFence: case Intrinsics::kVarHandleStoreStoreFence: case Intrinsics::kVarHandleCompareAndExchange: case Intrinsics::kVarHandleCompareAndExchangeAcquire: case Intrinsics::kVarHandleCompareAndExchangeRelease: case Intrinsics::kVarHandleCompareAndSet: case Intrinsics::kVarHandleGet: case Intrinsics::kVarHandleGetAcquire: case Intrinsics::kVarHandleGetAndAdd: case Intrinsics::kVarHandleGetAndAddAcquire: case Intrinsics::kVarHandleGetAndAddRelease: case Intrinsics::kVarHandleGetAndBitwiseAnd: case Intrinsics::kVarHandleGetAndBitwiseAndAcquire: case Intrinsics::kVarHandleGetAndBitwiseAndRelease: case Intrinsics::kVarHandleGetAndBitwiseOr: case Intrinsics::kVarHandleGetAndBitwiseOrAcquire: case Intrinsics::kVarHandleGetAndBitwiseOrRelease: case Intrinsics::kVarHandleGetAndBitwiseXor: case Intrinsics::kVarHandleGetAndBitwiseXorAcquire: case Intrinsics::kVarHandleGetAndBitwiseXorRelease: case Intrinsics::kVarHandleGetAndSet: case Intrinsics::kVarHandleGetAndSetAcquire: case Intrinsics::kVarHandleGetAndSetRelease: case Intrinsics::kVarHandleGetOpaque: case Intrinsics::kVarHandleGetVolatile: case Intrinsics::kVarHandleSet: case Intrinsics::kVarHandleSetOpaque: case Intrinsics::kVarHandleSetRelease: case Intrinsics::kVarHandleSetVolatile: case Intrinsics::kVarHandleWeakCompareAndSet: case Intrinsics::kVarHandleWeakCompareAndSetAcquire: case Intrinsics::kVarHandleWeakCompareAndSetPlain: case Intrinsics::kVarHandleWeakCompareAndSetRelease: // These intrinsics are on the blacklist and will fail a DCHECK in // SetIntrinsic() if their flags change on the respective dex methods. // Note that the DCHECK currently won't fail if the dex methods are // whitelisted, e.g. in the core image (b/77733081). Given that they are // exclusively VarHandle intrinsics, they should not be used outside // tests that do not enable hidden API checks. return HiddenApiAccessFlags::kBlacklist; default: // Remaining intrinsics are public API. We DCHECK that in SetIntrinsic(). return HiddenApiAccessFlags::kWhitelist; } } else { return HiddenApiAccessFlags::DecodeFromRuntime(GetAccessFlags()); }}
就是简单的获取函数flag,我们可以简单的认为一个函数的flag是固定的。
这个函数的返回值ApiList其实也是一个int值,具体的定位在at/libdexfile/dex/hidden_api_access_flags.h中:
enum ApiList { kWhitelist = 0, kLightGreylist, kDarkGreylist, kBlacklist, };
和这个ApiList对应的Action定义在hidden_api.h中:
enum Action { kAllow, kAllowButWarn, kAllowButWarnAndToast, kDeny};
我们先来看看GetActionFromAccessFlags()是如何根据ApiList得到Action的:
inline Action GetActionFromAccessFlags(HiddenApiAccessFlags::ApiList api_list) { if (api_list == HiddenApiAccessFlags::kWhitelist) { return kAllow; } EnforcementPolicy policy = Runtime::Current()->GetHiddenApiEnforcementPolicy(); if (policy == EnforcementPolicy::kNoChecks) { // Exit early. Nothing to enforce. return kAllow; } // if policy is "just warn", always warn. We returned above for whitelist APIs. if (policy == EnforcementPolicy::kJustWarn) { return kAllowButWarn; } DCHECK(policy >= EnforcementPolicy::kDarkGreyAndBlackList); // The logic below relies on equality of values in the enums EnforcementPolicy and // HiddenApiAccessFlags::ApiList, and their ordering. Assertions are in hidden_api.cc. if (static_cast(policy) > static_cast(api_list)) { return api_list == HiddenApiAccessFlags::kDarkGreylist ? kAllowButWarnAndToast : kAllowButWarn; } else { return kDeny; }}
如果ApiList为kWhitelist,或者GetHiddenApiEnforcementPolicy()返回的策略为kNoChecks,则不做任何处理。
这里涉及到了EnforcementPolicy,我们看看它的定义:
enum class EnforcementPolicy { kNoChecks = 0, kJustWarn = 1, // keep checks enabled, but allow everything (enables logging) kDarkGreyAndBlackList = 2, // ban dark grey & blacklist kBlacklistOnly = 3, // ban blacklist violations only kMax = kBlacklistOnly,};
kNoChecks:允许调用所有API,不做任何检测
kJustWarn:允许调用所有API,但是对于私有API的调用会打印警告log
kDarkGreyAndBlackList:会阻止调用dark grey或black list中的API
kBlacklistOnly:会阻止调用black list中的API
到这里我们也就明白了ApiList,Action,EnforcementPolicy这三者的关系。
Google其实是通过EnforcementPolicy的配置,将ApiList转成Action。脑海里突然想到了我们天天说的要将业务和实现分离,这就是例子....
下面将GetActionFromAccessFlags()的处理逻辑整理成一个表格,不想看看代码的看这个表格就可以了:
ApiList | |||||
kWhitelist | kLightGreylist | kDarkGreylist | kBlacklist | ||
EnforcementPolicy | kNoChecks | kAllow | kAllow | kAllow | kAllow |
kJustWarn | kAllow | kAllowButWarn | kAllowButWarn | kAllowButWarn | |
kDarkGreyAndBlackList | kAllow | kAllowButWarn | kDeny | kDeny | |
kBlacklistOnly | kAllow | kAllowButWarn | kAllowButWarnAndToast | kDeny |
接下来再看下GetMemberActionImpl()函数会做哪些判断:
templateAction GetMemberActionImpl(T* member, HiddenApiAccessFlags::ApiList api_list, Action action, AccessMethod access_method) { DCHECK_NE(action, kAllow); // Get the signature, we need it later. MemberSignature member_signature(member); Runtime* runtime = Runtime::Current(); // Check for an exemption first. Exempted APIs are treated as white list. // We only do this if we're about to deny, or if the app is debuggable. This is because: // - we only print a warning for light greylist violations for debuggable apps // - for non-debuggable apps, there is no distinction between light grey & whitelisted APIs. // - we want to avoid the overhead of checking for exemptions for light greylisted APIs whenever // possible. const bool shouldWarn = kLogAllAccesses || runtime->IsJavaDebuggable(); if (shouldWarn || action == kDeny) { if (member_signature.IsExempted(runtime->GetHiddenApiExemptions())) { action = kAllow; // Avoid re-examining the exemption list next time. // Note this results in no warning for the member, which seems like what one would expect. // Exemptions effectively adds new members to the whitelist. MaybeWhitelistMember(runtime, member); return kAllow; } if (access_method != kNone) { // Print a log message with information about this class member access. // We do this if we're about to block access, or the app is debuggable. member_signature.WarnAboutAccess(access_method, api_list); } } if (kIsTargetBuild) { uint32_t eventLogSampleRate = runtime->GetHiddenApiEventLogSampleRate(); // Assert that RAND_MAX is big enough, to ensure sampling below works as expected. static_assert(RAND_MAX >= 0xffff, "RAND_MAX too small"); if (eventLogSampleRate != 0 && (static_cast(std::rand()) & 0xffff) < eventLogSampleRate) { member_signature.LogAccessToEventLog(access_method, action); } } if (action == kDeny) { // Block access return action; } // Allow access to this member but print a warning. DCHECK(action == kAllowButWarn || action == kAllowButWarnAndToast); if (access_method != kNone) { // Depending on a runtime flag, we might move the member into whitelist and // skip the warning the next time the member is accessed. MaybeWhitelistMember(runtime, member); // If this action requires a UI warning, set the appropriate flag. if (shouldWarn && (action == kAllowButWarnAndToast || runtime->ShouldAlwaysSetHiddenApiWarningFlag())) { runtime->SetPendingHiddenApiWarning(true); } } return action;}
kLogAllAccesses表示是否强制打印警告,默认值为false。
IsJavaDebuggable()则是看我们的App是release版本还是debug版本。
当app为debug版本或者action为kDeny时,这时还会调用IsExempted()来看这个函数是否真的需要处理。如果函数在豁免名单中,则不会处理,返回kAllow。并且调用MaybeWhitelistMember()将这个API的flag修改为kWhitelist。
MaybeWhitelistMember()实现:
templatestatic ALWAYS_INLINE void MaybeWhitelistMember(Runtime* runtime, T* member) REQUIRES_SHARED(Locks::mutator_lock_) { if (CanUpdateMemberAccessFlags(member) && runtime->ShouldDedupeHiddenApiWarnings()) { member->SetAccessFlags(HiddenApiAccessFlags::EncodeForRuntime( member->GetAccessFlags(), HiddenApiAccessFlags::kWhitelist)); }}
所以现在我们知道了其实系统是有2个列表的,一个列表是系统中自带的list,另外一个是进程启动时,设置的豁免列表。
接着上面继续分析,如果不在豁免名单里面,并且这个函数访问模式不是kNone,则会打印警告Log.
hidden_api.h中定义了几种访问模式:
enum AccessMethod { kNone, // internal test that does not correspond to an actual access by app kReflection, kJNI, kLinking,};
下面讲一下这几种模式分别在什么情况下使用:
1.kNone是一种测试模式,这种模式下不会打印任何log。比如linker中检测某个函数是否存在,则会用这种模式。
下面是class_linker.cc中的代码:
// Returns true if `method` is either null or hidden.// Does not print any warnings if it is hidden.static bool CheckNoSuchMethod(ArtMethod* method, ObjPtr dex_cache, ObjPtr class_loader) REQUIRES_SHARED(Locks::mutator_lock_) { return method == nullptr || hiddenapi::GetMemberAction(method, class_loader, dex_cache, hiddenapi::kNone) // do not print warnings == hiddenapi::kDeny;}
2.kReflection如其名字,java层反射调用API时使用的。
下面是java_lang_Class.cc中的代码:
static jobject Class_getDeclaredMethodInternal(JNIEnv* env, jobject javaThis, jstring name, jobjectArray args) {................. if (result == nullptr || ShouldBlockAccessToMember(result->GetArtMethod(), soa.Self())) { return nullptr; } return soa.AddLocalReference(result.Get());}templateALWAYS_INLINE static bool ShouldBlockAccessToMember(T* member, Thread* self) REQUIRES_SHARED(Locks::mutator_lock_) { hiddenapi::Action action = hiddenapi::GetMemberAction( member, self, IsCallerTrusted, hiddenapi::kReflection); if (action != hiddenapi::kAllow) { hiddenapi::NotifyHiddenApiListener(member); } return action == hiddenapi::kDeny;}
3.kJNI则是在JNI层通过FindMethodID()/FindFieldID()调用函数时使用的。
下面是jni_internal.cc中的代码:
static jmethodID FindMethodID(ScopedObjectAccess& soa, jclass jni_class, const char* name, const char* sig, bool is_static) REQUIRES_SHARED(Locks::mutator_lock_) { ............. if (method != nullptr && ShouldBlockAccessToMember(method, soa.Self())) { method = nullptr; } if (method == nullptr || method->IsStatic() != is_static) { ThrowNoSuchMethodError(soa, c, name, sig, is_static ? "static" : "non-static"); return nullptr; } return jni::EncodeArtMethod(method);}templateALWAYS_INLINE static bool ShouldBlockAccessToMember(T* member, Thread* self) REQUIRES_SHARED(Locks::mutator_lock_) { hiddenapi::Action action = hiddenapi::GetMemberAction( member, self, IsCallerTrusted, hiddenapi::kJNI); if (action != hiddenapi::kAllow) { hiddenapi::NotifyHiddenApiListener(member); } return action == hiddenapi::kDeny;}
4.kLinking是在linker调用FindResolvedMethod()/FindResolvedField()时使用,针对动态链接的方式。
下面是jni_internal.cc中的代码:
ArtMethod* ClassLinker::FindResolvedMethod(ObjPtr klass, ObjPtr dex_cache, ObjPtr class_loader, uint32_t method_idx) {........ if (resolved != nullptr && hiddenapi::GetMemberAction( resolved, class_loader, dex_cache, hiddenapi::kLinking) == hiddenapi::kDeny) { resolved = nullptr; } ..... return resolved;}
到现在GetMemberActionImpl()主要代码分析完了,剩下最后提示的部分:
emplateAction GetMemberActionImpl(T* member, HiddenApiAccessFlags::ApiList api_list, Action action, AccessMethod access_method) { ................... if (action == kDeny) { // Block access return action; } // Allow access to this member but print a warning. DCHECK(action == kAllowButWarn || action == kAllowButWarnAndToast); if (access_method != kNone) { // Depending on a runtime flag, we might move the member into whitelist and // skip the warning the next time the member is accessed. MaybeWhitelistMember(runtime, member); // If this action requires a UI warning, set the appropriate flag. if (shouldWarn && (action == kAllowButWarnAndToast || runtime->ShouldAlwaysSetHiddenApiWarningFlag())) { runtime->SetPendingHiddenApiWarning(true); } } return action;}
如果action为kDeny,则直接返回,不是就会看是否需要弹出对话框等UI上的提示。
下面是打印警告Log的代码:
void MemberSignature::WarnAboutAccess(AccessMethod access_method, HiddenApiAccessFlags::ApiList list) { LOG(WARNING) << "Accessing hidden " << (type_ == kField ? "field " : "method ") << Dumpable(*this) << " (" << list << ", " << access_method << ")";}
分析到此暂告一段落。总结一下:
1.对我们APP开发者来说,有3种做法会处触发API检查:a.java层反射; b.jni调用; c.provided方式的动态链接
整理如下:
类型 | 触发non-sdk检查的函数 |
hiddenapi::kJNI(Jni调用java Api) | FindMethodID() |
FindFieldID() | |
hiddenapi::kLinking(Linker动态链接) | FindResolvedMethod() |
ResolveMethodWithoutInvokeType() | |
FindResolvedField() | |
FindResolvedFieldJLS() | |
hiddenapi::kReflection(java反射) | Class_getPublicFieldRecursive() |
Class_getDeclaredField() | |
Class_getDeclaredConstructorInternal() | |
Class_getDeclaredMethodInternal() | |
Class_newInstance() |
2.API的检查结果都是由hidden_api.h中的GetMemberAction()返回的
3.ApiList,Action,EnforcementPolicy关系整理如下:
ApiList | |||||
kWhitelist | kLightGreylist | kDarkGreylist | kBlacklist | ||
EnforcementPolicy | kNoChecks | kAllow | kAllow | kAllow | kAllow |
kJustWarn | kAllow | kAllowButWarn | kAllowButWarn | kAllowButWarn | |
kDarkGreyAndBlackList | kAllow | kAllowButWarn | kDeny | kDeny | |
kBlacklistOnly | kAllow | kAllowButWarn | kAllowButWarnAndToast | kDeny |
4.如果Runtime::Current()->GetHiddenApiEnforcementPolicy()的返回值为kNoChecks,也就是0,则允许访问,并且这个函数并不是inline,就可以被我们比较容易的hook并修改返回值。
5.对于私有API调用,还会调用GetMemberActionImpl()进一步处理,如果Action为kDeny,还会看通过IsExempted()来看是否在豁免名单中,如果在,则会返回kAllow,并修改该API为kWhitelist。
接下来我们通过Hook GetHiddenApiEnforcementPolicy()来绕过non-sdk API检查。
休息一会再回来接着写.......
更多相关文章
- 没有一行代码,「2020 新冠肺炎记忆」这个项目却登上了 GitHub 中
- Android(安卓)4.1-4.2 默认窗体旋转180 度代码
- Android开发之《Android应用开发揭秘》UI事件汇总
- Android代码调试报错
- No implementation found for native Landroid/
- 懒人爱家务_onInterceptTouchEvent与onTouchEvent
- android多国语言的国家代码
- 有关于Android多个module混淆的问题
- 避免OOM的一种方式