ejecución remota manual de java

Un escenario de demanda

Cuando ocurre un error en el servidor, pero porque uno o dos registros no se imprimen y el problema específico no se puede localizar al final, es muy molesto. La ejecución remota puede proporcionar funciones de mejora dinámica para el programa sin modificar el programa del servidor. , Imprima la información que desee.

2. En el proceso de implementación del programa, necesitamos resolver los siguientes 3 problemas

· ¿Cómo compilar el código Java enviado al servidor?
· ¿Cómo ejecutar el código Java compilado?
· ¿Cómo recopilar el resultado de la ejecución del código Java?

El primer problema está resuelto:
necesita implementar una interfaz de archivo de carga para cargar el archivo de clase compilado al servidor. Este archivo de clase es el archivo ejecutado final. Este archivo puede proporcionarle las capacidades de salida del servicio que desee.
El segundo problema está resuelto:
deje que el cargador de clases cargue esta clase para generar un objeto Class y luego llame a cierto método en reflexión.
La solución al segundo problema:
aquí está el método de salida directa al registro.

Tres realizaciones

1.HotSwapClassLoader.java
public class HotSwapClassLoader extends ClassLoader{
    
    

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

    public Class loadByte(byte[] classByte){
    
    

        return defineClass(null,classByte,0,classByte.length);
    }
}

Lo que hace HotSwapClassLoader es simplemente exponer el método protegido
defineClass ( ) en la clase principal (es decir, java.lang.ClassLoader) . Usaremos este método para convertir la matriz de bytes [] de la clase Java enviada para su ejecución en un objeto Class.
HotSwapClassLoader no anula el método loadClass () o findClass (), por lo que si no cuenta la llamada manual externa al
método loadByte () , el rango de búsqueda de clases de este cargador de clases es exactamente el mismo que su cargador de clases padre. se llama la máquina virtual, se
entregará a la clase principal para que se cargue de acuerdo con el modelo de delegación principal. El constructor especifica el cargador de clases que carga la clase HotSwapClassLoader como
cargador de clases padre . Este paso es la clave para darse cuenta de que el código de ejecución enviado puede acceder a la biblioteca de clases de referencia del lado del servidor.

2.HackSystem.java
public class HackSystem {
    
    
    public final static InputStream in = System.in;
    public 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 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);
    }
}

La segunda clase es implementar el proceso de reemplazar java.lang.System con la clase HackSystem definida por nosotros mismos. Modifica directamente
la parte del grupo de constantes en la matriz de bytes [] de acuerdo con el formato de archivo Class, y cambia la constante constant_Utf8_info de la contenido especificado en el grupo constante. Reemplácelo con
una nueva cadena, el código específico se muestra en el siguiente listado de código [3.ClassModifier.java]. La parte de ClassModifier involucrada en la operación de la matriz de bytes [] es principalmente convertir el byte [] a int y String, y encapsular la operación de reemplazo de datos de byte [] en ByteUtils que se muestran en la lista de código [4.ByteUtils.java ] en

3.ClassModifier.java
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_LEGTH = {
    
    -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_LEGTH[tag];
            }
        }
        return classByte;
    }

    public int getConstantPoolcount(){
    
    
        return ByteUtils.bytes2Int(classByte, CONSTANT_POOL_COUNT_INDEX,u2);
    }


}

La matriz de bytes [] procesada por ClassModifier se pasará al método HotSwapClassLoader.loadByte () para cargar la clase.
Después de reemplazar la referencia de símbolo con la matriz de bytes [] aquí, haga referencia directamente a la clase HackSystem en el código Java con el cliente y luego compilar la clase generada Es exactamente lo mismo. Esta implementación no solo evita que el cliente dependa de clases específicas al escribir código de ejecución temporal (de lo contrario, no se puede introducir
HackSystem), sino que también evita que el servidor afecte la salida de otros programas después de modificar la salida estándar.

4.ByteUtils.java
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;

    }
}

4.JavaclassExecuter.java

Se han explicado las cuatro clases de soporte. Echemos un vistazo a la última clase, JavaclassExecuter, que es el punto de entrada para llamadas externas. La
lógica de ensamblaje de las clases de soporte anteriores se llama para completar el trabajo de carga de clases. Solo un método execute () JavaclassExecuter, ingresado con
el en línea con el formato de archivo de clase byte [] matriz java.lang. El sistema reemplazó las referencias simbólicas utilizadas para generar un objeto de clase HotSwapClassLoader de carga, ya que cada vez que el método execute () de ejecución generará un nueva instancia del cargador de clases, por lo que la misma clase se puede cargar repetidamente. Luego, el método main () de este objeto Class es llamado por reflexión.Si ocurre alguna excepción durante el período, la información de la excepción se
imprime en HackSystem.out, y finalmente la información en el búfer se devuelve como resultado del método. El código de implementación de JavaclassExecuter es el
siguiente:

public class JavaclassExecuter {
    
    
    public static String execute(byte[] classByte){
    
    
        HackSystem.clearBuffer();
        ClassModifier cm = new ClassModifier(classByte);
        byte[] modiBytes = cm.modifyUTF8Constant("java/lang/System","sgcc/supplier/service/front/shops/controller/remoteexecute/tmppak/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 (Exception e) {
    
    
            e.printStackTrace();
        }
        return HackSystem.getBufferString();
    }
}

Cuatro verificación

Método de verificación 1:

Escriba una clase Java de forma arbitraria, el contenido no importa, solo envíe información a System.out, llamada TestClass, y colóquela en el directorio raíz de la unidad C del servidor. Luego cree un archivo JSP y escriba el siguiente contenido, puede ver los resultados de ejecución de esta clase en el navegador, este tipo de verificación requiere el establecimiento de un proyecto web ordinario y coloque 4 clases de soporte y un código jsp en En el proyecto , el código jsp se puede poner directamente en index.jsp. Lo anterior se usa para probar la información de la clase TestClass.java y la información jsp.

public class TestClass{
    
    
    public static void main(String[] args){
    
    
       System.out.println("这是第一句。。。");
       System.out.println("这是第二句。。。");
       System.out.println("这是第三句。。。");
    }
}
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="java.lang.*" %>
<%@ page import="java.io.*" %>
<%@ page import="com.esgcc.*" %><%--这里传入的是4个支持类的路径--%>
<%
  InputStream is = new FileInputStream("C:/TestClass.class");
  byte[] b = new byte[is.available()];
  is.read(b);
  is.close();

  out.println("<textarea style='width:1000;height:800'>");
  out.println(JavaclassExecuter.execute(b));
  out.println("</textarea>");
%>

Inicie el proyecto y podrá ver el resultado de la página:
Inserte la descripción de la imagen aquí

Método de verificación dos:

La primera es enviar la información a la página. También podemos enviar la información directamente a la consola. Aquí solo necesita crear un proyecto java arbitrario y colocar las 4 clases de soporte y el siguiente código en la mayor cantidad posible. el código de implementación es en realidad el mismo:

public class MainEntrance {
    
    
    public MainEntrance() {
    
    
    }

    public static void main(String[] args) {
    
    
        try {
    
    
            InputStream is = new FileInputStream("c:/TestClass.class");
            byte[] b = new byte[is.available()];
            is.read(b);
            is.close();
            JavaclassExecuter.execute(b);
        } catch (IOException var3) {
    
    
            var3.printStackTrace();
        }

    }
}

Ejecute el método principal anterior para obtener el resultado:
Inserte la descripción de la imagen aquí

Método de verificación tres:

Aquí está para usar una nueva interfaz en SpringBoot, use la interfaz para activar la ejecución, cargue los 4 códigos de clase de soporte anteriores al servidor y cargue el TestClass.class llamado al directorio opt. El código de implementación de la interfaz es el siguiente :.
el código se muestra a continuación:

@Slf4j
@RestController
@RequestMapping("/remote")
public class RemoteExecuteController {
    
    


    /**
     * @param
     * @return
     */
    @PostMapping(value = "/execute")
    @ApiOperation(value = "远程", notes = "远程")
    @ApiImplicitParams({
    
    
            @ApiImplicitParam(name = "jsonParams", value = "参数集合: \n" +
                    "{\n" +
                    "}", required = true, dataTypeClass = String.class, paramType = "query"),
            @ApiImplicitParam(name = "userToken", value = "token", required = true, dataTypeClass = String.class, paramType = "header")
    })
    public String remoteExecute(String jsonParams) {
    
    

        try {
    
    

            log.info("::::::::::::::::::::::进入执行接口::::::::::::::::::::::");

            InputStream is = new FileInputStream("/opt/TestClass.class");
            byte[] b = new byte[is.available()];
            is.read(b);
            is.close();
            log.info("::::::::::::::::::::::远程执行日志打印开始::::::::::::::::::::::");
            log.info(JavaclassExecuter.execute(b));
            log.info("::::::::::::::::::::::远程执行日志打印结束::::::::::::::::::::::");
        } catch (IOException e) {
    
    
            e.printStackTrace();
            log.error(e.getMessage());
        }
        return "success";
    }

}

Verifique el registro después de llamar a la interfaz a través del cartero, hay una salida:
Inserte la descripción de la imagen aquí

Cinco resumen

Esto es solo una demostración. En realidad, no llama al método que desea llamar en la clase TestClass. Solo proporciona un marco, con la esperanza de inspirar a los amigos que ven estos códigos.

Materiales de referencia:

"Comprensión en profundidad de la máquina virtual java: 3.ª edición"

Supongo que te gusta

Origin blog.csdn.net/m0_46897923/article/details/112653799
Recomendado
Clasificación