Android Hook - 隐藏API拦截机制

862 阅读16分钟

从 Android 9(API 级别 28)开始,Android 平台对应用能使用的非 SDK 接口实施了限制。

本文聚焦Android P(9),即第一个引入隐藏API限制的版本,通过分析其源码,揭示系统在调用隐藏API时的拦截过程。

在解析拦截机制的过程中,我们会梳理其关键点,尝试找出可能绕过限制的方法。这些发现将为后续研究如何突破隐藏API的封锁提供依据。

本文是隐藏API绕过系列文章的第一篇,旨在介绍Android系统中对调用隐藏API进行拦截的实现机制。在此基础上,后续文章将逐步介绍多种隐藏API绕过的实现方式,同时结合更高版本的系统源码,分析一些绕过方式失效的原因。

最后,对于不熟悉Android Hook技术的读者,隐藏API绕过则是一个很好的学习案例,后续文章将结合代码实践,介绍Hook相关的技巧工具

一、背景

1、介绍隐藏API

从 Android 9(API 级别 28)开始,Android 平台对应用能使用的非 SDK 接口实施了限制。这些接口在源码的注释中使用**@hide**来说明,例如:

/**
 * Returns the object that represents the current runtime.
 * @return the runtime object
 *
 * @hide
 */
@UnsupportedAppUsage
@SystemApi(client = MODULE_LIBRARIES)
@libcore.api.IntraCoreApi
public static VMRuntime getRuntime() {
    return THE_ONE;
}

详细介绍可以见官方文档针对非 SDK 接口的限制,总的来说有以下要点:

  1. 隐藏API的名单。参考非 SDK API 名单,对隐藏API的限制记录在系统名单中,而名单则分为whitelistgreylistblacklist等。访问不同级别名单的API,系统的处理方式不同,在blacklist中的API通常会抛出异常,在greylist则可能运行访问并在Logcat打印警告。

    在源码中,我们也可以找到这些名单,例如hiddenapi-force-blacklist.txt:

    Ldalvik/system/VMRuntime;->setHiddenApiExemptions([Ljava/lang/String;)V
    ...
    
  2. 隐藏API的名单会随着Android版本的升级而变更。参考确定接口属于哪个名单

  3. 可以通过adb更改 API强制执行策略。参考如何允许访问非 SDK 接口,通过:

    adb shell settings put global hidden_api_policy  1
    

    可以临时修改系统的强制执行策略,从而允许APP对隐藏API的访问。


2、调用时的表现

当应用尝试调用隐藏API,由于所在限制名单的不同而表现不同,具体参考访问受限的非 SDK 接口时可能会出现的预期行为,但总的来说:

  • 反射获取指定方法/属性,会抛出 NoSuchMethodException/NoSuchFieldException异常。
  • 反射获取方法/数量列表,结果中不会获取到非 SDK 成员。
  • 使用JNI获取MethodID/FieldID,返回 NULL,并抛出 NoSuchMethodError

您可以使用 adb logcat 来查看这些日志消息,这些消息显示在所运行应用的 PID 下。举例而言,日志中可能包含如下条目:

Accessing hidden field Landroid/os/Message;->flags:I (light greylist, JNI)

这里透露的重点是,隐藏API机制限制的是通过反射等手段获取目标Method对象的这个过程,而一旦我们获取到目标Method对象后,对它的调用则不经过检查


3、运行时豁免名单

VMRuntime.java

package dalvik.system;

public final class VMRuntime {
  ...
  /**
   * Sets the list of exemptions from hidden API access enforcement.
   *
   * @param signaturePrefixes
   *         A list of signature prefixes. Each item in the list is a prefix match on the type
   *         signature of a blacklisted API. All matching APIs are treated as if they were on
   *         the whitelist: access permitted, and no logging..
   *
   * @hide
   */
  public native void setHiddenApiExemptions(String[] signaturePrefixes);
  ...
}

在进行具体源码分析前,需要介绍一个重要方法,即VMRuntime.setHiddenApiExemptions()

根据注释,这个方法可以在运行时给虚拟机设置隐藏API的豁免名单,在此名单中的方法可以不受限制的调用隐藏API,并且这个名单是根据方法签名进行前缀匹配来检验。

集合源码hiddenapi-force-blacklist.txt,所谓的方法签名规则和JNI方法签名类似,即:

Ldalvik/system/VMRuntime;->setHiddenApiExemptions([Ljava/lang/String;)V
Ljava/lang/invoke/MethodHandles$Lookup;->IMPL_LOOKUP:Ljava/lang/invoke/MethodHandles$Lookup;
...

因此,如果我们把**L**作为前缀添加到豁免名单中,就相当于给所有方法都添加了豁免。

遗憾的是,setHiddenApiExemptions()本身就是隐藏API,这是一个鸡生蛋蛋生鸡的问题。

但是我们最终的目标就可以转换为绕过隐藏API机制,进而调用setHiddenApiExemptions()方法


二、Android P源码分析

Android隐藏API绕过.drawio.svg

如图所示,隐藏API的拦截机制主要有四个检查点:

  1. 检查目标方法的accessflag。accessflag低29,30位记录着ApiList类型,分别为白名单,灰名单等,如果是白名单,则返回允许。方法的accessflag在系统dex文件编译时就已经指定。

  2. 检查系统的隐藏API拦截策略。这个策略为kNoChecks则说明不拦截,这对应前文使用adb指令修改的hidden_api_policy标志。

  3. 检查调用栈是否可信。这里指的可信,即指首个调用反射方法(getMethod)的类/方法是否可信,例如在Android P中,如果调用者是系统类,那么不需要拦截,否则连系统自身也调用不了隐藏API了。

    在Android不同版本中,对"可信"这个判断条件不同,是造成部分针对旧版本的绕过机制失效的重要原因。

  4. 检查豁免名单。即检查目标方法是否在通过VMRuntime.setHiddenApiExemptions()设置的豁免名单内,是则不拦截。

接下来将参考Android P源码,以反射/JNI获取Method对象的过程为例,详细介绍隐藏API的拦截机制。

1、反射/JNI调用入口

1.1、反射调用

Class.java

public Method getDeclaredMethod(String name, Class<?>... parameterTypes)
    throws NoSuchMethodException, SecurityException {
    // Android-changed: ART has a different JNI layer.
    return getMethod(name, parameterTypes, false);
}

// BEGIN Android-added: Internal methods to implement getMethod(...).
private Method getMethod(String name, Class<?>[] parameterTypes, boolean recursivePublicMethods)
        throws NoSuchMethodException {
    ...
    //1、实际调用getDeclaredMethodInternal()这个jni方法
    Method result = recursivePublicMethods ? getPublicMethodRecursive(name, parameterTypes)
                                           : getDeclaredMethodInternal(name, parameterTypes);
    ..
    return result;
}

@FastNative
private native Method getDeclaredMethodInternal(String name, Class<?>[] args);
  • JAVA方法最终调用会调用Class_getDeclaredMethodInternal()方法。

java_lang_Class.cc

static jobject Class_getDeclaredMethodInternal(JNIEnv* env, jobject javaThis,
                                               jstring name, jobjectArray args) {
  ScopedFastNativeObjectAccess soa(env);
  ...
  //1、传入方法名和参数类型,获取目标方法对象
  Handle<mirror::Method> result = hs.NewHandle(
      mirror::Class::GetDeclaredMethodInternal<kRuntimePointerSize, false>(
          soa.Self(),
          DecodeClass(soa, javaThis),
          soa.Decode<mirror::String>(name),
          soa.Decode<mirror::ObjectArray<mirror::Class>>(args)));
  //2、进入隐藏拦截进制判断,这里传入的方法对应的ArtMethod指针和当前线程
  if (result == nullptr || ShouldBlockAccessToMember(result->GetArtMethod(), soa.Self())) {
    return nullptr;
  }
  //3、将前面得到Method对象返回
  return soa.AddLocalReference<jobject>(result.Get());
}
  • 首先传入方法名和参数类型,获取目标方法,即Method对象。可以看到,这里是先找到Method对象,再检查它是否应该被拦截的。
  • 调用ShouldBlockAccessToMember检查是否应该拦截。

java_lang_Class.cc

// 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_) {
  //1、传入参数为:访问的成员(属性、方法)、当前线程、IsCallerTrusted回调、方法类型,返回值为检查结果
  hiddenapi::Action action = hiddenapi::GetMemberAction(
      member, self, IsCallerTrusted, hiddenapi::kReflection);
  if (action != hiddenapi::kAllow) {
    hiddenapi::NotifyHiddenApiListener(member);
  }
  //2、判断检查结果是否为拒绝
  return action == hiddenapi::kDeny;
}

enum Action {
  kAllow,  //允许访问
  kAllowButWarn, //允许但有warn日志
  kAllowButWarnAndToast, //允许但有warn日志、Toast提示
  kDeny  //不允许
};
  • hiddenapi::GetMemberAction()方法进行调用拦截判断,其中参数hiddenapi::kReflection表示是由反射调用的。
  • hiddenapi::GetMemberAction()方法值为action,从代码可以看出,只要返回Action.kDeny才表示不允许访问。

1.2、JNI调用

jni_internal.cc

enum AccessMethod {
  kNone,  //  测试模式,不会出现在实际场景访问权限
  kReflection, //反射
  kJNI,		  //JNI调用
  kLinking,	  //动态链接过程
};

static jmethodID GetMethodID(JNIEnv* env, jclass java_class, const char* name, const char* sig) {
  ...
  //1、调用FindMethodID
  return ShouldBlockAccessToMember(soa, java_class, name, sig, false);
}

static jmethodID FindMethodID(ScopedObjectAccess& soa, jclass jni_class,
                            const char* name, const char* sig, bool is_static)
  ..  
  ArtMethod* method = nullptr;
  auto pointer_size = Runtime::Current()->GetClassLinker()->GetImagePointerSize();
	//2、根据名称和参数类型,找到目标ArtMethod指针
  if (c->IsInterface()) {
    method = c->FindInterfaceMethod(name, sig, pointer_size);
  } else {
    method = c->FindClassMethod(name, sig, pointer_size);
  }
	//3、检查是否应该被拦截
  if (method != nullptr && ShouldBlockAccessToMember(method, soa.Self())) {
    method = nullptr;
  }
  ...
  //4、将ArtMethod转成jmethodID
  return jni::EncodeArtMethod(method);
}

