首页 > 其他分享 >【Web】随便聊聊应用ASM CoreAPI修改字节码那些事

【Web】随便聊聊应用ASM CoreAPI修改字节码那些事

时间:2024-03-27 22:58:36浏览次数:28  
标签:CoreAPI Web 字节 ClassVisitor u2 asm public ASM String

目录

前言

ASM概念

Java字节码&ClassFile

核心理念:拆分修改重组

修改字节码最简化模型

代码示例

ASM修改类的基本信息

ASM修改类的字段

ASM修改类的方法

常规实现

AdviceAdapter实现


前言

本文速通下ASM最最萌新直观的部分,理解浅薄,纯小白文

pom依赖

<dependencies>
    <dependency>
        <groupId>org.ow2.asm</groupId>
        <artifactId>asm</artifactId>
        <version>9.2</version>
    </dependency>
    <dependency>
        <groupId>org.ow2.asm</groupId>
        <artifactId>asm-util</artifactId>
        <version>9.2</version>
    </dependency>
    <dependency>
        <groupId>org.ow2.asm</groupId>
        <artifactId>asm-commons</artifactId>
        <version>9.2</version>
    </dependency>
</dependencies>

ASM概念

ASM(Objectweb ASM)是一个轻量级的 Java 字节码操作框架,可以用于动态生成类、修改现有类的字节码以及分析类文件。ASM 提供了一种基于事件驱动的 API,使得开发者可以方便地操作和修改 Java 类的字节码。

Java字节码&ClassFile

Java 字节码是一种由 JVM 执行的二进制指令集,它是 Java 源代码编译后生成的中间形式。Java 字节码包含了 JVM 能够识别和执行的指令,通过这些指令可以实现对 Java 类的各种操作和逻辑。

ClassFile 结构则是描述 Java 类文件格式的一种规范,它定义了一个类文件的结构和包含的信息。在 ClassFile 结构中,包含了类的版本号、常量池、访问标志、字段表、方法表等内容,这些信息都是以特定的格式和顺序存储在类文件中的。

Java 字节码实际上就是存储在 ClassFile 结构中的具体指令和数据,它们是 JVM 执行的基本单位。当 JVM 加载一个类文件时,会解析其中的 ClassFile 结构,然后将其中的字节码指令加载到内存中,并按照指令顺序执行,从而实现对 Java 类的功能。

ClassFile结构

ClassFile {
    u4             magic;
    u2             minor_version;
    u2             major_version;
    u2             constant_pool_count;
    cp_info        constant_pool[constant_pool_count-1];
    u2             access_flags;
    u2             this_class;
    u2             super_class;
    u2             interfaces_count;
    u2             interfaces[interfaces_count];
    u2             fields_count;
    field_info     fields[fields_count];
    u2             methods_count;
    method_info    methods[methods_count];
    u2             attributes_count;
    attribute_info attributes[attributes_count];
}
field_info {
    u2             access_flags;
    u2             name_index;
    u2             descriptor_index;
    u2             attributes_count;
    attribute_info attributes[attributes_count];
}
method_info {
    u2             access_flags;
    u2             name_index;
    u2             descriptor_index;
    u2             attributes_count;
    attribute_info attributes[attributes_count];
}
// method的方法体由Code属性表示
Code_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u2 max_stack; 
    u2 max_locals; 
    u4 code_length;
    u1 code[code_length];
    u2 exception_table_length;
    {   u2 start_pc;
        u2 end_pc;
        u2 handler_pc;
        u2 catch_type;
    } exception_table[exception_table_length];
    u2 attributes_count;
    attribute_info attributes[attributes_count];
}
// u? 表示占用?个字节

核心理念:拆分修改重组

CoreAPI最重要的三个类:

  • ClassReader: 读取字节码文件,并拆分为不同的部分(decompose)

  • ClassVisitor: 对字节码中某一部分进行修改(modify)FieldVisitor、MethodVisitor

  • ClassWriter: 将各个部分重组为完整的字节码文件(recompose)FieldWriter、MethodWriter

ClassReader用于读取字节码文件,ClassWriter用于生成字节码文件,ClassVisitor用于修改字节码

ClassWriter 本身就是一个 ClassVisitor 的实现类,它可以接受对类的访问和修改操作,并将修改后的类文件输出为字节数组。


当需要生成新的类文件时,通常会创建一个继承自 ClassVisitor 的自定义 Visitor 类,通过重写其中的方法来实现对类文件的定制化操作,然后将这个 Visitor 类传递给 ClassWriter,利用 ClassWriter 输出最终的类字节码。

需要修改类文件时,要以ClassReader为旧字节码的入口,ClassWriter为新字节码的出口,中间可以通过多个ClassVisitor来修改字节码。

修改字节码最简化模型

将下面的cv替换为我们自定义的ClassVisitor子类即可

