Pull to refresh

Comments 37

Вам не кажется, что эта статья больше для «Я пиарюсь» подходит?
С одной стороны да, но с другой стороны статья о защите .NET и все предыдущие стати по теме обфускации тоже были опубликованы в этом блоге.
А как ваша обфускация влияет на быстродействие? Можно ли обфусцирвать игровые проекты (там, где идёт интенсивная работа с графикой) без потери производительности? Есть ли подводные камни?
Подробных тестов по теме производительности мы еще не проводили, но большинство алгоритмов никак не влияют на быстродействие. Единственное исключение — String Encription, этот алгоритм, вероятно, добавит некоторые доп. накладные расходы. Но разница в быстродействии будет очень несущественной и, скорее всего, в реальном приложении её не заметить.

Кроме того, при необходимости, нужные методы можно исключить из обработки с помощью атрибута ObfuscationAttribute
Не всякий разработчик готов отправлять свой не обфусцированный код кому попало.
Вот как вы можете гарантировать что отправленные на защиту проекты не попадут в третьи руки в незащищенном виде?
Вопрос безопасности упоминается в статье, в частности мы можем оформить с Вами договор о неразглашении.
Я считаю что вам необходимо выпустить оффлайновую версию протектора. И на нем раскручиваться.
А уже заработав некоторую известность переводить сервис в облако.
NDA в России не пользуется популярностью. Многие даже не знают для чего он и чем он может помочь.
Самое важное в области защиты ПО — это годами наработанное имя и репутация.
Офлайн версия, ориентированная на крупных клиентов, есть в наших планах. Но ею мы займемся немного позже, в первую очередь сейчас мы ориентированны на небольшие компании и для них SaaS доступ — возможность существенного сокращения расходов.
[параноик mode on] Но если сборки уйдут через вас, доказать это будет почти не реально для мелкого разработчика. Так что думайте на тему как заработать репутацию. [/параноик mode off]
[параноик mode on]Перед отправкой сборки на наш сервис, добавляйте в нее специальную пометку (класс/строку/атрибут), и если она уйдет через нас, то это легко можно будет отследить ;)[/параноик mode off]

А если серьезно, то репутация зарабатывается только долгой безупречной работой и больше никак. Разве что косвенными подтверждениями серьезности намерений могут быть давно зарегистрированное юр. лицо, аттестат продавца от 2009 года, прошлые успешные проекты.
Только я по заголовку решил что речь пойдет о проблемах безопасности приложений в Azure?
Как-то не подумал о такой неоднозначности, пожалуй подправлю.
Куда-то не туда попал, прошлый комментарий — ответ на неоднозначность с облаком Azure.

По теме WCF:
1. Программа исключает из обфускации типы, переименование которых анализатор считает небезопасным. Я сейчас честно говоря, сходу не вспомню прописывали ли мы конкретно WCF исключения, но многие стандартные базовые классы/атрибуты уже добавлены, и мы постепенно расширяем список исключений
2. WCF объекты можно сохранить в отдельной сборке, и вообще не добавлять ее при обфускации, а обфусцировать только тело программы
3. Можно исключать из обработки любые отдельные классы с помощью стандартного атрибута ObfuscationAttribute
чертов ctrl+enter макрос.

Очень интересно было бы узнать об используемых технологиях. Вот эти я узнал:
Renaming, External Method Call Hiding — mono.cecil
Assembly Merging — ilmerge

а чем вы делаете статический анализ?

еще вопрос:
Decomposition — что вы делаете с виртуальными методами и делегатами. Ведь вы ломаете
a) сигнатуру
б) таблицу виртуальных методов
В тексте же написано, что виртуальные методы остаются методами.
Да, как верно заметил mayorovp, виртуальные методы мы не выносим т.к. одно из главных правил декомпозиции не ломать логику CLR, а с делегатами все ок, от декомпозиции их суть не меняется — просто вместо метода, которому указатель на this передается не явно через стандартный механизм CLR, мы делаем статический метод, которому этот указатель передается явно. Если так можно выразится, то бинарная совместимость в этом случае полная, а логическая связь метода и типа к которому он относился ранее — нарушается.
Получается что для «восстановления» достаточно переместить тело метода обратно? Идентифицировать такие методы будет не так уж и сложно. На такую утилиту уйдет пара часов. По пути можно придавать локальным переменным и классам читабельные имена.

