Java安全学习-ASM学习小记

admin 2026-01-01 05:10:10 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文记录了Java字节码操作框架ASM的学习笔记。文章解析了ClassReader、ClassVisitor和ClassWriter核心类的原理与协作,提供了环境配置。通过代码演示了读取类信息、增删字段及修改方法的具体实现,展示了动态操作字节码的技术细节,对Java安全研究和代码审计具有参考价值。 综合评分: 83 文章分类: 代码审计,逆向分析,安全工具


cover_image

Java安全学习-ASM学习小记

原创

摆烂的beizeng

土拨鼠的安全屋

2025年12月31日 16:27 山东

简介

在学习之前先去看一下这三篇文章做一个前置知识

JAVA安全|字节码篇:字节码文件结构与解读JAVA安全|字节码篇:常见字节码指令(JVM指令)JAVA安全|字节码篇:字节码操作框架—ASM(原理)

在来个y4taker博客中的图总结

img

环境

<properties>
&nbsp; <maven.compiler.source>8</maven.compiler.source>
&nbsp; <maven.compiler.target>8</maven.compiler.target>
&nbsp; <asm.version>9.3</asm.version>
</properties>
<dependencies>
&nbsp; <dependency>
&nbsp; &nbsp; <groupId>org.ow2.asm</groupId>
&nbsp; &nbsp; <artifactId>asm</artifactId>
&nbsp; &nbsp; <version>${asm.version}</version>
&nbsp; </dependency>
&nbsp; <dependency>
&nbsp; &nbsp; <groupId>org.ow2.asm</groupId>
&nbsp; &nbsp; <artifactId>asm-commons</artifactId>
&nbsp; &nbsp; <version>${asm.version}</version>
&nbsp; </dependency>
&nbsp; <dependency>
&nbsp; &nbsp; <groupId>org.ow2.asm</groupId>
&nbsp; &nbsp; <artifactId>asm-util</artifactId>
&nbsp; &nbsp; <version>${asm.version}</version>
&nbsp; </dependency>
&nbsp; <dependency>
&nbsp; &nbsp; <groupId>org.ow2.asm</groupId>
&nbsp; &nbsp; <artifactId>asm-tree</artifactId>
&nbsp; &nbsp; <version>${asm.version}</version>
&nbsp; </dependency>
&nbsp; <dependency>
&nbsp; &nbsp; <groupId>org.ow2.asm</groupId>
&nbsp; &nbsp; <artifactId>asm-analysis</artifactId>
&nbsp; &nbsp; <version>${asm.version}</version>
&nbsp; </dependency>

</dependencies>

ASM使用

在asm当中有三个最重要的类,关系如下图

img

  • ClassReader类,负责读取.class文件里的内容,然后拆分成各个不同的部分。
  • ClassVisitor类,负责对.class文件中某一部分里的信息进行修改。
  • ClassWriter类,负责将各个不同的部分重新组合成一个完整的.class文件。

ClassVisitor类

用于生成和转换已编译类的 ASM API 是基于 ClassVisitor 抽象类的,将它收到的所有方法调用都委托给另一个 ClassVisitor 类,会调用该类的visitXXX方法,这个类可以看作一个事件筛选器

访问顺序

visit visitSource? visitOuterClass? ( visitAnnotation | visitAttribute )*
( visitInnerClass | visitField | visitMethod )*
visitEnd

方法解析

public abstract class ClassVisitor {
&nbsp; &nbsp; // 构造方法
&nbsp; &nbsp; public ClassVisitor(int api);
&nbsp; &nbsp; public ClassVisitor(int api, ClassVisitor cv);
&nbsp; &nbsp; // 访问类的基本信息。version参数表示类的版本号,access参数表示类的访问标志,name参数表示类的内部名称,signature参数表示类的泛型签名(如果适用),superName参数表示父类的内部名称,interfaces参数表示类实现的接口的内部名称数组
&nbsp; &nbsp; public void visit(int version, int access, String name, String signature, String superName, String[] interfaces);
&nbsp; &nbsp; // 访问源文件和调试信息。source参数表示源文件的名称,debug参数表示调试信息
&nbsp; &nbsp; public void visitSource(String source, String debug);
&nbsp; &nbsp; // 访问外部类信息。owner参数表示外部类的内部名称,name参数表示外部类的名称,desc参数表示外部类的描述符
&nbsp; &nbsp; public void visitOuterClass(String owner, String name, String desc);
&nbsp; &nbsp; // 访问类的注解,返回一个AnnotationVisitor实例,用于访问注解的内容。desc参数表示注解的描述符,visible参数表示注解是否在运行时可见
&nbsp; &nbsp; AnnotationVisitor visitAnnotation(String desc, boolean visible);
&nbsp; &nbsp; // 访问类的属性(Attribute),例如SourceFile属性。attr参数表示要访问的属性
&nbsp; &nbsp; public void visitAttribute(Attribute attr);
&nbsp; &nbsp; // 访问内部类信息。name参数表示内部类的内部名称,outerName参数表示内部类的外部类的内部名称,innerName参数表示内部类的名称,access参数表示内部类的访问标志
&nbsp; &nbsp; public void visitInnerClass(String name, String outerName, String innerName, int access);
&nbsp; &nbsp; // 访问类的字段。access参数表示字段的访问标志,name参数表示字段的名称,desc参数表示字段的描述符,signature参数表示字段的泛型签名(如果适用),value参数表示字段的初始值
&nbsp; &nbsp; public FieldVisitor visitField(int access, String name, String desc, String signature, Object value);
&nbsp; &nbsp; // 访问类的方法。access参数表示方法的访问标志,name参数表示方法的名称,desc参数表示方法的描述符,signature参数表示方法的泛型签名(如果适用),exceptions参数表示方法声明的异常类型的内部名称数组
&nbsp; &nbsp; public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions);
&nbsp; &nbsp; // 访问类的结束,表示不再访问该类的任何内容
&nbsp; &nbsp; void visitEnd();
}

ClassReader类

该类解析 ClassFile 内容,并针对遇到的每个字段、方法和字节码指令调用给定 ClassVisitor 的相应访问方法。这个类可以看作一个事件生产者

ClassWriter类

ClassWriter 类是 ClassVisitor 抽象类的一个子类,它直接以二进制形式生成编译后的类。它会生成一个字节数组形式的输出,其中包含了已编译类,可以用 toByteArray 方法来提取。这个类可以看作一个事件消费者

MethodVisitor类

访问Java方法的访问者类,用于生成和转换已编译方法的 ASM API 是基于 MethodVisitor 抽象类的,它由 ClassVisitor 的 visitMethod 方法返回。

访问顺序

visitAnnotationDefault?
( visitAnnotation | visitParameterAnnotation | visitAttribute )*
( visitCode
( visitTryCatchBlock | visitLabel | visitFrame | visitXxxInsn | visitLocalVariable | visitLineNumber )*
visitMaxs )?
visitEnd

对非抽象方法,如果存在注解和属性,必须先访问;其次是按顺序访问字节代码,这些访问在visitCode与visitMaxs之间

方法解析

