hiddenapi无法反射问题

一.摘要

    最近在q上遇到一个问题,有同事反应为什么我反射的方法在q上找不到对应的方法了,只获得了一些class.java的方法,这是q上在art中新加的hiddenapi相关的策略做了限制导致的,那么我们来看看是怎么限制的吧

二.代码分析

1.测试代码:


void test() {
    List list = new ArrayList();
    Class mclass = null;
    try {
        mclass = Class.forName("android.app.Utils");
        Log.d(TAG, mclass.getName());
        // android.app.SmtPCUtils
        Method[] methods = mclass.getMethods();
        for (Method s : methods) {
            Log.d(TAG, s.getName());
            list.add(s.getName());
        }
        Log.d(TAG, list.toString());
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }
}

2.系统源码:

2.1 getMethods

public Method[] getMethods() throws SecurityException {

    List<Method> methods = new ArrayList<Method>();

    // 获取public方法

    getPublicMethodsInternal(methods);

    /*

     * Remove duplicate methods defined by superclasses and

     * interfaces, preferring to keep methods declared by derived

     * types.

     */

    CollectionUtils.removeDuplicates(methods, Method.ORDER_BY_SIGNATURE);

    return methods.toArray(new Method[methods.size()]);

}



private void getPublicMethodsInternal(List<Method> result) {

    // 获取该类中宣告可以调用的方法

    Collections.addAll(result, getDeclaredMethodsUnchecked(true));

    if (!isInterface()) {

        // Search superclasses, for interfaces don't search java.lang.Object.

        for (Class<?> c = superClass; c != null; c = c.superClass) {

            // 获取该类的父类中可以调用的方法

            Collections.addAll(result, c.getDeclaredMethodsUnchecked(true));

        }

    }

    // Search iftable which has a flattened and uniqued list of interfaces.

    Object[] iftable = ifTable;

    if (iftable != null) {

        for (int i = 0; i < iftable.length; i += 2) {

            Class<?> ifc = (Class<?>) iftable[i];

            // 获取接口方法

            Collections.addAll(result, ifc.getDeclaredMethodsUnchecked(true));

        }

    }

}

这里我们会去获取android.app.SmtPCUtils类的所有方法:打个断点:

我们从断点中确实可以看得,我们没有获得android.app.SmtPCUtils类的方法,那我们直接看看getDeclaredMethodsUnchecked方法吧:

2.2 Class_getDeclaredMethodsUnchecked

http://aosp.opersys.com/xref/android-10.0.0_r41/xref/art/runtime/native/java_lang_Class.cc#575

static jobjectArray Class_getDeclaredMethodsUnchecked(JNIEnv* env, jobject javaThis,

                                                      jboolean publicOnly) {

  ScopedFastNativeObjectAccess soa(env);

  StackHandleScope<2> hs(soa.Self());



  hiddenapi::AccessContext hiddenapi_context = GetReflectionCaller(soa.Self());

  // public_only = false, publicOnly传进来的值是true, JNI_FALSE=false

  bool public_only = (publicOnly != JNI_FALSE);



  Handle<mirror::Class> klass = hs.NewHandle(DecodeClass(soa, javaThis));

  size_t num_methods = 0;

  for (ArtMethod& m : klass->GetDeclaredMethods(kRuntimePointerSize)) {

    uint32_t modifiers = m.GetAccessFlags();

    // Add non-constructor declared methods.

    if ((modifiers & kAccConstructor) == 0 &&

        IsDiscoverable(public_only, hiddenapi_context, &m)) {

      ++num_methods;

    }

  }

  auto ret = hs.NewHandle(mirror::ObjectArray<mirror::Method>::Alloc(

      soa.Self(), GetClassRoot<mirror::ObjectArray<mirror::Method>>(), num_methods));

  if (ret == nullptr) {

    soa.Self()->AssertPendingOOMException();

    return nullptr;

  }

  num_methods = 0;

  // 遍历方法

  for (ArtMethod& m : klass->GetDeclaredMethods(kRuntimePointerSize)) {

    uint32_t modifiers = m.GetAccessFlags();

    if ((modifiers & kAccConstructor) == 0 &&

        // 判断diddenapi权限

        IsDiscoverable(public_only, hiddenapi_context, &m)) {

      DCHECK_EQ(Runtime::Current()->GetClassLinker()->GetImagePointerSize(), kRuntimePointerSize);

      DCHECK(!Runtime::Current()->IsActiveTransaction());

      // 获取能反射的方法

      ObjPtr<mirror::Method> method =

          mirror::Method::CreateFromArtMethod<kRuntimePointerSize, false>(soa.Self(), &m);

      if (method == nullptr) {

        soa.Self()->AssertPendingException();

        return nullptr;

      }

      // 将方法放到ret中,最后返回

      ret->SetWithoutChecks<false>(num_methods++, method);

    }

  }

  return soa.AddLocalReference<jobjectArray>(ret.Get());

}

