解决AndroidP-HiddenApi
2019-12-19 本文已影响0人
Liuqc
androidp hideapi源码
template<typename T>
inline Action GetMemberAction(T* member,
Thread* self,
std::function<bool(Thread*)> fn_caller_is_trusted,
AccessMethod access_method)
REQUIRES_SHARED(Locks::mutator_lock_) {
DCHECK(member != nullptr);
// Decode hidden API access flags.
// NB Multiple threads might try to access (and overwrite) these simultaneously,
// causing a race. We only do that if access has not been denied, so the race
// cannot change Java semantics. We should, however, decode the access flags
// once and use it throughout this function, otherwise we may get inconsistent
// results, e.g. print whitelist warnings (b/78327881).
//获取访问标记
HiddenApiAccessFlags::ApiList api_list = member->GetHiddenApiAccessFlags();
//判断是否属于白名单api和隐藏api执行策略
Action action = GetActionFromAccessFlags(member->GetHiddenApiAccessFlags());
if (action == kAllow) {
// Nothing to do.
return action;
}
//检查是否是系统调用,属于隐藏api,豁免情况,是bootclassloader加载的,而此方法中巧妙的使getClassLoader()返回bootclassloader
if (fn_caller_is_trusted(self)) {
// Caller is trusted. Exit.
return kAllow;
}
// 是hidden-api,调用者也不是系统,获取签名信息,在判断是否豁免名单
return detail::GetMemberActionImpl(member, api_list, action, access_method);
}
inline bool IsCallerTrusted(ObjPtr<mirror::Class> caller) REQUIRES_SHARED(Locks::mutator_lock_) {
//classloader是否是bootclassloader,如果我们能Class中classloader变成bootclassloader,即可豁免
return !caller.IsNull() &&
detail::IsCallerTrusted(caller, caller->GetClassLoader(), caller->GetDexCache());
}
方案一
public class ReflectionP {
@TargetApi(Build.VERSION_CODES.P)
private static void clearClassLoaderInClass(Class cls) {
try {
Class unsafeClass = Class.forName("sun.misc.Unsafe");
Field unsafeInstanceField = unsafeClass.getDeclaredField("theUnsafe");
unsafeInstanceField.setAccessible(true);
Object unsafeInstance = unsafeInstanceField.get(null);
Method objectFieldOffset = unsafeClass.getMethod("objectFieldOffset", Field.class);
Field classLoaderField = Class.class.getDeclaredField("classLoader");
classLoaderField.setAccessible(true);
Method putObject = unsafeClass.getMethod("putObject", Object.class, long.class, Object.class);
//Class中ClassLoader设置为nulll
long offset = (long) objectFieldOffset.invoke(unsafeInstance, classLoaderField);
putObject.invoke(unsafeInstance, cls, offset, null);
} catch (Exception e) {
e.printStackTrace();
}
}
@TargetApi(Build.VERSION_CODES.P)
private static void restoreLoaderInClass(Class cls) {
try {
Field classLoaderField = Class.class.getDeclaredField("classLoader");
classLoaderField.setAccessible(true);
if (cls != null && !cls.isPrimitive() && classLoaderField.get(cls) == null) {
classLoaderField.set(cls, Thread.currentThread().getContextClassLoader());
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static <T> T breakAndroidP(Func<T> func) {
T result;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
clearClassLoaderInClass(func.getClass());
result = func.call();
restoreLoaderInClass(func.getClass());
} else {
result = func.call();
}
return result;
}
public interface Func<T> {
T call();
}
}
Class类部分源码
public ClassLoader getClassLoader() {
if (isPrimitive()) {
return null;
}
//classloader为空,返回BootClassLoader
return (classLoader == null) ? BootClassLoader.getInstance() : classLoader;
}
由以上源码可知,如果class中classloader为null,则返回bootclassloader。从hideapi源码可知,如果是bootclassloader加载的,可豁免。
方案2
可通过unsafe把需要使用的私有api放到白名单中
template<typename T>
Action GetMemberActionImpl(T* member,
HiddenApiAccessFlags::ApiList api_list,
Action action,
AccessMethod access_method) {
...
const bool shouldWarn = kLogAllAccesses || runtime->IsJavaDebuggable();
if (shouldWarn || action == kDeny) {
//是否在豁免名单,可在把需要反射的方法放入GetHiddenApiExemptions中
if (member_signature.IsExempted(runtime->GetHiddenApiExemptions())) {
action = kAllow;
MaybeWhitelistMember(runtime, member);
return kAllow;
}
...
return action;
}
方案3
/**
* Source code from Tinker
*/
final class HiddenApiReflection {
private HiddenApiReflection() {
}
/**
* Locates a given field anywhere in the class inheritance hierarchy.
*
* @param instance an object to search the field from.
* @param name field name
* @return a field object
* @throws NoSuchFieldException if the field cannot be located
*/
static Field findField(Object instance, String name) throws NoSuchFieldException {
for (Class<?> clazz = instance.getClass(); clazz != null; clazz = clazz.getSuperclass()) {
try {
Field field = clazz.getDeclaredField(name);
if (!field.isAccessible()) {
field.setAccessible(true);
}
return field;
} catch (NoSuchFieldException e) {
// ignore and search next
}
}
throw new NoSuchFieldException("Field " + name + " not found in " + instance.getClass());
}
static Field findField(Class<?> originClazz, String name) throws NoSuchFieldException {
for (Class<?> clazz = originClazz; clazz != null; clazz = clazz.getSuperclass()) {
try {
Field field = clazz.getDeclaredField(name);
if (!field.isAccessible()) {
field.setAccessible(true);
}
return field;
} catch (NoSuchFieldException e) {
// ignore and search next
}
}
throw new NoSuchFieldException("Field " + name + " not found in " + originClazz);
}
/**
* Locates a given method anywhere in the class inheritance hierarchy.
*
* @param instance an object to search the method from.
* @param name method name
* @param parameterTypes method parameter types
* @return a method object
* @throws NoSuchMethodException if the method cannot be located
*/
static Method findMethod(Object instance, String name, Class<?>... parameterTypes)
throws NoSuchMethodException {
for (Class<?> clazz = instance.getClass(); clazz != null; clazz = clazz.getSuperclass()) {
try {
Method method = clazz.getDeclaredMethod(name, parameterTypes);
if (!method.isAccessible()) {
method.setAccessible(true);
}
return method;
} catch (NoSuchMethodException e) {
// ignore and search next
}
}
throw new NoSuchMethodException("Method "
+ name
+ " with parameters "
+ Arrays.asList(parameterTypes)
+ " not found in " + instance.getClass());
}
/**
* Locates a given method anywhere in the class inheritance hierarchy.
*
* @param clazz a class to search the method from.
* @param name method name
* @param parameterTypes method parameter types
* @return a method object
* @throws NoSuchMethodException if the method cannot be located
*/
static Method findMethod(Class<?> clazz, String name, Class<?>... parameterTypes)
throws NoSuchMethodException {
for (; clazz != null; clazz = clazz.getSuperclass()) {
try {
Method method = clazz.getDeclaredMethod(name, parameterTypes);
if (!method.isAccessible()) {
method.setAccessible(true);
}
return method;
} catch (NoSuchMethodException e) {
// ignore and search next
}
}
throw new NoSuchMethodException("Method "
+ name
+ " with parameters "
+ Arrays.asList(parameterTypes)
+ " not found in " + clazz);
}
/**
* Locates a given constructor anywhere in the class inheritance hierarchy.
*
* @param instance an object to search the constructor from.
* @param parameterTypes constructor parameter types
* @return a constructor object
* @throws NoSuchMethodException if the constructor cannot be located
*/
static Constructor<?> findConstructor(Object instance, Class<?>... parameterTypes)
throws NoSuchMethodException {
for (Class<?> clazz = instance.getClass(); clazz != null; clazz = clazz.getSuperclass()) {
try {
Constructor<?> ctor = clazz.getDeclaredConstructor(parameterTypes);
if (!ctor.isAccessible()) {
ctor.setAccessible(true);
}
return ctor;
} catch (NoSuchMethodException e) {
// ignore and search next
}
}
throw new NoSuchMethodException("Constructor"
+ " with parameters "
+ Arrays.asList(parameterTypes)
+ " not found in " + instance.getClass());
}
/**
* Replace the value of a field containing a non-null array, by a new array containing the
* elements of the original array plus the elements of extraElements.
*
* @param instance the instance whose field is to be modified.
* @param fieldName the field to modify.
* @param extraElements elements to append at the end of the array.
*/
static void expandFieldArray(Object instance, String fieldName, Object[] extraElements)
throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
Field jlrField = findField(instance, fieldName);
Object[] original = (Object[]) jlrField.get(instance);
Object[] combined = (Object[]) Array.newInstance(original.getClass().getComponentType(), original.length + extraElements.length);
// NOTE: changed to copy extraElements first, for patch load first
System.arraycopy(extraElements, 0, combined, 0, extraElements.length);
System.arraycopy(original, 0, combined, extraElements.length, original.length);
jlrField.set(instance, combined);
}
/**
* Replace the value of a field containing a non-null array, by a new array containing the
* elements of the original array plus the elements of extraElements.
*
* @param instance the instance whose field is to be modified.
* @param fieldName the field to modify.
*/
static void reduceFieldArray(Object instance, String fieldName, int reduceSize)
throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
if (reduceSize <= 0) {
return;
}
Field jlrField = findField(instance, fieldName);
Object[] original = (Object[]) jlrField.get(instance);
int finalLength = original.length - reduceSize;
if (finalLength <= 0) {
return;
}
Object[] combined = (Object[]) Array.newInstance(original.getClass().getComponentType(), finalLength);
System.arraycopy(original, reduceSize, combined, 0, finalLength);
jlrField.set(instance, combined);
}
}