abstract class MethodVisitor { // public accessors ommited
&nbsp; &nbsp;// 构造方法
&nbsp; &nbsp;MethodVisitor(int api);
&nbsp; &nbsp;MethodVisitor(int api, MethodVisitor mv);

&nbsp; &nbsp;// 访问方法的注解默认值
&nbsp; &nbsp;AnnotationVisitor visitAnnotationDefault();
&nbsp; &nbsp;// 访问方法的注解
&nbsp; &nbsp;AnnotationVisitor visitAnnotation(String desc, boolean visible);
&nbsp; &nbsp;// 访问方法参数的注解
&nbsp; &nbsp;AnnotationVisitor visitParameterAnnotation(int parameter,
&nbsp; &nbsp;String desc, boolean visible);
&nbsp; &nbsp;// 访问方法的属性
&nbsp; &nbsp;void visitAttribute(Attribute attr);
&nbsp; &nbsp;// 访问方法的字节码指令部分
&nbsp; &nbsp;void visitCode();

&nbsp; &nbsp;// 访问方法的帧(Frame)。type参数表示帧的类型,nLocal参数表示局部变量的数量,local参数表示局部变量数组,nStack参数表示操作数栈的数量,stack参数表示操作数栈数组
&nbsp; &nbsp;void visitFrame(int type, int nLocal, Object[] local, int nStack,
&nbsp; &nbsp;Object[] stack);
&nbsp; &nbsp;// 访问方法的一条指令,指令没有操作数
&nbsp; &nbsp;void visitInsn(int opcode);
&nbsp; &nbsp;// 访问方法的一条指令,指令操作数为单个整数
&nbsp; &nbsp;void visitIntInsn(int opcode, int operand);
&nbsp; &nbsp;// 访问方法的一条指令,指令操作数为局部变量索引
&nbsp; &nbsp;void visitVarInsn(int opcode, int var);
&nbsp; &nbsp;// 访问方法的一条指令,指令操作数为类型描述符
&nbsp; &nbsp;void visitTypeInsn(int opcode, String desc);
&nbsp; &nbsp;// 访问方法的一条指令,指令操作数为字段的信息。opc参数表示指令的操作码,owner参数表示字段所属的类名,name参数表示字段的名称,desc参数表示字段的描述符
&nbsp; &nbsp;void visitFieldInsn(int opc, String owner, String name, String desc);
&nbsp; &nbsp;// 访问方法的一条指令,指令操作数为方法的信息。opc参数表示指令的操作码,owner参数表示方法所属的类名,name参数表示方法的名称,desc参数表示方法的描述符
&nbsp; &nbsp;void visitMethodInsn(int opc, String owner, String name, String desc);
&nbsp; &nbsp;// 访问方法的一条动态方法调用指令。name参数表示方法的名称,desc参数表示方法的描述符,bsm参数表示引导方法(bootstrap method)的句柄,bsmArgs参数表示引导方法的参数。
&nbsp; &nbsp;void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs);
&nbsp; &nbsp;// 访问方法的一条跳转指令
&nbsp; &nbsp;void visitJumpInsn(int opcode, Label label);
&nbsp; &nbsp;// 访问方法的标签(Label),用于标记代码的位置
&nbsp; &nbsp;void visitLabel(Label label);
&nbsp; &nbsp;// 访问方法的一条指令,将常量加载到操作数栈上
&nbsp; &nbsp;void visitLdcInsn(Object cst);
&nbsp; &nbsp;// 访问方法的一条指令,对局部变量进行增量操作
&nbsp; &nbsp;void visitIincInsn(int var, int increment);
&nbsp; &nbsp;// 访问方法的一条表格跳转指令。min参数表示最小的键值,max参数表示最大的键值,dflt参数表示默认跳转目标的标签,labels参数表示每个键值对应的跳转目标的标签数组
&nbsp; &nbsp;void visitTableSwitchInsn(int min, int max, Label dflt, Label[] labels);
&nbsp; &nbsp;// 访问方法的一条查找跳转指令。dflt参数表示默认跳转目标的标签,keys参数表示键值数组,labels参数表示每个键值对应的跳转目标的标签数组
&nbsp; &nbsp;void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels);
&nbsp; &nbsp;// 访问方法的一条多维数组创建指令。desc参数表示数组的元素类型的描述符,dims参数表示数组的维度
&nbsp; &nbsp;void visitMultiANewArrayInsn(String desc, int dims);
&nbsp; &nbsp;// 访问方法的一个try-catch块。start参数表示try块的起始标签,end参数表示try块的结束标签,handler参数表示catch块的处理程序标签,type参数表示捕获的异常类型的描述符
&nbsp; &nbsp;void visitTryCatchBlock(Label start, Label end, Label handler, String type);
&nbsp; &nbsp;// 访问方法的局部变量。name参数表示局部变量的名称,desc参数表示局部变量的描述符,signature参数表示局部变量的泛型签名(如果适用),start参数表示变量的作用域的起始标签,end参数表示变量的作用域的结束标签,index参数表示局部变量的索引
&nbsp; &nbsp;void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index);
&nbsp; &nbsp;// 访问方法的行号信息。line参数表示行号,start参数表示行号对应的代码位置的标签
&nbsp; &nbsp;void visitLineNumber(int line, Label start);
&nbsp; &nbsp;// 访问方法的最大栈大小和最大局部变量数量。maxStack参数表示最大栈大小,maxLocals参数表示最大局部变量数量
&nbsp; &nbsp;void visitMaxs(int maxStack, int maxLocals);
&nbsp; &nbsp;// 访问方法的结束,表示不再访问该方法的任何内容
&nbsp; &nbsp;void visitEnd();
}

来一个例子学习一下

读取字节码

// 从文件系统中加载字节码
byte[] bytecode = Files.readAllBytes(Paths.get("path/to/MyClass.class"));

// 或者从类加载器中加载字节码
InputStream is = getClass().getClassLoader().getResourceAsStream("com/example/MyClass.class");
byte[] bytecode = is.readAllBytes();

// 创建 ClassReader 实例
ClassReader classReader = new ClassReader(bytecode);

解析字节码

要解析字节码,我们需要创建一个自定义的 ClassVisitor 实现。以下是一个简单的示例,用于打印类名和方法名:

package ocean.zbz;

import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

public class AsmStu extends ClassVisitor {

&nbsp; &nbsp; // 使用 ASM5 作为 Opcodes 版本,并调用父类构造函数
&nbsp; &nbsp; public AsmStu() {
&nbsp; &nbsp; &nbsp; &nbsp; super(Opcodes.ASM5);
&nbsp; &nbsp; }

&nbsp; &nbsp; // 重写 visit 方法,用于在访问类时输出类名
&nbsp; &nbsp; public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
&nbsp; &nbsp; &nbsp; &nbsp; // 打印类名
&nbsp; &nbsp; &nbsp; &nbsp; System.out.println("Class: " + name);
&nbsp; &nbsp; &nbsp; &nbsp; // 调用父类 visit 方法,以便继续处理类信息
&nbsp; &nbsp; &nbsp; &nbsp; super.visit(version, access, name, signature, superName, interfaces);
&nbsp; &nbsp; }

&nbsp; &nbsp; // 重写 visitMethod 方法,用于在访问类中的方法时输出方法名

&nbsp; &nbsp; public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
&nbsp; &nbsp; &nbsp; &nbsp; // 打印方法名
&nbsp; &nbsp; &nbsp; &nbsp; System.out.println("Method: " + name);
&nbsp; &nbsp; &nbsp; &nbsp; // 调用父类 visitMethod 方法,以便继续处理方法信息
&nbsp; &nbsp; &nbsp; &nbsp; return super.visitMethod(access, name, descriptor, signature, exceptions);
&nbsp; &nbsp; }
}

visit()方法参数说明:

  1. version:类文件的版本号,表示类文件的 JDK 版本。例如,JDK1.8 对应的版本号为 52(0x34),JDK 11 对应的版本号为 55(0x37)
  2. access:类访问标志,表示类的访问权限和属性。例如,ACC_PUBLIC(0x0001)表示类是公共的,ACC_FINAL(0x0010)表示类是 final 的。可以通过位运算组合多个访问标志
  3. name:类的内部名称,用斜线代替点分隔包名和类名。例如com/example/MyClass
  4. signature:类的泛型签名,如果类没有泛型信息,此参数为 null
  5. superName:父类的内部名称。对于除java.lang.Object之外的所有类,此参数都不为 null
  6. interfaces:类实现的接口的内部名称数组。如果类没有实现任何接口,此参数为空数组

