0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

javaassit如何实现代对目标类的代理

科技绿洲 来源:了不起 作者:了不起 2023-09-25 11:18 次阅读

有没有想过,XMind是如何被破解的?那么今天我们就来看看javaassit这项技术,其实在你接触的很多其他工具中这个工具早就被广泛使用了

javaassit

我们知道,java是一门面向对象的编程语言,更是一门面向切面的编程语言,正是这个特性,让Java更加地灵活。

可能你写过基于Spring AOP的代码,其原理都是基于JDK动态代理或者CGLIB来实现,其局限性在于我们只能以方法作为连接点,来实现基于方法执行过程的代理。

你可还知道更厉害的代理工具:AspectJ、javaassit,这些都是基于字节码,属于更底层,但是功能更强大的代理。

知识点

  • ASM

通过指令修改class字节码,主要基于ClassReader结合JVM指令集直接操作字节码,Cglib即是通过该技术实现。

  • JavaAssit

基于org.javassist:javassist类库提供的CtPool工具类对字节码进行修改

  • Instrumentation

JVM提供的一个可以修改已加载类的类库,通过编写java代码即可完成对字节码的修改

  • JavaAgent

JVM加载类之前与JVM运行时,基于JavaAssit、Instrumentation实现字节码修改并加载到JVM

应用场景

  • IDE的调试功能,例如 Eclipse、IntelliJ IDEA
  • 热部署功能,例如 JRebel、XRebel、spring-loaded
  • 线上诊断工具,例如 Btrace、Greys,还有阿里的 Arthas
  • 性能分析工具,例如 Visual VM、JConsole、TProfiler等
  • 全链路性能检测工具,例如 Skywalking、Pinpoint等

示例

下面我们基于javaagent以及运行时Attach的模式看下javaassit如何实现目标类的代理的:

基于javaagent

  1. 编写代理类

方法签名固定,方法名为 premain参数分别对应args(不是数组)以及Instrumentation

public class JavaAgent {

    private static final String TARGET_CLASS_NAME = "com.sucl.blog.javaassit.Target";

    public static void premain(String args, Instrumentation instrumentation){
        AgentHelper.create(TARGET_CLASS_NAME).proxy(args, instrumentation);
    }

}
  1. 打包代理类

这里我们借助maven插件 maven-shade-plugin ,主要是为了打包时修改/META-INF/MANIFEST.MF文件,需要加上Premain-Class这项

< plugin >
    < groupId >org.apache.maven.plugins< /groupId >
    < artifactId >maven-shade-plugin< /artifactId >
    < version >2.3< /version >
    < executions >
        < execution >
            < phase >package< /phase >
            < goals >
                < goal >shade< /goal >
            < /goals >
            < configuration >
                < transformers >
                    < transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer" >
                        < manifestEntries >
                            < Premain-Class >com.sucl.blog.agent.JavaAgent< /Premain-Class >
                            < Agent-Class >com.sucl.blog.agent.AttachAgent< /Agent-Class >
                            < Can-Redefine-Classes >true< /Can-Redefine-Classes >
                            < Can-Retransform-Classes >true< /Can-Retransform-Classes >
                        < /manifestEntries >
                    < /transformer >
                < /transformers >
            < /configuration >
        < /execution >
    < /executions >
< /plugin >
  1. 编写测试类

目的很简单,每隔3秒打印当前时间

public class JavaAgentMain {
    public static void main(String[] args) throws InterruptedException {
        Target target = new Target();
        while (true) {
            target.print(new Date());
            TimeUnit.SECONDS.sleep(3);
        }
    }
}

@Slf4j
class Target {
    public void print(Object obj) {
        log.info("打印内容:{}", obj);
    }
}
  1. 配置代理

如何让我们编写的代理生效,这里提供两种方法:

  • 当你使用IDEA启动时,可以在Config Configurations中通过配置VM OPTION,添加如下内容:

-javaagent:/your_jar_path/agent.jar=param=value

  • 当你使用java命令启动时:

java -javaagent:/path/agent.jar=param=value -jar xxx.jar

  1. 测试

执行测试类main方法,你可以看到,在打印时间前后,分别会打印“开始执行方法:print”,“结束执行方法:print”,这也是我们代理类实现的功能。