template<typename T>
ALWAYS_INLINE static bool ShouldBlockAccessToMember(T* member, Thread* self)
    REQUIRES_SHARED(Locks::mutator_lock_) {
  //5、通用调用GetMemberAction方法,
  hiddenapi::Action action = hiddenapi::GetMemberAction(
      member, self, IsCallerTrusted, hiddenapi::kJNI);
  if (action != hiddenapi::kAllow) {
    hiddenapi::NotifyHiddenApiListener(member);
  }

  return action == hiddenapi::kDeny;
}

JNI调用和反射调用的区别:

  1. 根据名称和参数类型,找到目标ArtMethod指针,最终返回jmethodID。而反射则返回Method对象。
  2. 调用同名的ShouldBlockAccessToMember()方法,都是传入ArtMethod指针。
  3. 最终调用hiddenapi::GetMemberAction()方法,区别是IsCallerTrustedhiddenapi::kJNI两个参数。

💡无论是反射还是JNI,都是使用ArtMethod指针表示目标方法,如果我们把某个公开方法的ArtMethod指针替换成隐藏方法的ArtMethod指针,不就可以直接使用了吗?


1.3、核心方法GetMemberAction

hidden_api.h

template<typename T>
inline Action GetMemberAction(T* member,
                              Thread* self,
                              std::function<bool(Thread*)> fn_caller_is_trusted,
                              AccessMethod access_method)
  ...  
  //1、获取成员(属性、方法)的accessFlags
  HiddenApiAccessFlags::ApiList api_list = member->GetHiddenApiAccessFlags();

  //2、将accessFlags转成对应的Action
  Action action = GetActionFromAccessFlags(member->GetHiddenApiAccessFlags());
  if (action == kAllow) {
    // Nothing to do.
    return action;
  }
  
  //3、继续检查调用栈是否可信
  if (fn_caller_is_trusted(self)) {
    // Caller is trusted. Exit.
    return kAllow;
  }

  // Member is hidden and caller is not in the platform.
  //4、最后,检查是否在豁免名单内。
  return detail::GetMemberActionImpl(member, api_list, action, access_method);
}

