java 手动实现远程执行功能(深入理解java虚拟机)
1、功能类
功能类共有五,分别是:
package org.jvm; import java.io.*; /** * 对字节数组操作的工具类 */ public class ByteUtils { public static int byte2Int(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[] origialBytes,int offset,int len,byte[] replaceBytes){ byte[] newBytes=new byte[origialBytes.length+(replaceBytes.length-len)]; System.arraycopy(origialBytes,0,newBytes,0,offset); System.arraycopy(replaceBytes,0,newBytes,offset,replaceBytes.length); System.arraycopy(origialBytes,offset+len,newBytes,offset+replaceBytes.length,origialBytes.length-offset-len); return newBytes; } }
package org.jvm; import java.io.*; /** * 对测试类class文件的字节数组执行替换,将oldStr替换成newStr */ 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,5,-1,5,9,9,3,3,5,5,5,5}; private final int u1=1; private final int u2=2; private byte[] classByte; public ClassModifier(byte[] classByte){ this.classByte=classByte; } public byte[] modiftyUTF8Constant(String oldStr,String newStr){ int cpc=getConstantPoolCount(); int offset=CONSTANT_POOL_COUNT_INDEX+u2; for(int i =0;i<cpc;i++){ //取出CONSTANT_UTF8_info中标志部分 int tag= ByteUtils.byte2Int(classByte, offset, u1); //判断是否为CONSTANT_UTF8_info数据类型 if(tag==CONSTANT_UTF8_info){ //取出CONSTANT_UTF8_info中字符串的长度 int len=ByteUtils.byte2Int(classByte,offset+u1,u2); offset+=(u1+u2); //取出CONSTANT_UTF8_info中的字符串部分 String str=ByteUtils.bytes2String(classByte,offset,len); //通过字符串部分比较是否为需要修改的CONSTANT_UTF8_info if(str.equalsIgnoreCase(oldStr)){ //将新字符串的值打散成字节数组 byte[] strBytes=ByteUtils.string2Bytes(newStr); //将表示字符串长度值的两个字节分别以16进制的形式装在字节数组中 byte[] strLen=ByteUtils.int2Bytes(newStr.length(),u2); //将CONSTANT_UTF8_info中表示length部分进行替换 classByte=ByteUtils.bytesReplace(classByte,offset-u2,u2,strLen); //将CONSTANT_UTF8_info中字符串部分进行替换 classByte=ByteUtils.bytesReplace(classByte,offset,len,strBytes); return classByte; //如不是需要修改的CONSTANT_UTF8_info,则跳过这个类型,接着循环 }else { offset+=len; } //如果不是CONSTANT_UTF8_info数据类型,根据tag跳转CONSTANT_ITEM_LENGTH中定义的字节数 }else { offset+=CONSTANT_ITEM_LENGTH[tag]; } } return classByte; } public int getConstantPoolCount(){
package org.jvm; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.io.PrintStream; /** * 用于替换System的输出,将测试类中每次System.out的内容输出到字节数组流中,最后一次性输出到页面 */ 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 static final PrintStream err=out; public static String getBuffer(){ return buffer.toString(); } public static void clearBuffer(){ buffer.reset(); } }
package org.jvm; /** * 测试类的类加载器,通过字节数组的方式进行加载 */ public class HotSwapClassloader extends ClassLoader{ public HotSwapClassloader(){ super(HotSwapClassloader.class.getClassLoader()); } public Class loadByte(byte[] classByte){ return defineClass(null,classByte,0,classByte.length); } }
package org.jvm; import java.lang.reflect.Method; /** * 执行类,通过反射调用测试类中的main方法,最后取出HackSystem中字节数组流中的数据进行返回 */ public class JavaClassExecuter { public static String executer(byte[] classByte) throws NoSuchMethodException {
HackSystem.clearBuffer(); ClassModifier classModifier=new ClassModifier(classByte); byte[] modiByte=classModifier.modiftyUTF8Constant("java/lang/System","org/jvm/HackSystem"); HotSwapClassloader loader=new HotSwapClassloader(); Class cs=loader.loadByte(modiByte); try { Method method=cs.getMethod("main", new Class[]{String[].class}); method.invoke(null,new String []{null}); }catch (Throwable throwable){ throwable.printStackTrace(HackSystem.out); } return HackSystem.getBuffer(); } }
2、测试类
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页面
test.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、将test.jsp放在项目中能访问到的位置,如项目的根路径中
5、在浏览器中访问jsp页面即可,如http://192.168.3.235:8080/test.jsp即可看到页面中会输出
-----this is test class out println----
好记性不如烂笔头^_^!