0


java安全必学之Javassist 学习

Javassist 学习

环境搭建

这个非常简单,只需要你自己加一个依赖

<!-- https://mvnrepository.com/artifact/javassist/javassist --><dependency><groupId>javassist</groupId><artifactId>javassist</artifactId><version>3.12.1.GA</version></dependency>

简单了解

Java programming ASSISTant,Java编程助手。是Java中编辑字节码的类库,它使Java程序能够在运行时定义一个新类,并在JVM加载时修改类文件。

Java中所有的类都被编译为class文件来运行,在编译完class文件之后,类不能再被显示修改,而Javassist就是用来处理编译后的class文件,它可以用来修改方法或者新增方法,并且不需要深入了解字节码,还可以生成一个新的类对象。

ClassPool这个类是javassist的核心组件之一。ClassPool是CtClass对象容器,CtClass对象必须从该对象获得。

一些基础的语法

从上文的demo中可以看到部分使用方法,在javassist中CtClass代表的就是类class,ClassPool就是CtClass的容器,ClassPool维护了所有创建的CtClass对象,需要注意的是当CtClass数量过大会占用大量内存,需要调用CtClass.detach()释放内存。

ClassPool重点有以下几个方法:

  1. getDefault() 单例获取ClassPool
  2. appendClassPath() 将目录添加到ClassPath
  3. insertClassPath() 在ClassPath插入jar
  4. get() 根据名称获取CtClass对象
  5. toClass() 将CtClass转为Class 一旦被转换则不能修改
  6. makeClass() 创建新的类或接口

更多移步官方文档:http://www.javassist.org/html/javassist/ClassPool.html

CtClass需要关注的方法:

  1. addConstructor() 添加构造函数
  2. addField() 添加字段
  3. addInterface() 添加接口
  4. addMethod() 添加方法
  5. freeze() 冻结类使其不能被修改
  6. defrost() 解冻使其能被修改
  7. detach() 从ClassPool中删除类
  8. toBytecode() 转字节码
  9. toClass() 转Class对象
  10. writeFile() 写入.class文件
  11. setModifiers() 设置修饰符

移步:http://www.javassist.org/html/javassist/CtClass.html

CtMethod继承CtBehavior,需要关注的方法:

  1. insertBefore 在方法的起始位置插入代码
  2. insterAfter 在方法的所有 return 语句前插入代码
  3. insertAt 在指定的位置插入代码
  4. setBody 将方法的内容设置为要写入的代码,当方法被 abstract修饰时,该修饰符被移除
  5. make 创建一个新的方法

简单使用

创建class

packagejavassist;importjava.io.IOException;publicclassCreate{publicstaticvoidmain(String[] args)throwsNotFoundException,CannotCompileException,IOException{ClassPool pool =ClassPool.getDefault();CtClass cc = pool.makeClass("javassist.Person");// 添加字段CtField name =newCtField(pool.get("java.lang.String"),"name", cc);
        name.setModifiers(Modifier.PRIVATE);
        cc.addField(name,CtField.Initializer.constant("LJL"));CtField age =newCtField(pool.get("int"),"age", cc);
        age.setModifiers(Modifier.PRIVATE);
        cc.addField(age,CtField.Initializer.constant(18));// 添加getter和setter方法
        cc.addMethod(CtNewMethod.getter("getName", name));
        cc.addMethod(CtNewMethod.getter("getAge", age));
        cc.addMethod(CtNewMethod.setter("setName", name));
        cc.addMethod(CtNewMethod.setter("setAge", age));// 添加构造函数CtConstructor cons =newCtConstructor(newCtClass[]{}, cc);
        cons.setBody("{name = \"nn0nkey\";}");
        cc.addConstructor(cons);CtConstructor cons1 =newCtConstructor(newCtClass[]{pool.get("java.lang.String"), pool.get("int")}, cc);
        cons1.setBody("{name = $1; age = $2;}");
        cc.addConstructor(cons1);// 添加toString方法CtMethod toString =newCtMethod(pool.get("java.lang.String"),"toString",newCtClass[]{}, cc);
        toString.setModifiers(Modifier.PUBLIC);
        toString.setBody("{return \"name: \" + name + \", age: \" + age;}");
        cc.addMethod(toString);// 将类写入文件
        cc.writeFile("F:\\IntelliJ IDEA 2023.3.2\\java脚本\\CC1_5_6_7\\target\\classes");}}

需要注意的是在setBody()中我们使用了

$

符号代表参数

// $0代表this $1代表第一个传入的参数 类推
printName.setBody("{System.out.println($0.name);}");

body中代码该怎么写就怎么写

我们运行就可以生成我们的class文件

image-20240624164859703

还是非常完美的

使用CtClass对象

这里一共有三种方法

反射调用
publicclassUseit{publicstaticvoidmain(String[] args)throwsNotFoundException,CannotCompileException,InstantiationException,IllegalAccessException,NoSuchMethodException,InvocationTargetException{ClassPool classPool=ClassPool.getDefault();CtClass cc=classPool.get("javassist.Person");Object person = cc.toClass().newInstance();Method setName=person.getClass().getDeclaredMethod("setName",String.class);
        setName.setAccessible(true);
        setName.invoke(person,"k1n9");Method tostring=person.getClass().getMethod("toString");Object result = tostring.invoke(person);System.out.println(result);}}

看代码就很容易看明白就是获取class后实例化,然后步骤就和反射一样了

加载class文件

这个和上面只能说大差不差,就是修改了获取class的方法

ClassPool pool =ClassPool.getDefault();
        pool.appendClassPath("F:\\IntelliJ IDEA 2023.3.2\\java脚本\\CC1_5_6_7\\target\\classes");// 获取上面生成的类CtClass ctClass = pool.get("javassist.Person");Object person = ctClass.toClass().newInstance();//  ...... 下面和通过反射的方式一样去使用

需要注意的点是appendClassPath中填入目录,不要具体的文件

通过接口的方式

这个方法是最方便的,因为反射确实太麻烦;我们考虑为该类写一个接口

//// Source code recreated from a .class file by IntelliJ IDEA// (powered by FernFlower decompiler)//packagejavassist;publicinterfacePersonI{voidsetName(String var1);voidsetAge(int var1);StringgetName();intgetAge();StringtoString();}

然后我们只需要为我们的类实现这个接口,就可以直接调用这个方法了

ClassPool pool =ClassPool.getDefault();
        pool.appendClassPath("F:\\IntelliJ IDEA 2023.3.2\\java脚本\\CC1_5_6_7\\target\\classes");// 获取接口CtClass codeClassI = pool.get("javassist.PersonI");// 获取上面生成的类CtClass ctClass = pool.get("javassist.Person");// 使代码生成的类,实现 PersonI 接口
        ctClass.setInterfaces(newCtClass[]{codeClassI});// 以下通过接口直接调用 强转PersonI person =(PersonI)ctClass.toClass().newInstance();System.out.println(person.getName());
        person.setName("llll");System.out.println(person.toString());

需要注意的就是一定要强转

修改现有的类对象

这个也是我们在实战中比较好用的一个方法

比如我们修改我们的toString方法

packagejavassist;importjavassist.ClassPool;importjavassist.CtClass;importjavassist.CtMethod;importjavassist.NotFoundException;importjavassist.CannotCompileException;importjava.io.IOException;publicclassRefresh{publicstaticvoidmain(String[] args)throwsNotFoundException,CannotCompileException,IOException{ClassPool pool =ClassPool.getDefault();// 获取 Person 类CtClass person = pool.getCtClass("javassist.Person");// 获取 toString 方法CtMethod toString =newCtMethod(pool.get("java.lang.String"),"toString",newCtClass[]{}, person);// 修改 toString 方法的函数体
        toString.setBody("{return \"Are you joker?\";}");// 将修改后的类写入文件(可选)
        person.writeFile("F:\\IntelliJ IDEA 2023.3.2\\java脚本\\CC1_5_6_7\\target\\classes");}}

image-20240624171244614

也是成功了好吧

标签: 学习

本文转载自: https://blog.csdn.net/2301_79724395/article/details/139931811
版权归原作者 nn0nkey k1n9 所有, 如有侵权,请联系我们删除。

“java安全必学之Javassist 学习”的评论:

还没有评论