>> > 开始执行方法:print
14:46:09.457 [main] INFO com.sucl.blog.javaassit.Target - 打印内容:Fri Mar 10 14:46:09 CST 2023
 >> > 结束执行方法:print

基于Attach

  1. 编写代理类

方法签名固定,方法名为 attachmain ,参数分别对应args(不是数组)以及Instrumentation; 和上面的相比唯一的不同是方法名称。

public class AttachAgent {
    
    private static final String TARGET_CLASS_NAME = "com.sucl.blog.javaassit.Target";
    
    public static void agentmain(String args, Instrumentation instrumentation){
        System.out.println(String.format(" >> > agentmain starting, args: %s",args));
        AgentHelper.create(TARGET_CLASS_NAME).proxy(args, instrumentation);
        System.out.println(String.format(" >> > agentmain finished"));
    }

}
  1. 打包代理类

同样借助插件 maven-shade-plugin ,主要是为了打包时修改/META-INF/MANIFEST.MF文件,需要加上Agent-Class这项

< !-- 省略 ...-- >
< Agent-Class >com.sucl.blog.agent.AttachAgent< /Agent-Class >
< !-- 省略 ...-- >

注意,这里我们使用了ClassPool、CtClass、CtMethod相关的类,记得在pom.xml中引入对应的依赖

< dependency >
    < groupId >org.javassist< /groupId >
    < artifactId >javassist< /artifactId >
< /dependency >
  1. 编写测试类

测试类完全一样,由于启动代理织入的方式不一样,因此分为两个类

public class AttachAgentMain {

    public static void main(String[] args) throws InterruptedException {
        Target target = new Target();
        while (true) {
            target.print(new Date());
            TimeUnit.SECONDS.sleep(3);
        }
    }

}
  1. 执行代理

如何将编写的代码(AttachAgent)织入到目标类完成对目标类(Target)方法的代理?

这里我们需要用到jdk中的tool.jar,你可以在测试模块中添加下面的依赖:

< dependency >
    < groupId >com.sun< /groupId >
    < artifactId >tools< /artifactId >
    < version >1.8< /version >
    < scope >system< /scope >
    < systemPath >${java.home}/../lib/tools.jar< /systemPath >
< /dependency >

如何在运行时进行代理织入:

public class AttachAgentTests {

    private static String JAR_PATH = AttachAgentTests.class.getClassLoader().getResource("").getPath().replace("test-classes/","")+"agent.jar";
    