Если смотреть на остальные степени защиты, то:
External Method Call Hiding
тут надо найти вашу табличку ссылок на методы и по довольно редкому опкоду calli вернуть обратно тела функций.

остается только String Encryption:
который можно сломать в рантайме. воткнув хуки после получения каждой строки типа
// строку достали, расшифровали
stloc_0 // и сохранили в локальную переменную
// волшебная вставка
ldloc_0 // расшифрованная строка
ldc_i4 100500 // порядковый номер вот такой кодовой вставки
call MyHook.SaveDecryptedString // (string value, int stringNum)
//

в итоге мы получаем таблицу всех использованных строк программы, конечно в процессе программу придётся подергать или прогнать тесты с хорошим покрытием

всё это легко пишется с помощью mono.cecil
Не существует защиты, которую в принципе невозможно снять, и единственный важный критерий оценки — соотношение стоимости взлома защищенного продукта со стоимостью его приобретения. Соответственно единственное что должна делать защита — делать это соотношение крайне не выгодным для хакера.
Если кто-то заявляет что его защита дает 100% гарантии, то значит он врет :)

> Получается что для «восстановления» достаточно переместить тело метода обратно
Теоретически да, но фактически далеко не всегда возможно определить где был этот метод.

> По пути можно придавать локальным переменным и классам читабельные имена.
Это не возможно, т.к. процесс обфускации необратим. Максимум можно использовать информацию из окружения (по делегатам, по типам данных и т.п.). Очень многое из этого и так делается в ILSpy (между прочим у него очень умный статический анализатор). Собственно то, насколько это мало помогает, Вы можете видеть сами.

External Method Call Hiding — не совсем табличка, опять же можно написать анализатор который по типовым конструкциям пытается найти скрытые методы, там так же используются переменные различные в разных контекстах, но взломщик при очень большом желании сможет написать программу для анализа. Точно так же как мы можем изменив всего пару строчек кода полностью уничтожить все зацепки на которые он рассчитывал (например тот же calli всего лишь один из вараинтов, и взят он лишь по причине своей необычности на сегодняшний день). А с онлайн сервисом такие обновления производить особенно удобно.

По String Encryption, в текущий момент времени — соглашусь, способ описанный Вами весьма не плох (кроме сложности покрытия тестами, как правило никогда не удастся извлечь из программы все строки). Но решается это столь же просто — несколько алгоритмов проверки целостности кода, отказ от одной точки шифрования строк, в пользу нескольких (динамически генерируемых и разбрасываемых по отдельным классам) + возможно интеллектуальный алгоритм предотвращения «сдапливания».

Насчет того, что все это легко пишется — очень сильно сомневаюсь, на разработку сервиса у нас ушло около года, и при этом обратный процесс восстановления значительно более сложная процедура (намного проще имея полную информацию уничтожать её, чем имея лишь остатки данных восстанавливать логические связи).

И что еще более важно, сейчас у нас есть большая фора, пока сервис не набрал очень большой популярности, никто не станет писать специализированные анаизаторы под придуманные лично нами алгоритмы. И за это время, мы планируем добавить еще очень много всего вкусного и интересного (в частности небольшую собственную виртуальную машину для самых любопытных хакеру частей кода :)
>Теоретически да, но фактически далеко не всегда возможно определить где был этот метод.
Сейчас это очень просто т.к. все прямые вызовы найти довольно легко. Но даже если вы попытаетесь скрыть их через calli или jmp, то это добавит только пару часов в работу по написанию деобфускатора. Причем соотношение будет не в вашу пользу. Вы тратите месяц на создания «не ломающего» алгоритма сокрытия вызова, я трачу день на восстановление оригинального вызова. Причина в том что мне не нужен «работающий» код, мне нужен «оригинальный» код.

>Это не возможно, т.к. процесс обфускации необратим
Я писал про " читабельные имена", а не «оригинальные имена». Если взломщик хочет «своровать» код, то ему придется потратить время на переименование. Это вполне реализуемо.

>External Method Call Hiding
>но взломщик при очень большом желании сможет написать программу для анализа
«Взломщик» это в первую очередь профессионал. Найти шаблон и обратить алгоритм будет стоить одинаково или дешевле разработки такого алгоритма(выше и ниже написано почему). Причем ваши «изменения» сервиса никак не защитят взламываемое или воруемое приложение. Его уже скачали и ломают. С каждым вашим релизом взломщик будет посылать вам своё мега-приложение-болванку на обфускацию и получать свежие алгоритмы. Может даже нейросеть прикрутит :)

>Насчет того, что все это легко пишется — очень сильно сомневаюсь, на разработку сервиса у нас ушло около года.
Сочувствую, сам написал раньше «мега-защиты» которые ломали за день.
Взлом делается быстрее защиты, за счет того что надо получить читабельный, похожий на оригинал код для дальнейшего анализа. Причем «запускаемость» кода часто не нужна.
>и при этом обратный процесс восстановления значительно более сложная процедура (намного проще имея полную информацию уничтожать её, чем имея лишь остатки данных восстанавливать логические связи).
Но вы же её не уничтожаете(кроме имен). Только размазываете тонким слоем, теряя производительность, портируемость и другие качества проекта.
Вот потому OpenSource смотрит на всяческие системы защиты кода и копирайта как на… детей :)
На словах все очень просто, а Вы бы попробовали ;)

> мне нужен «оригинальный» код.
Оригинального кода уже нет, взломщику точно так же нужно строить все связи и искать сложные зависимости для того чтобы внести изменения и не сломать работу программы. Допустим есть классы a, b и их интерфейс IBase. Если в одном из них переименовать метод а в другом — нет, то программа просто вылетит при загрузки этого типа данных. Аналогично и с рефлекшином, только теперь имена бессмысленны и скрыты под String Encryption. И как и нам хакеру придется выявлять атрибуты содержащие в себе строковые имена.

> Только размазываете тонким слоем, теряя производительность, портируемость и другие качества проекта.
А можно конкретнее, в чем потеря портируемости и особенно производительности? Cтатические функции медленнее чем методы экземпляров или короткие имена вида a,b,c работают хуже чем оригинальные?

> Причем «запускаемость» кода часто не нужна.
Если цель только посмотреть один маленький кусочек исходного кода — то это ошибка не обфускатора, а разработчика лицензионных ограничений. Правильная защита строиться по другому, и никакой обфускатор здесь не поможет. Для того чтобы защиту трудно было снять, она должна быть очень плотно интегрирована в программу, и проверки лицензии должны быть во множестве мест программы. А как самый идеальный случай, для особенно надежной защиты, я бы порекомендовал использовать ключи с собственным процессором для защищенного выполнения кода внутри них. В такой ключ можно вынести несколько реальных алгоритмов из программы и этим усложнить обход защиты на несколько порядков.
>На словах все очень просто, а Вы бы попробовали ;)
А я это не только пробую.
>Оригинального кода уже нет, взломщику точно так же нужно строить все связи и искать сложные зависимости для того чтобы внести изменения и не сломать работу программы.
Вы защищаете от «кражи исходников» и «взлома защиты». Для первого варианта нужно восстановить название файлов, места вызовов и строки. Как это сделать я описывал выше. Остается кропотливая работа человека по анализу кода и переименованию. Этим обычно не занимаются, а ломают защиту, меняют логотип и продают как свою. Тут мы приходим ко второму виду взлома. Где «Оригинального кода уже нет, взломщику точно так же нужно строить все связи и искать сложные зависимости для того чтобы внести изменения и не сломать работу программы» делать не надо, надо только получить код похожий на оригинал для анализа. Как обернуть ваши способы защиты я писал выше.
> Допустим есть классы a, b и их интерфейс IBase. Если в одном из них переименовать метод а в другом — нет, то программа просто вылетит при загрузки этого типа данных.
К сожалению вы неправы. Имена классов/членов класса не используются, используются только токены метаданных, и собственно если «переименовать» класс в метаданных, то он «переименуется» везде. Учитывая что вы сливаете все сборки в одну, вы облегчаете задачу обратного переименования, ведь меж сборочного биндинга нет.

>Аналогично и с рефлекшином, только теперь имена бессмысленны и скрыты под String Encryption. И как и нам хакеру придется выявлять атрибуты содержащие в себе строковые имена.
Как украсть ваши строки я уже писал. Если бы я видел сам алгоритм или пример его встраивания, я бы выдал еще более интересные варианты взлома. Шифрование строк и упаковка кода должны «возбуждать» только очень неопытных программистов. Если проект тяжело сидит на рефлекшене то взломщику придётся разбирать что и куда идёт. Но это «заслуга» не вашего обфускатора, и без него, даже с исходниками такой код сложно анализировать.

>А можно конкретнее, в чем потеря портируемости и особенно производительности? Cтатические функции медленнее чем методы экземпляров или короткие имена вида a,b,c работают хуже чем оригинальные?
Портируемость:
Как ведет себя обфускатор с Medium Trust кодом? Ведь некоторые опкоды и их сочетания являются unverifiable(calli к примеру). Это windows azure, sharepoint, silverlight, unity3D, windows phone, clickOnce web приложений.
Часть опкодов не поддерживается Mono разных версий.
Производительность:
Про имена я сказал выше. Они к осполнению кода отношения не имеют и участвуют в биндинге.
IL Оптимизатор ничего не знает о calli, в итоге вы эффективно мешаете оптимизатору инлайнить методы. Об оптимизации хвостовой рекурсии можно вообще забыть. Помимо того что вы «раздуваете» стек и создаете «неявные» точки в потоке исполнения в которых могут появиться «асинхронные» исключения(ThreadAbortException). Еще неизвестно проверяете ли вы CER аттрибуты и не нарушаете ли контракты надежности.

Причем это то что я видел в ваших примерах. Я представляю насколько всё печальнее на самом деле.

>я бы порекомендовал использовать ключи с собственным процессором для защищенного выполнения кода внутри них
Именно, обфускация бесполезная трата денег.
>восстановить название файлов, места вызовов и строки.
Опечатался.
восстановить названия типов, места вызовов и строки.

Возникла мысль. Вы дебаг символы надеюсь вытираете? Mono.Cecil их старается сохранить, было бы приятно их обнаружить.
> Этим обычно не занимаются, а ломают защиту, меняют логотип и продают как свою
Чтобы сменить лого, совсем не обязательно лезть в код, достаточно открыть ресурсы. А вот чтобы снять защиту, особенно когда она хорошо распределена по программе и программа при этом обфусцированна, нужно очень много сил и времени, а главное после выпуска новой версии, все придется повторять вновь, что делает такой «взлом» абсолютно нерентабельным.

> К сожалению вы неправы. Имена классов/членов класса не используются, используются только токены метаданных
Это не так, токен каждого метода храниться отдельно и если не учитывать связи между типами, то программа работать не будет :) Можно конечно все методы «a» переименовать разом в одно имя, а все методы «b» в другое, но учитывая что таких методов «a» несколько сотен, они все разные и никак друг с другом не связаны, это очень вряд ли хоть чуть-чуть поможет с читаемостью кода.

> IL Оптимизатор ничего не знает о calli, в итоге вы эффективно мешаете оптимизатору инлайнить методы
Суть External Method Call Hiding как раз в том, что скрываются вызовы только _внешних_ методов из других сборок. Вызовы методов внутри сборки остаются нетронутыми. А касательно inline, Вы сильно переоцениваете оптимизатор, как правило он может заинлайнить внешние вызовы только с атрибутом TargetedPatchingOptOut (только в случае NGen), MethodImplAttribute (FW 4.5) и подобными. С уверенностью скажу что никаких «неявных» точек в стеке не появляется и «асинхронные» эксепшены не происходят. Если бы это было не так, из среды .NET не возможно было бы обращаться к неуправляемому коду, а это не так, более того, множество методов внутри самой CLR заканчиваются неуправляемым кодом.

> Если проект тяжело сидит на рефлекшене то взломщику придётся разбирать что и куда идёт
Намного интереснее это выглядит после нашего обфускатора, когда вместо какого-нибудь type.GetMethod(«GetPrice») увидит:
1. calli по которому еще надо догадаться, что это был вызов GetMethod
2. Вызов какого-нибудь «d(int, int)» по которому нужно догадаться что это String Encription
3. Расковыряв всю эту цепочку пользователь получит строку, пусть это будет «a» (скорее всего примерно так и будет) и по нему надо каким-то волшебным образом восстановить, что это бывший «GetPrice»
И так почти на каждой строчке.

> Возникла мысль. Вы дебаг символы надеюсь вытираете? Mono.Cecil их старается сохранить, было бы приятно их обнаружить.
Нет, конечно мы их не сохраняем, они там и не могут оказаться, т.к. как я говорил чуть раньше, мы не правим старую сборку а создаем с нуля полностью новую.

> Именно, обфускация бесполезная трата денег.
Как раз наоборот, покупать аппаратные ключи и встраивать их в необфусцированную .NET программу — столь же глупо как и пытаться добавить защиту хардверными ключами в Open Source приложение выложенное на github (разницы фактически никакой). Только комплексная защита может дать желаемый эффект.
По поводу технологий — в основе ядра нашего обфускатора лежит пропатченный и доработанный нами mono.cecil и поверх него строится вся логика дерева разбора, перименования, линковки библиотек и т.д.

Никакие другие технологии типа того же ilmerge мы не используем. Более того, мы не производим никаких изменений в пришедшей на вход сборке, а на основе механизмов cecil создаем полностью новую.
Выглядит опасно. Интересно как при таком тяжелом рефакторинге дела с совместимостью. Как разруливается явная и неявная реализация интерфейсов? Всё считается виртуальным? Как обфусцируются делегаты, с оглядкой на граф ссылок? Что со статическими методами и конструкторами? Что со строками, содержащими имена из метаданных (типа имен свойств в реализации INotifyPropertyChanged или атрибутов вроде ContentProperty)?
Скорее всего, код который работает с рефлексией и его логика построена на захардкоженном знании о структуре кода полетит. Например, всякие системы плагинов, MEF-ориентированные сборки и свои Dependency Injection велосипеды.
Пробовали отлаживать на сложных расширяемых приложениях?
Разницы между явной и неявной реализацией интерфейсов для обфускатора нет, т.к. и те и другие всегда будут виртуальными функциями, которые не попадают под декомпозицию. А renamer умеет строить умные деревья зависимостей чтобы дать правильные имена. Про делегаты уже писал выше, конструкторы остаются на месте, статические методы тем более прекрасно декомпозитятся.

По поводу имен которые идут текстом в коде и в атрибутах — одна из самых сложных тем, у нас целый ряд анализаторов отвечает за прописывание исключений и обновление имен (анализатор атрибутов, анализато WPF, анализатор Reflection обращений). Но все равно, в сложных случаях, они могут не справиться и тогда спасает их явное исключение через ObfuscationAttribute.

> Например, всякие системы плагинов, MEF-ориентированные сборки и свои Dependency Injection велосипеды.
Если писать их правильно, то все будет работать. Я лично, очень рекомендую использовать Reflection вызовы по пользовательским атрибутам, а не по жестко забитым в коде именам, а так же проверять типы через typeof а не сравнением строк. Ряд достаточно простых правил, позволяет писать Reflection код, хорошо совместимый с любым умным обфускатором.

> Пробовали отлаживать на сложных расширяемых приложениях?
Да, тестирование и разрешение всяких сложных случаев растянулось у нас больше чем на 5 месяцев, поэтому с одной стороны могу с уверенностью сказать что многие сложные системы заработают без малейшего изменения кода. С другой стороны — точно есть случаи в которых потребуется вмешательство программиста.
Еще интересный вопрос. Как ведет себя обфускатор с Medium Trust кодом? Ведь некоторые опкоды и их сочетания являются unverifiable(calli к примеру).
Вопрос очень актуален для сайтов, windows azure приложения, sharepoint приложений, silverlight приложений, unity3D приложений, windows phone приложений.
Лучшая защита приложения это разумная цена и своевременный выпуск релизов.
Абсолютно согласен защита должна быть комплексной от технических мер до административных.
Ему про Кузьму, а он – про Ерему.
Ребята — респект! За Вами вменяемая ценовая политика и работоспособная Free — версия.
Есть интересная идея: off-line — за деньги, online — бесплатно.
Спасибо, мы будем стараться.
Sign up to leave a comment.

Articles