字节码Javassist 使用Bytecode指令码生成含有自定义注 解的类和方法

Posted 九师兄

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了字节码Javassist 使用Bytecode指令码生成含有自定义注 解的类和方法相关的知识,希望对你有一定的参考价值。

1.概述

上一篇文章:【字节码】Javassist 通过字节码插桩监控方法采集运行时入参 出参和异常信息

转载来源于:小傅哥的字节码编程-(公众号:bugstack虫洞栈) 仅供学习。

转载:https://github.com/fuzhengwei/itstack-demo-bytecode

在 Javassist 中不仅提供了高级 API 用于创建和修改类、方法,还提供了低级 API 控制字节码指令的方式进行操作类、方法。

有了这样的 javassist API 在一些特殊场景下就可以使用字节码指令控制方法。

接下来我们通过字节码指令模拟一段含有自定义注解的方法修改和生成。在修改的过程中会将原有方法计算 息费 的返回值替换成 0 ,最后我们使用这样的技术去生成一段计算息费的方法。通过这样的练习学会字节码操作。

2.案例目标

  1. 使用指令码修改原有方法返回值
  2. 使用指令码生成一样的方法

测试方法


import com.agent.introduction.RpcGatewayClazz;
import com.agent.introduction.RpcGatewayMethod;

import java.math.BigDecimal;

@RpcGatewayClazz(clazzDesc = "用户信息查询服务", alias = "api", timeOut = 500)
public class AnnotationDemo 

    @RpcGatewayMethod(methodDesc = "查询息费", methodName = "interestFee")
    public double queryInterestFee(String uId)
        return BigDecimal.TEN.doubleValue();  // 模拟息费计算返回
    



这里使用的注解是测试中自定义的,模拟一个相当于网关接口的暴漏。

3.技术实现

注解类如下

package com.agent.introduction;

public @interface RpcGatewayClazz 

    String clazzDesc() default "";
    String alias() default "";
    long timeOut() default 350;



package com.agent.introduction;

public @interface RpcGatewayMethod 

    String methodName() default "";
    String methodDesc() default "";
    


主体类如下

 /**
     * @throws Exception
     */
    public void strToInt() throws Exception 

        ClassPool pool = ClassPool.getDefault();
        // 类、注解
        CtClass ctClass = pool.get(AnnotationDemo.class.getName());
        // 通过集合获取自定义注解
        Object[] clazzAnnotations = ctClass.getAnnotations();
        RpcGatewayClazz rpcGatewayClazz = (RpcGatewayClazz) clazzAnnotations[0];
        System.out.println("RpcGatewayClazz.clazzDesc:" + rpcGatewayClazz.clazzDesc());
        System.out.println("RpcGatewayClazz.alias:" + rpcGatewayClazz.alias());
        System.out.println("RpcGatewayClazz.timeOut:" + rpcGatewayClazz.timeOut());

        // 方法、注解
        CtMethod ctMethod = ctClass.getDeclaredMethod("queryInterestFee");
        RpcGatewayMethod rpcGatewayMethod = (RpcGatewayMethod) ctMethod.getAnnotation(RpcGatewayMethod.class);
        System.out.println("RpcGatewayMethod.methodName:" + rpcGatewayMethod.methodName());
        System.out.println("RpcGatewayMethod.methodDesc:" + rpcGatewayMethod.methodDesc());

        // 获取指令码
        MethodInfo methodInfo = ctMethod.getMethodInfo();
        CodeAttribute codeAttribute = methodInfo.getCodeAttribute();
        CodeIterator iterator = codeAttribute.iterator();
        while (iterator.hasNext()) 
            int idx = iterator.next();
            int code = iterator.byteAt(idx);
            System.out.println("指令码:" + idx + " > " + Mnemonic.OPCODE[code]);
        

        // 通过指令码改写方法
        ConstPool cp = methodInfo.getConstPool();
        Bytecode bytecode = new Bytecode(cp);
        bytecode.addDconst(0);
        bytecode.addReturn(CtClass.doubleType);
        methodInfo.setCodeAttribute(bytecode.toCodeAttribute());

        String filePathNotTest = FileUtils.getFilePathNotTest("");
        System.out.println("输出目录:" + filePathNotTest);
        // 输出类的内容
        ctClass.writeFile(filePathNotTest);

    

运行结果如下

RpcGatewayClazz.clazzDesc:用户信息查询服务
RpcGatewayClazz.alias:api
RpcGatewayClazz.timeOut:500
RpcGatewayMethod.methodName:interestFee
RpcGatewayMethod.methodDesc:查询息费
指令码:0 > getstatic
指令码:3 > invokevirtual
指令码:6 > dreturn
输出目录:/Users/lcc/IdeaProjects/lcc_work/test-javaagent/javaagent-api/target/classes/

生成的类如下


package com.agent.entity;

import com.agent.introduction.RpcGatewayClazz;
import com.agent.introduction.RpcGatewayMethod;

