网站首页 > 技术教程 正文
前言
相信很多人都“免费激活”过 IDEA吧,在IDEA 的vmoptions配置里,加行配置就行:
或者是这样“拖到IDEA窗口中”的形式:
再或者用过一些APM工具,在JVM启动脚本上增加了
-javaagent:/path/to/apm-agent.jar,就可以自动进行追踪。再或者用过Arthas之类的JVM诊断工具,这些工具都是通过Java Agent的技术去实现的。**
比如上面说的“免费激活”,其实就是在运行时期修改了验证license的相关代码。JAVA 李 Agent 这么强大的功能,你难道不打算自己亲自写一个试试吗?
基础知识
Java Agent 算是JVM的一个插件,以一个Jar包的形式存在。可以做到在运行时期,修改你的字节码文件,从而达到增强、修改等效果,通过 JVM 提供的 Instrumentation API来实现。
第一个 Java Agent
一个Java Agent,由以下几个组件构成:
- Agent Class - Agent的功能类
 - Packaging - 在MANIFEST.MF文件中定义Agent Class的位置和方式
 - “装载点”,比如-javaagent:<jarfile>[=arguments],指定加载的agent.jar文件
 
废话不多说,下面正式开始编写这个Agent
1. 创建Agent Class
首先要创建一个Agent Class,这个Class作为我们Agent插件的入口类。配置好-javaagent后,JVM在启动时会执行我们Agent Class的premain方法
import java.lang.instrument.Instrumentation;
public class Agent {
  public static void premain(String args, Instrumentation instrumentation){
    ClassLoggerTransformer transformer = new ClassLoggerTransformer();
    instrumentation.addTransformer(transformer);
  }
}
复制代码在premain方法中,除了args参数,还有一个instrumentation对象。这个是Java Agent的核心对象,通过该对象可以注册ClassFileTransformer。
**ClassFileTransformer **就是负责字节码转换的核心接口了,已注册的ClassFileTransformer可以拦截JVM中所有类的加载,并且可以获取到已加载类的字节码,来看一下这个接口的源码:
public interface ClassFileTransformer {
    byte[]
    transform(  ClassLoader         loader,
                String              className,//className,全类名(包括路径,"/"分割)
                Class<?>            classBeingRedefined,//类定义转换时的Class对象,初始加载时为空
                ProtectionDomain    protectionDomain,//protection...
                byte[]              classfileBuffer)//加载的Class字节码数据
        throws IllegalClassFormatException;
}
复制代码2. 定义一个Transformer
了解了ClassFileTransformer接口之后,现在来写一个ClassLoggerTransformer实现类。为了简单,这个实现类只有一个功能:将已加载的字节码转储到文件中
public class ClassLoggerTransformer implements ClassFileTransformer {
    
    //返回值是替换的字节码数据
  @Override
  public byte[] transform(ClassLoader loader,
                          String className,
                          Class<?> classBeingRedefined,
                          ProtectionDomain protectionDomain,
                          byte[] classfileBuffer) throws IllegalClassFormatException {
    try {
      Path path = Paths.get("classes/" + className + ".class");
      //将字节码数据classfileBuffer,存储到classes目录下,以.class文件作为后缀
      Files.write(path, classfileBuffer); 
    } catch (Throwable ignored) { // ignored, don’t do this at home kids
    } finally { return classfileBuffer; }
  }
}
复制代码好了,第一个Agent 的功能代码部分已经完成了,下面需要 Agent 解决入口的配置
3. 构建 agent.jar
现在我们需要将代码构建成一个Jar,并且Jar内的MANIFEST.MF文件中,需要包含Agent Class的配置,最终我们的MANIFEST.MF文件应该是这样:
Manifest-Version: 1.0
Premain-Class: com.github.kongwu.agentsamples.firstagent.Agent//Agent Class的全类名
Can-Redefine-Classes: true //允许重新定义
Can-Retransform-Classes: true //允许运行时转换
复制代码通过Maven的构建插件,很容易完成MANIFEST.MF文件的配置:
<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-jar-plugin</artifactId>
  ...
  <configuration>
    <archive>
      <manifestFile>src/main/resources/META-INF/MANIFEST.MF</manifestFile>
    </archive>
  </configuration>
  ...
