番外篇-根据前文的文件上传系统实现一个Java在线运行工具

首先,我们要先对前文的上传系统进行修改,首先是前端

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title></title>
</head>
<body>
        <textarea rows="100%" cols="100%" id="java">
        </textarea>
<input type="button" value="提交" onclick="uploads()">
</body>
<script src="js/jquery.js"></script>
<script>
    var uploads=function () {
            var form = new FormData();
            form.append("java",document.getElementById("java").value);
            $.ajax({
                type: "POST",
                url: "/test/upload",
                data:form,
                async: true,
                processData: false,
                contentType: false,
                success: function (result) {
                },
                error: function (e) {
                }
            });
    }
</script>
</html>

在这里插入图片描述
上传成功,因为字段不符,后台会报错,不要紧,查看缓存文件
在这里插入图片描述
成功获取Java文件,修改上传代码,准备调用javac进行编译获取.class文件

		parse(request.getInputStream());
        request.getInputStream().close();
        write("java","上传文件/"+"test.java");
        new File("缓存文件/"+hm.get("java")).delete();

在这里插入图片描述
jdk8以下想要调用javac需要到jdk中提取tools.jar,请依照如何查看Javac源码自行查找添加

protected void doPost(javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException, IOException {
        parse(request.getInputStream());
        request.getInputStream().close();
        write("java","上传文件/test.java");
        new File("缓存文件/"+hm.get("java")).delete();
        Main.compile(new String[]{"上传文件/test.java"});
    }

在这里插入图片描述
准备修改.class文件,将对应的常量池符号修改为我们自己定义的包装类,最后将所有打印信息返回给浏览器,Javap随意打开一段包含system.out.println的class文件

Constant pool:
   #1 = Methodref          #6.#15         // java/lang/Object."<init>":()V
   #2 = Fieldref           #16.#17        // java/lang/System.out:Ljava/io/PrintStream;
   #3 = String             #18            // xx
   #4 = Methodref          #19.#20        // java/io/PrintStream.println:(Ljava/lang/String;)V
   #5 = Class              #21            // test
   #6 = Class              #22            // java/lang/Object
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               main
  #12 = Utf8               ([Ljava/lang/String;)V
  #13 = Utf8               SourceFile
  #14 = Utf8               test.java
  #15 = NameAndType        #7:#8          // "<init>":()V
  #16 = Class              #23            // java/lang/System
  #17 = NameAndType        #24:#25        // out:Ljava/io/PrintStream;
  #18 = Utf8               xx
  #19 = Class              #26            // java/io/PrintStream
  #20 = NameAndType        #27:#28        // println:(Ljava/lang/String;)V
  #21 = Utf8               test
  #22 = Utf8               java/lang/Object
  #23 = Utf8               java/lang/System
  #24 = Utf8               out
  #25 = Utf8               Ljava/io/PrintStream;
  #26 = Utf8               java/io/PrintStream
  #27 = Utf8               println
  #28 = Utf8               (Ljava/lang/String;)V
{
  public test();
    descriptor: ()V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 2: 0

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #3                  // String xx
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 4: 0
        line 5: 8
}
SourceFile: "test.java"

这是我们准备替换system的类

import java.io.ByteArrayOutputStream;
import java.io.PrintStream;

public class sys {
    public static ByteArrayOutputStream bos=new ByteArrayOutputStream();

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

    public void println(String s){
        out.println(s);
    }
    public void println(){
        out.println("\n");
    }
    public String getArr(){
        out.flush();
        return new String(bos.toByteArray());
    }
}

还是老样子,javap反编译,查看不同

Constant pool:
   #1 = Methodref          #6.#20         // java/lang/Object."<init>":()V
   #2 = Fieldref           #21.#22        // sys.out:Ljava/io/PrintStream;
   #3 = String             #23            //
   #4 = Methodref          #24.#25        // java/io/PrintStream.println:(Ljava/lang/String;)V
   #5 = Class              #26            // Bean
   #6 = Class              #27            // java/lang/Object
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               LocalVariableTable
  #12 = Utf8               this
  #13 = Utf8               LBean;
  #14 = Utf8               main
  #15 = Utf8               ([Ljava/lang/String;)V
  #16 = Utf8               args
  #17 = Utf8               [Ljava/lang/String;
  #18 = Utf8               SourceFile
  #19 = Utf8               Bean.java
  #20 = NameAndType        #7:#8          // "<init>":()V
  #21 = Class              #28            // sys
  #22 = NameAndType        #29:#30        // out:Ljava/io/PrintStream;
  #23 = Utf8
  #24 = Class              #31            // java/io/PrintStream
  #25 = NameAndType        #32:#33        // println:(Ljava/lang/String;)V
  #26 = Utf8               Bean
  #27 = Utf8               java/lang/Object
  #28 = Utf8               sys
  #29 = Utf8               out
  #30 = Utf8               Ljava/io/PrintStream;
  #31 = Utf8               java/io/PrintStream
  #32 = Utf8               println
  #33 = Utf8               (Ljava/lang/String;)V
{
  public Bean();
    descriptor: ()V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 1: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   LBean;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #2                  // Field sys.out:Ljava/io/PrintStream;
         3: ldc           #3                  // String
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 3: 0
        line 4: 8
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       9     0  args   [Ljava/lang/String;
}

所以我们需要修改

	#23 = Utf8               java/lang/System

	#28 = Utf8               sys

由于java/lang/System属于utf-8字段,它在.class中保存的形式基本确定,为

01 00 10 6A 61 76 61 2F 6C 61 6E 67 2F 53 79 73 74 65 6D
         j  a  v  a  /  l  a  n  g  /  S  y  s  t  e  m

所以我们只需要循环遍历,将其替换为

01 00 03 73 79 73
	     s  y  s

我们先手动修改一下字节码文件
在这里插入图片描述
test原本的代码

public class test {
    public static void main(String[] args) {
        System.out.println("xx");
    }
}

运行
在这里插入图片描述
成功,开始写替换程序

话不多说,直接上代码

import java.io.FileInputStream;

/**
 * 2019-10-19 15:33
 * 贺驰宇
 */
public class tool {
    /**
     * system字节码
     */
    static byte[] system = new byte[]{
            0x01,
            0x00, 0x10,
            0x6A, 0x61, 0x76, 0x61, 0x2F, 0x6C, 0x61, 0x6E, 0x67, 0x2F, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6D
    };
    /**
     * sys字节码
     */
    static byte[] sys = new byte[]{
            0x01,
            0x00, 0x03,
            0x73, 0x79, 0x73
    };

    /**
     * 通用的单个字节码替换方法
     * @param file        需要替换的class文件位置
     * @param replace     即将被替换的字节码
     * @param replacement 替换的字节码
     * @return 最终生成的字节码
     */
    public static byte[] systemToSys(String file, byte[] replace, byte[] replacement) {
        //当前class对象与被替换对象的匹配度
        int size = 0;
        //替换对象和被替换对象的差值
        int reduceSize = replace.length - replacement.length;
        try {
            //获取class字节码
            FileInputStream fis = new FileInputStream(file);
            byte[] bytes = fis.readAllBytes();
            fis.close();
            //根据差值,决定最终生成的字节码大小
            byte[] cache = new byte[bytes.length - reduceSize];
            for (int i = 0; i < bytes.length; i++) {
                //是否完全匹配
                if (size == replace.length) {
                    //需要替换的字节码的前半段长度
                    int beginIndex = i - replace.length;
                    int j;
                    //进行循环插入
                    for (j = 0; j < beginIndex; j++)
                        cache[j] = bytes[j];
                    for (; j < beginIndex + replacement.length; j++)
                        cache[j] = replacement[j - beginIndex];
                    for (; j < bytes.length - reduceSize; j++)
                        cache[j] = bytes[j + reduceSize];
                    return cache;
                }
                //是否匹配
                if (replace[size] == bytes[i]) {
                    size++;
                } else {
                    size = 0;
                    if (replace[size] == bytes[i])
                        size++;
                }
            }
            return bytes;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    public static void main(String[] args) {
        new tool().systemToSys("xx.class", system, sys);
    }
}

由于类加载我在之前的虚拟机类加载机制(二)类加载的过程已经写过了,稍作更改

public class TestClassLoader extends ClassLoader {
    private byte[] bytes;

    public TestClassLoader(byte[] bytes) {
        super(sys.class.getClassLoader());
        this.bytes = bytes;
    }

    public Class<?> findClass(String name) {
        return defineClass(name, bytes, 0, bytes.length);
    }
}
protected void doPost(javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException, IOException {
        sys.bos.reset();
        parse(request.getInputStream());
        request.getInputStream().close();
        write("java","上传文件/test.java");
        new File("缓存文件/"+hm.get("java")).delete();
        Main.compile(new String[]{"上传文件/test.java"});
        byte[] bytes=tool.systemToSys("上传文件/test.class",tool.system,tool.sys);
        try {
            Class clzz=new TestClassLoader(bytes).findClass("test");
            Method m =clzz.getMethod("main",String[].class);
            m.invoke(null,new Object[]{new String[]{}});
        } catch (Exception e) {
            e.printStackTrace();
        }
        response.getWriter().println(sys.getArr());
    }

在这里插入图片描述
成功

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值