因为看到在缩短payload的时候会用到,赶紧来学习一下,参考文章:https://www.yishuifengxiao.com/2023/04/04/javassist%E5%9F%BA%E7%A1%80%E5%85%A5%E9%97%A8%E7%AC%94%E8%AE%B0/
这是官方文档:http://www.javassist.org/tutorial/tutorial.html
javassist介绍
Javassist 是一个开源的分析、编辑和创建Java字节码的类库.;其主要优点在于简单快速. 直接使用 java 编码的形式, 而不需要了解虚拟机指令, 就能动态改变类的结构, 或者动态生成类。
使用前导入jar包
<dependency> <groupId>org.javassist</groupId> <artifactId>javassist</artifactId> <version>3.28.0-GA</version> </dependency>
|
Javassist中最为重要的是ClassPool
,CtClass
, CtMethod
以及CtField
这几个类.
ClassPool
: 一个基于Hashtable
实现的CtClass
对象容器, 其中键是类名称, 值是表示该类的CtClass
对象
CtClass
: CtClass
表示类, 一个CtClass
(编译时类)对象可以处理一个class文件, 这些CtClass
对象可以从ClassPool
获得
CtMethod
: 表示类中的方法
CtField
: 表示类中的字段
CtConstructor
:可读写的类构造方法对象
ClassPool相关方法
getDefault
: 返回默认的ClassPool
是单例模式的,一般通过该方法创建我们的ClassPool
;
appendClassPath
, insertClassPath
: 将一个ClassPath
加到类搜索路径的末尾位置 或 插入到起始位置。通常通过该方法写入额外的类搜索路径,以解决多个类加载器环境中找不到类的尴尬;
toClass
: 将修改后的CtClass
加载至当前线程的上下文类加载器中,CtClass
的toClass
方法是通过调用本方法实现。需要注意的是一旦调用该方法,则无法继续修改已经被加载的class;
get
, getCtClass
: 根据类路径名获取该类的CtClass
对象,用于后续的编辑。
ClassPool对象的创建
ClassPool pool = new ClassPool(true);
ClassPool pool1 = ClassPool.getDefault();
|
为减少ClassPool可能导致的内存消耗; 可以从ClassPool中删除不必要的CtClass对象. 或者每次创建新的ClassPool对象。
ctClass.detach();
ClassPool pool2 = new ClassPool(true);
|
CtClass相关方法
- freeze: 冻结一个类,使其不可修改;
- isFrozen : 判断一个类是否已被冻结;
- prune : 删除类不必要的属性,以减少内存占用。调用该方法后,许多方法无法将无法正常使用,慎用;
- defrost : 解冻一个类,使其可以被修改。如果事先知道一个类会被defrost, 则禁止调用 prune 方法;
- detach : 将该class从ClassPool中删除;
- writeFile : 根据CtClass生成 .class 文件;
- toClass : 通过类加载器加载该CtClass。
- setInterfaces: 添加父接口
- setSuperclass: 添加父类
获取CtClass
CtClass ctClass = classPool.get("org.clown.ssist.Student");
CtClass ctClass1 = pool.getOrNull("org.clown.ssist.Student"); ctClass.freeze(); System.out.println(ctClass.isFrozen());
|
创建CtClass
CtClass ctClass2 = pool.getAndRename("org.clown.ssist.Student", "org.clown.ssist.Teacher");
CtClass ctClass3 = pool.makeClass("org.clown.ssist.Student");
CtClass ctClass4 = classPool.makeClass(new FileInputStream(new File("target/classes/org/clown/ssist/Student.class")));
|
创建一个类然后写入
ClassPool cp = ClassPool.getDefault(); CtClass ctClass = cp.makeClass("Temp.Hello"); ctClass.writeFile();
|
然后就会根据名称保存到对应的目录下,将类持久化了到文件中
CtClass基础信息
就是一些类的各种基础信息,类全名、类方法、类字段等
String simpleName = ctClass.getSimpleName();
String name = ctClass.getName();
String packageName = ctClass.getPackageName();
CtClass[] interfaces = ctClass.getInterfaces();
CtClass superclass = ctClass.getSuperclass();
CtMethod ctMethod = ctClass.getDeclaredMethod("getName()", new CtClass[] {pool.get(String.class.getName()), pool.get(String.class.getName())});
CtField ctField = ctClass.getField("name");
ctClass.isArray();
ctClass.isPrimitive();
ctClass.isInterface();
ctClass.isEnum();
ctClass.isAnnotation();
ctClass.freeze ()
ctClass.isFrozen()
ctClass.prune()
ctClass.defrost()
|
对CtClass进行操作
ctClass.addInterface(...);
ctClass.addConstructor(...);
ctClass.addField(...);
ctClass.addMethod(...);
|
CtClass编译
Class clazz = ctClass.toClass();
ClassFile classFile = ctClass.getClassFile();
byte[] bytes = ctClass.toBytecode();
|
CtMethod相关方法
CtMthod
代表类中的某个方法,可以通过CtClass
提供的API获取或者CtNewMethod
新建,通过CtMethod
对象可以实现对方法的修改。
CtNewMethod有点类似一个工具类,里面的方法都是静态方法,比如生成一个新的CtMethod
insertBefore : 在方法的起始位置插入代码;
insterAfter : 在方法的所有 return 语句前插入代码以确保语句能够被执行,除非遇到exception;
insertAt : 在指定的位置插入代码;
setBody: 将方法的内容设置为要写入的代码,当方法被 abstract修饰时,该修饰符被移除;
make : 创建一个新的方法,本质就是调用CtNewMethod#make
CtMethod属性获取
CtClass ctClass5 = pool.get(TestService.class.getName()); CtMethod ctMethod = ctClass5.getDeclaredMethod("selectOrder");
String methodName = ctMethod.getName();
CtClass returnType = ctMethod.getReturnType();
ctMethod.getLongName();
ctMethod.getSignature();
List<String> argKeys = new ArrayList<>(); MethodInfo methodInfo = ctMethod.getMethodInfo(); CodeAttribute codeAttribute = methodInfo.getCodeAttribute(); LocalVariableAttribute attr = (LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag); int len = ctMethod.getParameterTypes().length;
int pos = Modifier.isStatic(ctMethod.getModifiers()) ? 0 : 1; for (int i = pos; i < len; i++) { argKeys.add(attr.variableName(i)); }
|
CtMethod方法体修改
ctMethod.insertBefore("");
ctMethod.insertAfter("");
ctMethod.insertAt(10, "");
ctMethod.addParameter(CtClass);
ctMethod.setName("newName");
ctMethod.setBody("{$0.name = $1;}");
ctMethod.make("kawa",CtClass);
|
异常块添加
在方法中加入try catch块, 需要注意的是, 必须在插入的代码中, 加入return值$e代表异常信息.插入的代码片段必须以throw或return语句结束
CtMethod m = ...; CtClass etype = ClassPool.getDefault().get("java.io.IOException"); m.addCatch("{ System.out.println($e); throw $e; }", etype);
try { } catch (java.io.IOException e) { System.out.println(e); throw e; }
|
类搜索路径
我们前面获取的ClassPool他有自己的类搜索路径,如果程序运行在JBoss或Tomcat等web服务器上,可能会找不到用户自己的类,我们需要手动添加一个类搜索路径。
下面是各种添加类搜素路径的各种方式
pool.insertClassPath(new ClassClassPath(Student.getClass()));
pool.appendClassPath(new ClassClassPath(this.getClass()));
|
该方式添加的时候,比如上面的Student.class,可以将class所在的整个jar添加到搜索路径
pool.insertClassPath("/xxx/lib");
|
ClassPool pool = ClassPool.getDefault(); ClassPath cp = new URLClassPath("www.sample.com", 80, "/out/", "com.test"); pool.insertClassPath(cp);
|
上述代码将http://www.sample.com:80/out添加到类搜索路径。并且这个URL只能搜索`com.test`包里面的类。
ClassPool cp = ClassPool.getDefault(); byte[] buf = 字节数组; String name = 类名; cp.insertClassPath(new ByteArrayClassPath(name, buf)); CtClass cc = cp.get(name);
|
ClassPool cp = ClassPool.getDefault(); InputStream ins = class文件对应的输入流; CtClass cc = cp.makeClass(ins);
|
读写字节码
ClassPool pool = ClassPool.getDefault(); CtClass cc = pool.get("test.Rectangle");
|
ClassPool
的getDefault()
方法将会查找系统默认的路径来搜索test.Rectable
对象,然后将获取到的CtClass
对象赋值给cc变量,如果对象没有被找到,那么get()
方法就会创建出一个默认的CtClass
对象,然后放入到HashTable
中,同时将当前创建的对象返回。
byte[] b = cc.toBytecode(); Class clazz = cc.toClass();
|
toClass()
方法调用使得当前线程中的context class loader加载此CtClass类,然后生成java.lang.Class
对象。
对类的相关操作
主要还是学一下具体的使用,太深入的东西先不看,参考文章:https://blog.csdn.net/weixin_54902210/article/details/129562446
这里基于前面创建的Hello类进行操作
ClassPool cp = ClassPool.getDefault(); CtClass ctClass = cp.makeClass("Temp.Hello"); ctClass.writeFile();
|
添加属性
package org.clown.ssist;
import javassist.ClassPool; import javassist.CtClass; import javassist.CtField; import javassist.Modifier;
public class demo2 { public static void main(String[] args) throws Exception{ ClassPool classPool = ClassPool.getDefault(); CtClass ctClass = classPool.makeClass("Temp.Hello");
CtField name = new CtField(classPool.get("java.lang.String"), "name", ctClass); name.setModifiers(Modifier.PUBLIC); ctClass.addField(name,CtField.Initializer.constant("Sentiment")); ctClass.writeFile(); } }
|
赋值也可以这样
ctClass.addField(name,"name=\"Sentiment\"");
|
但这种赋值偏向于用构造器等进行初始化
添加方法
方法可以设置的返回类型
public static CtClass booleanType; public static CtClass charType; public static CtClass byteType; public static CtClass shortType; public static CtClass intType; public static CtClass longType; public static CtClass floatType; public static CtClass doubleType; public static CtClass voidType;
|
这里不支持直接用String,是因为在java字节码中,参数和返回类型的String一般都是用常量池中字符串的索引值,要设置String类型的话就和前面一样用classPool.getCtClass(“java.lang.String”)
package org.clown.ssist;
import javassist.*;
public class demo2 { public static void main(String[] args) throws Exception{ ClassPool classPool = ClassPool.getDefault(); CtClass ctClass = classPool.makeClass("Temp.Hello");
CtField name = new CtField(classPool.get("java.lang.String"), "name", ctClass); name.setModifiers(Modifier.PUBLIC); ctClass.addField(name,CtField.Initializer.constant("Sentiment"));
CtMethod test = new CtMethod(CtClass.voidType, "Test", new CtClass[]{CtClass.intType, CtClass.charType}, ctClass); ctClass.setModifiers(Modifier.PUBLIC); ctClass.addMethod(test); ctClass.writeFile();
} }
|
设置方法体
package org.clown.ssist;
import javassist.*;
public class demo2 { public static void main(String[] args) throws Exception{ ClassPool classPool = ClassPool.getDefault(); CtClass ctClass = classPool.makeClass("Temp.Hello");
CtField name = new CtField(classPool.get("java.lang.String"), "name", ctClass); name.setModifiers(Modifier.PUBLIC); ctClass.addField(name,CtField.Initializer.constant("Sentiment"));
CtMethod test = new CtMethod(CtClass.voidType, "Test", new CtClass[]{CtClass.intType, CtClass.charType}, ctClass); ctClass.setModifiers(Modifier.PUBLIC); ctClass.addMethod(test);
test.setBody("System.out.println(\"Hello World\");"); ctClass.writeFile();
} }
|
方法体前后插入代码
test.insertBefore("System.out.println(\"我在前面插入:\"+$1);"); test.insertAfter("System.out.println(\"我在后面插入了:\"+$2);");
|
添加构造器
CtConstructor cons = new CtConstructor(new CtClass[]{classPool.getCtClass("java.lang.String")}, ctClass); cons.setBody("{$0.name = $1;}"); ctClass.addConstructor(cons); ctClass.writeFile();
|
无参构造去掉中间的参数即可
修改已有类
用ClassPool获取CtClass之后进行修改,我们对我们我们前面创建的Hello.class进行修改
package org.clown.ssist;
import javassist.ClassPool; import javassist.CtClass; import javassist.CtConstructor;
public class demo3 { public static void main(String[] args) throws Exception { System.out.println(System.getProperty("user.dir")); ClassPool classPool = ClassPool.getDefault(); classPool.insertClassPath("D:\\CTF\\Java\\JavaCode\\JavassistLearn"); CtClass ctClass = classPool.getCtClass("Temp.Hello"); CtConstructor constructor = ctClass.getConstructors()[0]; constructor.setBody("{System.out.println(\"changing\");}"); ctClass.writeFile(); } }
|
可以看到成功修改
这里添加类路径的时候要注意在包名的上一层,不然会找不到类,因为get的时候用完整包名会自动添加上路径进行搜索
比如上面的添加路径为:D:\CTF\Java\JavaCode\JavassistLearn,搜索时就是这样:D:\CTF\Java\JavaCode\JavassistLearn\Temp\Hello.class
一些特殊变量
就是我们前面使用的$1,$0那些
标识符 |
作用 |
$0、$1、$2、 $3等 |
this和方法参数(1-N是方法参数的顺序) |
$args |
方法参数数组,类型为Object[] |
$$ |
所有方法参数,例如:m($$)相当于m($1,$2,…) |
$cflow(…) |
control flow 变量 |
$r |
返回结果的类型,在强制转换表达式中使用。 |
$w |
包装器类型,在强制转换表达式中使用。 |
$_ |
方法的返回值 |
$sig |
类型为java.lang.Class的参数类型对象数组 |
$type |
类型为java.lang.Class的返回值类型 |
$class |
类型为java.lang.Class的正在修改的类 |