无论是反射还是JNI,最终都会调用到GetMemberAction()这个核心方法,注释中说明了隐藏API机制拦截的四个过程,前面已经介绍过了,不再赘述。

💡如果我们可以使用Hook将GetMemberAction()替换掉,使得它总是返回kAllow,就可以绕过隐藏API机制。

接下来看看每个过程的细节。


2、目标方法AccessFlags

2.1、AccessFlags记录方法所属的隐藏名单

art_field.h

class ArtField FINAL {
  HiddenApiAccessFlags::ApiList GetHiddenApiAccessFlags() REQUIRES_SHARED(Locks::mutator_lock_) {
      return HiddenApiAccessFlags::DecodeFromRuntime(GetAccessFlags());
  }
  
  ...
}


//0b110000000000000000000000000000
static constexpr uint32_t kAccHiddenApiBits =         0x30000000;  // field, method

hidden_api_access_flags.h

class HiddenApiAccessFlags {
  //1、统计后缀0的个数,因此这里是28
  static const int kAccFlagsShift = CTZ(kAccHiddenApiBits);
  
	static ALWAYS_INLINE ApiList DecodeFromRuntime(uint32_t runtime_access_flags) {
    // This is used in the fast path, only DCHECK here.
    DCHECK_EQ(runtime_access_flags & kAccIntrinsic, 0u);
    //2、只获取access_flags的高29、30位,右移转成ApiList
    uint32_t int_value = (runtime_access_flags & kAccHiddenApiBits) >> kAccFlagsShift;
    return static_cast<ApiList>(int_value);
  }
  ..
}

art_method.cc

class ArtMethod FINAL {
  // Note: GetAccessFlags acquires the mutator lock in debug mode to check that it is not called for
    // a proxy method.
    template <ReadBarrierOption kReadBarrierOption = kWithReadBarrier>
    uint32_t GetAccessFlags() {
      if (kCheckDeclaringClassState) {
        GetAccessFlagsDCheck<kReadBarrierOption>();
      }
      return access_flags_.load(std::memory_order_relaxed);
    }
  
protected:
    // Field order required by test "ValidateFieldOrderOfJavaCppUnionClasses".
    // The class we are a part of.
    GcRoot<mirror::Class> declaring_class_;

    // Access flags; low 16 bits are defined by spec.
    // Getting and setting this flag needs to be atomic when concurrency is
    // possible, e.g. after this method's class is linked. Such as when setting
    // verifier flags and single-implementation flag.
    std::atomic<std::uint32_t> access_flags_;
}
  • ArtMethod的成员属性access_flags_中,然后读取access_flags_的高两位(29, 30),转成ApiListaccess_flags_是在类编译过程中写入到dex文件的,我们常见的Public、Private等属性也记录在这个值中。
  • ApiList就是隐藏名单的类型,前面介绍背景时中提到过,在hidden_api_access_flags.h中定义。
