核心思想就是使用UnSafe直接操作内存
先来看一段代码:
1 | template<typename T> |
从上述代码可以看到,当调用者来自系统classloader加载时,即fn_caller_is_trusted(self)返回了true即可绕过限制,因此问题变成了如何将对应类的class对象中的classloader设为null,即BootClassLoader加载。
反射直接拿classloader是行不通的,因为该字段在深灰名单中,会抛NoSuchFiledException。
因此问题就变得复杂。
这里介绍一个Java中的上帝类sun.misc.Unsafe,该类很强大,可以直接操作对象内存,这也是为什么取名为Unsafe的原因,相关使用可以看 http://mishadoff.com/blog/java-magic-part-4-sun-dot-misc-dot-unsafe/ 你可能之前从来没有使用过这个类,但是其实你已经不知不觉中使用到了,Gson中就使用了该类进行对象创建工作。
这个类有一个静态方法getUnsafe,但是该方法做了调用限制,只允许系统Classloader加载的类进行调用,否则会抛异常。但是它有一个静态变量theUnsafe,该字段在Android P中是浅灰名单,可以安全调用。并且这个类在android.jar中是引用不到的,因此必须做一层封装才可进行调用
1 | package github.io.lizhangqu.unsafe; |
所以最终问题就变成了如何用Unsafe将classloader设为null,即如下代码中的字段。
1 | public final class Class<T> implements java.io.Serializable, |
java.lang.Class对象隐式继承自java.lang.Object
1 | public class Object { |
因此要将classLoader设为null,即需要找到该字段在内存中的偏移地址。
我们通过构造一个和java.lang.Class一样的类,用于辅助找到该偏移地址
1 | public class ReflectWrapper { |
classLoaderOffsetHelper对象的偏移地址就是classLoader的偏移地址,为什么呢?
因为他们都隐式继承自java.lang.Object对象,并且都是该对象的第一个引用类型的字段。
首先反射拿到该字段
1 | Field classLoader = ReflectWrapper.class.getDeclaredField("classLoaderOffsetHelper"); |
使用Unsafe得到该字段在内存中的偏移
1 | long classLoaderOffset = UnSafeWrapper.getUnSafe().objectFieldOffset(classLoader); |
校验该偏移指向的地址是否是ClassLoader对象
1 | if (UnSafeWrapper.getUnSafe().getObject(ReflectWrapper.class, classLoaderOffset) instanceof ClassLoader) { |
将该偏移指向的对象进行替换,设成null
1 | Object originalClassLoader = UnSafeWrapper.getUnSafe().getAndSetObject(ReflectWrapper.class, classLoaderOffset, null); |
完整的代码如下
1 | @Keep |
之后的事情就变得很简单了,所有反射工作都通过ReflectWrapper进行调用,现在该类就是上帝类,可以为所欲为。
对应实现可以参考
1 | package github.io.lizhangqu.reflect; |
