- Баллы
- 88
Здравствуйте, администраторы серверов и простые кодеры. 
Сегодня я расскажу, как менять код Java без декомпилирования/компилирования.
Мы будем заниматься чем-то похожим, но это будет ассемблирование и дисассемблирование.
В чем отличие между декомпилированием и дисассемблированием?
Декомпиляция на выходе дает обычный код Java и при его повторном компилировании могут возникнуть кучи ошибок по причинам того, что компилятор не в силах разресолвить необходимые зависимости.
С дисассемблированием все проще. На выходе мы получаем байт-код Java, который легко изменить и собрать обратно (ассемблирование).
Шаг 1: Утилиты для ассемблирования/дисассемблирования
Самой лучшей утилитой я считаю Krakatau Bytecode Tools.
На её примере мы и будем работать.
Загрузить утилиту можно тут: https://github.com/Storyyeller/Krakatau
Прочитать README данной утилиты можно тут: README.txt
Устанавливать утилиту не нужно, просто извлеките скачанный архив в нужную папку и скрипты готовы для запуска.
Внимание! Для компиляции при помощи Krakatau необходима Java Development Kit!
(Не путать с ассемблированием!)
Для запуска Krakatau необходима Python версии 2.7!
Шаг 2: Разбираем простую программу и смотрим что да как.
Давайте создадим простую программу на Java, которая выводит в консоль "test output", скомпилируем её и потом дисассемблируем.
После сборки в "Runnable JAR File" мы кидаем его в рабочую папку и разбираем при помощи Krakatau.
Для начала перейдем в ту папку, где лежат скрипты Krakatau. Нам понадобится disassemble.py
Указываем параметры -r (recursive) и -out <путь> (путь, куда будут положены разобранные .class файлы) и потом путь до нашего .jar файла.
Нормальный выхлоп разбирания .jar файла без ошибок:
Смотрим папку, которую мы указали в параметре -out, и что мы видим? Появились файлы с расширением .j, это и есть файлы с байт-кодом.
.j файлы открываются почти любым блокнотом (в других проблема с переносами строк)
Я использую Notepad++.
Тут можно посмотреть весь код класса: http://pastebin.com/k0bBVHPs
Давайте оставим инициализацию класса и перейдем к функции main:
Естественно отвечает за функцию main, которой в аргументы идет [Ljava/lang/String; (Массив строк)
Перейдем к коду.
Выглядит как:
Давайте разберем его. getstatic получает значение статического объекта, а именно java/lang/System.
java/lang/System эквивалентна java.lang.System, что мы и видим на скриншоте ниже. Ошибок нет.
А далее из под него достает объект out типа Ljava/io/PrintStream, а это эквивалентно java.io.PrintStream, разбираем код дальше..
Пушает строку 'test output' в стак.
А вот это уже веселее. Она вызывает метод println из java/io/PrintStream и отдает ему аргумент Ljava/lang/String;
Просто, не так ли?
(Для доп. информации: для статических методов используется invokestatic)
А теперь немного повеселимся. Давайте заставим Java-машину вызывать метод RuBukkit вместо println!
Заменяем
на
Теперь собираем! Нам понадобится assemble.py
(Думаю аргументы уже всем понятны)
Вуаля! Сборка успешно завершена, не проверяя, существует ли данный метод!
Заменяем наш старый Main.class в .jar файле на новый и пытаемся его запустить!
Double-вуаля! Java-машина попыталась вызвать java.io.PrintStream.RuBukkit, но не нашла метода, выбив нам исключение.
Стоп, что это? Main.java:5?
Откуда Java-машина знает, что у меня именно в этом файле и на этой строке находится данный вызов?
Давайте это аккуратно исправим! Я хочу, что бы при данном исключении показывало файл RuBukkit.org и строку 1337!
В конце .j файла есть нужная нам строчка:
Изменим её на:
А номер строки можно найти в разделе .linenumbertable
L0 в нашем случае это функция System.out.println("test output");
Ей и присуждена строка под номером 5
А L8 это return, а если быть точнее, конец функции.
Давайте сравним с IDE?
Заменяем на..
Теперь собираем и тестируем..
Все получилось!
Вот и мой маленький туториал завершен.
Кому нужна дополнительная помощь или связь со мной - можете написать мне.
Всем удачи!

Сегодня я расскажу, как менять код Java без декомпилирования/компилирования.
Мы будем заниматься чем-то похожим, но это будет ассемблирование и дисассемблирование.
В чем отличие между декомпилированием и дисассемблированием?
Декомпиляция на выходе дает обычный код Java и при его повторном компилировании могут возникнуть кучи ошибок по причинам того, что компилятор не в силах разресолвить необходимые зависимости.
С дисассемблированием все проще. На выходе мы получаем байт-код Java, который легко изменить и собрать обратно (ассемблирование).
Java Bytecode / Википедия:
https://ru.wikipedia.org/wiki/Байт-код_Java
Список OP-кодов Java байткода:
https://en.wikipedia.org/wiki/Java_bytecode_instruction_listings
Основы Byte-кода Java (Хабрахабр):
http://habrahabr.ru/post/111456/
https://ru.wikipedia.org/wiki/Байт-код_Java
Список OP-кодов Java байткода:
https://en.wikipedia.org/wiki/Java_bytecode_instruction_listings
Основы Byte-кода Java (Хабрахабр):
http://habrahabr.ru/post/111456/
Шаг 1: Утилиты для ассемблирования/дисассемблирования
Самой лучшей утилитой я считаю Krakatau Bytecode Tools.
На её примере мы и будем работать.
Загрузить утилиту можно тут: https://github.com/Storyyeller/Krakatau
Прочитать README данной утилиты можно тут: README.txt
Устанавливать утилиту не нужно, просто извлеките скачанный архив в нужную папку и скрипты готовы для запуска.
Внимание! Для компиляции при помощи Krakatau необходима Java Development Kit!
(Не путать с ассемблированием!)
Для запуска Krakatau необходима Python версии 2.7!
Шаг 2: Разбираем простую программу и смотрим что да как.
Давайте создадим простую программу на Java, которая выводит в консоль "test output", скомпилируем её и потом дисассемблируем.
После сборки в "Runnable JAR File" мы кидаем его в рабочую папку и разбираем при помощи Krakatau.
Для начала перейдем в ту папку, где лежат скрипты Krakatau. Нам понадобится disassemble.py
Указываем параметры -r (recursive) и -out <путь> (путь, куда будут положены разобранные .class файлы) и потом путь до нашего .jar файла.
Нормальный выхлоп разбирания .jar файла без ошибок:
Смотрим папку, которую мы указали в параметре -out, и что мы видим? Появились файлы с расширением .j, это и есть файлы с байт-кодом.
.j файлы открываются почти любым блокнотом (в других проблема с переносами строк)
Я использую Notepad++.
Тут можно посмотреть весь код класса: http://pastebin.com/k0bBVHPs
Давайте оставим инициализацию класса и перейдем к функции main:
Код:
.method public static main : ([Ljava/lang/String;)V
Перейдем к коду.
Код:
System.out.println("test output");
Код:
L0: getstatic Field java/lang/System out Ljava/io/PrintStream;
L3: ldc 'test output'
L5: invokevirtual Method java/io/PrintStream println (Ljava/lang/String;)V
Давайте разберем его. getstatic получает значение статического объекта, а именно java/lang/System.
java/lang/System эквивалентна java.lang.System, что мы и видим на скриншоте ниже. Ошибок нет.
А далее из под него достает объект out типа Ljava/io/PrintStream, а это эквивалентно java.io.PrintStream, разбираем код дальше..
Код:
ldc 'test output'
Код:
invokevirtual Method java/io/PrintStream println (Ljava/lang/String;)V
Просто, не так ли?

(Для доп. информации: для статических методов используется invokestatic)
А теперь немного повеселимся. Давайте заставим Java-машину вызывать метод RuBukkit вместо println!
Заменяем
Код:
L5: invokevirtual Method java/io/PrintStream println (Ljava/lang/String;)V
Код:
L5: invokevirtual Method java/io/PrintStream RuBukkit (Ljava/lang/String;)V
Теперь собираем! Нам понадобится assemble.py
(Думаю аргументы уже всем понятны)
Вуаля! Сборка успешно завершена, не проверяя, существует ли данный метод!
Заменяем наш старый Main.class в .jar файле на новый и пытаемся его запустить!
Double-вуаля! Java-машина попыталась вызвать java.io.PrintStream.RuBukkit, но не нашла метода, выбив нам исключение.
Стоп, что это? Main.java:5?
Откуда Java-машина знает, что у меня именно в этом файле и на этой строке находится данный вызов?
Давайте это аккуратно исправим! Я хочу, что бы при данном исключении показывало файл RuBukkit.org и строку 1337!
В конце .j файла есть нужная нам строчка:
Изменим её на:
Код:
.sourcefile 'RuBukkit.org'
А номер строки можно найти в разделе .linenumbertable
L0 в нашем случае это функция System.out.println("test output");
Ей и присуждена строка под номером 5
А L8 это return, а если быть точнее, конец функции.
Давайте сравним с IDE?
Заменяем на..
Код:
.linenumbertable
L0 1337
L8 6
.end linenumbertable
Теперь собираем и тестируем..
Все получилось!
Вот и мой маленький туториал завершен.
Кому нужна дополнительная помощь или связь со мной - можете написать мне.
Всем удачи!

