Время от времени пентестерам приходится сталкиваться с Java-приложениями. Это могут быть различные серверы, клиенты или просто десктопные программы. И иногда возникает необходимость «пропатчить» такое приложение. Зачем это нужно? Каждый случай возникновения такой необходимости уникален. К примеру:

  • Сложный протокол общения между сервером и клиентом. Чтобы отправлять произвольные запросы – патчим;
  • Захардкожены настройки. Чтобы поменять – патчим;
  • Для демонстрации последствий проблем типа «race condition» – патчим.

Конечно, в каждом случае можно решить проблему, не прибегая к модификации приложения. Но зачастую это самый простой и быстрый способ добиться необходимого результата. В этой статье мы расскажем, как легко и быстро изменить функционал в приложении на Java.

Application


Для рассказа нам понадобится подопытное приложение. Конечно, мы напишем его сами. Его структура на рисунке 1.

image
рисунок 1

Как видим, оно достаточно простое: два класса, которые запускают приложение и рисуют GUI. Вот их исходники. Main.java:

1    package com.mycompany; 
2     
3    public class Main 
4    { 
5        public static void main(String[] args) 
6        { 
7            javax.swing.SwingUtilities.invokeLater(new Runnable() 
8                { 
9                    public void run() { 
10                       new Window(); 
11                   } 
12               }); 
13       } 
14   } 
15

Window.java
1    package com.mycompany; 
2     
3    import com.mycompany.Targets.*; 
4    import javax.swing.*; 
5     
6    public class Window extends JFrame 
7    { 
8        private JPanel panel1; 
9        private JLabel Level1Value; 
10       private JLabel Level2Value; 
11       private JLabel Level3Value; 
12    
13       public Window() 
14       { 
15           super("Simple example"); 
16    
17           Level1Value.setText(new Level1().getText()); 
18           Level2Value.setText(new Level2().getText()); 
19           Level3Value.setText(new Level3().getText()); 
20    
21           getContentPane().add(panel1); 
22    
23           setSize(300,100); 
24           setLocationRelativeTo(null); 
25    
26           setVisible(true); 
27           setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); 
28       } 
29   } 
30

Классы очень простые. Main – создаёт окно Window. Window – печатает текст в 3 поля. Текст он берёт из наших целевых классов, на примере которых мы и будем рассматривать способы модификации. Как выглядит запущенное приложение, смотрите на рисунке 2.

image
рисунок 2

Из инструментов нам понадобится только JDK и JavaDecompiler. Приступаем к патчу.

Level 1


Наше приложение – это jar-архив. Для начала просто вставляем его в JavaDecompiler и получаем исходник интересующего нас класса. Level1.java:

1    package com.mycompany.Targets; 
2     
3    public class Level1 
4    { 
5        public String getText() 
6        { 
7            return "Data"; 
8        } 
9    } 
10

Замечательно, теперь мы копируем этот код в текстовый файл с расширением «java». Это самый простой класс, который только можно придумать. Теперь меняем этот исходник, как пожелаем. Мы заменили возвращаемое значение на «My msg». Далее выполняем команду:

image

В результате мы получим готовый Level1.class. Затем нам нужно просто добавить его в наш jar-архив. Сделаем это следующей командой:

image

Обратим внимание, что мы создали путь, идентичный названию пакета, в котором расположен наш класс, и переложили туда бинарник.
Результат – на рисунке 3.

image
рисунок 3

Рассмотрим случай посложнее.

Level 2


Проделываем всё то же самое и получаем исходник. Level2.java:

1    package com.mycompany.Targets; 
2     
3    public class Level2 
4    { 
5        public String getText() 
6        { 
7            return "Super " + (new Level1().getText()); 
8        } 
9    }

В нём я поменяю «Super» на «New». И попытаюсь скомпилировать:

image

Не получилось, потому что в классе есть ссылка на другой объект из этого пакета. Но java очень дружелюбна и позволяет указать при компиляции любой путь, где лежат классы. Т.е. можно просто выполнить команду:

image

В результате, у нас опять готовый бинарник. Точно так же обновляем jar-архив и смотрим результат на рисунке 4:
image

image
рисунок 4

Ну и рассмотрим последний случай, в котором добавится ещё немного сложностей.

Level 3


Декомпилируем наш класс. Level3.java:

1    package com.mycompany.Targets; 
2     
3    import jd.core.CoreConstants; 
4     
5    public class Level3 
6    { 
7        public String getText() 
8        { 
9            return CoreConstants.class.getSimpleName() + " " 
10                   + (new Level2().getText()); 
11       } 
12   }