对比了一些p和q的代码发现在获取方法前多了一个判断IsDiscoverable的判断,我们可以大概猜测到是这个判断导致我们获取不到所有方法了,我将这个判断去掉后就能获取到方法,这里有不一一说明过程了.

2.3 IsDiscoverable

我们在继续看IsDiscoverable这个方法吧:

// Returns true if a class member should be discoverable with reflection given

// the criteria. Some reflection calls only return public members

// (public_only == true), some members should be hidden from non-boot class path

// callers (hiddenapi_context).

template<typename T>

ALWAYS_INLINE static bool IsDiscoverable(bool public_only,

                                         const hiddenapi::AccessContext& access_context,

                                         T* member)

    REQUIRES_SHARED(Locks::mutator_lock_) {

  if (public_only && ((member->GetAccessFlags() & kAccPublic) == 0)) {

    return false;

  }



  return !hiddenapi::ShouldDenyAccessToMember(

      member, access_context, hiddenapi::AccessMethod::kNone);

}

这里返回true就代表该方法可以进行反射,我们先来看第一个判断:

  if (public_only && ((member->GetAccessFlags() & kAccPublic) == 0)) {

    return false;

  }

public_only传进来的是false,这一步会跳过,那么我们继续看ShouldDenyAccessToMember,在该方法中会调用到ShouldDenyAccessToMemberImpl.

2.4 ShouldDenyAccessToMemberImpl

我们直接看到ShouldDenyAccessToMemberImpl的代码:

art/runtime/hidden_api.cc

template<typename T>

bool ShouldDenyAccessToMemberImpl(T* member, ApiList api_list, AccessMethod access_method) {

  DCHECK(member != nullptr);

  Runtime* runtime = Runtime::Current();



  EnforcementPolicy policy = runtime->GetHiddenApiEnforcementPolicy();

  DCHECK(policy != EnforcementPolicy::kDisabled)

      << "Should never enter this function when access checks are completely disabled";



  const bool deny_access =

      // 判断当前策略

      (policy == EnforcementPolicy::kEnabled) &&

      IsSdkVersionSetAndMoreThan(runtime->GetTargetSdkVersion(),

                                 api_list.GetMaxAllowedSdkVersion());

  ......

  return deny_access;

}

这里会判断policy的状态,注意这个不是selinux权限,EnforcementPolicy的定义如下:

enum class EnforcementPolicy {

  kDisabled             = 0,

  kJustWarn             = 1,  // keep checks enabled, but allow everything (enables logging)

  kEnabled              = 2,  // ban dark grey & blacklist

  kMax = kEnabled,

};

2.5 进程策略设置

这个策略是在app进程启动时设置的,q上除了一些系统白名单应用,其他的普通app都是打开的:

http://192.99.106.107:8080/xref/android-10.0.0_r32/xref/frameworks/base/services/core/java/com/android/server/am/ProcessList.java#1562

// startProcessLocked传进来的disableHiddenApiChecks值为false,

// mService.mHiddenApiBlacklist.isDisabled()是去判断这个hidden_api_blacklist_exemptions的值,有兴趣的可以在点开看看,这里不进行过多描述了

