Цель этой статьи — предупредить разработчиков о бесполезности использования обфускаторов шифрования файлов классов для защиты приложений и о бесполезности расходования средств на них.
Все методы защиты описаны в фундаментальной работе Дмитрия Лескова — Защити свой 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 kinash@licel.ru
*/
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