class HiddenApiAccessFlags {
 public:
  enum ApiList {
    kWhitelist = 0,  //白名单, 0x00
    kLightGreylist,  //浅灰名单, 0x01
    kDarkGreylist,   //深灰名单, 0x10
    kBlacklist,      //黑名单, 0x11
  };
  ...
}

💡如果我们可以修改ArtMethod的access_flags_,使得高两位为0,就等价于把目标方法放入到白名单中。


2.2、检查系统隐藏API策略

hidden_api.h

enum class EnforcementPolicy {
  kNoChecks             = 0,
  kJustWarn             = 1,  // 保持检查,一切都允许(仅仅记录日志)
  kDarkGreyAndBlackList = 2,  // 禁止深灰色和黑名单
  kBlacklistOnly        = 3,  // 只禁止黑名单
  kMax = kBlacklistOnly,
};

inline Action GetActionFromAccessFlags(HiddenApiAccessFlags::ApiList api_list) {
  //1、如果是白名单内,直接允许调用
  if (api_list == HiddenApiAccessFlags::kWhitelist) {
    return kAllow;
  }

  //2、获取隐藏名单处理策略,如果是不检查,那么全部返回允许
  EnforcementPolicy policy = Runtime::Current()->GetHiddenApiEnforcementPolicy();
  if (policy == EnforcementPolicy::kNoChecks) {
    // Exit early. Nothing to enforce.
    return kAllow;
  }

  //3、其他情况都需要继续检查,不具体看
  ...
}

runtime.h

class Runtime {
  ...
	// Whether access checks on hidden API should be performed.
  hiddenapi::EnforcementPolicy hidden_api_policy_;

	hiddenapi::EnforcementPolicy GetHiddenApiEnforcementPolicy() const {
    return hidden_api_policy_;
  }
  
  void SetHiddenApiEnforcementPolicy(hiddenapi::EnforcementPolicy policy) {
    hidden_api_policy_ = policy;
  }
  ...
}
  1. 如果api_listkWhitelist,那么不拦截,后续也不需要检查了。
  2. 如果隐藏名单处理策略hidden_api_policy_kNoChecks,那么不拦截,后续也不需要检查了。
  3. hidden_api_policy_Runtime对象的一个成员变量。

💡如果我们可以修改Runtime.hidden_api_policy_kNoChecks,不就可以关闭检查策略了。


3、调用栈是否可信

3.1、查找反射调用者

java_lang_Class.cc