</plugin>
复制代码只需要在项目的
src/main/resources/META-INF/路径下,增加一个MANIFEST.MF的模板,模板文件里按照上面介绍的定义即可,最后mvn clean package就可以构建出我们的agent jar了(默认目录在${projectpath}/target/),这个jar包内会包含我们上面的MANIFEST.MF模板文件
好了,大功告成,我们第一款Agent已经开发完了,下面来测试一下:
## 随便找个项目,或者可执行的jar包
## 默认的启动方式,直接java -jar
java -jar first-app.jar
## 启动时添加我们刚才构建的first-agent.jar
java -javaagent:/path/to/first-agent.jar -jar first-app.jar
复制代码增加agent运行后,所有运行时期加载的Class的字节码,就会转储到我们的classes目录下了
如果你是在 IDEA 中运行,也可以在Run/Debug Configurations面板中,add vm options
上面这个例子好像有点过于简单,只是“拦截”了字节码数据进行了转储,并没有进行字节码的修改。其实
ClassFileTransformer.transform的返回值,就是我们要替换的数据;只需要在transform方法中返回新的字节码数据,就可以做到增强/替换类了(不过这个增强/替换是有一些限制的,比如不能修改方法签名之类的,本文不做过多介绍)
介绍完了基本的Agent实现,下面来学习一个Agent的实际例子:通过Agent来“免费激活”
通过Agent 实现“免费激活”
前言中提到的,IDEA“免费激活”的工具也是通过Agent实现的,其实基本原理很简单,就是写一个Agent,动态修改验证license的那些代码而已。
比如我们使用一款需要授权许可证的Java 软件,其内部验证许可证的代码是下面这段(伪代码)
public boolean verifyLicense(String encryptedLicense){
	//请求服务器验证许可……
    boolean passed = licenseServer.verifyLicense(encryptedLicense);
    if(!passed){
    	//do sth
    }
    return passed;
}
复制代码那么我们只需要通过Agent,动态的来修改这个verifyLicense方法,将验证结果修改为直接通过,就可以绕过这个许可验证机制了,还不用修改原始Jar包
public boolean verifyLicense(String encryptedLicense){
    //修改后,直接返回true
	return true;
}
复制代码那么怎么修改这个类方法呢?
有两种方式:
- 提前解压jar包,反编译那个Class文件,得到Java文件后修改verifyLicense方法后重新编译
 - 在ClassFileTransformer实现中,通过传入的该类字节码数据,使用一些字节码操作工具进行修改
 
本文例子为了简单,使用第一种方式,提前反编译、修改,再保存重新编译的Class文件到Agent 项目里:
只需要创建一个ClassFileTransformer,进行这个验证许可证Class的替换:
import java.io.IOException;
import java.io.InputStream;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;
public class HackVerifierClassFileTransformer implements ClassFileTransformer {
    @Override
    public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
        //只替换这个验证的类 - LicenseVerifier
        if(className.equals("com/github/kongwu/agentsamples/firstagent/verifierapp/LicenseVerifier")){
            return loadHackClassBuffer(loader);
        }
        return null;
    }
    private byte[] loadHackClassBuffer(ClassLoader loader) {
        //反编译 -> 修改 -> 重新编译的LicenseVerifier.class 文件,换个后缀防止被JVM自动加载
        try (InputStream input = loader.getResourceAsStream("LicenseVerifier.classdata")){
            int n;
            ByteArrayOutputStream outputBuffer = new ByteArrayOutputStream();
            byte[] buffer = new byte[4096];//4k
            //-1 : end of file
            while (-1 != (n = input.read(buffer))) {
                outputBuffer.write(buffer,0,n);
            }
            return outputBuffer.toByteArray();
        }catch (IOException e){
            System.err.println("load the hackfile failed!");
            return null;
        }
    }
}
复制代码然后在Agent Class中,注册这个
HackVerifierClassFileTransformer
import java.lang.instrument.Instrumentation;
public class Agent {
  public static void premain(String args, Instrumentation instrumentation){
    HackVerifierClassFileTransformer transformer = new HackVerifierClassFileTransformer();
    instrumentation.addTransformer(transformer);
  }
}
复制代码最后只需要像上面那样,配置下MANIFEST.MF的生成,然后构建Agent Jar包,就完成了我们这个“免费激活”的Agent 插件
以后运行该 Java 软件时,只需要增加
-javaagent:/path/to/hack-agent.jar,就实现了“免费激活”
## 默认的启动方式,直接java -jar
java -jar verifier-app.jar
## 启动时添加我们刚才构建的agent.jar
java -javaagent:/path/to/hack-agent.jar -jar verifier-app.jar
复制代码文中例子完整的代码在github.com/kongwu-/age…,有需要的同学可以自行下载
常用的字节码操作类库
- ASM
 - cglib
 - javaassist
 - ByteBuddy
 