Здесь добавим строчку "-update-". Как мы можем сразу заметить, добавился сторонний пакет. И, если мы попытаемся собрать, получим ошибку:

image

И опять же, java выручает своей дружелюбностью. Ей можно просто указать путь, где лежат jar-архивы зависимостей. Делается это так:

image

(да, тут указание пути класса лишнее, т.к. он запакован в архив и лежит в той же директории, но т.к. это не всегда так, я его всё же указал)

И далее всё так же – результат на рисунке 5.

image

image
Рисунок 5

Итог


В итоге мы рассмотрели примеры, как просто и быстро можно модифицировать Java-приложения. Конечно, рассмотрели мы на примере jar-архива, но вы же понимаете, что если классы не запакованы, то всё гораздо проще?

Комментарии (15)


  1. Lure_of_Chaos
    04.05.2016 15:42
    +4

    Для той же задачи можно облегчить себе жизнь и использовать ASM или агенты, т.к. JAD не всегда хорошо справляется с декомпиляцией java 5+ байткода, особенно перед этим обфусцированного, да и вообще декомпилированный код чаще очень сложно читать.


    1. lany
      04.05.2016 16:37

      Можно попробовать FernFlower или Procyon. JAD, конечно, мёртвенький.


      1. Stiver
        04.05.2016 21:05
        +1

        >> Можно попробовать FernFlower

        В IntelliJ IDEA он уже встроен, так что даже ничего искать не надо.


    1. USSCLTD
      04.05.2016 16:37

      Да, разумеется — существует много путей достижения цели.
      Но когда справляется JAD, зачем усложнять себе жизнь?


      1. Lure_of_Chaos
        04.05.2016 17:06

        Потому что не всегда справляется, а так — да.

        Для еще большей легкости модификации байт-кода в рантайме я лично для себя открыл tapestry-plastic


        1. lany
          04.05.2016 19:12

          Можете ещё в сторону Byte Buddy поглядеть.


          1. Lure_of_Chaos
            04.05.2016 19:28

            Очень вкусная штучка, спасибо!


    1. dougrinch
      04.05.2016 17:00

      Что значит "ASM или агенты"? Первое — про способ преобразования класса, второе — про точку входа.


      1. Lure_of_Chaos
        04.05.2016 17:07

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


        1. dougrinch
          04.05.2016 17:54

          Так нет же, это ортогональные вещи. Пропатчив байткод класса через asm все еще остается вопрос "как подсунуть этот байткод в jvm". С другой стороны, зайдя в jvm через агента, нужно еще как-то изменить байткод класса.


          1. Lure_of_Chaos
            04.05.2016 19:27

            как подсунуть этот байткод в jvm

            с помощью загрузчиков классов, конечно.
            зайдя в jvm через агента, нужно еще как-то изменить байткод класса

            или подсунуть свой класс вместо существующего.


            1. dougrinch
              04.05.2016 20:52

              А почему нельзя подсунуть байткод, сделанный асмом, через агента? Или почему нельзя подсунуть свой класс вместо существующего в загрузчик классов?


  1. sleeply4cat
    05.05.2016 09:47
    +4

    Статья, честно говоря, совсем «для начинающих». Даже если говорить только о техническом аспекте, не затрагивая, собственно, тестирования, ради которого всё затевалось, то мы не увидели ни типовых ошибок декомпиляции, коих эта древняя софтина генерирует тонны, ни потери типизации в циклах for-each и дженериках, ни «сборки» в кучу анонимных классов из разных файлов…
    Кроме того, не было сказано ровным счётом ничего об модификации обфусцированных приложений, даже простейших вариантов вроде запаковывания в jar A.class и a.class, не уживающихся после распаковки в NTFS или перемещения всех классов в корневой пакет, чтобы код был некомпилируем стандартным JDK.


    1. USSCLTD
      05.05.2016 09:47

      Ага, статья совсем «для начинающих». Название, на мой взгляд, полностью это отражает — «Easy Hack».
      Этот метод патча ни в коем случае не претендует на универсальность. Он даже не способен обойти простейшие механизмы защиты, как вы правильно заметили.
      Однако, этот метод работает очень во многих случаях — далеко не все бинарники обфуцируют.
      Думаю, эта инструкция пригодится людям далёким от java, которые не хотят лезть в байткод и разбираться в java-машине


  1. ExplosiveZ
    05.05.2016 10:15
    +2

    Первые 5-10 страниц любой книги. Боже, куда же катится Habrahabr! Верните мне Habrahabr 2011/2013, когда я ничего не понимал и приходилось гуглить сложные термины, чтобы стать умнее… Или это я уже настолько умный, что всё понимаю?(