//1、如果外部第一次调用Class.class或反射方法的地方,是来源于platform DEX file,那么认为是系统调用的
static bool IsCallerTrusted(Thread* self) REQUIRES_SHARED(Locks::mutator_lock_) {  
  //2、回溯JAVA堆栈,找到第一个不是来自java.lang.Class和java.lang.invoke的栈
  struct FirstExternalCallerVisitor : public StackVisitor {
    explicit FirstExternalCallerVisitor(Thread* thread)
        : StackVisitor(thread, nullptr, StackVisitor::StackWalkKind::kIncludeInlinedFrames),
          caller(nullptr) {
    }

    //3、访问当前栈帧,返回true说明要继续向上查找,caller指针用于记录查找结果
    bool VisitFrame() REQUIRES_SHARED(Locks::mutator_lock_) {
      ArtMethod *m = GetMethod();
      if (m == nullptr) {
        //4、native线程调用,那么判断为非系统的调用,不继续查找
        caller = nullptr;
        return false;
      } else if (m->IsRuntimeMethod()) {
        // 5、判断是否虚拟机内部方法,是则继续查找
        return true;
      }

      ObjPtr<mirror::Class> declaring_class = m->GetDeclaringClass();
      //6、如果是classloader是BootStrapClassLoad,也就是classLoader为空
      if (declaring_class->IsBootStrapClassLoaded()) {      
        //6.1、如果是Class类,那么向上再找
        if (declaring_class->IsClassClass()) {
          return true;
        }      
        //6.2、检查 java.lang.invoke 包中的类。在撰写本文时,感兴趣的类是 MethodHandles 和 MethodHandles.Lookup,但这有可能发生变化,因此保守地覆盖整个包。注意 java.lang.invoke 中的静态初始化器是允许的,不需要进一步的堆栈检查。
        //也就是说,如果包名为java.lang.invoke,那么继续向上查找
        ObjPtr<mirror::Class> lookup_class = mirror::MethodHandlesLookup::StaticClass();
        if ((declaring_class == lookup_class || declaring_class->IsInSamePackage(lookup_class))
            //并且不是构造方法或静态方法
            && !m->IsClassInitializer()) {
          return true;
        }
      }
	  	//7、如果classloader不是BootStrapClassLoad,那么此时caller就为第一个调用反射的类
      //如果classloader是BootStrapClassLoad,但又不是Class或者在java.lang.invoke包内,那么也找到了
      caller = m;
      return false;
    }

    ArtMethod* caller;
  };
 
  FirstExternalCallerVisitor visitor(self);
  //根据调用栈向上查找反射入口
  visitor.WalkStack();
  
  //8、如果找到调用反射的方法,那么进一步检查它是否可信,找不到说明来自native,直接不可信
  return visitor.caller != nullptr &&
         hiddenapi::IsCallerTrusted(visitor.caller->GetDeclaringClass());
}

这一步骤大家可以结合代码和注释查看,实际并不复杂。目标是找到第一个调用反射相关方法的类,通常也就是我们应用代码中的类。

这里是以反射调用过程中传入ShouldBlockAccessToMember()中的IsCallerTrusted()为例的,JNI调用过程也会传入同名的IsCallerTrusted()方法,但是实现稍有不同,但目标相同。

大家看自行对比两者的实现。

代码中有一个重点是如果碰到Class.java或者java.lang.invoke包名下的类会继续网上查找,目的是拦截MethodHandles机制的调用。

在常见的应用代码中,例如:

public class Test{
    public void test(){
        Class clazz = Class.forName("xxx");
        clazz.getDeclaredMethod(...);
    }
}

按照向上查找的逻辑,首次调用的方法即为Test.test()

💡如果首个调用getDeclaredMethod()的方法是某个系统包下的方法,不就可以正常调用了。


3.2、系统类的判断

java_lang_Class.cc

hidden_api.h

inline bool IsCallerTrusted(ObjPtr<mirror::Class> caller) REQUIRES_SHARED(Locks::mutator_lock_) {
  //1、传入参数判断
  return !caller.IsNull() &&
      detail::IsCallerTrusted(caller, caller->GetClassLoader(), caller->GetDexCache());
}

ALWAYS_INLINE
inline bool IsCallerTrusted(ObjPtr<mirror::Class> caller,
                            ObjPtr<mirror::ClassLoader> caller_class_loader,
                            ObjPtr<mirror::DexCache> caller_dex_cache)
    REQUIRES_SHARED(Locks::mutator_lock_) {
  //1、如果classloader为null,则认为是boot class loader,因此是来自系统的调用
  if (caller_class_loader.IsNull()) {
    return true;
  }

  if (!caller_dex_cache.IsNull()) {
    const DexFile* caller_dex_file = caller_dex_cache->GetDexFile();
    //2、根据dex文件判断是platform,这个标志在dex文件加载时会设置,主要是通过dex文件路径是否在/framework路径下判断的
    if (caller_dex_file != nullptr && caller_dex_file->IsPlatformDexFile()) {
      // Caller is in a platform dex file.
      return true;
    }
  }

  ...

  return false;
}