@RpcGatewayClazz(
    clazzDesc = "用户信息查询服务",
    alias = "api",
    timeOut = 500L
)
public class AnnotationDemo 
    public AnnotationDemo() 
    

    @RpcGatewayMethod(
        methodDesc = "查询息费",
        methodName = "interestFee"
    )
    public double queryInterestFee(String var1) 
        return 0.0;
    


4.分析

4.1 读取类自定义注解

  		ClassPool pool = ClassPool.getDefault();
        // 类、注解
        CtClass ctClass = pool.get(AnnotationDemo.class.getName());
        // 通过集合获取自定义注解
        Object[] clazzAnnotations = ctClass.getAnnotations();
        RpcGatewayClazz rpcGatewayClazz = (RpcGatewayClazz) clazzAnnotations[0];
        System.out.println("RpcGatewayClazz.clazzDesc:" + rpcGatewayClazz.clazzDesc());
        System.out.println("RpcGatewayClazz.alias:" + rpcGatewayClazz.alias());
        System.out.println("RpcGatewayClazz.timeOut:" + rpcGatewayClazz.timeOut());

ctClass.getAnnotations() ,可以获取所有的注解,进行操作

输出结果:

RpcGatewayClazz.clazzDesc:用户信息查询服务
RpcGatewayClazz.alias:api
RpcGatewayClazz.timeOut:500

4.2 读取方法的自定义注解

 // 方法、注解
        CtMethod ctMethod = ctClass.getDeclaredMethod("queryInterestFee");
        RpcGatewayMethod rpcGatewayMethod = (RpcGatewayMethod) ctMethod.getAnnotation(RpcGatewayMethod.class);
        System.out.println("RpcGatewayMethod.methodName:" + rpcGatewayMethod.methodName());
        System.out.println("RpcGatewayMethod.methodDesc:" + rpcGatewayMethod.methodDesc());

在读取方法自定义注解时,通过的是注解的 class 获取的,这样按照名称可以只获取最需要的注解名称。

输出结果:

RpcGatewayMethod.methodName:interestFee
RpcGatewayMethod.methodDesc:查询息费

4.3 读取方法指令码

// 获取指令码
        MethodInfo methodInfo = ctMethod.getMethodInfo();
        CodeAttribute codeAttribute = methodInfo.getCodeAttribute();
        CodeIterator iterator = codeAttribute.iterator();
        while (iterator.hasNext()) 
            int idx = iterator.next();
            int code = iterator.byteAt(idx);
            System.out.println("指令码:" + idx + " > " + Mnemonic.OPCODE[code]);
        

这里的指令码就是一个方法编译后在 JVM 执行的操作流程。
输出结果

指令码:0 > getstatic
指令码:3 > invokevirtual
指令码:6 > dreturn

4.4 通过指令修改方法

// 通过指令码改写方法
ConstPool cp = methodInfo.getConstPool();
Bytecode bytecode = new Bytecode(cp);
bytecode.addDconst(0);
bytecode.addReturn(CtClass.doubleType);
methodInfo.setCodeAttribute(bytecode.toCodeAttribute());

addDconst ,将 double 型0推送至栈顶
addReturn ,返回 double 类型的结果

此时的方法的返回值已经被修改,下面的是新的 class 类;


package com.agent.entity;

import com.agent.introduction.RpcGatewayClazz;
import com.agent.introduction.RpcGatewayMethod;

@RpcGatewayClazz(
    clazzDesc = "用户信息查询服务",
    alias = "api",
    timeOut = 500L
)
public class AnnotationDemo 
    public AnnotationDemo() 
    

    @RpcGatewayMethod(
        methodDesc = "查询息费",
        methodName = "interestFee"
    )
    public double queryInterestFee(String var1) 
        return 0.0;
    


可以看到查询息费的返回结果已经是 0.0D 。如果你的程序被这样操作,那么还是很危险的。所以
有时候会进行一些混淆编译,降低破解风险。

5.使用指令码生成方法

