Javassist动态编程
|字数总计:2.4k|阅读时长:10分钟|阅读量:
javassist介绍
Javassist
是一个开源的分析、编辑和创建Java字节码的类库,Java 字节码存储在称为类文件的二进制文件中。每个类文件包含一个 Java 类或接口。是由东京工业大学的数学和计算机科学系的 Shigeru Chiba (千叶 滋)所创建的。其主要的优点,在于简单,而且快速。直接使用java编码的形式,而不需要了解虚拟机指令,就能动态改变类的结构,或者动态生成类。
学习一下javassist主要的几个类
javassist几个重要的类
下面关于类的资料基本是网上copy的
ClassPool
ClassPool
:一个基于哈希表(Hashtable
)实现的CtClass
对象容器,其中键名是类名称,值是表示该类的CtClass
对象
常用方法
1 2 3 4 5 6 7 8 9 10 11 12
| ClassPool getDefault() 返回默认的类池。比如 ClassPool classPool = ClassPool.getDefault(); ClassPath insertClassPath(String pathname) 在搜索路径的开头插入目录或jar(或zip)文件。 ClassPath insertClassPath(ClassPath cp) ClassPath在搜索路径的开头插入一个对象。 java.lang.ClassLoader getClassLoader() 获取类加载器 CtClass get(java.lang.String classname) 从源中读取类文件,并返回对CtClass 表示该类文件的对象的引用。 ClassPath appendClassPath(ClassPath cp) 将ClassPath对象附加到搜索路径的末尾。 CtClass makeClass(java.lang.String classname) 创建一个新的public类
|
CtClass
CtClass:表示一个类,一个CtClass
(编译时类)对象可以处理一个class
文件,这些CtClass对象可以从ClassPool获得。
一般都是对CtClass类对象进行操作,比如添加方法,添加成员属性
1
| CtClass ctClass = classPool.makeClass("cc2.classdemo2");
|
常用方法
1 2 3 4 5 6 7 8 9 10 11 12
| void setSuperclass(CtClass clazz) 更改超类,除非此对象表示接口。
java.lang.Class<?> toClass(java.lang.invoke.MethodHandles.Lookup lookup) 将此类转换为java.lang.Class对象。 byte[] toBytecode() 将该类转换为类文件。
void writeFile() 将由此CtClass 对象表示的类文件写入当前目录。
void writeFile(java.lang.String directoryName) 将由此CtClass 对象表示的类文件写入本地磁盘。
CtConstructor makeClassInitializer() 制作一个空的类初始化程序(静态构造函数)。
|
CtMethod
CtMethod:表示类中的方法。超类为CtBehavior,很多有用的方法都在CtBehavior
也就是关于添加方法的一系列操作
1 2 3 4 5 6
| void insertBefore (java.lang.String src) 在正文的开头插入字节码。 void insertAfter (java.lang.String src) 在正文的末尾插入字节码。 void setBody (CtMethod src, ClassMap map) 从另一个方法复制方法体。
|
CtConstructor
CtConstructor的实例表示一个构造函数。它可能代表一个静态构造函数。
也就是创建有参或者无参函数
1 2 3 4 5 6
| void setBody(java.lang.String src) 设置构造函数主体。 void setBody(CtConstructor src, ClassMap map) 从另一个构造函数复制一个构造函数主体。 CtMethod toMethod(java.lang.String name, CtClass declaring) 复制此构造函数并将其转换为方法。
|
ClassClassPath
定义在 java.lang.Class 中获取类文件的搜索路径。
构造方法
1 2
| ClassClassPath(java.lang.Class<?> c) 创建一个搜索路径。
|
比如下面的代码,是在默认系统搜索路径获取demo对象
1 2 3
| ClassPool classPool = ClassPool.getDefault();
CtClass ctClass = classPool.get("cc2.demo");
|
但是我们可以通过insertClassPath
修改这个搜索路径
如下
1 2
| classPool.insertClassPath(new ClassClassPath(test_javassist3.class.getClass())); CtClass ctClass = classPool.get("cc1.cc1_test");
|
功能一:动态生成类
也就是通过javassist上面的几个类以及对应的方法可以构造一个恶意的类,其中的方法,属性,接口等等我们都可以自己定义
动态生成类需要以下几个步骤(重点为前3步或者前4步)
- 获取默认类池
ClassPool classPool = ClassPool.getDefault();
- 创建一个自定义类
CtClass ctClass = classPool.makeClass();
- 添加实现接口/属性/构造方法/普通方法
- 写入磁盘
- 进行验证(也就是调用该类的方法)
简单的动态生成一个含有calcmethod(弹计算器)方法的类classdemo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| package cc2;
import javafx.util.converter.LocalDateStringConverter; import javassist.*;
import java.io.IOException; import java.lang.reflect.InvocationTargetException;
public class test_javassist { public static void main(String[] args) throws CannotCompileException, IOException, ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException { ClassPool classPool = ClassPool.getDefault(); CtClass ctClass = classPool.makeClass("cc2.classdemo"); CtMethod ctMethod = CtNewMethod.make("public void calcmethod(){java.lang.Runtime.getRuntime().exec(\"calc\");}",ctClass); ctClass.addMethod(ctMethod);
byte[] bytes = ctClass.toBytecode(); ctClass.writeFile("D:\\projects\\java\\java1\\src\\main\\java");
ClassLoader loader = new Loader(classPool); Class<?> aClass = loader.loadClass("cc2.classdemo"); aClass.getDeclaredMethod("calcmethod").invoke(aClass.newInstance());
} }
|
运行结果
在指定目录下生成classdemo.class
并且加载该类并调用calcmethod方法成功
然后更加丰富一点,在这个基础上添加接口,属性等等
比如下面这个,我们新建了一个classdemo2.class,实现了Serializable接口
,新增了id和name属性,新增了无参和有参构造方法,并且通过实例化,反射调用getname和getid方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
| package cc2;
import javassist.*; import javassist.bytecode.AccessFlag;
import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException;
public class test_javassist { public static void main(String[] args) throws CannotCompileException, IOException, ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException, NotFoundException {
ClassPool classPool = ClassPool.getDefault(); CtClass ctClass = classPool.makeClass("cc2.classdemo2"); ctClass.setInterfaces(new CtClass[]{classPool.makeInterface("java.io.Serializable")});
CtField id = new CtField(CtClass.intType, "id", ctClass); id.setModifiers(AccessFlag.PUBLIC); ctClass.addField(id);
CtField name = new CtField(classPool.get("java.lang.String"), "name", ctClass); name.setModifiers(AccessFlag.PUBLIC); ctClass.addField(name);
CtConstructor ctConstructor1 = CtNewConstructor.make("public classdemo2(){};", ctClass); ctClass.addConstructor(ctConstructor1); CtConstructor ctConstructor = CtNewConstructor.make("public classdemo2(int id,String name){this.id = id;this.name=name;}", ctClass); ctClass.addConstructor(ctConstructor);
CtMethod ctMethod = CtNewMethod.make("public void calcmethod(){java.lang.Runtime.getRuntime().exec(\"calc\");}",ctClass); ctClass.addMethod(ctMethod);
CtMethod getid_method = CtNewMethod.make("public int getid(){return this.id;}", ctClass); ctClass.addMethod(getid_method); CtMethod getname_method = CtNewMethod.make("public String getname(){return this.name;}", ctClass); ctClass.addMethod(getname_method);
byte[] bytes = ctClass.toBytecode();
File file = new File(new File(System.getProperty("user.dir"), "/src/main/java/cc2/"),"classdemo2.class"); FileOutputStream fileOutputStream = new FileOutputStream(file); fileOutputStream.write(bytes); fileOutputStream.close();
ClassLoader loader = new Loader(classPool); Class<?> aClass = loader.loadClass("cc2.classdemo2");
Constructor<?> constructor = aClass.getConstructor(int.class, String.class); Object o = constructor.newInstance(555, "Sk1y"); System.out.println(aClass.getDeclaredMethod("getid").invoke(o)); System.out.println(aClass.getDeclaredMethod("getname").invoke(o)); } }
|
运行结果
功能二:动态获取类方法
一般步骤
- 获取默认类池
ClassPool classPool = ClassPool.getDefault();
- 获取目标类
CtClass ctClass = classPool.get("cc2.demo");
- 获取类的方法
CtMethod hello = ctClass.getDeclaredMethod("hello");
- 插入任意代码
hello.insertBefore("{java.lang.Runtime.getRuntime().exec(\"calc\");}");
- 转换为class对象
Class c = ctClass.toClass();
- 反射调用对象
demo o = (demo)aClass.newInstance();
- 执行方法
o.hello();
首先整一个测试类demo.java
1 2 3 4 5 6 7 8
| package cc2;
public class demo { public void hello(){ System.out.println("hello world!!!"); } }
|
编译
test_javassist2.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| package cc2;
import javassist.*;
public class test_javassist2 { public static void main(String[] args) throws NotFoundException, CannotCompileException, InstantiationException, IllegalAccessException { ClassPool classPool = ClassPool.getDefault(); CtClass ctClass = classPool.get("cc2.demo"); CtMethod hello = ctClass.getDeclaredMethod("hello"); hello.insertBefore("{java.lang.Runtime.getRuntime().exec(\"calc\");}");
Class aClass = ctClass.toClass(); demo o = (demo)aClass.newInstance(); o.hello();
} }
|
运行结果,调用hello方法,会弹计算器
功能三:动态获取类信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| package cc2;
import javassist.*;
import java.lang.reflect.Array; import java.util.Arrays;
public class test_javassist3 { public static void main(String[] args) throws NotFoundException { ClassPool classPool = ClassPool.getDefault();
classPool.insertClassPath(new ClassClassPath(test_javassist3.class.getClass())); CtClass ctClass = classPool.get("cc2.demo"); System.out.println(ctClass.getName()); System.out.println(ctClass.getSimpleName()); System.out.println(ctClass.getSuperclass().getName()); System.out.println(Arrays.toString(ctClass.getInterfaces())); for (CtConstructor constructor : ctClass.getConstructors()) { System.out.println(constructor); } for (CtMethod method : ctClass.getMethods()) { System.out.println(method); } } }
|
运行结果
总结
个人感觉javassist很神奇,通常我们生成class文件,是通过对java文件进行编译生成的,但是有了javassist之后,就可以直接动态生成class文件。
除此之外,动态获取类方法,把一个安全的类方法,通过insertBefore
可以插入任意恶意代码,进行命令执行。不过限制在于需要有javassist这个依赖。
在学习过程中,看到了nice0e3师傅关于免杀的一些想法,学到了学到了🐂,文章链接在参考链接3
参考链接
- Java之Javassist动态编程 - Zh1z3ven - 博客园 (cnblogs.com)
- (2条消息) Java动态编程之javassist_Ricky_Fung的博客-CSDN博客
- Java安全之Javassist动态编程 - nice_0e3 - 博客园 (cnblogs.com)