前言
置信很多人都“收费激活”过 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
文中例子残缺的代码在https://github.com/kongwu-/agent-samples,有须要的同学能够自行下载
罕用的字节码操作类库
- ASM
- cglib
- javaassist
- ByteBuddy
以上的几个字节码操作类库,最举荐的是ByteBuddy,应用形式上最简略
总结
本文介绍的这个“收费激活”的形式,仅用于学习交换,不要用于一些非法的场景,做一个遵纪守法的好公民,不然会被请去喝茶就不太好了……
原创不易,转载请在结尾驰名文章起源和作者。如果我的文章对您有帮忙,请点赞/珍藏/关注激励反对。
发表回复