visitMethod()方法参数说明:

  1. access:方法访问标志,表示方法的访问权限和属性。例如,ACC_PUBLIC(0x0001)表示方法是公共的,ACC_STATIC(0x0008)表示方法是静态的。可以通过位运算组合多个访问标志
  2. name:方法的名称。例如,doSomething<init>(构造方法)
  3. descriptor:方法的描述符,表示方法的参数类型和返回值类型。例如,对于方法void doSomething(int),描述符为(I)V
  4. signature:方法的泛型签名,如果方法没有泛型信息,此参数为 null
  5. exceptions:方法抛出的异常的内部名称数组。如果方法没有声明抛出任何异常,此参数为空

实验类

public class Foo {
&nbsp; &nbsp; public void execute() {
&nbsp; &nbsp; &nbsp; &nbsp; System.out.println("test changed method name");
&nbsp; &nbsp; }

&nbsp; &nbsp; public void changeMethodContent() {
&nbsp; &nbsp; &nbsp; &nbsp; System.out.println("test changed method");
&nbsp; &nbsp; }
}
public static void main(String[] args) throws IOException {
&nbsp; &nbsp; String path = "/Users/ocean/Cybersecurity/Java_project/AsmStu/src/main/java/Foo.class";

&nbsp; &nbsp; ClassVisitor asmStu = new AsmStu();
&nbsp; &nbsp; ClassReader classReader = new ClassReader(Files.readAllBytes(Paths.get(path)));
&nbsp; &nbsp; classReader.accept(asmStu,0);

}

img

修改字节码

字段修改

要添加、修改或删除字段,我们需要扩展 ClassVisitor 类并重写 visitField 方法,下面是一个示例,用于在类中添加一个名为newField的字段,并删除名为toBeRemovedField的字段,测试类

public class Foo {
&nbsp; &nbsp; String toBeRemovedField;

&nbsp; &nbsp; public void execute() {
&nbsp; &nbsp; &nbsp; &nbsp; System.out.println("test changed method name");
&nbsp; &nbsp; }

&nbsp; &nbsp; public void changeMethodContent() {
&nbsp; &nbsp; &nbsp; &nbsp; System.out.println("test changed method");
&nbsp; &nbsp; }
}
package ocean.zbz;

import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.Opcodes;

public class UpdaateFieldClassVisitor extends ClassVisitor {
&nbsp; &nbsp; public UpdaateFieldClassVisitor(ClassVisitor classVisitor) {
&nbsp; &nbsp; &nbsp; &nbsp; super(Opcodes.ASM5, classVisitor);
&nbsp; &nbsp; }

&nbsp; &nbsp; public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) {
&nbsp; &nbsp; &nbsp; &nbsp; // 删除名为 toBeRemovedField 的字段
&nbsp; &nbsp; &nbsp; &nbsp; if ("toBeRemovedField".equals(name)) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return null;
&nbsp; &nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp; &nbsp; &nbsp; return super.visitField(access, name, descriptor, signature, value);
&nbsp; &nbsp; }
&nbsp; &nbsp; public void visitEnd() {
&nbsp; &nbsp; &nbsp; &nbsp; // 添加名为 newField 的字段
&nbsp; &nbsp; &nbsp; &nbsp; FieldVisitor newFieldVisitor = super.visitField(Opcodes.ACC_PRIVATE, "newField", "Ljava/lang/String;", null, null);
&nbsp; &nbsp; &nbsp; &nbsp; if (newFieldVisitor != null) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; newFieldVisitor.visitEnd();
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; super.visitEnd();
&nbsp; &nbsp; }
}

visitField()的方法参数说明:

  1. access:字段访问标志,表示字段的访问权限和属性。例如,ACC_PUBLIC(0x0001)表示字段是公共的,ACC_STATIC(0x0008)表示字段是静态的。可以通过位运算组合多个访问标志
  2. name:字段的名称。例如myField
  3. descriptor:字段的描述符,表示字段的类型。例如,对于类型为 int 的字段,描述符为I
  4. signature:字段的泛型签名,如果字段没有泛型信息,此参数为 null
  5. value:字段的常量值,如果字段没有常量值,此参数为 null。需要注意的是,只有静态且已赋值的字段才会有常量值
