首先,我们要先对前文的上传系统进行修改,首先是前端
<!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());
}
成功