Почему Java-агент зависает и не выполняет никаких действий?

Я пишу курсовую работу для университета. Задача — написать две программы, одна из которых собирает некоторую информацию, подписывает ее цифровым ключом и сохраняет зашифрованные данные и ключ в отдельные файлы. Следующая программа собирает ту же информацию, считывает зашифрованные данные и ключи и проверяет цифровую подпись.

Шаг номер два — написать агент, использующий java.lang.instrument.Instrumentation; и, возможно, сторонние библиотеки, такие как javaassist, для замены байт-кода, чтобы программа всегда выдавала успешный результат проверки.

Я пытался реализовать это с помощью туториалов, но моя программа застревает на этапе изменения.

Вот анализ байткода, который нужно изменить

static void checkSign(java.lang.String, java.lang.String, java.lang.String) throws java.lang.Exception;
    Code:
       0: new           #206                // class java/io/BufferedReader
       3: dup
       4: new           #258                // class java/io/FileReader
       7: dup
       8: aload_0
       9: invokespecial #260                // Method java/io/FileReader."<init>":(Ljava/lang/String;)V
      12: invokespecial #219                // Method java/io/BufferedReader."<init>":(Ljava/io/Reader;)V
      15: astore_3
      16: aload_3
      17: invokevirtual #225                // Method java/io/BufferedReader.readLine:()Ljava/lang/String;
      20: astore        4
      22: invokestatic  #262                // Method java/util/Base64.getDecoder:()Ljava/util/Base64$Decoder;
      25: aload         4
      27: invokevirtual #268                // Method java/util/Base64$Decoder.decode:(Ljava/lang/String;)[B
      30: astore        5
      32: aload_3
      33: invokevirtual #245                // Method java/io/BufferedReader.close:()V
      36: new           #206                // class java/io/BufferedReader
      39: dup
      40: new           #258                // class java/io/FileReader
      43: dup
      44: aload_1
      45: invokespecial #260                // Method java/io/FileReader."<init>":(Ljava/lang/String;)V
      48: invokespecial #219                // Method java/io/BufferedReader."<init>":(Ljava/io/Reader;)V
      51: astore        6
      53: aload         6
      55: invokevirtual #225                // Method java/io/BufferedReader.readLine:()Ljava/lang/String;
      58: astore        7
      60: aload         6
      62: invokevirtual #245                // Method java/io/BufferedReader.close:()V
      65: invokestatic  #262                // Method java/util/Base64.getDecoder:()Ljava/util/Base64$Decoder;
      68: aload         7
      70: invokevirtual #268                // Method java/util/Base64$Decoder.decode:(Ljava/lang/String;)[B
      73: astore        8
      75: ldc_w         #274                // String RSA
      78: invokestatic  #276                // Method java/security/KeyFactory.getInstance:(Ljava/lang/String;)Ljava/security/KeyFactory;
      81: astore        9
      83: new           #281                // class java/security/spec/X509EncodedKeySpec
      86: dup
      87: aload         8
      89: invokespecial #283                // Method java/security/spec/X509EncodedKeySpec."<init>":([B)V
      92: astore        10
      94: aload         9
      96: aload         10
      98: invokevirtual #286                // Method java/security/KeyFactory.generatePublic:(Ljava/security/spec/KeySpec;)Ljava/security/PublicKey;
     101: checkcast     #290                // class java/security/interfaces/RSAPublicKey
     104: astore        11
     106: ldc_w         #274                // String RSA
     109: invokestatic  #292                // Method javax/crypto/Cipher.getInstance:(Ljava/lang/String;)Ljavax/crypto/Cipher;
     112: astore        12
     114: aload         12
     116: iconst_2
     117: aload         11
     119: invokevirtual #297                // Method javax/crypto/Cipher.init:(ILjava/security/Key;)V
     122: aload         12
     124: aload         5
     126: invokevirtual #301                // Method javax/crypto/Cipher.doFinal:([B)[B
     129: astore        13
     131: new           #17                 // class java/lang/String
     134: dup
     135: aload         13
     137: invokespecial #304                // Method java/lang/String."<init>":([B)V
     140: astore        14
     142: aload         14
     144: aload_2
     145: invokevirtual #237                // Method java/lang/String.equals:(Ljava/lang/Object;)Z
     148: ifeq          163
     151: getstatic     #10                 // Field java/lang/System.out:Ljava/io/PrintStream;
     154: ldc_w         #305                // String Verification successfully!
     157: invokevirtual #26                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
     160: goto          171
     163: getstatic     #10                 // Field java/lang/System.out:Ljava/io/PrintStream;
     166: ldc           #204                // String Verification failed!
     168: invokevirtual #26                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
     171: return

Это мой агент

import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;

import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;

public class AgentMain {