public static void main(String[] args) throws IOException {
&nbsp; &nbsp; String path = "/Users/ocean/Cybersecurity/Java_project/AsmStu/src/main/java/Foo.class";

&nbsp; &nbsp; ClassVisitor asmStu = new AsmStu();
&nbsp; &nbsp; ClassReader classReader = new ClassReader(Files.readAllBytes(Paths.get(path)));
&nbsp; &nbsp; UpdaateFieldClassVisitor updaateFieldClassVisitor = new UpdaateFieldClassVisitor(asmStu);
&nbsp; &nbsp; classReader.accept(updaateFieldClassVisitor,0);
}

img

方法修改

要添加、修改或删除方法,我们需要扩展 ClassVisitor 类并重写 visitMethod 方法。下面是一个示例,用于在类中添加一个名为newMethod的方法,并删除名为toBeRemovedMethod的方法,测试代码:

public class Foo {
&nbsp; &nbsp; String toBeRemovedField;

&nbsp; &nbsp; public void execute() {
&nbsp; &nbsp; &nbsp; &nbsp; System.out.println("test changed method name");
&nbsp; &nbsp; }

&nbsp; &nbsp; public void changeMethodContent() {
&nbsp; &nbsp; &nbsp; &nbsp; System.out.println("test changed method");
&nbsp; &nbsp; }

&nbsp; &nbsp; public void toBeRemovedMethod() {
&nbsp; &nbsp; &nbsp; &nbsp; System.out.println("toBeRemovedMethod");
&nbsp; &nbsp; }
}

要修改方法内的指令,我们需要扩展 MethodVisitor 类并重写相应的 visit 方法。以下是一个示例

import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

public class MyMethodAdapter extends MethodVisitor {

&nbsp; &nbsp; public MyMethodAdapter(MethodVisitor methodVisitor) {
&nbsp; &nbsp; &nbsp; &nbsp; super(Opcodes.ASM5, methodVisitor);
&nbsp; &nbsp; }

&nbsp; &nbsp; @Override
&nbsp; &nbsp; public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) {
&nbsp; &nbsp; &nbsp; &nbsp; // 在方法调用前添加 System.out.println
&nbsp; &nbsp; &nbsp; &nbsp; mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
&nbsp; &nbsp; &nbsp; &nbsp; mv.visitLdcInsn("Entering method: " + name);
&nbsp; &nbsp; &nbsp; &nbsp; mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);

&nbsp; &nbsp; &nbsp; &nbsp; // 原始方法调用
&nbsp; &nbsp; &nbsp; &nbsp; super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
&nbsp; &nbsp; }
}
public static void main(String[] args) throws Exception {
&nbsp; &nbsp; String path = "/Users/ocean/Cybersecurity/Java_project/AsmStu/src/main/java/Foo.class";

&nbsp; &nbsp; ClassWriter classWriter = new ClassWriter(0);
&nbsp; &nbsp; ClassVisitor updateMethodAdaptorClassVisitor = new UpdateMethodAdaptorClassVisitor(classWriter);
&nbsp; &nbsp; ClassReader classReader = new ClassReader(Files.readAllBytes(Paths.get(path)));
&nbsp; &nbsp; classReader.accept(updateMethodAdaptorClassVisitor,0);

&nbsp; &nbsp; &nbsp; &nbsp; classReader.accept(myMethodClassVisitor, 0);
&nbsp; &nbsp; }

可以发现已经能修改了原来class

img

方法的增加和删除

package ocean.zbz;

import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

