首页
社区
课程
招聘
[原创]另一种绕过 Android 9以上非SDK接口调用限制的方法
发表于: 2021-7-20 01:31 19255

[原创]另一种绕过 Android 9以上非SDK接口调用限制的方法

2021-7-20 01:31
19255

原文:另一种绕过 Android 9以上非SDK接口调用限制的方法

从Android9开始,Google开始在Android平台限制对非SDK接口的调用。只要应用使用非SDK接口,或尝试使用反射或JNI来调用非SDK接口,都会收到某些限制。而且随着Android版本的升级,这种限制越来越强,被限制的接口也越来越多。这些限制对于Android平台上的一些黑科技来说(插件化,热修复,App双开,性能监控,Art Hook等),简直就是致命的。所以,各路大神都纷纷寻找绕过这个限制的手段。

最近在翻阅Android ART虚拟机源码时,发现另外一种简单绕过限制的方法。经测试,能够在Android 9-12上稳定运行。

下面先介绍主流的绕过限制的原理,再详细介绍这种全新的绕过策略,并在最后给出完整的源码实现。

一般调用一个非SDK接口,大多数情况下都是在Java层通过反射获取Class对应的方法Method,然后通过Method.invoke实现方法调用。Class.getDeclaredMethod最终都会进入一个native方法getDeclaredMethodInternal中,这个方法的实现如下:
(以下源码全部来自Android 11)

根据函数的名称,显而易见,这里通过ShouldDenyAccessToMember()这个函数来进行了调用限制。这个函数返回false,则返回一个空的jobject对象,上层就获取不到此方法对应的Method。

然后,在mirror::Class::GetDeclaredMethodInternal函数里,也有几处调用了ShouldDenyAccessToMember这个函数的判断,对返回结果进行限制:

从这几处的逻辑来看,虚拟机里是通过hiddenapi::ShouldDenyAccessToMember这个函数进行访问限制的。
绕过的方法似乎只能是对这个函数的返回值进行篡改。目前主流的一些绕过方法确实也是这样做的。

目前,开源社区里已有好几个绕过非SDK调用限制的方法。主要的思路都是对ShouldDenyAccessToMember这个函数进行干涉,想办法修改其返回值为false。

由于ShouldDenyAccessToMember函数是一个导出函数,打开libart.so可以看到其查找到对应的函数符号。很容易想到一个最简单的思路是,使用native inline hook技术,hook住ShouldDenyAccessToMember函数,使其不调用原函数,直接返回false。这样就简单绕过了调用限制。

另外,如果不适用使用hook技术,也可以修改这个函数返回值。只需要修改这个函数里调用的Runtime::Current()->GetHiddenApiEnforcementPolicy()接口的返回值,使其返回值为EnforcementPolicy.kDisabled,这样ShouldDenyAccessToMember函数就一定返回false。
因为ShouldDenyAccessToMember函数里有这样一行代码:

不过这种方案比较麻烦的点是,需要使用内存搜索的方式查找Runtime类里的hidden_api_policy_成员的偏移量,根据偏移量和current_runtime的地址计算出hidden_api_policy_的地址,再修改这个地址的内存值为EnforcementPolicy.kDisabled

在Android9中还可以使用双重反射(元反射)的方式来绕过。但在android11中元反射的方式已经被google屏蔽。
另外,还有一个取巧的方式,可以绕过google的这种屏蔽。将双重反射调用的相关代码单独编译成一个dex文件,然后构造一个DexFile对象来执行双重反射的方法,并且在加载时,设置加载的classloader为空,从而绕过限制。具体代码如下:

这样为什么可行?
跟踪源码可以知,dexFile.loadClass()时,最终会执行到ClassLinker::RegisterDexFileLocked中,这个函数调用了InitializeDexFileDomain

InitializeDexFileDomain这个函数传进来的classloader为空时,以下的DetermineDomainFromLocation则必定返回Domain::kPlatform,则这个dex_file对应的hiddenapi_domain_成员对象的值就是Domain::kPlatform

这个值又是怎样影响到ShouldDenyAccessToMember()函数的返回结果呢?
回到ShouldDenyAccessToMember()函数,有这样的逻辑:

其中,caller_context对应的就是调用方的context, 其对应的domain就是dex_file的domain,上面返回的是kPlatform, 而callee_context是根据class和dex_file来计算的,这个值也是kPlatform。因此上面代码中的caller_context.CanAlwaysAccess(callee_context)就返回了true,因为IsDomainMoreTrustedThan函数仅仅就是比较两个domain值的大小:

这样,ShouldDenyAccessToMember()也就返回了false,从而绕过访问限制。
这种方案的本质是,本来App的DexFile对应的domain应该是kApplication, 由于加载class时传进来了空的classloader,导致domain值变成了kPlatform,从而绕过了访问限制。

另外,google工程师已经想到了方法来堵住这个方案,修复的代码已提交,不过,可能是由于测试用例没有全部通过,代码目前并没有合入到主分支中。
相关Patch提交是:https://android-review.googlesource.com/c/platform/art/+/1668945

这个Patch的改法其实很简单,就是在DetermineDomainFromLocation()函数中,当classloader为空时,返回Domain::kApplication, 而不是Domain::kPlatform

此外,还有一种比较巧妙的方法绕过方法,借助了Java操作内存的Unsafe类。
实现源码为:https://github.com/LSPosed/AndroidHiddenApiBypass

这个方案的实现步骤为:

下面这个是java.lang.Class的镜像类,使用镜像类是为了避免反射获取java.lang.Class里面的私有成员对象。

下面代码是源码中将MethodHandle的artFieldOrMethod成员(对应native层ArtMethod指针)转换为mirror::Method(对应Java层java.lang.Method)的流程:

这个函数没有调用ShouldDenyAccessToMember函数,因此,此方法可行。

调用隐藏接口,除了使用上面提到的Class.getDeclaredMethod这个方法以外,也可以在native层通过JNIEnv->GetMethodId()来获取方法对应的jmethodId,然后调用JNIEnv->CallObjectMethod()来执行对应的method:

或许我们可以从native层可以找到一些突破口。
先看看JNIEnv->GetMethodId()的源码:

FindMethodJNI函数中,又看到熟悉的:ShouldDenyAccessToMember函数。也就是说,在这个流程里,也是通过ShouldDenyAccessToMember函数来限制App获取非SDK方法对应的ArtMethod对象。
再看看c->FindClassMethod(name, sig, pointer_size);这个调用流程里有没有类似的限制:

显然,FindClassMethod这个流程里并没有调用ShouldDenyAccessToMember函数,因此没有访问限制。
这个函数的主要流程是从类对象mirror::Class以及其父类中遍历所有ArtMethod指针,查找与目标name和signture匹配的ArtMethod指针。

既然mirror::Class::FindClassMethod这个函数中没有对非SDK接口进行限制的逻辑,那我们何不直接调用这个函数呢?
若要调用一个非公开的native函数,需满足以下两个条件:

先看第一个条件,FindClassMethod是一个非inline函数,并且在系统的libart.so文件符号表中,可以查找到这个函数对应符号为:

_ZN3art6mirror5Class15FindClassMethodENSt3__117basic_string_viewIcNS2_11char_traitsIcEEEES6_NS_11PointerSizeE

因此,可以使用linux动态库dl接口轻松获取到这个函数的地址,代码如下:

不过,这里需要注意的是,从Android7.0开始,Android系统限制了App使用dlopen打开系统动态库。不过,笔者之前开发了一个库可以轻松绕过这种限制。
源码:bypass_dlfunctions
实现原理:另一种绕过Android系统库访问限制的方法

再看第二个条件,这个函数需要传递四个参数,第一个是这个函数所在类mirror::Class的指针, 第二个参数是方法的name,第三个参数是方法的签名,第四个参数是指针的大小。显然,除了第一个对象,其他几个都是已知的。
难点是第一个参数,回到上面,看看FindMethodJNI这个函数是如何获取到mirror::Class指针:

Decode函数源码如下:

最终调用到了Thread::DecodeJObject函数,这个函数的逻辑比较清晰,就是从jobject对应的引用类型的table中查找对应的mirror::Object对象:

查看libart.so的符号表,发现DecodeJObject这个函数也是导出函数,对应的符号为:

_ZNK3art6Thread13DecodeJObjectEP8_jobject

这个函数的第一个参数是当前线程native Thread的指针。这个指针可以有两种方法获取到,第一种方式是反射获取Thread.java的nativePeer成员变量,这个值里存的就是native Thread的指针,反射过程代码如下在:

使用这种方式需要反射获取Thread.java中的私有成员变量nativePeer,目前这个变量被列入了greylist中,greylist中的api可以被调用,但在未来更高的TargetSDK版本可能会将其列入黑名单中。

为此,我们可以使用另外一种方法获取native Thread指针。

在JNIEnv结构体中,第一个位置是虚函数表,第二个位置就是native Thread指针。每个线程中JNIEnv都是已知的。因此,可以通过下面方法简单得到Thread指针,和上面的方法得到的结果刚好一致。

有了native Thread指针,调用Thread::DecodeJObject函数就能获取到当前class对应的mirror::Class指针, 再通过这个mirror::Class指针,方法的名称以及签名,调用mirror::Class::FindClassMethod函数获取到方法对应的ArtMethod指针,这个指针就是此方法的jmethodId。调用CallObjectMethod并传入这个jmethodId便可实现非SDK接口的调用。至此,成功绕过ART虚拟机对非SDK接口的调用限制。

完整代码流程如下:

上面的实现中,最终是反射调用了两个隐藏接口:

ShouldDenyAccessToMember的代码实现中,检查了member对应的class名称前缀是否包含在GetHiddenApiExemptions返回的vector中。所以的class名称都以“L”开头,设置"L"到这个HiddenApiExemptions中,ShouldDenyAccessToMember函数就一直返回false。

完整的源码发布到github:bypassHiddenApiRestriction

使用方法:build.gradle中添加以下依赖:

App入口代码添加以下代码即可:

 
 
// art/runtime/native/java_lang_Class.cc
static jobject Class_getDeclaredMethodInternal(JNIEnv* env, jobject javaThis,
                                               jstring name, jobjectArray args) {
  ScopedFastNativeObjectAccess soa(env);
  StackHandleScope<1> hs(soa.Self());
  ......
  Handle<mirror::Method> result = hs.NewHandle(
      mirror::Class::GetDeclaredMethodInternal<kRuntimePointerSize, false>(
          soa.Self(),
          klass,
          soa.Decode<mirror::String>(name),
          soa.Decode<mirror::ObjectArray<mirror::Class>>(args),
          GetHiddenapiAccessContextFunction(soa.Self())));
  if (result == nullptr || ShouldDenyAccessToMember(result->GetArtMethod(), soa.Self())) {
    return nullptr;
  }
  return soa.AddLocalReference<jobject>(result.Get());
}
// art/runtime/native/java_lang_Class.cc
static jobject Class_getDeclaredMethodInternal(JNIEnv* env, jobject javaThis,
                                               jstring name, jobjectArray args) {
  ScopedFastNativeObjectAccess soa(env);
  StackHandleScope<1> hs(soa.Self());
  ......
  Handle<mirror::Method> result = hs.NewHandle(
      mirror::Class::GetDeclaredMethodInternal<kRuntimePointerSize, false>(
          soa.Self(),
          klass,
          soa.Decode<mirror::String>(name),
          soa.Decode<mirror::ObjectArray<mirror::Class>>(args),
          GetHiddenapiAccessContextFunction(soa.Self())));
  if (result == nullptr || ShouldDenyAccessToMember(result->GetArtMethod(), soa.Self())) {
    return nullptr;
  }
  return soa.AddLocalReference<jobject>(result.Get());
}
 
template <PointerSize kPointerSize, bool kTransactionActive>
ObjPtr<Method> Class::GetDeclaredMethodInternal(
    Thread* self, ObjPtr<Class> klass, ObjPtr<String> name, ObjPtr<ObjectArray<Class>> args,
    const std::function<hiddenapi::AccessContext()>& fn_get_access_context) {
    ......
 
  bool m_hidden = hiddenapi::ShouldDenyAccessToMember(&m, fn_get_access_context, access_method);
    if (!m_hidden && !m.IsSynthetic()) {
      // Non-hidden, virtual, non-synthetic. Best possible result, exit early.
      return Method::CreateFromArtMethod<kPointerSize, kTransactionActive>(self, &m);
    } else if (IsMethodPreferredOver(result, result_hidden, &m, m_hidden)) {
      // Remember as potential result.
      result = &m;
      result_hidden = m_hidden;
    }
  }
      ......
      DCHECK(!m.IsMiranda());  // Direct methods cannot be miranda methods.
      bool m_hidden = hiddenapi::ShouldDenyAccessToMember(&m, fn_get_access_context, access_method);
      if (!m_hidden && !m.IsSynthetic()) {
        // Non-hidden, direct, non-synthetic. Any virtual result could only have been
        // hidden, therefore this is the best possible match. Exit now.
        DCHECK((result == nullptr) || result_hidden);
        return Method::CreateFromArtMethod<kPointerSize, kTransactionActive>(self, &m);
      } else if (IsMethodPreferredOver(result, result_hidden, &m, m_hidden)) {
      }
    }
  }
 
  return result != nullptr ? Method::CreateFromArtMethod<kPointerSize,kTransactionActive>(self, result) : nullptr;
}
template <PointerSize kPointerSize, bool kTransactionActive>
ObjPtr<Method> Class::GetDeclaredMethodInternal(
    Thread* self, ObjPtr<Class> klass, ObjPtr<String> name, ObjPtr<ObjectArray<Class>> args,
    const std::function<hiddenapi::AccessContext()>& fn_get_access_context) {
    ......
 
  bool m_hidden = hiddenapi::ShouldDenyAccessToMember(&m, fn_get_access_context, access_method);
    if (!m_hidden && !m.IsSynthetic()) {
      // Non-hidden, virtual, non-synthetic. Best possible result, exit early.
      return Method::CreateFromArtMethod<kPointerSize, kTransactionActive>(self, &m);
    } else if (IsMethodPreferredOver(result, result_hidden, &m, m_hidden)) {
      // Remember as potential result.
      result = &m;
      result_hidden = m_hidden;
    }
  }
      ......
      DCHECK(!m.IsMiranda());  // Direct methods cannot be miranda methods.
      bool m_hidden = hiddenapi::ShouldDenyAccessToMember(&m, fn_get_access_context, access_method);
      if (!m_hidden && !m.IsSynthetic()) {
        // Non-hidden, direct, non-synthetic. Any virtual result could only have been
        // hidden, therefore this is the best possible match. Exit now.
        DCHECK((result == nullptr) || result_hidden);
        return Method::CreateFromArtMethod<kPointerSize, kTransactionActive>(self, &m);
      } else if (IsMethodPreferredOver(result, result_hidden, &m, m_hidden)) {
      }
    }
  }
 
  return result != nullptr ? Method::CreateFromArtMethod<kPointerSize,kTransactionActive>(self, result) : nullptr;
}
 
 
// art/runtime/hidden_api.h #ShouldDenyAccessToMember()
EnforcementPolicy policy = Runtime::Current()->GetHiddenApiEnforcementPolicy();
if (policy == EnforcementPolicy::kDisabled) {
  return false;
}
// art/runtime/hidden_api.h #ShouldDenyAccessToMember()
EnforcementPolicy policy = Runtime::Current()->GetHiddenApiEnforcementPolicy();
if (policy == EnforcementPolicy::kDisabled) {
  return false;
}
// art/runtime/runtime.h
// 这是一个inline函数,因此只能通过内存搜索才能获取到这个成员变量.
hiddenapi::EnforcementPolicy GetHiddenApiEnforcementPolicy() const {
   return hidden_api_policy_;
}
 
  // Whether access checks on hidden API should be performed.
 hiddenapi::EnforcementPolicy hidden_api_policy_;
// art/runtime/runtime.h
// 这是一个inline函数,因此只能通过内存搜索才能获取到这个成员变量.
hiddenapi::EnforcementPolicy GetHiddenApiEnforcementPolicy() const {
   return hidden_api_policy_;
}
 
  // Whether access checks on hidden API should be performed.
 hiddenapi::EnforcementPolicy hidden_api_policy_;
DexFile dexFile = new DexFile(dexFile);
Class<?> bootstrapClass = dexFile.loadClass(BootstrapClass.class.getCanonicalName(), null);
Method exemptAll = bootstrapClass.getDeclaredMethod("exemptAll");
return  (boolean) exemptAll.invoke(null);
DexFile dexFile = new DexFile(dexFile);
Class<?> bootstrapClass = dexFile.loadClass(BootstrapClass.class.getCanonicalName(), null);
Method exemptAll = bootstrapClass.getDeclaredMethod("exemptAll");
return  (boolean) exemptAll.invoke(null);
// art/runtime/class_linker.cc
void ClassLinker::RegisterDexFileLocked(const DexFile& dex_file,
    ObjPtr<mirror::DexCache> dex_cache, ObjPtr<mirror::ClassLoader> class_loader) {
    ...
    // Let hiddenapi assign a domain to the newly registered dex file.
    hiddenapi::InitializeDexFileDomain(dex_file, class_loader);
    ...                                     
}
// art/runtime/class_linker.cc
void ClassLinker::RegisterDexFileLocked(const DexFile& dex_file,
    ObjPtr<mirror::DexCache> dex_cache, ObjPtr<mirror::ClassLoader> class_loader) {
    ...
    // Let hiddenapi assign a domain to the newly registered dex file.
    hiddenapi::InitializeDexFileDomain(dex_file, class_loader);
    ...                                     
}
// // art/runtime/hidden_api.cc
  void InitializeDexFileDomain(const DexFile& dex_file, ObjPtr<mirror::ClassLoader> class_loader) {
    Domain dex_domain = DetermineDomainFromLocation(dex_file.GetLocation(), class_loader);
 
    if (IsDomainMoreTrustedThan(dex_domain, dex_file.GetHiddenapiDomain())) {
      dex_file.SetHiddenapiDomain(dex_domain);
    }
  }
 
static Domain DetermineDomainFromLocation(const std::string& dex_location,
                                            ObjPtr<mirror::ClassLoader> class_loader) {
 
    ...
 
    if (LocationIsOnSystemFramework(dex_location.c_str())) {
      return Domain::kPlatform;
    }
 
    if (class_loader.IsNull()) {
      LOG(WARNING) << "DexFile " << dex_location
          << " is in boot class path but is not in a known location";
      return Domain::kPlatform;
    }
    return Domain::kApplication;
  }
// // art/runtime/hidden_api.cc
  void InitializeDexFileDomain(const DexFile& dex_file, ObjPtr<mirror::ClassLoader> class_loader) {
    Domain dex_domain = DetermineDomainFromLocation(dex_file.GetLocation(), class_loader);
 
    if (IsDomainMoreTrustedThan(dex_domain, dex_file.GetHiddenapiDomain())) {
      dex_file.SetHiddenapiDomain(dex_domain);
    }
  }
 
static Domain DetermineDomainFromLocation(const std::string& dex_location,
                                            ObjPtr<mirror::ClassLoader> class_loader) {
 
    ...
 
    if (LocationIsOnSystemFramework(dex_location.c_str())) {
      return Domain::kPlatform;
    }
 
    if (class_loader.IsNull()) {
      LOG(WARNING) << "DexFile " << dex_location
          << " is in boot class path but is not in a known location";
      return Domain::kPlatform;
    }
    return Domain::kApplication;
  }
// art/runtime/hidden_api.h
 template<typename T> inline bool ShouldDenyAccessToMember(T* member,
     const std::function<AccessContext()>& fn_get_access_context,
     AccessMethod access_method) {
     ...
    // Determine which domain the caller and callee belong to.
    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());
 
    // Check if the caller is always allowed to access members in the callee context.
    if (caller_context.CanAlwaysAccess(callee_context)) {
      return false;
    }
    ...
 
  }
 
    // Returns true if this domain is always allowed to access the domain of `callee`.
    bool CanAlwaysAccess(const AccessContext& callee) const {
      return IsDomainMoreTrustedThan(domain_, callee.domain_);
    }
// art/runtime/hidden_api.h
 template<typename T> inline bool ShouldDenyAccessToMember(T* member,
     const std::function<AccessContext()>& fn_get_access_context,
     AccessMethod access_method) {
     ...
    // Determine which domain the caller and callee belong to.
    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());
 
    // Check if the caller is always allowed to access members in the callee context.
    if (caller_context.CanAlwaysAccess(callee_context)) {
      return false;
    }
    ...
 
  }
 
    // Returns true if this domain is always allowed to access the domain of `callee`.
    bool CanAlwaysAccess(const AccessContext& callee) const {
      return IsDomainMoreTrustedThan(domain_, callee.domain_);
    }
// art/libartbase/base/hiddenapi_domain.h
enum class Domain : char {
    kCorePlatform = 0,
    kPlatform,
    kApplication,
  };
 
  inline bool IsDomainMoreTrustedThan(Domain domainA, Domain domainB) {
    return static_cast<char>(domainA) <= static_cast<char>(domainB);
  }
// art/libartbase/base/hiddenapi_domain.h
enum class Domain : char {
    kCorePlatform = 0,
    kPlatform,
    kApplication,
  };
 
  inline bool IsDomainMoreTrustedThan(Domain domainA, Domain domainB) {
    return static_cast<char>(domainA) <= static_cast<char>(domainB);
  }
 
 
 
 
 
static final public class Class {
        private transient ClassLoader classLoader;
        private transient java.lang.Class<?> componentType;
        private transient Object dexCache;
        private transient Object extData;
        private transient Object[] ifTable;
        private transient String name;
        private transient java.lang.Class<?> superClass;
        private transient Object vtable;
        private transient long iFields;
        private transient long methods;
        private transient long sFields;
        private transient int accessFlags;
        private transient int classFlags;
        private transient int classSize;
        private transient int clinitThreadId;
        ...
        ...
  }
static final public class Class {
        private transient ClassLoader classLoader;
        private transient java.lang.Class<?> componentType;
        private transient Object dexCache;
        private transient Object extData;
        private transient Object[] ifTable;
        private transient String name;
        private transient java.lang.Class<?> superClass;
        private transient Object vtable;
        private transient long iFields;
        private transient long methods;
        private transient long sFields;
        private transient int accessFlags;
        private transient int classFlags;
        private transient int classSize;
        private transient int clinitThreadId;
        ...
        ...
  }