ClassReader cr = new ClassReader("classfile");
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
ClassVisitor cv = new ClassVisitor(Opcodes.ASM9, cw) {
    // TODO
};
cr.accept(cv, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
cw.toByteArray();

代码示例

ASM修改类的基本信息

假设要将一个类的访问修饰符修改为 public,或添加一个实现的接口 "Comparable",下面是一个示例代码:

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

public class CustomClassVisitor extends ClassVisitor {

    public CustomClassVisitor(ClassVisitor cv) {
        super(Opcodes.ASM9, cv);
    }

    @Override
    public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
        // 修改类的访问修饰符为 public
        super.visit(version, access | Opcodes.ACC_PUBLIC, name, signature, superName, interfaces);
        
        // 添加一个实现的接口 "Comparable"
        String[] newInterfaces = new String[interfaces.length + 1];
        System.arraycopy(interfaces, 0, newInterfaces, 0, interfaces.length);
        newInterfaces[interfaces.length] = "java/lang/Comparable";
        super.visit(version, access, name, signature, superName, newInterfaces);
    }
}

public class ClassModifier {
    public static byte[] modifyClass(byte[] originalClass) {
        ClassReader cr = new ClassReader(originalClass);
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
        CustomClassVisitor cv = new CustomClassVisitor(cw);
        cr.accept(cv, 0);
        return cw.toByteArray();
    }
}

ASM修改类的字段

当使用 ASM 修改类的字段时,可以通过自定义 ClassVisitor 的子类来实现。

假设要向一个类中添加一个名为 "newField" 的字段,下面是一个示例代码:

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

public class CustomClassVisitor extends ClassVisitor {

    public CustomClassVisitor(ClassVisitor cv) {
        super(Opcodes.ASM9, cv);
    }

    @Override
    public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) {
        // 添加一个名为 "newField" 的字段
        if ("newField".equals(name)) {
            return null; // 跳过已存在的字段
        }
        return super.visitField(access, name, descriptor, signature, value);
    }

    @Override
    public void visitEnd() {
        // 在 visitEnd 方法中添加一个新字段 "newField"
        FieldVisitor fv = super.visitField(Opcodes.ACC_PRIVATE, "newField", "Ljava/lang/String;", null, null);
        if (fv != null) {
            fv.visitEnd();
        }
        super.visitEnd();
    }
}

public class ClassModifier {
    public static byte[] modifyClass(byte[] originalClass) {
        ClassReader cr = new ClassReader(originalClass);
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
        CustomClassVisitor cv = new CustomClassVisitor(cw);
        cr.accept(cv, 0);
        return cw.toByteArray();
    }
}

ASM修改类的方法

当使用 ASM 修改类的方法时,同样可以通过自定义 ClassVisitor 的子类来实现。

假设我们要向一个类中添加一个方法进入和退出时打印日志的方法,下面是一个示例代码:

常规实现

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

public class MethodInOutVisitor extends ClassVisitor {
    private final String methodName;
    private final String methodDesc;

    public MethodInOutVisitor(int api, ClassVisitor classVisitor, String methodName, String methodDesc) {
        super(api, classVisitor);
        this.methodName = methodName;
        this.methodDesc = methodDesc;
    }

    @Override
    public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
        MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
        if (name.equals(methodName) && descriptor.equals(methodDesc)) {
            mv = new MethodExitAdapter(api, mv);
        }
        return mv;
    }

    private static class MethodExitAdapter extends MethodVisitor {
        public MethodExitAdapter(int api, MethodVisitor methodVisitor) {
            super(api, methodVisitor);
        }

        @Override
        public void visitCode() {
            super.visitCode();
            super.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
            super.visitLdcInsn("Method Enter...");
            super.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
        }

        @Override
        public void visitInsn(int opcode) {
            if (opcode == Opcodes.ATHROW || (opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN)) {
                super.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
                super.visitLdcInsn("Method Exit...");
                super.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
            }
            super.visitInsn(opcode);
        }
    }
}

AdviceAdapter实现

AdviceAdapter 是 ASM 框架中的一个类,用于在方法的进入和退出时插入额外的字节码指令。它是 MethodVisitor 的子类,提供了便捷的方法来处理方法的进入和退出逻辑。

通过继承 AdviceAdapter 类,可以重写其中的 onMethodEnteronMethodExit 方法,在这两个方法中可以添加需要在方法进入和退出时执行的逻辑。例如,在 onMethodEnter 方法中可以插入代码来记录方法的进入,并在 onMethodExit 方法中可以插入代码来记录方法的退出或处理方法的返回值等操作。

AdviceAdapter 简化了在方法级别进行字节码操作的流程,使开发者能够更方便地在方法进入和退出时插入自定义的逻辑,实现一些在运行时对方法进行监控、调试或增强的需求。

import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.commons.AdviceAdapter;

public class MethodInOutVisitor extends ClassVisitor {
    private final String methodName;
    private final String methodDesc;

    public MethodInOutVisitor(int api, ClassVisitor classVisitor, String methodName, String methodDesc) {
        super(api, classVisitor);
        this.methodName = methodName;
        this.methodDesc = methodDesc;
    }