    @Test
    public void attachAgent() throws Exception {
        String pid = findPid(KEY); // 通过jps命令找到AttachAgentMain执行的pid
        VirtualMachine virtualMachine = VirtualMachine.attach(pid);
        virtualMachine.loadAgent(JAR_PATH.substring(1));
        virtualMachine.detach();
    }
}
  1. 测试
  • a. 先执行测试代码(AttachAgentMain.java),此时每间隔3秒会打印当前时间。
  • b. 执行代理织入方法(AttachAgentTests#attachAgent)
  • c. 观察测试代码输出结果,你会会发现此时每次打印时间前后都会有“开始执行方法:print”,“结束执行方法:print”

AgentHelper

public class AgentHelper {

    private String targetClassName;

    private AgentHelper(String targetClassName) {
        this.targetClassName = targetClassName;
    }

    public static AgentHelper create(String targetClassName){
        AgentHelper agentHelper = new AgentHelper(targetClassName);
        return agentHelper;
    }

    public void proxy(String args, Instrumentation instrumentation){
        Class targetClass = obtainTargetClass(instrumentation);

        try {
            instrumentation.addTransformer(new SimpleTransformer(targetClassName), true);
            instrumentation.retransformClasses(targetClass); //
        } catch (Exception e) {
            System.out.println(String.format(" >> > agentmain failure, error: %s: %s", e.getClass().getName(),e.getLocalizedMessage()));
            e.printStackTrace();
        }
    }

    private Class obtainTargetClass(Instrumentation instrumentation) {
        Class targetClass = null;
        for (Class loadedClass : instrumentation.getAllLoadedClasses()) {
            if(targetClassName.equals(loadedClass.getName())){
                targetClass = loadedClass;
            }
        }

        if(targetClass == null){
            try {
                // 无法加载
                targetClass = Class.forName(targetClassName);
            } catch (ClassNotFoundException e) {
                System.out.println(String.format(" >> > Class [%s] not found", targetClassName));
            }
        }
        return targetClass;
    }

    public static class SimpleTransformer implements ClassFileTransformer {

        private String targetClassName;

        public SimpleTransformer(String targetClassName) {
            this.targetClassName = targetClassName;
        }

        @Override
        public byte[] transform(ClassLoader loader, String className, Class< ? > classBeingRedefined,
                                ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
            if(!className.equals(targetClassName.replaceAll(".","/"))){
                return null;
            }

            ClassPool classPool = ClassPool.getDefault();
            System.out.println(String.format("+++++ 代理类名:%s", className));
            try {
                CtClass ctClass = classPool.get(className.replace("/","."));
                CtMethod[] ctMethods = ctClass.getDeclaredMethods();
                for (CtMethod ctMethod : ctMethods) { // 所有类方法
                    ctMethod.insertBefore(String.format("{System.out.println(" >> > 开始执行方法:%s");}",ctMethod.getName()));
                    ctMethod.insertAfter(String.format("{System.out.println(" >> > 结束执行方法:%s");}",ctMethod.getName()));
                }
                return ctClass.toBytecode();
            } catch (NotFoundException | CannotCompileException | IOException e) {
                System.out.println(String.format("+++++ 代理出错:%s",e.getMessage()));
                e.printStackTrace();
            }
            return classfileBuffer;
        }
    }
}

通过上面的例子可以看到,两种方式的比对如下:

对比JavaAgentAttachAgent
/META-INF/MANIFEST.MFPremain-ClassAgent-Class
代理类方法名称premainattachmain
代理入口VM配置:-javaagentJVM attach进程ID
代理时机JVM加载字节码时程序运行时
作用Java桌面程序Web应用

原理

代理可以发送在编译时,类加载时或者是运行时。

这里你要清楚, java程序的入口是main方法 ,不管是普通程序(比如桌面应用、可执行jar)或是Web应用(在Web容器中运行的基于Servlet的应用)

以javaagent为例,是在执行main方法前对已经加载到JVM的类进行修改,从而实现对目标类的代理,这里的修改是在字节码层面的,当然我们可以基于ASM工具库来实现,但是门槛太高。

基于Instrumentation可以与编写java代码一样,实现修改字节码来

ClassPool:保存CtClass的池子,通过classPool.get(类全路径名)来获取CtClass CtClass:编译时类信息,它是一个class文件在代码中的抽象表现形式 CtMethod:对应类中的方法 CtField:对应类中的属性、变量

XMind

还记得XMind8的破解之法吗?

是不是需要在XMind.ini文件中插入这样一段:-javaagent:.../XMindCrack.jar 要是你打开这个jar,你会看到这样的内容:

图片

首先你需要知道其原理,是通过/plugins/net.xmind.verify.jar中提供的方法LicenseVerifier#doCheckLicenseKeyBlacklisted来进行身份校验

我们是不是只用修改License的校验方法 doCheckLicenseKeyBlacklisted ,忽略其校验过程并直接返回true就完事了?当然截图中就是这样做的,如果你想看懂那几行代码,可能你先要去学习ASM相关的知识。

InsnList insnList = methodNode.instructions;
insnList.clear();
insnList.add((AbstractInsnNode)new InsnNode(4));
insnList.add((AbstractInsnNode)new InsnNode(172));
methodNode.exceptions.clear();
methodNode.visitEnd();

以上代码其实就是讲方法体清除,并写入“return true”

结束语

通过示例了解javaassit如何实现代对目标类的代理。是不是觉得java应用程序都能被修改,那不是太不安全了?所以,你觉得呢...

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • JAVA
    +关注

    关注

    19

    文章

    2904

    浏览量

    102999
  • 编程语言
    +关注

    关注

    9

    文章

    1878

    浏览量

    33148
  • 代码
    +关注

    关注

    30

    文章

    4556

    浏览量

    66809
  • 代理
    +关注

    关注

    1

    文章

    18

    浏览量

    11152
收藏 人收藏

    评论

    相关推荐

    JDK动态代理的原理

    在Java中,动态代理是一种机制,允许在运行时动态地创建代理对象来代替某个实际对象,从而在其前后执行额外的逻辑。 为什么JDK动态代理只能代理接口
    的头像 发表于 09-30 10:51 315次阅读

    求教labview是否实现代码编程? 多谢

    本帖最后由 leasor 于 2013-7-15 14:11 编辑 求教labview是否实现代码编程? 多谢, 当我看见一些基本的编程结构, 也使用图形方式, 我可耻的吐了, 使用脚本一的编程方式对于这些大牛来说, 实现
    发表于 07-15 13:58

    基于QT+OpenCv的目标跟踪算法实现

    目标跟踪算法作为一种有着非常广泛的应用的算法,在航空航天、智能交通、智能设备等领域有着非常广泛的应用。本系列博客将教大家在410c开发板上基于linux操作系统环境,采用QT+Opencv来实现
    发表于 09-21 10:42

    适配器模式和代理模式的区别

      代理模式  组成:  抽象角色:通过接口或抽象声明真实角色实现的业务方法。  代理角色:实现抽象角色,是真实角色的
    发表于 10-22 15:17

    网络爬虫nodejs爬虫代理配置

    设计多线程异步IO,实现代理IP并发处理,不仅繁琐,而且影响效率。)2、自动转发的爬虫代理(自动转发的爬虫代理IP”通过固定云代理服务地址,建立专线网络链接,
    发表于 09-01 17:23

    实现最佳音频性能的D放大器设计挑战是什么?

    本文将比较AB和D放大器的设计与性能,介绍D设计相关的主要挑战,说明更高的集成度如何帮助工程师更快的完成设计和实现成本与性能目标
    发表于 06-02 06:30

    实现代码自动生成的步骤

    文章目录一、 目的二、 基本思想三、 代码实现四、 其他工作五、 补充一、 目的工作中有时候感觉编程也是一种重复性劳动,尤其是涉及到读写数据一的内容,还有一些需要进行配置的场合,有时候就想,既然是
    发表于 08-17 09:14

    内核空间实现代码的相关资料分享

    嵌入式LINUX驱动学习之5.ioctl字符设备驱动编程(二)内核空间实现代码#include #include #include #include #include #include #include
    发表于 12-24 06:21

    java的动态代理

    ,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。 按照代理的创建时期,
    发表于 03-12 14:12 0次下载

    基于JDK和CGLB分别实现的动态代理

    本文档内容介绍了基于JDK和CGLB分别实现的动态代理及源代码
    发表于 03-12 14:56 0次下载

    如何在Golang中实现反向代理

    【导读】在本文中,我们将了解反向代理,它的应用场景以及如何在 Golang 中实现它。 反向代理是位于 Web 服务器前面并将客户端(例如 Web 浏览器)的请求转发到 Web 服务器的服务器。它们
    的头像 发表于 08-23 10:22 1913次阅读

    现代汽车集团明确2022年目标

    可能的一年。为实现这一目标现代汽车集团将筑牢顾客信赖的“环保领先(Top Tier)品牌”为根基,确保包括人工智能在内的软件核心技术,在自动驾驶驶、机器人(robotics)、UAM(城市空中移动出行)等未来事业领域规划出切实
    发表于 01-05 11:07 424次阅读
    <b class='flag-5'>现代</b>汽车集团明确2022年<b class='flag-5'>目标</b>

    http代理概述及代码实现方法

    本文详细介绍了Golang 实现 http 代理实现,在实际业务中有需求的同学可以学起来了!
    的头像 发表于 05-14 15:02 3240次阅读

    Golang实现一个简单的http代理

    本文详细介绍了Golang 实现 http 代理实现,在实际业务中有需求的同学可以学起来了!
    的头像 发表于 04-10 11:29 1109次阅读

    mybatis接口动态代理原理

    ,从而实现数据库操作的动态生成和执行。接下来,我将详细介绍MyBatis接口动态代理的原理。 动态代理概念介绍 在Java语言中,动态代理是一种使用
    的头像 发表于 12-03 11:52 436次阅读