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判断类的classloaderBootStrapClassLoader的话会进一步判断调用者类是否为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

posted @ 2023-01-09 11:21  怎么可以吃突突  阅读(341)  评论(0编辑  收藏  举报