    @Override
    public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
        MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
        if (name.equals(methodName) && descriptor.equals(methodDesc)) {
            mv = new AdviceAdapter(api, mv, access, name, descriptor) {
                @Override
                protected void onMethodEnter() {
                    getStatic(System.class, "out", "Ljava/io/PrintStream;");
                    visitLdcInsn("Method Enter...");
                    invokeVirtual(Type.getType("Ljava/io/PrintStream;"), getMethod("println", "(Ljava/lang/String;)V"));
                }

                @Override
                protected void onMethodExit(int opcode) {
                    if (opcode == ATHROW || (opcode >= IRETURN && opcode <= RETURN)) {
                        getStatic(System.class, "out", "Ljava/io/PrintStream;");
                        visitLdcInsn("Method Exit...");
                        invokeVirtual(Type.getType("Ljava/io/PrintStream;"), getMethod("println", "(Ljava/lang/String;)V"));
                    }
                }
            };
        }
        return mv;
    }
}

标签:CoreAPI,Web,字节,ClassVisitor,u2,asm,public,ASM,String
From: https://blog.csdn.net/uuzeray/article/details/137089940

相关文章

  • 股票行情数据API | 实时行情数据接口websocket接入方法
    今天给大家带来一个技术干货分享,如何通过接口API订阅并接入实时行情数据源报价,它的方法与步骤一、API地址及传参说明支持以下产品品类:美股、港股、A股、外汇、贵金属、商品、数字币github:https://github.com/alltick/realtime-forex-crypto-stock-tick-finance-websocket-a......
  • AI-Web-1.0靶场
    准备阶段下载地址Download:https://drive.google.com/open?id=140bO4W6v7fd_dWhCmcFHTf86zwL9s_xPDownload(Mirror):https://download.vulnhub.com/aiweb/AI-Web-1.0.7z网络设置将网络设置为桥接模式信息收集端口扫描nmap网络使用了NAT模式,先查看本机的VMware8(默......
  • 零基础Web3入门到精通
    Web3是互联网的下一代,它将使人们能够拥有自己的数据并控制自己的在线体验。Web3基于区块链技术,该技术为安全、透明和可信的交易提供支持。现在的web已被成为传统互联网了,那么如何抓住下一代互联网的红利呢,有没有想一起学习Web3的同学,大家可以一起探讨学习,请加QQ群:782128964......
  • JavaWeb学习笔记——第六天
    MySQL(一)数据库概述数据库即DataBase(DB),是存储和管理数据的仓库。数据库管理系统即DataBaseManagementSystem(DBMS),是操纵和管理数据库的大型软件。SQL即StructuredQueryLanguage,是操作关系型数据库的编程语言,定义了一套操作关系型数据库统一标准。常见的数据库产品......
  • .net6 core web项目发布部署到IIS,以Windows服务的形式部署启动,报错1053,报1067错误解
    安装NuGet包Microsoft.Extensions.Hosting.WindowsServices  varbuilder=WebApplication.CreateBuilder(newWebApplicationOptions{ContentRootPath=AppContext.BaseDirectory,Args=args});//Addservicestothecontainer.builder.Services.Add......
  • SpringBootWeb最新相关技术(上接maven):IDEA2023-Spring环境,http协议复习概览,web服务器To
    Spring官网HTTPs://spring.iospring生态(全家桶)基于SpringFramework基础框架。但如果我们基于该基础框架开发,会面临配置繁琐,入门难度大的问题,SpringBoot则可以快速开发(简化配置,快速开发)。1.SpringBootWeb入门使用SpringBoot开发一个Web应用,浏览器发起请求/hello之后,给浏......
  • webpack 入门笔记1
    webpack是一个综合性平台1为npm环境-packjson->依赖->依赖的编译器环境bale-->esj->程序.构建一个综合平台。2开发目录到生产目录;3打包优化将上百个依赖整合为若干chunk.提升下载速度.综合总线打通步骤1(node环境已下载)建立npm环境-与本地的链接npminit指令......
  • 如何用Flask中的Blueprints构建大型Web应用
    本文分享自华为云社区《构建大型Web应用Flask中的Blueprints指南》,作者:柠檬味拥抱。什么是Blueprints?Blueprints是Flask中的一种模式,用于将应用程序分解为可重用的模块。每个蓝图实际上是一个包含一组路由、视图和静态文件的Python模块。通过使用蓝图,我们可以将相关功能的代码......
  • 一款超酷、功能强大的一体化网站测试工具:Web-Check
    今天给大家一款网站一体化测试工具:Web-Check!Web-Check是一款功能强大的一体化工具,用于发现网站/主机的相关信息。用于检查网页的工具,用于确保网页的正确性和可访问性。它可以帮助开发人员和网站管理员检测网页中的错误和问题,并提供修复建议。它只需要输入一个网站就可以查看一......
  • GitHub WebHook 使用教程
    本文收录于Github.com/niumoo/JavaNotes,Java系列文档,数据结构与算法!本文收录于网站:https://www.wdbyte.com/,我的公众号:程序猿阿朗什么是WebHookWebHook直译是网络钩子,可以把WebHook看做一种通知方式,只要发生关注的事件,就会发送通知到我们指定的Web服务。使用WebHoo......