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

    文章

    2956

    浏览量

    104531
  • 编程语言
    +关注

    关注

    10

    文章

    1937

    浏览量

    34586
  • 代码
    +关注

    关注

    30

    文章

    4741

    浏览量

    68324
  • 代理
    +关注

    关注

    1

    文章

    42

    浏览量

    11198
收藏 人收藏

    评论

    相关推荐

    JDK动态代理的原理

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

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

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

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

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

    实现代码自动生成的步骤

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

    基于聚融合的多目标跟踪算法

    目标跟踪是多传感器数据融合中的一个重要问题。基于模式识别理论,提出了一种通过对传感器测量数据集,以区分源于不同目标的测量数据集合。对各个对应的
    发表于 07-01 08:40 18次下载

    java动态代理机制详解的和接口描述

    的我们的功能,我们更需要学习的是其底层是怎么样的一个原理,而AOP的原理就是java的动态代理机制,所以本篇随笔就是对java的动态机制进行一个回顾。 在java的动态代理机制中,有两个重要的或接口
    发表于 09-28 13:33 0次下载

    基于聚的多目标遗传算法在职责分配中的应用

    在面向对象软件设计与实现过程中,职责分配是其中最重要且复杂的步骤之一,它在很大程度上影响软件质量。为了实现职责自动分配的目标,从软件内聚
    发表于 11-28 17:35 0次下载
    基于聚<b class='flag-5'>类</b>的多<b class='flag-5'>目标</b>遗传算法在<b class='flag-5'>类</b>职责分配中的应用

    空间邻近的点目标实现方法

    了基于空间邻近的点目标方法,通过Voronoi建模识别点目标间的空间邻近关系,并以Voronoi势力范围来定义相似度准则,最终构建树结构以实现
    发表于 12-19 10:47 0次下载
    空间邻近的点<b class='flag-5'>目标</b>聚<b class='flag-5'>类</b><b class='flag-5'>实现</b>方法

    java的动态代理

    代理的对象与一个委托的对象关联,代理的对象本身并不真正实现服务,而是通过调用委托
    发表于 03-12 14:12 0次下载

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

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

    如何在Golang中实现反向代理

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

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

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

    Golang实现一个简单的http代理

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

    mybatis接口动态代理原理

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

    Python库解析:通过库实现代理请求与数据抓取

    在Python中,有多个库可以帮助你实现代理请求和数据抓取。这些库提供了丰富的功能和灵活的API,使得你可以轻松地发送HTTP请求、处理响应、解析HTML/XML/JSON数据,以及进行复杂的网络操作。
    的头像 发表于 10-24 07:54 128次阅读