首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐

  • 24-12-02 17:05
  • 2818
  • 13365
juejin.cn

从 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**来说明,例如:

java
代码解读
复制代码
/** * 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的限制记录在系统名单中,而名单则分为whitelist、greylist、blacklist等。访问不同级别名单的API,系统的处理方式不同,在blacklist中的API通常会抛出异常,在greylist则可能运行访问并在Logcat打印警告。

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

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

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

    shell
    代码解读
    复制代码
    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 下。举例而言,日志中可能包含如下条目:

shell
代码解读
复制代码
Accessing hidden field Landroid/os/Message;->flags:I (light greylist, JNI)

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


3、运行时豁免名单

VMRuntime.java

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方法签名类似,即:

shell
代码解读
复制代码
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

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

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

java_lang_Class.cc

c++
代码解读
复制代码
// 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

c++
代码解读
复制代码
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()方法,区别是IsCallerTrusted和hiddenapi::kJNI两个参数。

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


1.3、核心方法GetMemberAction

hidden_api.h

c++
代码解读
复制代码
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

c++
代码解读
复制代码
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

c++
代码解读
复制代码
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(int_value); } .. }

art_method.cc

c++
代码解读
复制代码
class ArtMethod FINAL { // Note: GetAccessFlags acquires the mutator lock in debug mode to check that it is not called for // a proxy method. template uint32_t GetAccessFlags() { if (kCheckDeclaringClassState) { GetAccessFlagsDCheck(); } return access_flags_.load(std::memory_order_relaxed); } protected: // Field order required by test "ValidateFieldOrderOfJavaCppUnionClasses". // The class we are a part of. GcRoot 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::atomicuint32_t> access_flags_; }
  • ArtMethod的成员属性access_flags_中,然后读取access_flags_的高两位(29, 30),转成ApiList。access_flags_是在类编译过程中写入到dex文件的,我们常见的Public、Private等属性也记录在这个值中。
  • ApiList就是隐藏名单的类型,前面介绍背景时中提到过,在hidden_api_access_flags.h中定义。
c++
代码解读
复制代码
class HiddenApiAccessFlags { public: enum ApiList { kWhitelist = 0, //白名单, 0x00 kLightGreylist, //浅灰名单, 0x01 kDarkGreylist, //深灰名单, 0x10 kBlacklist, //黑名单, 0x11 }; ... }

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


2.2、检查系统隐藏API策略

hidden_api.h

c++
代码解读
复制代码
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

c++
代码解读
复制代码
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_list是kWhitelist,那么不拦截,后续也不需要检查了。
  2. 如果隐藏名单处理策略hidden_api_policy_为kNoChecks,那么不拦截,后续也不需要检查了。
  3. hidden_api_policy_是Runtime对象的一个成员变量。

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


3、调用栈是否可信

3.1、查找反射调用者

java_lang_Class.cc

c++
代码解读
复制代码
//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 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 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()方法,但是实现稍有不同,但目标相同。

  • androidxref.com/9.0.0_r3/xr…
  • androidxref.com/9.0.0_r3/xr…

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

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

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

java
代码解读
复制代码
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

c++
代码解读
复制代码
inline bool IsCallerTrusted(ObjPtr caller) REQUIRES_SHARED(Locks::mutator_lock_) { //1、传入参数判断 return !caller.IsNull() && detail::IsCallerTrusted(caller, caller->GetClassLoader(), caller->GetDexCache()); } ALWAYS_INLINE inline bool IsCallerTrusted(ObjPtr caller, ObjPtr caller_class_loader, ObjPtr 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

c++
代码解读
复制代码
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开发技巧、思路感兴趣的,欢迎关注我的栏目。

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

注:本文转载自juejin.cn的MuYe的文章"https://juejin.cn/post/7440716088898060339"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2492) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

101
推荐
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2025 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top