一.摘要
最近在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都是打开的:
// 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,白名单配置路径为:
在系统中的路径为:
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去完成相应的功能