public class UpdateMethodClassVisitor extends ClassVisitor {
&nbsp; &nbsp; private String deleteMethodName;
&nbsp; &nbsp; private String deleteMethodDesc;
&nbsp; &nbsp; private int addMethodAcc;
&nbsp; &nbsp; private String addMethodName;
&nbsp; &nbsp; private String addMethodDesc;
&nbsp; &nbsp; private boolean flag = false;

&nbsp; &nbsp; protected UpdateMethodClassVisitor(ClassVisitor cv, String deleteMethodName, String deleteMethodDesc, int addMethodAcc,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;String addMethodName, String addMethodDesc) {
&nbsp; &nbsp; &nbsp; &nbsp; super(Opcodes.ASM5, cv);
&nbsp; &nbsp; &nbsp; &nbsp; this.deleteMethodName = deleteMethodName;
&nbsp; &nbsp; &nbsp; &nbsp; this.deleteMethodDesc = deleteMethodDesc;
&nbsp; &nbsp; &nbsp; &nbsp; this.addMethodAcc = addMethodAcc;
&nbsp; &nbsp; &nbsp; &nbsp; this.addMethodName = addMethodName;
&nbsp; &nbsp; &nbsp; &nbsp; this.addMethodDesc = addMethodDesc;
&nbsp; &nbsp; }

&nbsp; &nbsp; @Override
&nbsp; &nbsp; public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
&nbsp; &nbsp; &nbsp; &nbsp; // 删除名为deleteMethodName且描述为deleteMethodDesc的方法
&nbsp; &nbsp; &nbsp; &nbsp; // 因为有的方法可能name一致,但是参数不同
&nbsp; &nbsp; &nbsp; &nbsp; if (name.equals(deleteMethodName) && descriptor.equals(deleteMethodDesc)) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return null;
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; if (name.equals(addMethodName) && descriptor.equals(addMethodDesc)) flag = true;
&nbsp; &nbsp; &nbsp; &nbsp; return super.visitMethod(access, name, descriptor, signature, exceptions);
&nbsp; &nbsp; }

&nbsp; &nbsp; @Override
&nbsp; &nbsp; public void visitEnd() {
&nbsp; &nbsp; &nbsp; &nbsp; // 添加名为addMethodName且描述为addMethodDesc的方法
&nbsp; &nbsp; &nbsp; &nbsp; if (!flag) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; MethodVisitor methodVisitor = super.visitMethod(addMethodAcc, addMethodName, addMethodDesc, null, null);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (methodVisitor != null) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // 访问方法的字节码
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; methodVisitor.visitCode();
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // 添加return指令
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; methodVisitor.visitInsn(Opcodes.RETURN);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // 设置方法的最大操作数栈深度和最大局部变量表大小,空方法设置00即可
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; methodVisitor.visitMaxs(0, 0);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // 结束访问
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; methodVisitor.visitEnd();
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; super.visitEnd();
&nbsp; &nbsp; }
}
public static void main(String[] args) throws Exception{
&nbsp; &nbsp; // -------------------添加与删除Method-------------------
&nbsp; &nbsp; FileInputStream stream = new FileInputStream("/Users/ocean/Cybersecurity/Java_project/AsmStu/src/main/java/Foo.class");
&nbsp; &nbsp; // 加载字节码
&nbsp; &nbsp; ClassReader reader = new ClassReader(stream);
&nbsp; &nbsp; ClassWriter writer = new ClassWriter(reader, ClassWriter.COMPUTE_FRAMES);
&nbsp; &nbsp; // 实例化修改Method Visitor
&nbsp; &nbsp; UpdateMethodClassVisitor updateMethodClassVisitor = new UpdateMethodClassVisitor(writer, "toBeRemovedMethod", "()V", Opcodes.ACC_PUBLIC, "newMethod", "()V");
&nbsp; &nbsp; reader.accept(updateMethodClassVisitor, ClassReader.EXPAND_FRAMES);
&nbsp; &nbsp; // 写入新class中
&nbsp; &nbsp; FileOutputStream fileOutputStream = new FileOutputStream("temp.class");
&nbsp; &nbsp; byte[] updateByte = writer.toByteArray();
&nbsp; &nbsp; fileOutputStream.write(updateByte);
&nbsp; &nbsp; fileOutputStream.close();

}

可以看到已经修改成功

img


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:土拨鼠的安全屋 摆烂的beizeng《Java安全学习-ASM学习小记》

评论:0   参与:  0