if (!disableHiddenApiChecks && !mService.mHiddenApiBlacklist.isDisabled()) {

    app.info.maybeUpdateHiddenApiEnforcementPolicy(

            mService.mHiddenApiBlacklist.getPolicy());

    @ApplicationInfo.HiddenApiEnforcementPolicy int policy =

            app.info.getHiddenApiEnforcementPolicy();

    int policyBits = (policy << Zygote.API_ENFORCEMENT_POLICY_SHIFT);

    if ((policyBits & Zygote.API_ENFORCEMENT_POLICY_MASK) != policyBits) {

        throw new IllegalStateException("Invalid API policy: " + policy);

    }

    runtimeFlags |= policyBits;

}

我们设置的策略是getHiddenApiEnforcementPolicy获得的:

public @HiddenApiEnforcementPolicy int getHiddenApiEnforcementPolicy() {

    if (isAllowedToUseHiddenApis()) {

        return HIDDEN_API_ENFORCEMENT_DISABLED;

    }

    if (mHiddenApiPolicy != HIDDEN_API_ENFORCEMENT_DEFAULT) {

        return mHiddenApiPolicy;

    }

    return HIDDEN_API_ENFORCEMENT_ENABLED;

}



private boolean isAllowedToUseHiddenApis() {

    // 有系统签名的app可以使用hidden api

    if (isSignedWithPlatformKey()) {

        return true;

    } 

    // 是系统app

    else if (isSystemApp() || isUpdatedSystemApp()) {

        // 可以使用非sdk aip或者白名单应用可以使用hidden api

        return usesNonSdkApi() || isPackageWhitelistedForHiddenApis();

    } else {

        return false;

    }

}

我们可以看到有系统签名的app还有一部分系统应用也可以使用可以使用hidden api,白名单配置路径为:

http://192.99.106.107:8080/xref/android-10.0.0_r32/xref/frameworks/base/data/etc/hiddenapi-package-whitelist.xml

在系统中的路径为:

xxxx:/system/etc/sysconfig # ls

framework-sysconfig.xml google.xml hiddenapi-package-whitelist.xml qti_whitelist.xml

2.6 IsSdkVersionSetAndMoreThan

我们看完第一个判断条件后,在回到第二个判断条件IsSdkVersionSetAndMoreThan

IsSdkVersionSetAndMoreThan(runtime->GetTargetSdkVersion(), api_list.GetMaxAllowedSdkVersion());



inline bool IsSdkVersionSetAndMoreThan(uint32_t lhs, SdkVersion rhs) {

  return lhs != static_cast<uint32_t>(SdkVersion::kUnset) && lhs > static_cast<uint32_t>(rhs);

}

这里会判断当前运行的sdk版本和api允许运行的最大sdk版本,如果当前的运行的sdk版本大于允许运行的sdk最大版本就会返回true,返回true后就拒绝了反射该方法,返回false就可以反射该方法

sdk版本定义如下:

art/libartbase/base/hiddenapi_flags.h



// Returns the maximum target SDK version allowed to access this ApiList.

SdkVersion GetMaxAllowedSdkVersion() const { return kMaxSdkVersions[GetIntValue()]; }



// Maximum SDK versions allowed to access ApiList of given Value.

static constexpr SdkVersion kMaxSdkVersions[] {

  /* whitelist */ SdkVersion::kMax,

  /* greylist */ SdkVersion::kMax,

  /* blacklist */ SdkVersion::kMin,

  /* greylist-max-o */ SdkVersion::kO_MR1,

  /* greylist-max-p */ SdkVersion::kP,

};



// Names corresponding to Values.

static constexpr const char* kValueNames[] = {

  "whitelist",

  "greylist",

  "blacklist",

  "greylist-max-o",

  "greylist-max-p",

};



art/libartbase/base/sdk_version.h

enum class SdkVersion : uint32_t {

  kMin   =  0u,

  kUnset =  0u,

  kL     = 21u,

  kL_MR1 = 22u,

  kM     = 23u,

  kN     = 24u,

  kN_MR1 = 25u,

  kO     = 26u,

  kO_MR1 = 27u,

