Научит вас писать Java Agent и реализовать "бесплатную активацию"

Java
Научит вас писать Java Agent и реализовать "бесплатную активацию"

предисловие

Я считаю, что многие люди "активировали" IDEA бесплатно. В конфигурации IDEA vmoptions добавьте строку конфигурации:image.pngИли в виде "перетащить в окно IDEA" вот так:

Или использовали некоторые инструменты APM и добавили сценарий запуска JVM.-javaagent:/path/to/apm-agent.jar, его можно отслеживать автоматически. Или использовали диагностические инструменты JVM, такие как Arthas, эти инструменты проходят черезJava Agentтехнология достижения. **

Например, упомянутая выше «бесплатная активация» фактически изменяет соответствующий код для проверки лицензии во время выполнения. Агент — такая мощная функция в JAVA, не планируете ли вы написать ее сами и попробовать?

Базовые знания

Агент Java представляет собой подключаемый модуль JVM и существует в виде пакета Jar. Вы можете изменять свои файлы байт-кода во время выполнения для достижения улучшений, модификаций и т. д. с помощью инструментального API, предоставляемого JVM.

Первый Java-агент

Агент Java состоит из следующих компонентов:

  • Agent Class- Функциональный класс агента
  • Packaging- Где и как определить класс агента в файле MANIFEST.MF
  • "точка крепления",Например-javaagent:<jarfile>[=arguments], укажите загруженный файл agent.jar

Без лишних слов, давайте начнем писать этого агента

1. Создайте класс агента

Прежде всего, нам нужно создать класс агента, который используется в качестве класса входа нашего подключаемого модуля агента. Настроено-javaagentПосле этого JVM выполнит класс нашего агента.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. Определите трансформатор

Разобравшись с интерфейсом 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; }
  }
}

Что ж, часть функционального кода первого агента завершена, и агенту необходимо решить конфигурацию входа ниже.

3. Соберите файл agent.jar

Теперь нам нужно встроить код в банку, а файл MANIFEST.MF в банке должен содержать конфигурацию класса агента Наконец, наш файл MANIFEST.MF должен выглядеть так:

Manifest-Version: 1.0
Premain-Class: com.github.kongwu.agentsamples.firstagent.Agent//Agent Class的全类名
Can-Redefine-Classes: true //允许重新定义
Can-Retransform-Classes: true //允许运行时转换

Конфигурация файла MANIFEST.MF легко выполняется с помощью подключаемого модуля сборки Maven:

<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Мы можем создать наш jar агента (каталог по умолчанию ${projectpath}/target/), этот пакет jar будет содержать наш файл шаблона MANIFEST.MF, указанный выше.

Ну все готово, наш первый Агент разработан, давайте его протестировать:

## 随便找个项目,或者可执行的jar包

## 默认的启动方式,直接java -jar
java -jar first-app.jar

## 启动时添加我们刚才构建的first-agent.jar
java -javaagent:/path/to/first-agent.jar -jar first-app.jar

После добавления агента для запуска байт-код всех классов, загруженных во время выполнения, будет сброшен в наш каталог классов.

Если вы работаете в IDEA, вы также можете добавить параметры vm на панели Run/Debug Configurations.image.png


Приведенный выше пример кажется слишком простым, он просто «перехватывает» данные байт-кода и выгружает их, а не изменяет байт-код. На самом деле возвращаемое значение ClassFileTransformer.transform — это данные, которые мы хотим заменить; нам нужно только вернуть новые данные байт-кода в методе преобразования, чтобы улучшить/заменить класс (но это улучшение/замена имеет некоторые ограничения, например, сигнатура метода не может быть изменена и т. д., в этой статье не будет слишком многого)

После знакомства с базовой реализацией агента давайте изучим реальный пример агента: «Бесплатная активация» через агента.

«Бесплатная активация» через Агента

Как упоминалось в предисловии, инструмент «бесплатной активации» IDEA также реализуется через агента.На самом деле, основной принцип очень прост, то есть написать агента для динамического изменения кода, который проверяет лицензию.

Например, мы используем программное обеспечение Java, для которого требуется авторизованная лицензия, и код для внутренней проверки лицензии выглядит следующим образом (псевдокод):

public boolean verifyLicense(String encryptedLicense){
	//请求服务器验证许可……
    boolean passed = licenseServer.verifyLicense(encryptedLicense);
    if(!passed){
    	//do sth
    }
    return passed;
}

Затем нам нужно только динамически изменить метод verifyLicense через агент и изменить результат проверки, чтобы он прошел напрямую, чтобы этот механизм проверки лицензии можно было обойти без изменения исходного пакета Jar.

public boolean verifyLicense(String encryptedLicense){
    //修改后,直接返回true
	return true;
}

Итак, как изменить этот метод класса?

Есть два способа:

  1. Заранее разархивируйте пакет jar, декомпилируйте файл класса, получите файл Java, измените метод verifyLicense и перекомпилируйте
  2. В реализации ClassFileTransformer некоторые инструменты манипулирования байт-кодом используются для изменения входящих данных байт-кода.

Для простоты в примере в этой статье используется первый метод для предварительной декомпиляции и изменения, а затем сохранения перекомпилированного файла класса в проекте агента:

Просто создайте ClassFileTransformer, чтобы заменить этот класс лицензии проверки:


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;
        }
    }
}

Затем в классе агента зарегистрируйте этот 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, чтобы завершить наш подключаемый модуль агента «бесплатной активации».

При последующем запуске программного обеспечения 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/empty-/брат…, учащиеся, которым это необходимо, могут скачать его самостоятельно

Часто используемая библиотека для работы с байт-кодом

Из вышеперечисленных библиотек для работы с байт-кодом наиболее рекомендуемой является ByteBuddy, которую проще всего использовать.

Суммировать

Метод «бесплатной активации», представленный в этой статье, используется только для обучения и общения, а не для каких-то незаконных сценариев, чтобы быть хорошим гражданином, который соблюдает закон, иначе будет не очень хорошо, когда вас пригласят на чай. .

Оригинальность непростая, пожалуйста, перепечатайте в начале статьи известный источник и автора. Если моя статья оказалась для вас полезной, пожалуйста, поставьте лайк/добавьте в избранное/подпишитесь, чтобы поощрить поддержку.