- PVSM.RU - https://www.pvsm.ru -

Представьте, что Ваше приложение нагло крадут и выкладывают в сеть. И никак не понять, кто из честнейших клиентов допускает утечку. Выход ясен: достаточно просто выдавать клиентам приложения с различными версиями и по версии определять утечку.
Но что если ситуация усложнилась, и Вашу программу на этот раз крадёт хакер, и он уж позаботится, чтобы вычистить все следы, идентифицирующие программу. На такой случай разработаны универсальные методы внедрения в приложение секретных данных, так называемых водяных знаков или вотермарок (калька с английского watermark).
Мы рассмотрим здесь только Watermark'и предназначение которых – ни при каких условиях не быть удалёнными, чтобы создатель приложения имел возможность считать их после любых атак потенциального злоумышленника, а пользователи приложения о них не догадывались. Есть и другие виды вотермарок, предназначенные, например, для отслеживания изменений в приложении, эдакие скрытые чексуммы, и они также должны быть сложно удаляемы, но это уже другая история.
Прекрасный способ внедрения Watermark в приложение – это пофантазировать и придумать место, где никакой хакер Вашу вотермарку искать не будет: просто побоится потонуть в тоннах кода и забросит это дело. Если Вы разрабатываете визуальное приложение, то ничего не мешает менять цвет пикселя спрятанного в углу какой-нибудь кнопки в Богом забытом диалоговом окне. Цвет пикселя и будет вотермаркой. К сожалению такой случай не всегда приемлем и разработчикам удобнее воспользоваться каким-нибудь универсальным решением для внедрения вотермарки в уже скомпилированное приложение. Традиционно такую функцию встраивают в обфускаторы.
Итак, нам необходимо универсальное решения для вставки вотермарки в приложение. Универсальность накладывает серьёзные ограничения ведь мы не собираемся писать искусственный интеллект, который определит какой пиксель можно подкрасить, например. На первый взгляд можно подумать: «Так ведь так много есть места, где можно незаметно записать какие-то данные. Выбирай любое!» Но не будем торопиться.
Представим, что хакер – умный хакер и он точно знает, что вотермарки в приложении есть и их надо найти и обезвредить, оставив приложение работающим. Пусть после очередной попытки удаления вотермарки он каким-то неведомым образом может узнать смогли ли разработчики прочитать вотермарку или нет (а что, нам не жалко). Последовательно хакер будет выполнять следующие атаки:
Как можно заметить от последнего пункта защиты нет, но хотелось бы сделать так, чтобы немногие хакеры до него добрались.
И что же делать? Есть ли панацея? Окончательного ответа на этот вопрос нет, проблема эта очень объемная и давно изучается. Посмотрите, например, следующий обзор академических исследований на эту тему: citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.103.8892&rep=rep1&type=pdf [1].
Всё же есть два места относительно стабильные к изменениям, которым приложение нещадно подвергает хакер.
На этом моменте становится понятно, почему вотермаркеры идут в комплекте с обфускаторами: ведь мало какие другие виды программ настолько вмешиваются в код приложений.
Из всего вышесказанного про хакерские обфускаторы и протекторы (хотя бы из хакерской атаки номер два) ясно, что для считывания вотермарки придётся запустить приложение и как-то подключиться к нему во время работы. Мы предполагаем, что хакер может навесить протектор, запрещающий дебаг, а значит для извлечения вотермарки надо очень нежно касаться приложения. На ум приходят следующие варианты.
Алгоритм в общих чертах прост: в случайные типы добавляем статические «массивы» (напомню, что это не обязательно массивы) выбираем случайные методы и вставляем в них код, генерирующий вотермарк сообщение в «массиве» (делаем их несколько, чтобы хакеру пришлось попотеть, находя их все). Но нужно решить несколько важных проблем.
Проблем много, приступим.
В нашем вотермарк кодере/декодере мы храним массив статичных сигнатур sig[i] (сто штук, например) – случайных массивов по 100 байт. Они не меняются от версии к версии и секретом не являются. Мы полагаемся на хэш алгоритм SHA256 (это как MD5 и SHA1 только лучше), он обеспечит отсутствие коллизий в памяти и решит ещё несколько проблем. Напомню, не известно быстрых алгоритмов для того, чтобы по хэшу HASH найти строку STR такую, что SHA256(STR) = HASH. Вторым столпом, на котором будет держаться вотармарк, является алгоритм Rijndael (AES). Напомню, если KEY – секретный ключ для шифрования/дешифровки, то неизвестно эффективных алгоритмов как не зная KEY из строки AES(STR, KEY) получить STR.
От разработчика защищаемого приложения потребуется пароль, который является секретным и подлежит бережному хранению в сейфе. Когда пользователь вводит пароль PASS и сообщение MSG для создания вотермарки, мы генерируем последовательно:
Всё! Вотермарки Watermark[i] готовы к употреблению в случайно выбранных местах программы.
Для считывания вотермарок из памяти мы также генерируем indSig[i] по паролю и ищем в памяти вотермарканого процесса indSig[i]. Затем с помощью ключа KEY[i] = SHA256(indSig[i] + PASS + salt) расшифровываем секретное сообщение, следующее за indSig[i]. Не забываем проверить, что первые четыре байта сообщения равны MsgSig = SHA256(PASS + salt)[0..3].
Алгоритм SHA256 со своей способностью противостоять коллизиям гарантирует нам, что случайный блок данных не будет принят за вотермарку. Не зная пароль, хакер не сможет перетереть вотермарку, что опять же гарантируется антиколлизионностью SHA256. Найдя одну вотермарку, не имея пароля хакер не сможет создать мусорное сообщение или даже прочитать вотермарканое сообщение, что гарантируется алгоритмом Rijndael и короткой сигнатурой сообщения. По найденной вотермарке хакер не сможет найти без пароля остальные вотермарки, для чего как раз и генерируются индивидуальные сигнатуры и ключи. В итоге мы решили проблемы 4, 5 и 6, описанные ранее.
Для того, чтобы вотермарки-массивы перестали быть мёртвыми данными мы вовлекаем их в вычисления, проводимые в случайных методах. В этом случае, даже найдя вотермарк, его нельзя просто удалить, не повлияв на работоспособность приложения. Например пусть где-то в некоем методе было:
return baseOffset + 55;
Пусть вотермарк-массив MyClass.WatermarkArr содержит число 55 в ячейке MyClass.WatermarkArr[42]. Тогда код выше превратится в:
return baseOffset + MyClass.WatermarkArr[42];
Выглядит всё хорошо, но кто может гарантировать, что на момент выполнения кода вотермарк-массив уже создан? Для выяснения этого мы строим контролфлоу граф методов. Такое построение сопряжено со множеством сложностей, ведь методы вызываются через Wpf, Reflection, с помощью виртуальных вызовов, статических конструкторах и так далее. Мы стараемся анализировать как можно больше случаев и перестраховываться везде, где это возможно, применяя для этого собственный эмулятор .NET кода.
Watermark является весьма эффективным методом идентификации пользователя и/или конкретной сборки. Разумеется у этого метода, как и у любого другого, есть свои ограничения. В частности Вы не можете заранее собрать один дистрибутив для всех Ваших пользователей (хотя в случае с SaaS обфускатором эта проблема менее актуальна — можно обфусцировать программу на лету, непосредственно перед скачиванием). С другой стороны, для крупных индивидуальных продуктов использование такой защиты более чем оправдано, ведь пользователи, заранее зная, что за ними следят, намного более неохотно станут передавать Вашу интеллектуальную собственность третьим лицам.
Автор публикации: Дмитрий Косолобов, разработчик Appfuscator [2].
Автор: Anakonda
Источник [3]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/net/43256
Ссылки в тексте:
[1] citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.103.8892&rep=rep1&type=pdf: http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.103.8892&rep=rep1&type=pdf
[2] Appfuscator: http://Appfuscator.com
[3] Источник: http://habrahabr.ru/post/193666/
Нажмите здесь для печати.