整体思路

如果远程系统需要执行一段代码,但是我们现在又没有入口。

可以通过这种方式。

实际上对于日志信息,可能某个调用信息没有打印,也可以通过这个方式。

代码实现

核心代码

  • ByteUtils.java
package org.jvm;
/**
 * Bytes数组处理工具
 */
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 bytes2String(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;
	}
}
  • ClassModifier.java
package org.jvm;
/**
 * 修改Class文件,暂时只提供修改常量池常量的功能
 */
public class ClassModifier {
	private static final int CONSTANT_POOL_COUNT_INDEX = 8;
	private static final int CONSTANT_Utf8_info = 1;
	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;
	}
	
	public byte[] modifyUTF8Constant(String oldStr, String newStr){
		int cpc = getConstantPoolCount();
		int offset = CONSTANT_POOL_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.bytes2String(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 getConstantPoolCount(){
		return ByteUtils.bytes2Int(classByte, CONSTANT_POOL_COUNT_INDEX, u2);
	}
}
  • HackSystem.java
package org.jvm;
 
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.PrintStream;
 
/**
 * 为JavaClass劫持java.lang.System提供支持
 * 除了out和err外,其余的都直接转发给System处理
 */
public class HackSystem {
	public final static InputStream in = System.in;
	private static ByteArrayOutputStream buffer = new ByteArrayOutputStream();
	public final static 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 currentTimeMills(){
		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);
	}
}
  • HotSwapClassLoader.java
package org.jvm;
/**
 * 为了多次载入执行类而加入的加载器
 * 把defineClass方法开放出来,只有外部显式调用的时候才会使用到loadByte方法
 * 由虚拟机调用时,仍然按照原有的双亲委派规则使用loadClass方法进行类加载
 */
public class HotSwapClassLoader extends ClassLoader{
 
	public HotSwapClassLoader() {
		super(HotSwapClassLoader.class.getClassLoader());
	}
	public Class<?> loadByte(byte[] classByte){
		return defineClass(null,classByte,0,classByte.length);
	}
}
  • JavaClassExecuter.java
public class JavaClassExecuter {
 
    @SuppressWarnings({ "unused", "rawtypes", "unchecked" })
    public static String execute(byte[] classByte){
        HackSystem.clearBuffer();
        
        ClassModifier cm=new ClassModifier(classByte);
        byte[] modiBytes=cm.modifyUTF8Constant("java/lang/System", "test/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(HackSystem.out);
        }
        
        return HackSystem.getBufferString();
    }
}

测试类


package org.jvm;

/**
 * 测试类,在此类中打印想要在页面看到的内容,System.out输出的内容会存在HackSystem的字节数组输出流中
 */
public class TestClass {
    public static void main(String[] args) {
        System.out.println("-----this is test class out println----");
    }
}

3、jsp页面

  • index.jsp
<%@ page import="java.lang.*" %>
<%@ page import="java.io.*" %>
<%@ page import="org.jvm.*" %>
<%
InputStream inputStream=new FileInputStream("/opt/TestClass.class");
            byte[] b=new byte[inputStream.available()];
            inputStream.read(b);
            inputStream.close();
            out.println(JavaClassExecuter.executer(b));
%>

替换步骤

1、将 ByteUtils ClassModifier HackSystem HotSwapClassloader JavaClassExecuter TestClass 这六个.java文件上传到服务器通过javac进行编译成.class 文件

2、将编译好的TestClass放在/opt目录中

3、在tomcat的项目位置的WEB-INF/classes/中新建org/jvm文件夹,再将编译好的 ByteUtils ClassModifier HackSystem HotSwapClassloader JavaClassExecuter 放在WEB-INF/classes/org/jvm中

4、将 index.jsp 放在项目中能访问到的位置,如项目的根路径中

5、在浏览器中访问jsp页面即可。

参考资料

《深入理解 jvm》

java 手动实现远程执行功能(深入理解java虚拟机)

己动手实现远程执行功能(深入理解java虚拟机)