主体方法


    public void strToInt1() throws Exception 

        ClassPool pool = ClassPool.getDefault();

        // 创建类信息
        CtClass ctClass = pool.makeClass("com.agent.entity.AnnotationDemoV2");
        // 添加方法
        CtMethod mainMethod = new CtMethod(CtClass.doubleType, "queryInterestFee",
                new CtClass[]pool.get(String.class.getName()), ctClass);
        mainMethod.setModifiers(Modifier.PUBLIC);
        MethodInfo methodInfo = mainMethod.getMethodInfo();
        ConstPool cp = methodInfo.getConstPool();

        // 类添加注解
        AnnotationsAttribute clazzAnnotationsAttribute = new AnnotationsAttribute(cp,
                AnnotationsAttribute.visibleTag);
        Annotation clazzAnnotation = new
                Annotation("com/agent/introduction/RpcGatewayClazz", cp);
        clazzAnnotation.addMemberValue("clazzDesc", new StringMemberValue("用户信息查询服务", cp));
        clazzAnnotation.addMemberValue("alias", new StringMemberValue("api", cp));
        clazzAnnotation.addMemberValue("timeOut", new LongMemberValue(500L, cp));
        clazzAnnotationsAttribute.setAnnotation(clazzAnnotation);
        ctClass.getClassFile().addAttribute(clazzAnnotationsAttribute);


        // 方法添加注解
        AnnotationsAttribute methodAnnotationsAttribute = new
                AnnotationsAttribute(cp, AnnotationsAttribute.visibleTag);
        Annotation methodAnnotation = new
                Annotation("com/agent/introduction/RpcGatewayMethod", cp);
        methodAnnotation.addMemberValue("methodName", new StringMemberValue("查询息费", cp));
        methodAnnotation.addMemberValue("methodDesc", new
                StringMemberValue("interestFee", cp));
        methodAnnotationsAttribute.setAnnotation(methodAnnotation);
        methodInfo.addAttribute(methodAnnotationsAttribute);

        // 指令控制
        Bytecode bytecode = new Bytecode(cp);
        bytecode.addGetstatic("java/math/BigDecimal", "TEN",
                "Ljava/math/BigDecimal;");
        bytecode.addInvokevirtual("java/math/BigDecimal", "doubleValue", "()D");
        bytecode.addReturn(CtClass.doubleType);
        methodInfo.setCodeAttribute(bytecode.toCodeAttribute());

        // 添加方法
        ctClass.addMethod(mainMethod);


        String filePathNotTest = FileUtils.getFilePathNotTest("");
        System.out.println("输出目录:" + filePathNotTest);
        // 输出类的内容
        ctClass.writeFile(filePathNotTest);

    

运行后会生成如下类


package com.agent.entity;

import com.agent.introduction.RpcGatewayClazz;
import com.agent.introduction.RpcGatewayMethod;
import java.math.BigDecimal;

@RpcGatewayClazz(
    clazzDesc = "用户信息查询服务",
    alias = "api",
    timeOut = 500L
)
public class AnnotationDemoV2 
    @RpcGatewayMethod(
        methodName = "查询息费",
        methodDesc = "interestFee"
    )
    public double queryInterestFee(String var1) 
        return BigDecimal.TEN.doubleValue();
    

    public AnnotationDemoV2() 
    


5.1 分析

5.1.1 创建基础方法信息

  // 创建类信息
        CtClass ctClass = pool.makeClass("com.agent.entity.AnnotationDemoV2");
        // 添加方法
        CtMethod mainMethod = new CtMethod(CtClass.doubleType, "queryInterestFee",
                new CtClass[]pool.get(String.class.getName()), ctClass);
        mainMethod.setModifiers(Modifier.PUBLIC);
        MethodInfo methodInfo = mainMethod.getMethodInfo();
        ConstPool cp = methodInfo.getConstPool();

创建类和方法的信息在我们几个章节中也经常使用,主要是创建方法的时候需要传递;返回类型、方法名称、入参类型,以及最终标记方法的可访问量。

5.1.2 创建类使用注解

 // 类添加注解
        AnnotationsAttribute clazzAnnotationsAttribute = new AnnotationsAttribute(cp,
                AnnotationsAttribute.visibleTag);
        Annotation clazzAnnotation = new
                Annotation("com/agent/introduction/RpcGatewayClazz", cp);
        clazzAnnotation.addMemberValue("clazzDesc", new StringMemberValue("用户信息查询服务", cp));
        clazzAnnotation.addMemberValue("alias", new StringMemberValue("api", cp));
        clazzAnnotation.addMemberValue("timeOut", new LongMemberValue(500L, cp));
        clazzAnnotationsAttribute.setAnnotation(clazzAnnotation);
        ctClass.getClassFile().addAttribute(clazzAnnotationsAttribute);

  1. AnnotationsAttribute ,创建自定义注解标签

  2. Annotation ,创建实际需要的自定义注解,这里需要传递自定义注解的类路径

  3. addMemberValue ,用于添加自定义注解中的值。需要注意不同类型的值 XxxMemberValue 前缀不一样;StringMemberValue、LongMemberValue

  4. setAnnotation ,最终设置自定义注解。如果不设置,是不能生效的。

5.1.3 创建方法注解

		 // 方法添加注解
        AnnotationsAttribute methodAnnotationsAttribute = new
                AnnotationsAttribute(cp, AnnotationsAttribute.visibleTag);
        Annotation methodAnnotation = new
                Annotation("com/agent/introduction/RpcGatewayMethod", cp);
        methodAnnotation.addMemberValue("methodName"Javassist | 字节码增强技术

10-java安全基础——javassist字节码编程

10-java安全基础——javassist字节码编程

10-java安全基础——javassist字节码编程

字节码基于Byte Buddy语法创建的第一个 HelloWorld

Java动态字节技术之Javassist