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检查。

休息一会再回来接着写.......

 

 

 

更多相关文章

  1. 没有一行代码,「2020 新冠肺炎记忆」这个项目却登上了 GitHub 中
  2. Android(安卓)4.1-4.2 默认窗体旋转180 度代码
  3. Android开发之《Android应用开发揭秘》UI事件汇总
  4. Android代码调试报错
  5. No implementation found for native Landroid/
  6. 懒人爱家务_onInterceptTouchEvent与onTouchEvent
  7. android多国语言的国家代码
  8. 有关于Android多个module混淆的问题
  9. 避免OOM的一种方式

随机推荐

  1. Android(安卓)Retrofit 框架上传多张图片
  2. 【学习Android(安卓)NDK开发】native cod
  3. Android:只读EditText内容可滚动(禁止输
  4. Android 蓝牙状态机以及蓝牙启动状态机
  5. Android事件分发机制
  6. Tiny210(Android)串口收发测试通过
  7. android 流量统计实现思路
  8. TextView中ellipsize属性焦点异常处理
  9. 如何为香蕉派 banana pi BPI-M2编译Andro
  10. android菜单Tips