Патчим байт-код 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.

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

  1. Помоги взломать стим, у меня не выходит в той статье которой ты это делал через плагины в mozila , помоги пожалуйста, при покупке через вебмоней ты взламывал

  2. Большое спасибо за статью.
    Но скачав Lazy SSH. Запустив его, начал проверять аккаунт, и вылезли ошибки, мол типа аккаунт не корректный, ну и естественно треды не разморозились.

    1. А какие были ожидания? В конце статьи приложен оригинальный Lazy SSH. Чтобы его запустить в "особом" режиме необходимо в соответствии со статьей воспроизвести все этапы (кроме написания кода).

  3. Привет.

    Собрал jar, кинул его в каталог с lazy, пробую запустить батник появляется черное окно консоли и сразу же закрывается. При этом запуск lazy не происходит. Можешь подсказать почему так происходит?

  4. Не могу почему то ответить на коммент.
    Открыть cmd.exe и написать сначала set JAVA_TOOL_OPTIONS=-javaagent:Hook.jar
    , а потом "путь\Lazy SSH.exe"?

    1. В статье Hook.jar и Lazy SSH находились в одной директории, плюс текущая рабочая директория в cmd была той же.
      В противном случае видимо везде пути нужны полные.

  5. Пробовал по-разному писать в батнике пути:

    и как у вас в статье
    и с полным путем

    Сам lazy запускается только если в батнике написано вот так:

    start "" "полный_путь\Lazy SSH.exe"

    А со строкой
    set JAVA_TOOL_OPTIONS=-javaagent:Hook.jar
    уже не запускается.

    Еще пробовал первую строку писать так:
    set JAVA_TOOL_OPTIONS=-javaagent:путь\Hook.jar
    Но наверно это не правильно

  6. Как этот jar собрать из java файлов?
    Пробовал с помощью Java Development Kit через командную строку, но никак не могу понять, как перепрыгнуть на новую строку с пустым полем... Нажимаю Enter - копируется старая строка. А вот, чтобы так получилось (http://pad3.whstatic.com/images/thumb/3/31/Create-JAR-File-Step-5.jpg/670px-Create-JAR-File-Step-5.jpg), какую команду надо юзать?

  7. Ну вот я компилирую жабу в jar по этой инструкции
    http://ru.wikihow.com/создать-JAR-файл
    Для компиляции используется Java Development Kit (JDK) и командная строка.
    Инструкция более, чем понятна, но не могу понять, как сделать эти пути в одной и той же консоли.
    p.s. гугл не помог.

    1. Если задача поставить JDK в переменные окружения и вызвать компилятор или просто скопировать java-файлы в папку с JDK и оттуда скомпилировать - это сверхсложно, то статья явно не рассчитана на такой уровень подготовки читателя.

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

  8. Kaimi подскажите с вами есть какая то связь, хотелось бы предложить вам небольшую работу за вознаграждение.

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

  10. Да не то чтобы приспичило, я готов подождать, главное скажи что ты его скинешь))) Есть у кого получилось что и у Kaimi ??

  11. Kaimi у меня вопрос есть. Можно на javascript написать спамбот как на basic? вот на подобии такого: Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    Timer1.Start()
    Timer1.Enabled = True
    End Sub

    Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
    Timer1.Stop()
    Timer1.Enabled = False
    End Sub

    Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick
    Timer1.Interval = TextBox4.Text
    If CheckBox1.Checked Then
    SendKeys.Send(TextBox1.Text)
    SendKeys.Send("({enter})")
    Я не знаю как реализовать на java:( Заранее большое спасибо за ответ.

    1. Java != JavaScript. Без разницы на чем писать, если есть возможность использовать необходимые вещи. Если весь смысл бота на VB сводился к вставке текста в форму и нажатии клавиши enter, то для этого даже готовый софт найти можно настраиваемый.

      1. Ок. Ну я обобщил про Java и enter:D Мне это надо на смартфон андроид реализовать. Просто готовые работы в play markete не работают на новой os marshmallow 6.0.1:( Вот я и хочу приложение для андроид написать на java.

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

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

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

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

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

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

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

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

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

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *