Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
Реверс‑инжиниринг написанных на Java приложений, если разработчики не позаботились заранее об их защите, обычно не представляет трудностей. Именно чтобы усложнить жизнь хакерам и исследователям, используются различные инструменты, один из которых — защита с аппаратным ключом Sentinel. Сегодня мы рассмотрим способ обхода такой защиты.
Тем, кто читал мои предыдущие статьи, посвященные защите приложений на Java, известно самое слабое место такой защиты: прозрачность байт‑кода и невыносимая легкость восстановления его до исходников.
Поэтому разработчики таких инструментов стремятся как можно хитрее спрятать код от любопытных глаз хакеров, используя различные приемы: обфускацию, компиляцию в натив, шифрование. Наглядный пример последнего мы начали разбирать в статье «Беззащитная Java. Ломаем Java bytecode encryption». Тогда мы остановились на инлайн‑патчинге расшифрованного байт‑кода прямо из библиотеки агента JVMTI. Сегодня мы продолжим эту тему и научимся патчить непосредственно шифрованный код.
В качестве примера мы возьмем приложение, защищенное Sentinel Licensing API Java Class Library — специальной библиотекой защиты Java-классов, поставляемой к ключам типа Hasp. Почитать о том, как устроена эта библиотека, можно на сайте разработчика. Само приложение защищено ключом Hasp, в его рабочем каталоге присутствуют характерные для Sentinel файлы hasp_rt.exe
, hasp_windows_x64_34344.dll
, haspvlib_34344.dll
и специфические для Java HASPJava.dll
, HASPJava_x64.dll
, sntljavaclsrt.dll
, sntljavaclsrt_x64.dll
. Detect It Easy предсказуемо указывает на NetHASP dongle reference и Hardlock dongle reference.
Мы по опыту знаем, что ломать эти библиотеки «в лоб» лучше даже не пытаться, они весьма сурово виртуализованы и защищены от отладки и модификации. Поэтому смотрим на компилированные классы, содержащиеся внутри JAR. Там тоже все мрачно: кроме главного класса, все остальные пошифрованы с высокой энтропией, примерно как описано в статье «Беззащитная Java. Ломаем Java bytecode encryption». За исключением того, что криптор подключается не как JavaAgent при запуске приложения, а из главного класса, по сути представляющего собой загрузчик приложения c единственно открытым кодом:
public static void main(String[] stringArray) {try { String string; String string2 = System.getProperty("java.class.path"); int n = Math.max(string2.lastIndexOf(""), string2.lastIndexOf("/")); String string3 = string = string2.substring(0, ++n); if (JavaClsEntry.isWindows()) { string3 = string3 + "sntljavaclsrt"; if (0 == System.getProperty("sun.arch.data.model").compareTo("64")) { string3 = string3 + "_x64"; } string3 = string3 + ".dll"; } else if (JavaClsEntry.isLinux()) { string3 = string3 + "libsntljavaclsrt_x86_64.so"; } else { return; } File file = new File(string3); string3 = file.getAbsolutePath(); System.load(string3); Class<?> clazz = Class.forName("com.MainApp"); Method method = clazz.getMethod("main", String[].class); method.invoke(null, new Object[]{stringArray});}catch (Exception exception) { exception.printStackTrace();}
Загрузчик ничего не делает, кроме как загружает сентинеловскую нативную библиотеку защиты, а затем главный класс программы. Если ключ не в порядке, то главный класс попросту не расшифровывается и приложение рушится по неправильному формату байт‑кода класса. Собственно, даже такую ошибку можно получить только после того, как библиотека выдаст окошко отсутствия ключа.
Как же нам теперь быть? Ведь, поскольку здесь не происходит прямая подмена JIT-компилятора через JavaAgent, предложенный в упомянутой выше статье способ не годится. Попробуем подключиться отладчиком непосредственно к JIT-компилятору. Немного покурив документацию, обнаруживаем в библиотеке JVM.DLL
такую функцию:
JVM_DefineClassWithSource(JNIEnv *env, const char *name, jobject loader, const jbyte *buf, jsize len, jobject pd, const char *source)
Как видно из описания, эта функция компилирует класс из массива байт‑кода. Длина массива, название класса и даже исходного Java-модуля прилагается. При наличии ключа, установив в нашем любимом отладчике x64dbg точку останова на вход этой функции, мы получаем в регистре R9
расшифрованный класс, начиная с сигнатуры CAFEBABE
, и его длину в регистре R14
. В итоге его можно легко сдампить на диск следующей командой:
savedata MainApp.class, R9, R14
Источник: xakep.ru