// art/runtime/native/java_lang_invoke_MethodHandleImpl.cc
static jobject MethodHandleImpl_getMemberInternal(JNIEnv* env, jobject thiz) {
  ScopedObjectAccess soa(env);
  StackHandleScope<2> hs(soa.Self());
  Handle<mirror::MethodHandleImpl> handle = hs.NewHandle(
      soa.Decode<mirror::MethodHandleImpl>(thiz));
 
  const mirror::MethodHandle::Kind handle_kind = handle->GetHandleKind();
 
  MutableHandle<mirror::Object> h_object(hs.NewHandle<mirror::Object>(nullptr));
  if (handle_kind >= mirror::MethodHandle::kFirstAccessorKind) {
    ArtField* const field = handle->GetTargetField();
    h_object.Assign(mirror::Field::CreateFromArtField<kRuntimePointerSize, false>(
        soa.Self(), field, /* force_resolve= */ false));
  } else {
    ArtMethod* const method = handle->GetTargetMethod();
    if (method->IsConstructor()) {
      h_object.Assign(mirror::Constructor::CreateFromArtMethod<kRuntimePointerSize, false>(
          soa.Self(), method));
    } else {
      h_object.Assign(mirror::Method::CreateFromArtMethod<kRuntimePointerSize, false>(
          soa.Self(), method));
    }
  }
 
  return soa.AddLocalReference<jobject>(h_object.Get());
}
// art/runtime/native/java_lang_invoke_MethodHandleImpl.cc
static jobject MethodHandleImpl_getMemberInternal(JNIEnv* env, jobject thiz) {
  ScopedObjectAccess soa(env);
  StackHandleScope<2> hs(soa.Self());
  Handle<mirror::MethodHandleImpl> handle = hs.NewHandle(
      soa.Decode<mirror::MethodHandleImpl>(thiz));
 
  const mirror::MethodHandle::Kind handle_kind = handle->GetHandleKind();
 
  MutableHandle<mirror::Object> h_object(hs.NewHandle<mirror::Object>(nullptr));
  if (handle_kind >= mirror::MethodHandle::kFirstAccessorKind) {
    ArtField* const field = handle->GetTargetField();
    h_object.Assign(mirror::Field::CreateFromArtField<kRuntimePointerSize, false>(
        soa.Self(), field, /* force_resolve= */ false));
  } else {
    ArtMethod* const method = handle->GetTargetMethod();
    if (method->IsConstructor()) {
      h_object.Assign(mirror::Constructor::CreateFromArtMethod<kRuntimePointerSize, false>(
          soa.Self(), method));
    } else {
      h_object.Assign(mirror::Method::CreateFromArtMethod<kRuntimePointerSize, false>(
          soa.Self(), method));
    }
  }
 
  return soa.AddLocalReference<jobject>(h_object.Get());
}
jclass context_class = env->FindClass("android/content/Context");
jmethodID get_content_resolver_mid = env->GetMethodID(context_class, "getContentResolver", "()Landroid/content/ContentResolver;");
jobject content_resolver_obj = env->CallObjectMethod(context, get_content_resolver_mid);
jclass context_class = env->FindClass("android/content/Context");
jmethodID get_content_resolver_mid = env->GetMethodID(context_class, "getContentResolver", "()Landroid/content/ContentResolver;");
jobject content_resolver_obj = env->CallObjectMethod(context, get_content_resolver_mid);

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

收藏
免费 6
支持
分享
最新回复 (5)
雪    币: 4752
活跃值: (2923)
能力值: ( LV7,RANK:100 )
在线值:
发帖
回帖
粉丝
2
很强,学习
2021-7-20 13:39
0
雪    币: 0
活跃值: (532)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
学习!
2021-7-21 17:12
0
雪    币: 208
活跃值: (387)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
1. DecodeJObject 有 abi 边界问题,在 x86 下你的 DecodeJObject 会崩,详见:https://github.com/LSPosed/LSPosed/commit/114b4505347bd2eaf78721531c2a5fc577f0ce3d

2. 既然用 c++ 了,可以利用 JNI_OnLoad 没有隐藏 API 限制的特性,直接绕过。

3. 不利用 JNI_OnLoad 也可以直接找 `_ZN3artL32VMRuntime_setHiddenApiExemptionsEP7_JNIEnvP7_jclassP13_jobjectArray` 符号直接调用就行。
2021-7-23 20:50
0
雪    币: 1348
活跃值: (20)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
收藏学习,谢谢大佬!
2021-7-23 21:31
0
雪    币: 9
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
6
黑盒BlackBox,是一款虚拟引擎,可以在Android上克隆、运行虚拟应用,拥有免安装运行能力。黑盒可以掌控被运行的虚拟应用,做任何想做的事情。 https://github.com/FBlackBox/BlackBox
2022-4-20 13:14
0
游客
登录 | 注册 方可回帖
返回
//