2.proxy

java.lang.reflect.Proxy 提权

Proxy, 原本是 JDK 用于提供给开发者进行动态实现接口的东西. 可能用过, 也可能没有, 但是, 该章节抛开 Proxy 原本的功能不讲, 我们只关注…. 越权

Proxy 是如何提权的

Proxy, 是用来动态时间接口的, 他的结构大概想下面这样

[Proxy Instance] -> [InvocationHandler]

然后当我们尝试 proxy jdk.internal.access.JavaLangAccess(或者任何 jdk.internal.access 下的接口) 的时候, 有趣的一幕发生了. 该 proxy 并没有出现任何异常, 也就是该 proxy 已经拥有了jdk.internal.access 的访问权限

[Proxy Instance]
|- [Proxy Class] -> [ProxyPackage].[ProxyClassSimpleName]
|    `- [Proxy Module]
|          |
`[Class Loader]

也就是说, 这个 ProxyClass 所在的 Proxy Module, 已经拥有jdk.internal.access 的访问权限. 是否一切都清晰起来…..

这个时候, 此时给 ClassLoader define 一个 HackerClass = [ProxyPackage].HackerClass, 那么 HackerClass 也拥有jdk.internal.access 的访问权限…

开始提权

准备好一个自定义 ClassLoader, 为越权做好准备.

class HackerClassLoader extends ClassLoader {
    HackerClassLoader() { super(HackerClassLoader.class.getClassLoader()); }
    Class<?> define(byte[] code) {
        ProtectionDomain domain = new ProtectionDomain(null,
                new AllPermission().newPermissionCollection()
        );
        return defineClass(null, code, 0, code.length, domain);
    }
}

准备好 byte[] replace(byte[] source, byte[] target, byte[] replacement)

replace 源码见 UnsafeAccessor 项目

提前写好越权代码, 需要正确编译, 需要给编译器加上

  • --add-exports java.base/jdk.internal.misc=ALL-UNNAMED
  • --add-exports java.base/jdk.internal.access=ALL-UNNAMED 加上这些是为了保证我们能有正常通过.

准备好 Injector

class Injector {
    static {
        Class<?> klass = Injector.class;
        Module module = klass.getModule();
        // Injector 将会由 HackerClassLoader 进行加载.
        Module open = klass.getClassLoader().getClass().getModule();
        module.addExports(klass.getPackageName(), open);
        JavaLangAccess javaLangAccess = SharedSecrets.getJavaLangAccess();
        // 给 Injector 的 ClassLoader 所在的模块访问 `java.base/jdk.internal.misc` 的权利
        javaLangAccess.addExports(Object.class.getModule(), "jdk.internal.misc", open);
        // javaLangAccess.addExports(Object.class.getModule(), "jdk.internal.access", open);
    }
}

重要: 不要在任何地方直接使用 Injector, 因为 Injector 属于被动态加载的类, 而不是静态硬加载的类

打开一条访问 jdk.internal.access 的路

HackerClassLoader loader = new HackerClassLoader();
Class<?> JLA = Class.forName("jdk.internal.access.JavaLangAccess");
Object proxy = Proxy.newProxyInstance(loader, new Class[]{JLA}, (proxy0, method, args) -> null); 

重要: 不能直接使用 jdk.internal.access.JavaLangAccess.class, 将会得到一个 java.lang.IllegalAccessError

完整代码:

// 根据实际情况的不同, 这里的路径也会不同
try (InputStream source = HackerClassLoader.class.getResourceAsStream("Injector.class")) {
    byte[] data = source.readAllBytes();
    Class<?> JLA = Class.forName("jdk.internal.access.JavaLangAccess"); // 绕开 IllegalAccessError
    Object proxy = Proxy.newProxyInstance(loader, new Class[]{JLA}, (proxy0, method, args) -> null);
    String namespace = proxy.getClass().getPackageName();
    String targetName = namespace + ".Injector",
            targetJvmName = targetName.replace('.', '/');
    // 根据实际情况的不同, 此处的替换常量也会不同
    // 规则: (`Class.forName` 路径).replace('.', '/')
    data = replace(data, "io/github/karlatemp/unsafeaccessor/Injector", targetJvmName);
    data = replace(data, "Lio/github/karlatemp/unsafeaccessor/Injector;", "L" + targetJvmName + ";");
    Class<?> injectorClass = loader.define(data);
    // 执行 Injector 的 static {} 片段.
    Class.forName(injectorClass.getName(), true, loader);
} catch (Exception exception) {
    throw new ExceptionInInitializerError(exception);
}

执行结束, 此时, HackerClassLoader 所在的整个模块都可以自由使用 jdk.internal.misc.Unsafe