    public static void premain(String agentArgs, Instrumentation inst) {
        System.out.println("Ha, ha! This program was hacked ;-)");
        inst.addTransformer(new ClassFileTransformer() {
            @Override
            public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
                                    ProtectionDomain protectionDomain, byte[] classfileBuffer) {

                if (className.equals("Defender")) {
                    try {
                        System.out.println("I'm here");
                        ClassPool cp = ClassPool.getDefault();
                        System.out.println("Class pool created");
                        CtClass cc = cp.get("Defender"); 

                        CtMethod method = cc.getDeclaredMethod("checkSign");

                        String newBody = "{" +
                                "    System.out.println(\"Verification successfully!\");" +
                                "}";

                        method.setBody(newBody);

                        return cc.toBytecode();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
               
                return classfileBuffer;
            }
        });
    }
}

Я специально настроил вывод сообщения «Я здесь», чтобы понять, где ошибка.

Скомпилировав все классы в jar, я запустил его командой

java -javaagent:HackAgent.jar -jar Defender.jar

Но в результате я получаю вот это

Что я делаю не так?

JavaScript — это не Java.

Ghost 25.04.2024 22:36

Возможно, я что-то упускаю, но если цель состоит в том, чтобы «[сделать что-то] так, чтобы [вторая] программа всегда давала успешный результат проверки», зачем коду нужно что-то делать? Просто скажите: «Да, эти файлы проверены!» и выйти.

Mike 'Pomax' Kamermans 25.04.2024 22:40

Я заметил, что «Созданный пул классов» никогда не выводился.

David Conrad 25.04.2024 22:44

Как писал Дэвид, либо [A] код, который вы запускаете, не является тем кодом, который вы вставили, либо [B] код ClassPool.getDefault() выдает исключение, и почему-то это не печатается, потому что вы перенаправили syserr в другое место. Похоже на вариант [А]: вы используете что-то другое. Вероятно, вы что-то написали, скомпилировали, заархивировали, изменили то, что написали, скомпилировали, не смогли заархивировать и теперь используете старый jar.

rzwitserloot 25.04.2024 22:47

@Mike'Pomax'Kamermans Задача - манипулировать баткодом, чтобы обойти все проверки и отобразить результат

Артем Лебідь 25.04.2024 23:20

@DavidConrad Да, но я не знаю, почему на этапе получения ClassPool он просто застрял

Артем Лебідь 25.04.2024 23:22

@rzwitserloot Я все еще склоняюсь к тому, что агент работает некорректно, метод ClassPool.getDefault() не работает, но ошибку по этому поводу не выдает. Единственное, что странно, это не улавливается try catch

Артем Лебідь 25.04.2024 23:28

Кроме того, установлено ли у вас значение Can-Retransform-Classes в манифесте вашего агента?

David Conrad 26.04.2024 01:08

@rzwitserloot По какой-то полезной причине Java молча игнорирует Throwables, брошенные трансформерами. В коде Exception улавливаются, а другие Trowable, такие как Error, — нет. ClassNotFoundError тут приходит на ум.

Johannes Kuhn 26.04.2024 05:23

Попытка получить класс через ClassPool.getDefault() (без дополнительной настройки) не является правильным решением, поскольку вы просто перехватываете механизм загрузки классов по умолчанию. Вы должны использовать определение класса, полученное в параметре classfileBuffer. См. ByteArrayClassPath . Обычно я бы использовал библиотеку ASM; это более низкий уровень и требует большего понимания, но именно такое понимание того, что происходит, необходимо для таких задач. Javassist только делает вид, что все просто.

Holger 26.04.2024 10:43

Хотя конкретная проблема может быть намного проще. Как упомянул @JohannesKuhn, вы не ловите ClassNotFoundError (и возможно, вы все равно не сможете его поймать, например, когда верификатор уже выходит из строя до того, как ваш метод будет введен). И, насколько мы видим в вашей командной строке, в пути к классам Агента нет Javassist…

Holger 26.04.2024 10:49
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
0
11
76
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Этот вопрос явно является продолжением того вопроса, который был закрыт, поскольку вы представили только исходный код, но не объяснили, что вы пытались решить проблему. Здесь все наоборот. В любом случае, объединив оба вопроса, я вижу, что вы попробовали что-то сами, а не просто ищете кого-то, кто сделает всю работу за вас, и это похвально. Итак, хотя это не Javassist, а ответ AspectJ, я хочу показать вам, как решить проблему. Я попробовал использовать ваш исходный код.

import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;

@Aspect
public class DefenderAspect {
  @Around("cflow(execution(void checkSign(String, String, String)) && args(*, *, hash)) && call(String.new(byte[]))")
  public String replaceDecryptedSignString(String hash) {
    return hash;
  }
}

Pointcut сообщает AspectJ выбирать вызовы конструктора String(byte[]), если они находятся в потоке управления метода checkSign. Он также связывает третий параметр hash с аргументом метода рекомендации и использует его для простого возврата того, что должен вернуть вызов конструктора, чтобы удовлетворить условие dectyptedSignString.equals(hash) в вашем операторе if.

Ваш пример кода теперь создает этот журнал консоли:

...
General information: 
    Author: Artem Lebid
    Number in student list: 9
    Task number: 9
    Task description: Program Defender

...

MD5 Hash: 19ef14ef0778ac786cd0a32e83b5ab19
Verification successfully!
...

Это решение работает как с переплетением после компиляции, так и во время загрузки.

Конечно, как я уже сказал в своем комментарии, вы можете разработать аналогичное решение с помощью ASM, Byte Buddy, BCEL, Javassist или подобных фреймворков. AspectJ — мой выбор, потому что мне нравится его элегантный синтаксис.

Спасибо, ваше решение было полезным, но я также смог сделать это через Java-агент. Как я писал @Johannes Kuhn, действительно моя проблема заключалась в том, что агенту необходимо перехватывать Throwable, а не простые Exceptions

Артем Лебідь 12.06.2024 16:55

Я не понимаю вашего комментария. В моем ответе уже сказано, что он работает как для посткомпиляции, так и для плетения во время загрузки. Последний (LTW) через AspectJ является Java-агентом, так что же нового или нового в вашем (неописанном) решении?

kriegaex 12.06.2024 20:26

Другие вопросы по теме