关于java:手把手教你写一个Java-Agent实现免费激活

5次阅读

共计 5755 个字符,预计需要花费 15 分钟才能阅读完成。

前言

置信很多人都“收费激活”过 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;
}

那么怎么批改这个类办法呢?

有两种形式:

  1. 提前解压 jar 包,反编译那个 Class 文件,失去 Java 文件后批改 verifyLicense 办法后从新编译
  2. 在 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,应用形式上最简略

总结

本文介绍的这个“收费激活”的形式,仅用于学习交换,不要用于一些非法的场景,做一个遵纪守法的好公民,不然会被请去喝茶就不太好了……

原创不易,转载请在结尾驰名文章起源和作者。如果我的文章对您有帮忙,请点赞 / 珍藏 / 关注激励反对。

正文完
 0