В то время, пока 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 и установим переменную окружения в соответствии с мануалом:
1 |
set JAVA_TOOL_OPTIONS=-javaagent:e2j-agent.jar |
Теперь запускаем Lazy SSH. Мы получили .jar-файл. Беглый просмотр содержимого в JD-GUI показывает, что файл дополнительно ничем не защищен и реализация лицензионного механизма довольно тривиальна.
Сначала возникает мысль поправить .jar-файл, но по неизвестной мне причине (я не знаток Java, да и разбираться не было желания), этот файл не запускается, выдавая ошибку: Error: Invalid or corrupt jarfile.
Что ж, давайте воспользуемся тем же способом, что и e2j, который, судя по всему, имеет доступ к исполняемому байт-коду. Способ называется Java Agents и эта небольшая статья описывает необходимые основы для использования его в своих целях. Теперь нам нужно понять, как именно мы будем править байт-код. Пролистав содержимое метода formWindowOpened, можно отметить, что его квинтэссенция состоит в выполнении этих строк при удачной проверке лицензии:
1 2 3 4 5 6 |
this.lblStatus1.setText("OK."); this.isUser = true; this.txtThreads1.setEnabled(true); this.txtThreads2.setEnabled(true); in.close(); return; |
Причем на первую и предпоследнюю строки можно в общем-то забить. Возникает вопрос, как перейти к их выполнению, не производя модификации кучи условных переходов. Лирическое отступление: сначала я хотел скопировать байт-код, соответствующий этим строкам, в начало метода, но что-то постоянно не работало, поэтому я поступил немного иначе.
Если мы попытаемся запустить программу, то у нас появится несколько окон с сообщениями об ошибке. Код, отвечающий за них, расположен в самом конце метода:
Теперь давайте определим байт-код, соответствующий интересующим нас строкам кода. Мы его используем для замены кода в хвосте метода. Для определения я использовал IDA, хотя можно было бы воспользоваться, например, radare2.
Несколько минут, и у нас есть следующие соответствия:
1 2 3 |
this.isUser = true; // 2A04B50041 this.txtThreads1.setEnabled(true); // 2AB4000404B6015E this.txtThreads2.setEnabled(true); // 2AB4002704B6015E |
А заменяемому фрагменту кода:
1 2 3 4 5 6 7 8 9 |
catch (Exception ex) { JOptionPane.showMessageDialog(this.rootPane, "Something wrong :v", "Error", 0); } if (!this.isUser) { this.lblStatus1.setText("Wrong account !"); JOptionPane.showMessageDialog(this.rootPane, "Wrong account !", "Lazy SSH", 2); } |
Соответствует следующая последовательность:
1 |
2AB4001013022B1301E003B801E22AB400419A001B2AB4000D13022CB601222AB4001013022C13021D05B801E2 |
Переходим к написанию кода, который произведет такую замену. Нам понадобится класс, содержащий метод premain (как описано в статье, на которую я ссылался выше):
1 2 3 4 5 6 7 8 9 10 11 12 13 |
package jagent; import java.lang.instrument.Instrumentation; public class Hook { public static void premain(String agentArguments, Instrumentation instrumentation) { System.out.println("Hook start"); Transformer transformer = new Transformer(); instrumentation.addTransformer(transformer); } } |
И еще один класс, реализующий интерфейс ClassFileTransformer:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 |
package jagent; import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.IllegalClassFormatException; import java.security.ProtectionDomain; public class Transformer implements ClassFileTransformer { public byte[] transform ( ClassLoader loader, String className, Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer ) throws IllegalClassFormatException { if(className.equals("lazy/ssh/MainForm")) { System.out.println("Found desired class"); byte[] methodInternSign = hexToByteArray("2AB4001013022B1301E003B801E22AB400419A001B2AB4000D13022CB601222AB4001013022C13021D05B801E2"); byte[] passLicenseCheck = hexToByteArray("2A04B50041002AB4000404B6015E002AB4002704B6015E00000000000000000000000000000000000000000000"); int signatureIndex = findPattern(classfileBuffer, methodInternSign); if(signatureIndex != -1) { System.out.println("Found method internals signature"); for(int i = 0; i < passLicenseCheck.length; i++) { System.out.println ( String.format("%02X ", classfileBuffer[signatureIndex + i]) + "->" + String.format("%02X ", passLicenseCheck[i]) ); classfileBuffer[signatureIndex + i] = passLicenseCheck[i]; } System.out.println("Method has been fixed"); } } return classfileBuffer; } public static byte[] hexToByteArray(String s) { int len = s.length(); byte[] data = new byte[len / 2]; for (int i = 0; i < len; i += 2) data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i+1), 16)); return data; } public static int findPattern(byte[] data, byte[] pattern) { int[] failure = computeFailure(pattern); int j = 0; for (int i = 0; i < data.length; i++) { while (j > 0 && pattern[j] != data[i]) j = failure[j - 1]; if (pattern[j] == data[i]) j++; if (j == pattern.length) return i - pattern.length + 1; } return -1; } private static int[] computeFailure(byte[] pattern) { int[] failure = new int[pattern.length]; int j = 0; for (int i = 1; i < pattern.length; i++) { while (j>0 && pattern[j] != pattern[i]) j = failure[j - 1]; if (pattern[j] == pattern[i]) j++; failure[i] = j; } return failure; } } |
На мой взгляд код не нуждается в особом комментировании, но идея такова: в методе transform мы проверяем имя загружаемого класса, далее ищем сигнатуру, соответствующую байт-коду в конце интересующего нас метода, если находим, то производим замену и возвращаем измененный байт-код. Ну а hexToByteArray, findPattern, computeFailure - это вообще вспомогательные методы, взятые из Google. Как вы могли заметить, байт-код, которым заменяется оригинальный код, дополнен нулями, 0x00 - это NOP для JVM (список инструкций JVM), конечно, можно было бы поставить RET (0xB1), но опять же, во время тестов что-то не срослось и я решил оставить замену в текущем виде.
Еще нам потребуется Manifest для нашего jar-файла:
1 2 |
Manifest-Version: 1.0 Premain-Class: jagent.Hook |
Кстати, оказывается, в конце манифеста должна быть пустая строка, минут 10 потратил на проблему, почему мой .jar-файл не подгружается.
Нам осталось только собрать получившийся код, сделать из него .jar-файл и протестировать его работоспособность. Собираем, например, следующим образом:
1 2 |
javac -source 1.6 -target 1.6 ./jagent/*.java jar cvfm Hook.jar manifest.txt ./jagent/*.class |
source и target - для совместимости с более ранними версиями Java (так как я собирал с помощью JDK 8), директория jagent содержит .java-файлы, состоящие из кода, который я привел выше, ну а manifest.txt, естественно, содержит текст манифеста.
Протестируем получившийся Hook.jar, поместив его в директорию с программой Lazy SSH. Для удобства запуска я сделал простой .bat-файл:
1 2 3 |
@echo off set JAVA_TOOL_OPTIONS=-javaagent:Hook.jar start "" "Lazy SSH.exe" |
Запускаем и видим, что ошибки исчезли, а также поля, позволяющие задавать количество потоков, стали разблокированными, как и при наличии действующей лицензии. Mission accomplished.
Скачать: исходный код из статьи + Lazy SSH.
Рад нескольким вещам, что блог не пустует и как всегда радует интересными статьями. И ещё, что упомянули radare2 :) Thx! Есть отдельная презентация, посвященная анализу Java - http://radare.org/get/radare_java.pdf
В таком духе побольше бы статей. Спасибо.
Дай тебе бог здоровья, Каими. С рождеством
Спасибо, дядя
Четко
http://www.youtube.com/watch?v=o7uDERvtqhc
Это кто там такой тебя просил, что ты так углубился в проблему? (=
Кто-то. Имена не сообщают, да и я все равно не запоминаю.
Помоги взломать стим, у меня не выходит в той статье которой ты это делал через плагины в mozila , помоги пожалуйста, при покупке через вебмоней ты взламывал
Это было актуально 5 лет назад.
Большое спасибо за статью.
Но скачав Lazy SSH. Запустив его, начал проверять аккаунт, и вылезли ошибки, мол типа аккаунт не корректный, ну и естественно треды не разморозились.
А какие были ожидания? В конце статьи приложен оригинальный Lazy SSH. Чтобы его запустить в "особом" режиме необходимо в соответствии со статьей воспроизвести все этапы (кроме написания кода).
Благодарю, Kaimi.
Помоги разобраться,скачал,так же вылезли ошибки, какие этапы надо воспроизвести,непонял (
Код собрать в jar, сделать bat-файл и запустить...
Привет.
Собрал jar, кинул его в каталог с lazy, пробую запустить батник появляется черное окно консоли и сразу же закрывается. При этом запуск lazy не происходит. Можешь подсказать почему так происходит?
Не могу, надо через консоль запускать и смотреть, что происходит
Не могу почему то ответить на коммент.
Открыть cmd.exe и написать сначала set JAVA_TOOL_OPTIONS=-javaagent:Hook.jar
, а потом "путь\Lazy SSH.exe"?
В статье Hook.jar и Lazy SSH находились в одной директории, плюс текущая рабочая директория в cmd была той же.
В противном случае видимо везде пути нужны полные.
Пробовал по-разному писать в батнике пути:
и как у вас в статье
и с полным путем
Сам lazy запускается только если в батнике написано вот так:
start "" "полный_путь\Lazy SSH.exe"
А со строкой
set JAVA_TOOL_OPTIONS=-javaagent:Hook.jar
уже не запускается.
Еще пробовал первую строку писать так:
set JAVA_TOOL_OPTIONS=-javaagent:путь\Hook.jar
Но наверно это не правильно
Может быть, в любом случае я не знаток Java, может есть какие-то особенности...
Как этот 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), какую команду надо юзать?
Google -> как скомпилировать java в jar
@Kaimi, я нашел как скомпилировать java в jar. У меня вопрос совсем в другом.
Как сделать, чтобы с каждой новой строки были новые пути, как на скришоте?
http://pad3.whstatic.com/images/thumb/3/31/Create-JAR-File-Step-5.jpg/670px-Create-JAR-File-Step-5.jpg
Я не понимаю, что значит новые пути с каждой строки. Может человек набрал путь в консоли, нажал enter и сделал скриншот.
Ну вот я компилирую жабу в jar по этой инструкции
http://ru.wikihow.com/создать-JAR-файл
Для компиляции используется Java Development Kit (JDK) и командная строка.
Инструкция более, чем понятна, но не могу понять, как сделать эти пути в одной и той же консоли.
p.s. гугл не помог.
Если задача поставить JDK в переменные окружения и вызвать компилятор или просто скопировать java-файлы в папку с JDK и оттуда скомпилировать - это сверхсложно, то статья явно не рассчитана на такой уровень подготовки читателя.
Ну да, я не АС в программировании.
Но и ваша статья уж извините не рассчитана на опытных реверсеров =))
Программирование и умение пользоваться консолью, как бы сравнить... что-то из разряда заявления, что надо быть асом программирования, чтобы догадаться перезагрузить зависший компьютер.
Kaimi подскажите с вами есть какая то связь, хотелось бы предложить вам небольшую работу за вознаграждение.
В разделе О Блоге все указано
Kaimi подскажи плз, или тот у кого получилось, вообще не понятно что делаем с самого начала. Если можно как то получить сделанный тобой софт, скинь плз, не мучай народ))))
Идешь на exelab и заказываешь отвязку софта, если приспичило.
Да не то чтобы приспичило, я готов подождать, главное скажи что ты его скинешь))) Есть у кого получилось что и у Kaimi ??
У меня его нет. Я сделал то, что хотел, написал статью и удалил.
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:( Заранее большое спасибо за ответ.
Java != JavaScript. Без разницы на чем писать, если есть возможность использовать необходимые вещи. Если весь смысл бота на VB сводился к вставке текста в форму и нажатии клавиши enter, то для этого даже готовый софт найти можно настраиваемый.
Ок. Ну я обобщил про Java и enter:D Мне это надо на смартфон андроид реализовать. Просто готовые работы в play markete не работают на новой os marshmallow 6.0.1:( Вот я и хочу приложение для андроид написать на java.
Написать никто не мешает, только в случае с андроид вряд ли на уровне нажатия кнопок, в плане там вроде нельзя запросто взять и послать комбинацию клавиш другому приложению. А написать свой софт, который будет взаимодействовать по сети с каким-нибудь ресурсом - не проблема. Конкретное что-то не подскажу, под андроид не писал.
Привет Кайми. Помоги пожалуйста с заданием если сможешь. Это задание по программированию в институте. Дали задание: Выполнять можно на java и ruby. Я выбрал ruby, и изучил. Сделал задание, но не получилось. Вот само задание: Написать функцию, которая на вход принимает два футбольных счета - тот который загадал клиент когда делал ставку и реальный результат футбольного матча (то как сыграли команды на самом деле). На выходе нужно получить:
2 - если клиент полностью угадал счет
1 - если клиент угадал какая команда победит или угадал ничью
0 - если не угадал ничего
Вот код на ruby и итерпретатор ideone: http://ideone.com/v2GjFv и на pastebin: http://pastebin.com/UByWmPyQ Вот рецензия препода: В условиях просили написать функцию. В условиях ни слова про "коэффициент". Это не решение этой задачи. Я не совсем понимаю что имеется в виду под функцией? Извини что не по теме тут пишу.(
http://www.howtogeek.com/howto/programming/ruby/ruby-function-method-syntax/
А я не понимаю, что значит клиент угадал ничью, если на вход подаются два футбольных счета.
В текущей постановке угадать ничью можно только если полностью угадаешь счет.
Здравствуй. Сможешь показать на пастбине как сделать задание если время найдется?мне только начало покажи как делать. Какое нибудь одно задание на примере покажи(и на ruby и на java), что-то я не могу построить код, хоть и посмотрел про функцию.
Я уже сказал, что не понимаю, что требуется исходя из постановки задания. Функция на вход должна принимать счет, если нет специального кейса для ничьей, то угадать ничью = угадать полностью счет. А какие-нибудь абстрактные задания найдутся в гугле:
http://www.tutorialspoint.com/ruby/ruby_methods.htm
https://rubymonk.com/learning/books/1-ruby-primer/chapters/19-ruby-methods/lessons/69-new-lesson
...
Привет еще раз. Извини что беспокою 3 раз:) Но вот что ответил препод когда я ему написал что ты мне писал: не понимаю, что значит клиент угадал ничью, если на вход подаются два футбольных счета. Вот что написал препод: 1:1 2:2
угадал ничью.
Цитирую:
>>На выходе нужно получить:
>>2 - если клиент полностью угадал счет
>>1 - если клиент угадал какая команда победит или угадал ничью
>>0 - если не угадал ничего
Как можно ставить на победу я еще понимаю - указал у одной из команд больший счет.
Как поставить на ничью в данном случае?
Предсказать счет, например, 1:1? Если да, то почему тогда это считается угадыванием ничьей, а не полным угадыванием счета?
В этой задачи как я понимаю нет определенных рамок с условиями на примере ничьей. В принципе можно считать ничью, полным угадыванием счета, я так думаю. Цитирую с ваших слов: Как можно ставить на победу я еще понимаю - указал у одной из команд больший счет. Покажите пожалуйста на примере(на js или ruby) хотя-бы это если вас не затруднит.
http://pastebin.com/i18YvgEF
Сильно легче?
*Забыл дописать. Полное угадывание, так как нам уже известен счет 1:1. Ведь так?
Большое спасибо за помощь!:) все правильно сделано по заданию. Вот только мне препод сказал что это класс, а надо функцию чтоб восстановить исходные условия задачи. Я попробовал вместо класса функцию перевести, но не получилось(пишет ошибки что нужна константа) что там с константой делать надо, куда её поставить?
*Вот забыл дописать то что я изменял: https://ideone.com/jvWLRn
http://pastebin.com/YNhwtQVa