Print This Post Патчим байт-код Java на лету

Вторник, 23. Декабрь 2014
Раздел: Java, автор:

В то время, пока dx занимается поддержкой своего нового обфускатора и разработкой улучшенной версии класса для работы с HTTP на PHP, я подготовил небольшую статью, чтобы разбавить пустоту, царящую в блоге.

Речь пойдет о правке байт-кода Java, как видно из названия статьи. Когда-то давно я уже писал подобную статью про модификацию официального приложения VK под Android, но там я просто патчил байт-код, содержащийся в .class-файле. Теперь мы сделаем примерно то же самое, но на лету, без внесения изменений в файлы.

Начну с небольшой предыстории, зачем мне это вообще понадобилось (многие из моих статей все-таки связаны с какой-то реальной проблемой, которая у меня внезапно возникла). Итак: мне написал человек и попросил "помочь" с программой Lazy SSH. На первый взгляд программа ничем не выделялась, обычный .exe, определяемый PEiD, как: Microsoft Visual C++ 6.0 [Overlay]. Однако, наличие секции экспортов у исполняемого файла меня насторожило, а ее содержимое подсказало, с чем я имею дело: все экспортируемые функции обладали префиксом _Java_com_regexlab_j2e_, который недвусмысленно намекает, что программа на Java была чем-то преобразована в .exe. Google подсказал, что для этого был использован Jar2Exe от RegExLab. Также Google подсказал способ получения .jar-файла из исполняемого файла, по крайней мере для этого случая. Перейдем к делу.


Поместим файл e2j-agent.jar в директорию с Lazy SSH и установим переменную окружения в соответствии с мануалом:

Теперь запускаем Lazy SSH. Мы получили .jar-файл. Беглый просмотр содержимого в JD-GUI показывает, что файл дополнительно ничем не защищен и реализация лицензионного механизма довольно тривиальна.

jd-gui
Сначала возникает мысль поправить .jar-файл, но по неизвестной мне причине (я не знаток Java, да и разбираться не было желания), этот файл не запускается, выдавая ошибку: Error: Invalid or corrupt jarfile.

Что ж, давайте воспользуемся тем же способом, что и e2j, который, судя по всему, имеет доступ к исполняемому байт-коду. Способ называется Java Agents и эта небольшая статья описывает необходимые основы для использования его в своих целях. Теперь нам нужно понять, как именно мы будем править байт-код. Пролистав содержимое метода formWindowOpened, можно отметить, что его квинтэссенция состоит в выполнении этих строк при удачной проверке лицензии:

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

tail
Теперь давайте определим байт-код, соответствующий интересующим нас строкам кода. Мы его используем для замены кода в хвосте метода. Для определения я использовал IDA, хотя можно было бы воспользоваться, например, radare2.

ida

Несколько минут, и у нас есть следующие соответствия:

А заменяемому фрагменту кода:

Соответствует следующая последовательность:

Переходим к написанию кода, который произведет такую замену. Нам понадобится класс, содержащий метод premain (как описано в статье, на которую я ссылался выше):

И еще один класс, реализующий интерфейс ClassFileTransformer:

На мой взгляд код не нуждается в особом комментировании, но идея такова: в методе transform мы проверяем имя загружаемого класса, далее ищем сигнатуру, соответствующую байт-коду в конце интересующего нас метода, если находим, то производим замену и возвращаем измененный байт-код. Ну а hexToByteArray, findPattern, computeFailure - это вообще вспомогательные методы, взятые из Google. Как вы могли заметить, байт-код, которым заменяется оригинальный код, дополнен нулями, 0x00 - это NOP для JVM (список инструкций JVM), конечно, можно было бы поставить RET (0xB1), но опять же, во время тестов что-то не срослось и я решил оставить замену в текущем виде.

Еще нам потребуется Manifest для нашего jar-файла:

Кстати, оказывается, в конце манифеста должна быть пустая строка, минут 10 потратил на проблему, почему мой .jar-файл не подгружается.
Нам осталось только собрать получившийся код, сделать из него .jar-файл и протестировать его работоспособность. Собираем, например, следующим образом:

source и target - для совместимости с более ранними версиями Java (так как я собирал с помощью JDK 8), директория jagent содержит .java-файлы, состоящие из кода, который я привел выше, ну а manifest.txt, естественно, содержит текст манифеста.
Протестируем получившийся Hook.jar, поместив его в директорию с программой Lazy SSH. Для удобства запуска я сделал простой .bat-файл:

Запускаем и видим, что ошибки исчезли, а также поля, позволяющие задавать количество потоков, стали разблокированными, как и при наличии действующей лицензии. Mission accomplished.

Скачать: исходный код из статьи + Lazy SSH.

 Обсудить на форуме


Получать обновления на почту:     

Метки: , , , .

Комментариев: 51 к “Патчим байт-код Java на лету”


  1. Artur :

    Привет Кайми. Помоги пожалуйста с заданием если сможешь. Это задание по программированию в институте. Дали задание: Выполнять можно на java и ruby. Я выбрал ruby, и изучил. Сделал задание, но не получилось. Вот само задание: Написать функцию, которая на вход принимает два футбольных счета - тот который загадал клиент когда делал ставку и реальный результат футбольного матча (то как сыграли команды на самом деле). На выходе нужно получить:
    2 - если клиент полностью угадал счет
    1 - если клиент угадал какая команда победит или угадал ничью
    0 - если не угадал ничего
    Вот код на ruby и итерпретатор ideone: http://ideone.com/v2GjFv и на pastebin: http://pastebin.com/UByWmPyQ Вот рецензия препода: В условиях просили написать функцию. В условиях ни слова про "коэффициент". Это не решение этой задачи. Я не совсем понимаю что имеется в виду под функцией? Извини что не по теме тут пишу.(

    [Ответить]

    Kaimi:

    http://www.howtogeek.com/howto/programming/ruby/ruby-function-method-syntax/

    А я не понимаю, что значит клиент угадал ничью, если на вход подаются два футбольных счета.
    В текущей постановке угадать ничью можно только если полностью угадаешь счет.

    [Ответить]

    Artur:

    Здравствуй. Сможешь показать на пастбине как сделать задание если время найдется?мне только начало покажи как делать. Какое нибудь одно задание на примере покажи(и на ruby и на java), что-то я не могу построить код, хоть и посмотрел про функцию.

    [Ответить]

    Kaimi:

    Я уже сказал, что не понимаю, что требуется исходя из постановки задания. Функция на вход должна принимать счет, если нет специального кейса для ничьей, то угадать ничью = угадать полностью счет. А какие-нибудь абстрактные задания найдутся в гугле:
    http://www.tutorialspoint.com/ruby/ruby_methods.htm
    https://rubymonk.com/learning/books/1-ruby-primer/chapters/19-ruby-methods/lessons/69-new-lesson
    ...


  2. Artur :

    Привет еще раз. Извини что беспокою 3 раз:) Но вот что ответил препод когда я ему написал что ты мне писал: не понимаю, что значит клиент угадал ничью, если на вход подаются два футбольных счета. Вот что написал препод: 1:1 2:2
    угадал ничью.

    [Ответить]

    Kaimi:

    Цитирую:
    >>На выходе нужно получить:
    >>2 - если клиент полностью угадал счет
    >>1 - если клиент угадал какая команда победит или угадал ничью
    >>0 - если не угадал ничего

    Как можно ставить на победу я еще понимаю - указал у одной из команд больший счет.
    Как поставить на ничью в данном случае?
    Предсказать счет, например, 1:1? Если да, то почему тогда это считается угадыванием ничьей, а не полным угадыванием счета?

    [Ответить]

    Artur:

    В этой задачи как я понимаю нет определенных рамок с условиями на примере ничьей. В принципе можно считать ничью, полным угадыванием счета, я так думаю. Цитирую с ваших слов: Как можно ставить на победу я еще понимаю - указал у одной из команд больший счет. Покажите пожалуйста на примере(на js или ruby) хотя-бы это если вас не затруднит.

    [Ответить]

    Kaimi:

    http://pastebin.com/i18YvgEF
    Сильно легче?

    Artur:

    *Забыл дописать. Полное угадывание, так как нам уже известен счет 1:1. Ведь так?

    [Ответить]

    Artur:

    Большое спасибо за помощь!:) все правильно сделано по заданию. Вот только мне препод сказал что это класс, а надо функцию чтоб восстановить исходные условия задачи. Я попробовал вместо класса функцию перевести, но не получилось(пишет ошибки что нужна константа) что там с константой делать надо, куда её поставить?

    [Ответить]

    Artur:

    *Вот забыл дописать то что я изменял: https://ideone.com/jvWLRn

    Kaimi:

    http://pastebin.com/YNhwtQVa


Оставьте ваш комментарий