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