Как защитить java приложение от декомпиляции
Разбирался в обфускацированном коде - в принципе, было бы желание, особых проблем нет.
Возможность ходить в отладчике по обфускацированному коду, весь смысл обфускации накрывает медным тазом. Если бы на обфускацированном коде отладчик бы не работал (или работал не корректно), тогда да, было бы проще застрелится.
Более-менее сложная система - поддержка значительно дороже, чем написать код с нуля имея перед глазами уже реализованную "идею".
Главная защита системы - тупо ее объем и сложность. Без документации - код проще выбросить и переписать с нуля.
Mandarin |
---|
Конкурентам в основном. |
IMHO Вариантов несколько:
1. Подарите конкурентам, без документации.
Когда стоимость саппорта кода превысит все разумные рамки - разорятся, конкурентов не будет )))
2. Можно внести "закладки". Например: без регулярных патчей от создателей, через год будет выскакивать ошибка "пользоваться чужим кодом - не хорошо". (а если рандомно - вообще песня)
Когда начнет появляться у их заказчиков - может случится хороший скандал.
IMHO интерфейс обмена нормально разрабатывать, что бы backdoor'ов не было. А не лепить "что попало"
Думаю для "портить данные" вполне подойдет банальный анализ логов пакетов, которые по сети ходят (если SSL для шифрования трафика не используется). Вон Линейка явно не на Java написана, но это не помешало народу разобраться в протоколе и своих ботов писать. Innova явно вообще кучу бабла в защиту вливает - но ботов и на офф сайтах полно.
Защищать обмен данными - методом защиты кода от декомпиляции. оригинальненько. IMHO
Leonid Kudryavtsev |
---|
2. Можно внести "закладки". Например: без регулярных патчей от создателей, через год будет выскакивать ошибка "пользоваться чужим кодом - не хорошо". (а если рандомно - вообще песня) Когда начнет появляться у их заказчиков - может случится хороший скандал. |
взять дату какого нибудь свеже полученного soap пакета, взять год, вычесть из него 1242 и если цифра больше чем К, то в день делящийся на 7 и на 3 заменять + на - в какой нибудь формуле.
например, вы придумали какой-то "супер" алгоритм по анализу данных и хотите его продавать за кучу денег и "все" хотят купить :)
1) Не все согласятся куда-то отправлять данные
2) Не всегда есть возможность отправлять данные
3) Про крупные компании вообще бред
Пример (чисто абстрактно/надумано):
Придумали вы например как по фото идентифицировать человека с 99.9% вероятностью и решили создать приблуду для телефонов.
1) Это может сделать один человек
2) Использовать удаленный сервис не получится
3) Потентуй или не потентуй, но появится куча клонов с более красивым интерфейсом и грубо пропиариных через рекламу
И о вас и вашей приблуде никто и не вспомнит.
В природе не существует природе не существует программных продуктов, которые не поддавались бы взлому. И тут совершенно не важно на каком языке программирования написанна эта программа, будь то Java или C . Просто затраты на взлом могут быть различными и поэтому все основные действия по защите от нелегального использования должны быть направлены на затруднение декомпиляции.
В случае с Java дела обстоят достаточно плохо. Дело в том, что в отличии от C или C ++, Java компилятор не создает конечный машинный код, а всего лишь его платформенно независимое представление. Полученный байт-код содержит очень много осмысленной информации, которая может помочь разобраться взломщику программы в принципе её работы.
При создании программы, в подовляющем большенстве, вводятся сомодостаточные названия для классов и их методов. Например, класс ConfigMgr скорее всего бдует представлять сервис для управления конфигруцией программного продукта. А его метод getRootCataloog – будет возвращать корневой коталог, где физически расположена программа. Это, конечно, существенно облегчает понимание программы для программиста, но и одновременно помогает злоумышленнику, ведь при декомпиляции Java байт-кода получаются реальные названия классов и их методов.
1) Использование флагов компиляции
2) Написание двух версий программного продукта
3) “Затемнение” кода
4) Изменение байт-кода
5) Использование JNI
6) Выставление исходников программы по более высокой цене
7) Хранение методов в атрибутах
8) Применение глухих классов
1. Использование флагов декомпиляции
По существу нас интересуют только 3 флага компиляции, это – g , – O и без флага.
Флаг – g говорит компилятору добавлять номер строки и имя локальных переменных в конечный байт код.
Без флага – теряются имена локальных переменных, но сохраняютья номера строк.
Флаг – O – дополнительно удаляются номера строк.
Коментарии
Данный метод был приведен для общности картины и может являться лишь первым шагом на пути защиты java кода от взлома. Единственный вывод отсюда можно сделать, что всегда необходимо компилировать программу тольк с ключом компиляции – O .
2. Написание двух версий программного продукта
Эта достаточно хороший способ защиты. Например если необходимо выпустить демо версию, то берётся полная верися, из неё вырезается например сохранение в файл и затем раздаётся всем бесплатно. Таким образом нет особой нужды защищать программу. Если же клиент покупает программный продукт, то ему выдаётся полная версия программы.
Но этот подход не полностью может удовлетворть потребности в защите, например, если необходимо, чтобы даже полная версия работала определённый период времени (так называемая “временная” лицензия).
Коментарии
Данный метод имеет несравненный плюс при предоставлении кому-либо демонстрации, фактически вручается полурабочий образец, который сложно будет применять в каких либо других целях, кроме как ознакомление с программным продуктом. Да и в добавок в нем будет отсутствовать достаточно важная часть кода, которую невозможно будет получить даже при помощи декомпиляции.
3. “Затемнение” кода
Этот способ является на данный момент самым популярным среди методов защиты программ от декомпиляции.
На практике, если проект переходит определённый предел сложности, то разобраться в логике программы можно только с использованием коментариев в коде и технической подержки. На этом и оновывается данный метод. Во всем проекте происходит замена декларативных названий полей классов и методов на абстрактные, которые не несут какой-либо смысловой нагрузки.
Например был исходный текст :
private void loadPixie( URL url ) throws Exception
URLConnection connection = url.openConnection( );
in = new DataInputStream( connection.getInputStream() );
// Verify file format via magic numbers and version number.
if (in.readInt() != Constants.MAGIC)
throw new Exception( "Bad Pixie header" );
int v = in.readShort();
if (v != FILE_VERSION)
throw new Exception( "Bad Pixie version " + v );
// Skip unused fields.
readUnsignedVInt( ); // Frame table size always 1.
readUnsignedVInt( ); // Skip end of control commands.
// Object table size always 0.
// Skip unused fields.
// Block-read the rest.
int byteCount = readUnsignedVInt();
pixieBody = new byte[ byteCount ];
int maxBlockSize = byteCount/20+1;
Graphics fg = getGraphics( );
int bytesDone = 0;
while (bytesDone < byteCount)
int blockSize = byteCount-bytesDone;
if (blockSize > maxBlockSize)
in.readFully( pixieBody, bytesDone, blockSize );
// Update progress monitor.
progress = bytesDone / (float) byteCount;
fg.fillRect( 0, size().height-4,
( int )( progress * size().width), 4 );
после “затемнения” он принял вид:
private void _mth015E(void 867 % static 931)
void short + = 867 % static 931.openConnection();
private01200126013D = new DataInputStream( short +.getInputStream());
if( private01200126013D.readInt() != 0x5daa749)
throw new Exception("Bad Pixie header");
void do const throws = private01200126013D.readShort();
if( do const throws != 300)
throw new Exception("Bad Pixie version " + do const throws);
_fld015E = _ mth012B( );
short01200129 = _ mth012B( );
_fld013D013D0120import = new byte[ |=];
void void = |= / 20 + 1;
for( void catch 11 final = 0; catch 11 final < |=;)
void while if = |= - catch 11 final;
if( while if > void)
private01200126013D.readFully( _fld013D013D0120import, catch 11 final, while if);
catch 11 final += while if;
const = (float)catch 11 final / (float)|=;
=. fillRect( 0, size().height - 4, (int)(const * size().width), 4);
Как видно из приведённого примера, код стал плохо читаем и поэтому, чтобы разобраться в логике программы необходимо приложить на порядок больше усилий.
java / tools / crema / ). Crema рапространяется бесплатно (с незначительными ограничениями), а ее аналог интегрирован в Jbuilder начиная с версии 2.0.
Коментарии
Данный метод очень сложно реализуем, если в программном продукте необходимо предоставлять открытый API или если в проект взодят EJB (которые тоже должны иметь строго определённые методы и открытый API ). Компромисом может служить не тотальное переименование классов и методов, а только частичное, но для этого необходимо изначально проектировать систему с учётом дальнейшего её “затемнения”.
4. Изменение байт-кода
Основным недостатком предыдущего метода является то, что “затемнённый” код все равно может быть успешно компилирован, после декомпиляции. Против простеньких декомпиляторов можно побороться следующим способом. Всавлять лишние инструкции в байт-код. Например если после return в методе класса всавить java инструкцию pop , то многие декомпиляторы воспримут это как ошибку и не смогут коректно декомпилировать этот код. Хотя при этом JVM его сможет исполнять без ошибок.
Пример исходного файла:
void parse_fig_pointline( DataInputStream f,
int npoints, int xpoints[], int ypoints[] )
if (debug) editor.consoleMessage(
"parse_fig_pointline: npoints= " + npoints ) ;
String line = null;
int n_tokens = 0; /* number of tokens in current line */
int i =0; /*current index into point arrays */
while( i < npoints )
editor.consoleMessage( "parse_fig_pointline: " + line );
st = new StringTokenizer( line, " \n\r\t" );
for( int j=0; j < n_tokens; j+=2)
xpoints[ i] = fig_scale( Integer.parseInt(
ypoints[ i] = fig_scale( Integer.parseInt(
> catch( IOException e )
editor.consoleMessage( "Error: Not a valid FIG3.1 file
editor.consoleMessage( "on line " + line_number + ": " +line );
После применения DashO и декомпиляции JAD ’ом получился следующий код:
void a(DataInputStream datainputstream, int i1, int ai[], int ai1[])
d.a( "parse_fig_pointline: npoints= " + i1);
boolean flag = false;
stringtokenizer = new StringTokenizer(s1, " \n\r\t");
int i2 = Integer.parseInt(stringtokenizer.nextToken());
h < 30 ? (i2 +1)*g :i2 *g;
i2 = Integer.parseInt( stringtokenizer.nextToken());
h < 30 ? (i2 +1)*g :i2 *g;
if( l1 < j1) goto _L4; else goto _L3
if( k1 < i1) goto _L6; else goto _L5
d.a( "Error: Not a valid FIG3.1 file (pointline)");
Подобный код уже не откомпилируется.
Коментарии
Этот метод не универсален и это собственно его единственный недостаток, а в остальном он может бэффективно применяться наряду с другими методами защиты программных продуктов.
5. Использование JNI
Этот может принести ощутимый эффект, например в следующем случае. У нас есть определённая программа. Часть её функционального кода без которого она не будет работать переводится на C . А затем использую JNI API эта часть кода связывается с остальным java байт-кодом.
Если вы разрабатываете приложение Java, важно понимать, что файлы классов Java могут быть легко перепроектированы с использованием декомпиляторов Java. В этой статье мы рассмотрим, как файл класса Java подвергается обратному проектированию и как защитить исходный код от этого.
Исходный код Java скомпилирован в файл класса, который содержит байт-код. Виртуальная машина Java требует только файл класса для выполнения. Проблема в том, что файл класса можно легко декомпилировать в исходный код с помощью инструментов декомпиляции Java. Лучшее решение для предотвращения реверс-инжиниринга — это запутать файл класса, так что его будет очень сложно реверс-инжинировать. Согласно словарю, Obfuscate означает «сделать неясным или неясным». Это именно то, что делают многие инструменты Java-обфускатора, как описано ниже.
Декомпилируйте файл класса Java.
Прежде чем понять, как запутать Java-код, давайте сначала попытаемся понять, как кто-то может реконструировать ваше Java-приложение. Следующие 3 шага объясняют, как файл класса был обратно преобразован в исходный код Java.
1. Создайте HelloWorld.java, как показано ниже.
2. Скомпилируйте программу HelloWorld.java и выполните ее, чтобы убедиться, что она работает правильно.
Файл класса Java содержит только байт-код. Если вы попытаетесь просмотреть файл класса, он будет недоступен для чтения, как показано ниже.
3. Декомпилируйте файл HelloWorld.class и просмотрите исходный код.
Для этой демонстрации давайте используем Jad- декомпилятор, который является бесплатным для некоммерческого использования. Загрузите соответствующий jad для вашей платформы. Используйте jad для обратной разработки файла HelloWorld.class, чтобы получить исходный код, как показано ниже.
Запутать ваше Java-приложение
Давайте рассмотрим, как запутать и защитить ваш исходный код от обратного инжиниринга, используя ProGuard, бесплатное лицензионное программное обеспечение GPL.
Как заблокировать скомпилированные классы Java для предотвращения декомпиляции?
Я знаю, что это должна быть очень хорошо обсуждаемая тема в интернете, но я не мог прийти к какому-либо выводу после их ссылки.
многие люди предлагают obfuscator, но они просто переименовывают классы, методы и поля с жесткими для запоминания последовательностями символов, но как насчет чувствительных постоянных значений?
например, вы разработали компонент шифрования и дешифрования на основе метода шифрования на основе пароля. Теперь в этом случае любой средний Java-человек может использовать Джад декомпилировать файл класса и легко получить значение пароля (определяется как константа), а также соль и, в свою очередь, может расшифровать данные, написав небольшой независимой программы!
или такие чувствительные компоненты должны быть встроены в собственный код (например, VC++) и вызывать их через JNI, у?
некоторые из более продвинутых обфускаторов байт-кода Java делают гораздо больше, чем просто искажение имени класса. Zelix Классмастер, например, может также скремблировать поток кода таким образом, что это действительно трудно следовать и работает как отличный оптимизатор кода.
также многие из обфускаторов также могут скремблировать ваши строковые константы и удалять неиспользуемый код.
другое возможное решение (не обязательно исключая запутывание) - это использовать зашифрованные файлы JAR и пользовательский загрузчик классов, который выполняет дешифрование (предпочтительно с использованием собственной библиотеки времени выполнения).
третье (и, возможно, предлагающее самую сильную защиту) - использовать родные компиляторы раньше времени, такие как GCC или Excelsior JET, например, которые компилируют ваш Java-код непосредственно в собственный двоичный файл платформы.
в любом случае вы должны помнить, что, как говорят в Эстонии "замки для зверята." Это означает, что каждый бит кода доступен (загружен в память) во время выполнения и при наличии достаточного навыка, решимости и мотивации люди могут и будут декомпилировать, расшифровывать и взламывать ваш код. Ваша задача состоит в том, чтобы сделать процесс настолько неудобным, насколько вы можете, и все еще держать вещь работать.
пока у них есть доступ как к зашифрованным данным, так и к программному обеспечению, которое их расшифровывает, вы не можете сделать это полностью безопасным. Способы, которыми это было решено раньше, - использовать некоторую форму внешнего черного ящика для обработки шифрования / дешифрования, например, донглы, удаленные серверы аутентификации и т. д. Но даже тогда, учитывая, что пользователь имеет полный доступ к своей собственной системе, это только затрудняет, а не невозможно-если вы не можете привязать свой продукт непосредственно к функционал хранится в "черном ящике", как, скажем, на игровых серверах онлайн.
отказ от ответственности: я не эксперт по безопасности.
Это звучит как плохая идея: вы позволяете кому-то шифровать вещи с помощью "скрытого" ключа, который вы ему даете. Я не думаю, что это можно сделать безопасно.
возможно, асимметричные клавиши могут работать:
Я не уверен, но я считаю, что клиент действительно может зашифровать лицензионный ключ с открытым ключом, который вы ему дали. Затем вы можете расшифровать его с помощью закрытого ключа и повторно зашифровать.
вы можете сохранить отдельную пару открытого / закрытого ключа для каждого клиента, чтобы убедиться, что вы действительно получаете материал от правильного клиента - сейчас вы несут ответственность за ключи.
независимо от того, что вы делаете, его можно "декомпилировать". Черт,вы можете просто разобрать его. Или посмотрите на дамп памяти, чтобы найти константы. Видите ли, компьютер должен знать их, поэтому ваш код тоже должен знать.
постарайтесь не отправлять ключ как жестко закодированную константу в коде: сохраните его как параметр для каждого пользователя. Пользователь отвечает за этот ключ.
посмотри JavaWorld статьи взлом Java байт-код шифрования Владимир Рубцов. Это объясняет, почему шифрование файлов классов в основном бессмысленно.
@jatanp: или еще лучше, они могут декомпилировать, удалить код лицензирования и перекомпилировать. С Java я действительно не думаю, что есть правильное, защищенное от взлома решение этой проблемы. Даже злой маленький ключ не мог предотвратить это с Java.
мои собственные бизнес-менеджеры беспокоятся об этом, и я слишком много думаю. Но опять же, мы продаем наше приложение крупным корпорациям, которые, как правило, соблюдают условия лицензирования-как правило, Безопасная среда благодаря счетчикам бобов и адвокаты. Сам акт декомпиляции может быть незаконным, если ваша лицензия написана правильно.
Итак, я должен спросить, ты действительно нужна закаленная защита, как вы ищете для вашего приложения? Как выглядит ваша клиентская база? (Корпорации? Или подростковые геймерские массы, где это было бы более проблемой?)
Я не думаю, что существует какой-либо эффективный способ офлайн антипиратский. Индустрия видеоигр пыталась найти это много раз, и их программы всегда были взломаны. Единственное решение заключается в том, что программа должна быть запущена онлайн, подключенная к вашим серверам, чтобы вы могли проверить ключ lincense, и что лицензиат одновременно поддерживает только одно активное соединение. Вот как!--1-->World of Warcraft или Диабло строительство. Даже есть частные сервера для них разработана для обхода защиты.
сказав это, я не считаю, что средние/крупные корпорации используют незаконное скопированное программное обеспечение, потому что стоимость лицензии для них минимальна (возможно, я не знаю, сколько вы собираетесь взимать за свою программу) по сравнению со стоимостью пробной версии.
Если вы ищете лицензионное решение, вы можете проверить API для TrueLicense. Он основан на использовании несимметричных ключей. Однако это не означает, что ваше приложение не может быть взломано. Каждое приложение может быть взломано с достаточными усилиями. Что действительно важно, как Стю ответил, выясняя, насколько сильная защита вам нужна.
вы можете использовать шифрование байт-кода без страха.
дело в том, что приведенная выше статья "взлом шифрования байт-кода Java" содержит логическую ошибку. Основной претензией статьи является перед запуском все классы должны быть расшифрованы и переданы ClassLoader.defineClass(. ) метод. Но это не так.
предположение пропущено здесь при условии, что они работают в аутентичной или стандартной среде выполнения java. Ничто не может обязать защищенное java-приложение не только запускает эти классы, но даже расшифровывает и передает их в ClassLoader . Другими словами, если вы находитесь в стандартной JRE, вы не можете перехватить defineClass(. ) метод, потому что стандартная java не имеет API для этой цели, и если вы используете модифицированную JRE с исправленным ClassLoader или любой другой "хакерский трюк" вы не можете этого сделать, потому что защищенное java-приложение не будет работать вообще, и поэтому вам нечего будет перехватывать. И абсолютно не имеет значения, какой "patch finder" используется или какой трюк используется хакерами. Эти технические детали-совсем другая история.
Q: если я зашифровать .файлы классов и использовать пользовательский загрузчик классов для загрузки и расшифровки их на лету, это предотвратит декомпиляцию?
A: проблема предотвращения декомпиляции байт-кода Java почти так же стара, как и сам язык. Несмотря на ряд инструментов запутывания, доступных на рынке, начинающие программисты Java продолжают думать о новых и умных способах защиты своей интеллектуальной собственности. В этом выпуске Java Q&A я часто Развеиваю некоторые мифы вокруг идеи перефразированный в дискуссионных форумах.
крайняя легкость, с которой Java .файлы классов могут быть реконструированы в источники Java, которые очень похожи на оригиналы, имеет много общего с целями проектирования байт-кода Java и компромиссами. Среди прочего, байтовый код Java был разработан для компактности, независимости платформы, мобильности сети и простоты анализа интерпретаторами байтового кода и динамическими компиляторами JIT (just-in-time)/HotSpot. Возможно, скомпилированный .файлы класса выражают программиста намерение настолько ясно, что их легче анализировать, чем исходный исходный код.
можно сделать несколько вещей, если не предотвратить декомпиляцию полностью, то, по крайней мере, сделать ее более сложной. Например, в качестве шага после компиляции вы можете массировать .данные класса, чтобы сделать байтовый код более трудным для чтения при декомпиляции или труднее декомпилировать в действительный код Java (или оба). Такие методы, как выполнение экстремальных перегрузок имени метода, хорошо работают для первого и манипулируют поток управления для создания структур управления, которые невозможно представить через синтаксис Java, хорошо работает для последнего. Более успешные коммерческие обфускаторы используют сочетание этих и других методов.
к сожалению, оба подхода должны фактически изменить код, который будет работать JVM, и многие пользователи боятся (по праву), что это преобразование может добавить новые ошибки в их приложения. Кроме того, переименование метода и поля может привести к прекращению работы вызовов отражения. Изменение фактические имена классов и пакетов могут нарушить несколько других API Java (JNDI (Java Naming and Directory Interface), поставщики URL и т. д.). В дополнение к измененным именам, если связь между смещениями байт-кода класса и номерами исходных строк изменена, восстановление исходных трассировок стека исключений может стать затруднительным.
тогда есть возможность запутать исходный исходный код Java. Но по сути это вызывает аналогичный набор проблем. Шифровать, а не запутывание?
возможно, вышеизложенное заставило вас подумать: "Ну, а что, если вместо манипулирования байтовым кодом я зашифрую все свои классы после компиляции и расшифрую их на лету внутри JVM (что можно сделать с помощью пользовательского загрузчика классов)? Затем JVM выполняет мой исходный байтовый код, и все же нет ничего, чтобы декомпилировать или перепроектировать, верно?"
к сожалению, вы были бы неправы, как думая, что вы были первым, кто придумал эту идею, так и думая, что это действительно работает. И причина не имеет ничего общего с силой вашей схеме шифрования.
Читайте также: