1. 程式人生 > >深入原始碼分析non-sdk並繞過Android 9.0反射限制

深入原始碼分析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`.
template<typename T>
ALWAYS_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<typename T> inline Action GetMemberAction(T* member,Thread* self,std::function<bool(Thread*)> 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<Intrinsics>(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<int>(policy) > static_cast<int>(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()函式會做哪些判斷:

template<typename T>
Action 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<uint32_t>(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()實現:

template<typename T>
static 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<mirror::DexCache> dex_cache,
                              ObjPtr<mirror::ClassLoader> 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<jobject>(result.Get());
}



template<typename T>
ALWAYS_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);
}



template<typename T>
ALWAYS_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<mirror::Class> klass,
                                           ObjPtr<mirror::DexCache> dex_cache,
                                           ObjPtr<mirror::ClassLoader> 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()主要程式碼分析完了,剩下最後提示的部分:

emplate<typename T>
Action 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<MemberSignature>(*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檢查。

休息一會再回來接著寫.......