以上的几个字节码操作类库,最推荐的是ByteBuddy,使用方式上最简单
总结
本文介绍的这个“免费激活”的方式,仅用于学习交流,不要用于一些非法的场景,做一个遵纪守法的好公民,不然会被请去喝茶就不太好了……
原文链接:
https://juejin.cn/post/6944881900636864526
猜你喜欢
- 2025-07-03 阿里P8大佬总结的Nacos入门笔记,从安装到进阶小白也能轻松学会
 - 2025-07-03 Linux环境下,Jmeter压力测试的搭建及报错解决方法
 - 2025-07-03 Java 在Word中合并单元格时删除重复值
 - 2025-07-03 解压缩软件哪个好用?4款大多数人常用的软件~
 - 2025-07-03 Hadoop高可用集群搭建及API调用(hadoop3高可用)
 - 2025-07-03 lombok注解@Data没有toString和getter、setter问题
 - 2025-07-03 Apache Felix介绍(apache fineract)
 - 2025-07-03 Spring Boot官方推荐的Docker镜像编译方式-分层jar包
 - 2025-07-03 Gradle 使用手册(gradle详细教程)
 - 2025-07-03 字节二面:为什么SpringBoot的 jar可以直接运行?
 
欢迎 你 发表评论:
- 10-23Excel计算工龄和年份之差_excel算工龄的公式year
 - 10-23Excel YEARFRAC函数:时间的"年份比例尺"详解
 - 10-23最常用的10个Excel函数,中文解读,动图演示,易学易用
 - 10-23EXCEL中如何计算截止到今日(两个时间中)的时间
 - 10-2390%人不知道的Excel神技:DATEDIF 精准计算年龄,告别手动算错!
 - 10-23计算工龄及工龄工资(90%的人搞错了):DATE、DATEDIF组合应用
 - 10-23Excel中如何计算工作日天数?用这两个函数轻松计算,附新年日历
 - 10-23怎样快速提取单元格中的出生日期?用「Ctrl+E」批量搞定
 
- 最近发表
 - 
- Excel计算工龄和年份之差_excel算工龄的公式year
 - Excel YEARFRAC函数:时间的"年份比例尺"详解
 - 最常用的10个Excel函数,中文解读,动图演示,易学易用
 - EXCEL中如何计算截止到今日(两个时间中)的时间
 - 90%人不知道的Excel神技:DATEDIF 精准计算年龄,告别手动算错!
 - 计算工龄及工龄工资(90%的人搞错了):DATE、DATEDIF组合应用
 - Excel中如何计算工作日天数?用这两个函数轻松计算,附新年日历
 - 怎样快速提取单元格中的出生日期?用「Ctrl+E」批量搞定
 - Excel日期函数之DATEDIF函数_excel函数datedif在哪里
 - Excel函数-DATEDIF求司龄_exceldatedif函数计算年龄
 
 
- 标签列表
 - 
- 下划线是什么 (87)
 - 精美网站 (58)
 - qq登录界面 (90)
 - nginx 命令 (82)
 - nginx .http (73)
 - nginx lua (70)
 - nginx 重定向 (68)
 - Nginx超时 (65)
 - nginx 监控 (57)
 - odbc (59)
 - rar密码破解工具 (62)
 - annotation (71)
 - 红黑树 (57)
 - 智力题 (62)
 - php空间申请 (61)
 - 按键精灵 注册码 (69)
 - 软件测试报告 (59)
 - ntcreatefile (64)
 - 闪动文字 (56)
 - guid (66)
 - abap (63)
 - mpeg 2 (65)
 - column (63)
 - dreamweaver教程 (57)
 - excel行列转换 (56)
 
 

本文暂时没有评论,来添加一个吧(●'◡'●)