找到caller后,需要进一步判断caller是否可信:

  1. classloader为null。如果classloader为null则被认为是系统类,因此放过拦截。
  2. 类所在的dex文件是否位于/system/framework/目录IsPlatformDexFile()方法返回变量is_platform_dex_,根据dex_file.h代码中的注释,表示Dex文件是否位于/system/framework/目录。

💡dex文件所在的目录无法改变,但是如果我们把某个类的classloader设置为空,不就会被认为是系统类了吗。


4、豁免名单

4.1、GetMemberActionImpl

hidden_api.cc

template<typename T>
Action GetMemberActionImpl(T* member,
                           HiddenApiAccessFlags::ApiList api_list,
                           Action action,
                           AccessMethod access_method) {
  ...
  // Get the signature, we need it later.
  //1、获取方法签名
  MemberSignature member_signature(member);

  Runtime* runtime = Runtime::Current();
  
  const bool shouldWarn = kLogAllAccesses || runtime->IsJavaDebuggable();
  if (shouldWarn || action == kDeny) {
    //2、判断方法是否在豁免名单内
    if (member_signature.IsExempted(runtime->GetHiddenApiExemptions())) {
      action = kAllow;      
      //2.1、为了避免下次再检查豁免名单,MaybeWhitelistMember()可以把member的access_flag修改为白名单。
      MaybeWhitelistMember(runtime, member);
      return kAllow;
    }

    ...
  }
 
  ...

  return action;
}


template<typename T>
static ALWAYS_INLINE void MaybeWhitelistMember(Runtime* runtime, T* member)
    REQUIRES_SHARED(Locks::mutator_lock_) {
  //3、CanUpdateMemberAccessFlags()默认为true, ShouldDedupeHiddenApiWarnings()默认为true,
  if (CanUpdateMemberAccessFlags(member) && runtime->ShouldDedupeHiddenApiWarnings()) {
    //4、修改access_flags
    member->SetAccessFlags(HiddenApiAccessFlags::EncodeForRuntime(
        member->GetAccessFlags(), HiddenApiAccessFlags::kWhitelist));
  }
}

static ALWAYS_INLINE bool CanUpdateMemberAccessFlags(ArtMethod* method) {
  return !method->IsIntrinsic();
}

最后检查方法是否在豁免名单中,正如前文所述,豁免名单可以通过VMRuntime.setHiddenApiExemptions()方法设置。

并且当某个方法通过了豁免名单的检查,就会修改它accessflag的高两位为HiddenApiAccessFlags::kWhitelist,从而避免下次调用还需要经过那么复杂的检查。

💡能否直接调用VMRuntime.setHiddenApiExemptions()方法?


三、总结

经过对于Android P源码的仔细分析,我们明白了隐藏API拦截机制的关键步骤,并且发现最终要实现拦截,实际上有很多前提条件,包括方法的accessflags、系统当前的拦截机制开关、classloader不为null等等。

随着Android系统版本的迭代,官方不断优化代码从而使得前提条件更加严格,但是无论如何,我们Hook的思路仍然是阅读源码,然后见招拆招。

这些前提条件就是绕过拦截的突破口,甚至可以说这样的突破口显得有点多,这使得隐藏API绕过是一个学习常见Hook技巧工具的一个很好的实践例子。

对这些工具不熟悉的朋友可以关注下一篇文章,我们将会在那里了解详细的实践过程。

Android Hook - 隐藏API绕过实践


四、写在最后

1、源码下载

BypassHiddenApi

2、免责声明

本文涉及的代码,旨在展示和描述方案的可行性,可能存bug或者性能问题。

不建议未经修改验证,直接使用于生产环境。

3、转载声明

本文欢迎转载,转载请注明出处

4、留言讨论

你是否也在现实开发中遇到类似的场景或者应用,是否有更多的意见和想法,欢迎留言一起学习讨论。

5、欢迎关注

如果你对更多的Android Hook开发技巧、思路感兴趣的,欢迎关注我的栏目。

后续将提供更多优质内容,硬核干货。