Цель этой статьи — предупредить разработчиков о бесполезности использования обфускаторов шифрования файлов классов для защиты приложений и о бесполезности расходования средств на них.
Все методы защиты описаны в фундаментальной работе Дмитрия Лескова — Защити свой Java-код — через обфускаторы и не только .
Когда мы используем метод шифрования файлов класса, мы предполагаем, что байт-код зашифрован, и когда приложение запускает дешифрованный байт-код через пользовательский ClassLoader или JVMTI-интерфейс загружается в JVM.
Как обойти этот вид защиты вы можете найти в статье Дмитрия Лескова, но сегодня есть некоторые продукты, которые имеют собственный компонент, который взаимодействует с JVM и отслеживает режим отладки и / или неавторизованные агенты. Но, несмотря на все заверения разработчиков, эти продукты совсем не защищают ваш байт-код.
Чтобы показать вам уязвимость всех обфускаторов, которые шифруют классы, мы предпримем некоторые шаги.
Прежде всего, мы должны запустить зашифрованное приложение с параметром «-XX: + TraceClassLoading» и быть уверенным, что все зашифрованные файлы классов видны на этом уровне трассировки.
Загрузка исходных кодов OpenJDK и исправление его для сброса зашифрованных классов в файл.
Для эксперимента мы используем Debian Linux 6.0.5 (стабильная версия) и исходный пакет OpenJDK7. Если вы используете другую платформу, пожалуйста, смотрите OpenJDK Build README для инструкций о том, как собрать OpenJDK.
Чтобы свести к минимуму количество модификаций, если установлена опция -XX: + TraceClassLoading, JDK будет выгружать все загруженные классы в файл $ WORKDIR / classes.dump. Структура файла дампа:
{ int lengthClassName, byte[] className, int lengthByteCode, byte[] bytecode }, { next record … }, …
Давайте подготовим среду для сборки:
# apt-get install openjdk-6-jdk # apt-get build-dep openjdk-6
Затем мы должны загрузить исходные коды OpenJDK и наш патч , который добавит следующий код в функцию ClassFileParser :: parseClassFile в hotspot / src / share / vm / classfile / classFileParser.cpp:
// dumping class bytecode // dump file format: // length of the class name - 4 bytes // class name // length of the class bytecode - 4 bytes // byte code // ... next class ... ClassFileStream* cfs = stream(); FILE * pFile; int length = cfs->length(); int nameLength = strlen(this_klass->external_name()); pFile = fopen("classes.dump","ab"); // size of the class name fputc((int)((nameLength >> 24) & 0XFF), pFile ); fputc((int)((nameLength >> 16) & 0XFF), pFile ); fputc((int)((nameLength >> 8) & 0XFF), pFile ); fputc((int)(nameLength & 0XFF), pFile ); // class name fwrite (this_klass->external_name() , 1, nameLength, pFile ); // size of the class bytecode fputc((int)((length >> 24) & 0XFF), pFile ); fputc((int)((length >> 16) & 0XFF), pFile ); fputc((int)((length >> 8) & 0XFF), pFile ); fputc((int)(length & 0XFF), pFile ); // class bytecode fwrite (cfs->buffer() , 1 , length, pFile ); fclose(pFile);
Давайте попробуем собрать неизмененный JDK, чтобы убедиться, что он собирается правильно:
# export LANG=C ALT_BOOTDIR=/usr/lib/jvm/java-6-openjdk ALLOW_DOWNLOADS=true # make sanity && make
Примените патч и начните сборку:
# cd $OPENJDK_SRC # patch -p1 < $PATH_TO_PATCH_FILE # make
Затем измените dir на bin встроенной JVM: $ OPENJDK_SRC / build / linux-i586 / j2re-image / bin /
Чтобы проверить работу исправленного JRE, запустите java с -XX: + TraceClassLoading:
# ./java -XX:+TraceClassLoading
Если все правильно, вы увидите файл classes.dump со всеми классами, которые JRE загружает в начальной фазе.
А теперь самый интересный момент. Загрузите зашифрованное приложение, например, пробную версию обфускатора с классом шифрования. Я не буду упоминать по понятным причинам конкретные имена, достаточно найти в Google ключ «шифрование байт-кода».
Допустим, у нас есть SomeClassGuard.jar. Внутри SomeClassGuard.jar в иерархии com / **** / someclassguard / engine находятся файлы, зашифрованные классом, вы можете увидеть это сами, используя любой декомпилятор, или посмотреть заголовок файла в HEX-viewer.
Запустите SomeClassGuard.jar:
# ./java -XX:+TraceClassLoading -jar SomeClassGuard.jar
Хорошо, теперь у нас есть classes.dump, но нам нужно проанализировать этот файл. Давайте напишем Java-программу для этого конкретного случая:
package openjdkmod; import java.io.DataInputStream; import java.io.EOFException; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; /** * Classes dump format extractor class. * Author Ivan Kinash [email protected] */ public class ClassesDumpExractor { /** * Extract contents classes.dump to specified dir */ public static void main(String[] args) throws FileNotFoundException, IOException { if (args.length != 2) { System.err.println("Usage openjdkmod.ClassesDumpExtractor "); System.exit(-1); } File classesDumpFile = new File(args[0]); if (!classesDumpFile.exists()) { System.err.println("Source file: " + args[0] + " not found!"); System.exit(-1); } File outDir = new File(args[1]); if (!outDir.exists()) { outDir.mkdirs(); } DataInputStream din = new DataInputStream(new FileInputStream(classesDumpFile)); while (true) { try { int classNameLength = din.readInt(); byte[] classNameBytes = new byte[classNameLength]; din.readFully(classNameBytes); String className = new String(classNameBytes); System.out.println("className:" + className); int classLength = din.readInt(); byte[] classBytes = new byte[classLength]; din.readFully(classBytes); File parentDir = className.indexOf(".")>0?new File(outDir, className.substring(0,className.lastIndexOf(".")).replace(".", File.separator)):outDir; if(!parentDir.exists()) parentDir.mkdirs(); File outFile = new File(parentDir, (className.indexOf(".")>0?className.substring(className.lastIndexOf(".")+1):className)+".class"); FileOutputStream outFos = new FileOutputStream(outFile); outFos.write(classBytes); outFos.close(); } catch (EOFException e) { din.close(); return; } } } }
Давайте выполним скомпилированный класс следующим образом:
# java openjdkmod.ClassesDumpExractor classes.dump dump_directory
На выходе мы получим каталог с расшифрованными файлами классов.
Вывод
Классовое шифрование бессмысленно, опасно и дорогое занятие.
Если вы хотите защитить свой код, есть несколько способов сделать это:
- Использовать байт-код Java для компиляторов собственного кода
- Объедините классический обфускатор с обфускаторами шифрования строк
Для обеспечения максимальной безопасности используйте внешние устройства, которые поддерживают безопасное хранение и выполнение внутреннего байт-кода.
Вы можете использовать метод, описанный выше, для отладки различных приложений, когда вы хотите выяснить, какой байт-код загружается в JVM во время выполнения.
Примечание 1 : Вы можете достичь того же результата без какой-либо модификации JDK, используя класс sun.misc.Unsafe, если знаете о методах хранения классов внутри JVM.
Примечание 2 : Автор не несет ответственности за неправильное использование информации из статьи.
Примечание 3 : Оригинальный источник изображения: http://it.wikipedia.org/wiki/File:Netbeans-Duke.png