自己手动实现远程执行功能

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/weixin_45614509/article/details/102748954

在阅读《深入理解Java虚拟机》第二版,一书中。第九章看到的demo。

这个小东西实现的功能,就是让服务器可以在运行时期动态的执行代码,打印日志等, 而不需要修改服务器代码,重启服务器。一开始以为是比较高深的东西,但是实际上,确实不算很难。

书中提供了 5 个类用于动态执行代码。使用了一个 JSP 文件来作为触发执行操作的入口,并且获取程序的执行结果。需要说明的是这5个类以及这个 jsp 文件都是需要放在项目的代码中的。

jsp 文件中的内容就是代用那5个类的代码,去加载临时文件,执行其中的代码,然后将执行的结果显示到页面上

具体执行步骤就是

  1. 本地编写好临时代码并且编译成 class 文件
  2. 将编译好的 class 文件上传到服务器上的指定位置
  3. 访问 jsp 页面,执行代码并且返回代码的执行结果

这个服务器上的指定位置是在 jsp 文件中指定的,这个 jsp 文件就是一个触发加显示的作用,实际上你用一个接口去实现也是可以的。然后这个我觉得是不适合放在生产环境中的,比较有风险。

放上代码:

package com.java.test.load;

public class ByteUtils {

    public static int bytes2Int(byte[] b, int start, int len) {
        int sum = 0;
        int end = start + len;
        for (int i = start; i < end; i++) {
            int n = ((int) b[i]) & 0xff;
            n <<= (--len) * 8;
            sum = n + sum;
        }
        return sum;
    }

    public static byte[] int2Bytes(int value, int len) {
        byte[] b = new byte[len];
        for (int i = 0; i < len; i++) {
            b[len - i - 1] = (byte) ((value >> 8 * i) & 0xff);
        }
        return b;
    }

    public static String byte2String(byte[] b, int start, int len) {
        return new String(b, start, len);
    }

    public static byte[] string2Bytes(String str) {
        return str.getBytes();
    }

    public static byte[] bytesReplace(byte[] originalBytes, int offset, int len, byte[] replaceBytes) {
        byte[] newBytes = new byte[originalBytes.length + (replaceBytes.length - len)];
        System.arraycopy(originalBytes, 0, newBytes, 0, offset);
        System.arraycopy(replaceBytes, 0, newBytes, offset, replaceBytes.length);
        System.arraycopy(originalBytes, offset + len, newBytes, offset + replaceBytes.length, originalBytes.length - offset - len);
        return newBytes;
    }

}
package com.java.test.load;

/**
 * 修改class 文件,暂时只提供修改常量池常量的功能
 */
public class ClassModifier {

    /**
     * 常量池的起始偏移
     */
    private static final int CONSTANT_POLL_COUNT_INDEX = 8;

    /**
     * CONSTANT_Utf8_info 常量的tag标志
     */
    private static final int CONSTANT_Utf8_info = 1;

    /**
     * 常量池中11中常量所占的长度,CONSTANT_Utf8_info类型常量除外,因为它不是定长的
     */
    private static final int[] CONSTANT_ITEM_LENGTH = {-1, -1, -1, 5, 5, 9, 9, 3, 3, 5, 5, 5, 5};

    private static final int u1 = 1;
    private static final int u2 = 2;

    private byte[] classByte;

    public ClassModifier(byte[] classByte) {
        this.classByte = classByte;
    }

    /**
     * 修改常量池中 CONSTANT_Utf8_info 常量的内容
     *
     * @param oldStr 修改前的字符串
     * @param newStr 修改后的字符串
     * @return 修改结果
     */
    public byte[] modifyUTF8Constant(String oldStr, String newStr) {
        int cpc = getConstantPollCount();
        int offset = CONSTANT_POLL_COUNT_INDEX + u2;
        for (int i = 0; i < cpc; i++) {
            int tag = ByteUtils.bytes2Int(classByte, offset, u1);
            if (tag == CONSTANT_Utf8_info) {
                int len = ByteUtils.bytes2Int(classByte, offset + u1, u2);
                offset += (u1 + u2);
                String str = ByteUtils.byte2String(classByte, offset, len);
                if (str.equalsIgnoreCase(oldStr)) {
                    byte[] strBytes = ByteUtils.string2Bytes(newStr);
                    byte[] strLen = ByteUtils.int2Bytes(newStr.length(), u2);
                    classByte = ByteUtils.bytesReplace(classByte, offset - u2, u2, strLen);
                    classByte = ByteUtils.bytesReplace(classByte, offset, len, strBytes);
                    return classByte;
                } else {
                    offset += len;
                }

            } else {
                offset += CONSTANT_ITEM_LENGTH[tag];
            }
        }
        return classByte;
    }

    /**
     * 获取常量池中常量的数量
     */
    public int getConstantPollCount() {
        return ByteUtils.bytes2Int(classByte, CONSTANT_POLL_COUNT_INDEX, u2);
    }

}
package com.java.test.load;

import java.io.*;
import java.nio.channels.Channel;
import java.util.Properties;

/**
 * 为JavaClass 劫持 java.lang.System 提供支持
 * 除了 out 和 err 外,其余的都直接转发给System 处理
 */
public class HackSystem {

    public final static InputStream in = System.in;

    private static ByteArrayOutputStream buffer = new ByteArrayOutputStream();

    public static final PrintStream out = new PrintStream(buffer);

    public final static PrintStream err = out;

    public static String getBufferString() {
        return buffer.toString();
    }

    public static void clearBuffer() {
        buffer.reset();
    }

    public static void setSecurityManager(final SecurityManager s) {
        System.setSecurityManager(s);
    }

    public static SecurityManager getSecurityManager() {
        return System.getSecurityManager();
    }

    public static long currentTimeMillis() {
        return System.currentTimeMillis();
    }

    public static void arraycopy(Object src, int srcPos,
                                 Object dest, int destPos,
                                 int length) {
        System.arraycopy(src, srcPos, dest, destPos, length);
    }


    public static int identityHashCode(Object x) {
        return System.identityHashCode(x);
    }

    public static void setIn(InputStream in) {
        System.setIn(in);
    }

    public static void setOut(PrintStream out) {
        System.setOut(out);
    }

    public static void setErr(PrintStream err) {
        System.setErr(err);
    }

    public static Console console() {
        return System.console();
    }

    public static Channel inheritedChannel() throws IOException {
        return System.inheritedChannel();
    }

    public static long nanoTime() {
        return System.nanoTime();
    }

    public static Properties getProperties() {
        return System.getProperties();
    }

    public static String lineSeparator() {
        return System.lineSeparator();
    }

    public static void setProperties(Properties props) {
        System.setProperties(props);
    }

    public static String getProperty(String key) {
        return System.getProperty(key);
    }

    public static String getProperty(String key, String def) {
        return System.getProperty(key, def);
    }

    public static String setProperty(String key, String value) {
        return System.setProperty(key, value);
    }

    public static String clearProperty(String key) {
        return System.clearProperty(key);
    }

    public static String getenv(String name) {
        return System.getenv(name);
    }

    public static java.util.Map<String, String> getenv() {
        return System.getenv();
    }

    public static void exit(int status) {
        System.exit(status);
    }

    public static void gc() {
        System.gc();
    }

    public static void runFinalization() {
        System.runFinalization();
    }

    public static void runFinalizersOnExit(boolean value) {
        System.runFinalizersOnExit(value);
    }

    public static void load(String filename) {
        System.load(filename);
    }

    public static void loadLibrary(String libname) {
        System.loadLibrary(libname);
    }

    public static String mapLibraryName(String libname) {
        return System.mapLibraryName(libname);
    }

}
package com.java.test.load;


public class HotSwapClassLoader extends ClassLoader {

    public HotSwapClassLoader() {
        super(HotSwapClassLoader.class.getClassLoader());
    }

    public Class loadByte(byte[] classByte) {
        return defineClass(null, classByte, 0, classByte.length);
    }

}
package com.java.test.load;


import java.lang.reflect.Method;

/**
 * JavaClass 执行工具
 */
public class JavaClassExecuter {

    /**
     * 执行外部传过来的代表一个Java类的byte 数组
     * 将输入类的byte数组中代表 java.lang.System 的 CONSTANT_Utf8_info 常量修改为劫持后的 HackSystem类
     * 执行方法为该类的 static main(String[] args) 方法,输出结果为该类向 System.out/err 输出的信息
     *
     * @param classByte 代表一个 Java 类的数组
     * @return 执行结果
     */
    public static String execute(byte[] classByte) {
        HackSystem.clearBuffer();
        ClassModifier cm = new ClassModifier(classByte);

        byte[] modiBytes = cm.modifyUTF8Constant("java/lang/System", "com/java/test/load/HackSystem");
        HotSwapClassLoader loader = new HotSwapClassLoader();
        Class clazz = loader.loadByte(modiBytes);

        try {
            Method method = clazz.getMethod("main", new Class[]{String[].class});
            method.invoke(null, new String[]{null});
        } catch (Throwable e) {
            e.printStackTrace();
        }
        return HackSystem.getBufferString();
    }

}

那个 jsp 文件的代码就不放上来了,因为我在写的时候是使用 spring boot 的接口提供了一个接口,下面放下这个接口:

    @GetMapping("/execute")
    public String execute() throws IOException {
        FileInputStream fileInputStream = new FileInputStream("D://Test.class");
        byte[] bytes = new byte[fileInputStream.available()];
        fileInputStream.read(bytes);
        fileInputStream.close();
        return JavaClassExecuter.execute(bytes);
    }

猜你喜欢

转载自blog.csdn.net/weixin_45614509/article/details/102748954