  kP     = 28u,

  kQ     = 29u,

  kMax   = std::numeric_limits<uint32_t>::max(),

};

whitelist,greylist定义的方法所有进程都可以反射,blacklist定义的方法所有进程都无法反射,greylist-max-o定义的方法27以上的sdk无法反射,greylist-max-p定义的方法29以上的sdk无法反射

whitelist,greylist,blacklist,greylist-max-o,greylist-max-p名单的设置在分别在:

whitelist:如果你的应用有必须使用非SDK接口的充分理由,可以向Google提交功能请求,并提供用例详情;创建功能请求(不加@hide)

greylist:frameworks/base/config/hiddenapi-greylist.txt

blacklist:frameworks/base/config/hiddenapi-force-blacklist.txt , 方法加上/** @hide */就是黑名单

greylist-max-o:frameworks/base/config/hiddenapi-greylist-max-o.txt

greylist-max-p:frameworks/base/config/hiddenapi-greylist-max-p.txt

配置完成后直接make framework 最终这些方法的配置会生成在out/soong/hiddenapi/hiddenapi-flag.csv下,内容如下:

ndroid/animation/ValueAnimator$AnimatorUpdateListener;->onAnimationUpdate(Landroid/animation/ValueAnimator;)V,public-api,system-api,test-api,whitelist

Landroid/animation/ValueAnimator;-><clinit>()V,blacklist

Landroid/animation/ValueAnimator;->getRepeatCount()I,public-api,system-api,test-api,whitelist

Landroid/animation/ValueAnimator;->getRepeatMode()I,public-api,system-api,test-api,whitelist

Landroid/animation/ValueAnimator;->getScaledDuration()J,greylist-max-o

Landroid/animation/ValueAnimator;->getStartDelay()J,public-api,system-api,test-api,whitelist

Landroid/animation/ValueAnimator;->getTotalDuration()J,public-api,system-api,test-api,whitelist

Landroid/animation/ValueAnimator;->getValues()[Landroid/animation/PropertyValuesHolder;,public-api,system-api,test-api,whitelist

Landroid/animation/ValueAnimator;->initAnimation()V,greylist-max-o

Landroid/animation/ValueAnimator;->isInitialized()Z,greylist-max-o

Landroid/animation/ValueAnimator;->isPulsingInternal()Z,greylist-max-o

Landroid/animation/ValueAnimator;->isRunning()Z,public-api,system-api,test-api,whitelist

Landroid/animation/ValueAnimator;->isStarted()Z,public-api,system-api,test-api,whitelist

2.7 调试命令

在Android Q中,启用对非SDK接口的访问的命令已更改,开发者需要通过更改API强制策略来启用对开发设备上的非SDK接口的访问,ADB命令如下:

adb shell settings put global hidden_api_policy 1

API强制策略中的整数值可以设置为以下三个选项:
0:禁用所有非SDK接口检测。使用此设置会禁用非SDK接口使用的所有日志消息,并阻止您使用StrictMode API测试应用程序。 
1:允许访问所有非SDK接口,但打印日志消息,并显示任何非SDK接口使用情况的警告,使用此设置还允许您使用StrictMode API测试您的应用程序。
2:禁止使用属于黑名单的非SDK接口或目标API级别的受限制灰名单。 
建议开发者将数值设置为1,再通过自动化或人工测试遍历应用所有功能和界面,然后通过抓取的日志分析调用的非SDK接口。 
如需将API强制策略重置,可以使用以下命令进行设置: adb shell settings deleteglobal hidden_api_policy

三.APP怎么检查自己的代码有没有黑名单应用

1.需要进到安卓源码目录

2.make appcompat

3../art/tools/veridex/appcompat.sh --dex-file=你的APK --imprecise

会生成一系列结果,自己根据结果排查就行

四.总结

在q上google进一步限制了app调用非sdk api的权限,如果你一定要通过反射的方式调用API你可以做如下尝试:

1.app使用平台签名

2.APP改为系统权限并且加到白名单当中

不过我们最好是使用google提供的标准sdk去完成相应的功能

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值