Страницы

Поиск по вопросам

Показаны сообщения с ярлыком обфускация. Показать все сообщения
Показаны сообщения с ярлыком обфускация. Показать все сообщения

пятница, 31 января 2020 г.

Несколько вопросов по C# обфускации (SmartAssembly, internal функции)

#c_sharp #обфускация


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

Основная задача - сделать анализ с целью повторного использования настолько трудоемким,
чтобы дешевле было написать свой код, а не использовать мой. Уточню - не преследую
цель сделать анализ НЕвозможным,- просто, максимально усложнить!  

Пока остановился на SmartAssembly (версии 6). Деобфусцировал с помощью de4dot. Результат
оценивал в Reflector. В связи с этим есть ряд вопросов. В вопросах подразумевается,
что анализируемый exe или dll был ранее обфусцирован SmartAssembly. 

Вопросы:  


Можно ли с помощью какого либо метода (деобфускация, создание дампа процесса, отладка
или что либо другое) без значительных трудозатрат восстановить исходный код (можно
без имен переменных) internal и private функций с условием, что они не вызываются из
public функций? Если можно, то как?  
Аналогичный вопрос по поводу исходных имен параметров функций и локальных переменных,
используемых в internal и private функциях.  
Какой обфускатор посоветуете использовать вместо SmartAssembly (только чтобы лицензия
была не дороже 150-200$)?  


Несколько наблюдений (исходя из моих экспериментов):  


De4dot не восстанавливает имена локальных переменных после SmartAssembly (только
переименовывает исходя из их типов для облегчения анализа). А вот структуру кода нормально
восстанавливает. Еще восстанавливает имена параметров публичных функций.  
internal и private функций я не нашел в восстановленном с помощью De4dot файле. Причем,
их код не находится даже если вызываешь прямо из него (из кода internal или private
функции) public функцию (в то время как ее вызов из публик функции виден http://prntscr.com/78pxf8
и http://prntscr.com/78pxhs)  
Однако если приватная функция вызывается публичной, то ее можно обнаружить и распознать
структуру .

    


Ответы

Ответ 1



Имена локальных переменных не хранятся в скомпилированной в IL сборке. Они будут уничтожены независимо от использования обфускации. Имена локальных переменных не извлекает деобфускатор, а генерирует декомпилятор. Аргументы функций являются частью публичного интерфейса, поэтому хранятся. Как и всё публичное, они могут быть обфусцированы, если обфускатор не совсем бесполезный. Что такое "приватная функция, которая не вызывается из публичной функции"? А откуда она тогда вызывается? Или функция в конце концов будет вызвана по цепочке публичным кодом (и тогда её код будет в сборке), или она будет полностью отсутствовать в сборке (обфускаторы могут выкидывать неиспользуемый код более дерзко, чем компиляторы). Какой обфускатор посоветуете использовать вместо SmartAssembly (только чтобы лицензия была не дороже 150-200$)? Никакой. Обфускаторы для .NET — пустая трата денег. Чем меньше вы заплатите денег, тем меньше потеряете. Если хочется затруднить декомпиляцию, найдите любой обфускатор, который умеет обфусцировать названия публичных методов. Остальное вообще мусор, потому что всё равно бОльшая часть кода не приватная.

Ответ 2



Если функция/метод вызывается, то ее можно найти. Другое дело, что компилятор может ее заинлайнить и она станет частью вызывающей функции. Другой вариант - функция не вызывается и компилятор может ее выбросить. Соотвественно, ее потом никак не найти. То, что Вы не можете найти свою функцию ещё не значит, что ее там нет и ее сложно найти. Анализ все равно начинается с публичных функций и потом спускаются вниз. Имя функции/переменной для компилятора обычно ничего не значит. К примеру, если это переменная цикла, то главное, что бы совпадал тип. А ее имя не имеет никакого значения. Если функция используется локально (локально в понятии компилятора), то ее можно переименовать. Другое дело публичные методы, к которым могут обращаться с внешнего мира. Тут просто так не переименуешь - поломается весь код. Я не особо силен в .NET, но могу сказать, что самый лучший обсфукатор - это Ваш мозг. В большинстве случаев Ваш код понятен только Вам и туда внутрь мало кто будет смотреть. Если кому то нужно будет его использовать - главное будет разобраться с интерфейсом. А что там внутри и как оно обсфуцировано - это проблемы автора. Я лично против обсфукаторов. Это просто способ купить псевдоспокойствие за деньги, не более. Половина обсфукаторов только переименовывает имена переменных. Структуру кода даже не трогает. Некоторые делают простые преобразования (например, разворачивают условие if или переставляют инструкции, что бы запутать деобсфукаторы). Добавляя инструкции в код, они замедляют работу приложения. Также, иногда абсолютно не ведомо, что именно они там добавили. Может статистику считают, а может биткоины генерят. Если Вам страшно за свой код - удалите его с жесткого диска, сам диск сожгите. Так скорее всего никто никогда его не увидет и не сможет "повторно использовать". Менее параноидальный режим - использовать языки, которые меньше подвержены простому декомпилированию - С/С++ (только не CLI).

Ответ 3



De4dot не восстанавливает имена локальных переменных Их и без обфускации не восстановить. А вот структуру кода нормально восстанавливает. Это же логично? Еще восстанавливает имена параметров публичных функций. Они являются частью интерфейса для возможности вызова по имени. Возможно, какой-нибудь обфускатор и решит их вычистить. internal и private функций я не нашел в восстановленном с помощью De4dot файле. Возможно, какой-то оптимизатор их просто удалил как неиспользуемые. Однако если приватная функция вызывается публичной, то ее можно обнаружить и распознать структуру . Всё, что может найти jit-компилятор, может найти и декомпилятор. А если не может найти jit, то этот код бесполезен. Возможно, можно запутать декомпилятор кодом, генерируемым компилятором при использовании различных фич C#. Например, вызов по именам параметров, создающий локальные managed-ссылки на переменные, использование iterator function, async-await, приводящих к генерации goto, невалидных для шарповского кода, а также fault-блоков. Необходимость восстановления изначальных конструкций из этого поднимет требования к декомпилятору, но я не думаю, что обфускаторы на такое способны. Это скорее требование к самому коду. Да и декомпиляторы-то развиваются. Однажды случится так, что прошлая версия не могла восстановить код, а новая уже может.

четверг, 19 декабря 2019 г.

Обфусация кода, Защита от декомпиляции

#c_sharp #обфускация


Собственно, кто какие способы защиты от декомпиляции программы и кто какие способы
обфуксации кода написанного на c# знает?
    


Ответы

Ответ 1



Но я вижу, что здесь только издеваются, так что, закройте кто-нибудь тему @MadKeks никто над вами не издевается, не преувеличивайте. Дело в том, что необходимость в обфускации программ на C# сама по себе довольно сомнительна - очень существенная часть C# кода - это веб-приложения, веб-сервисы и прочий код, который работает на сервере, а следовательно, конечный пользователь его не видит и покопаться в нем не сможет даже с помощью ildasm'a или Reflector'a. Большая часть оставшегося софта - это программы, используемые очень ограниченным кругом лиц (внутрикорпоративные десктоп-приложения например), а следовательно, никому не придет в голову воровать их исходный код, не представляющий особой ценности. К примеру, на своей первой работе я получил в сопровождение кучу адского говнокода, нарушающего все возможные и невозможные каноны чистоты кода и в каккой-то степени благодаря этому уже являвшийся обфусцированным (справедливости ради, мои навыки на тот момент как раз того и стоили - из под моих пальцев вытекал точно такой же кривой говнокод). Так вот что тогда, что сейчас я бы не только не стал этот код воровать с помощью декомпиляции, я бы не стал брать его даже даром из исходников. В подобной защите, вероятно, нуждается серьезное проприетарное ПО, скажем, какой-нибудь Photoshop, но оно на C# обычно не пишется. Учитывая, что исходники практически на все случаи жизни можно отыскать на всяческих гитхабах и битбакетах, пляски с бубном для получения исходников становятся еще более сомнительными. Стоит также отметить вот что: на моей памяти 99% процентов случаев, когда программист волновался по этому поводу сводились к тому, что это был юный и неопытный разработчик, искренне уверенный в том, что его хелловорлды способны потрясти мир и произвести революцию в области софта, а потому срочно нуждаются в защите и сокрытии своих исходников. По мере профессионального взросления у них это обычно проходило. Впрочем, при желании материалов на эту тему можно найти достаточно. Например вот и вот UPD статья по обфускации в .NET обфускация в JS Стоит кстати заметить, что обфускация во многом зависит от средств конкретного языка

Ответ 2



В дополнение к ответу @DreamChild (с которым на 100% согласен), а также в рамках популяризации новых веяний в разработке на .NET, упомяну ещё одну альтернативу. С выходом Developer Preview Project N, вы можете скомпилировать ваше .NET-приложение в нативный код, и даже прилинковать к нему всю стандартную библиотеку (ну да, выйдет файл размером в типичное C++-приложение). Так что вскрыть его будет не легче, чем C++-программу (и даже труднее, т. к. спецы во взломе C++-кода должны ещё привыкнуть к коду, генерируемому AOT-компилятором .NET, и его системным библиотекам). Ещё раз повторюсь, что это занятие (обфускация) по моему мнению пустая трата времени.

среда, 18 декабря 2019 г.

Скрытие кода *.exe-файла

#шифрование #обфускация #дизассемблирование #обратная_разработка


Как можно скрыть код exe-файла (написаного, например, на Delphi) от расшифровки под
дизассемблером и, впоследствии, от реверса.

Строки, например, можно просто XOR-ить или смещать каждый символ на k позиций по
таблице ASCII (первое лучше, ИМХО). А вот как скрыть имена функций или даже целые блоки
кода? Ведь многие разработчики (особенно вирусописатели) любят это делать, тем самым
скрывая свои «творения» от исследования посторонними лицами. Таким же образом скрывается
от антивирусов всем известный SpyEye. А как этого добиться мне?
    


Ответы

Ответ 1



(Некоторое время не занимался реверсингом, поэтому информация может быть outdated) Если говорить о серьезной обфускации, то имеет смысл рассматривать только языки, которые компилируются в машинный код (C++, например). Языки с промежуточным слоем байт-кода обычно легко поддаются реверсингу и, судя по всему, пока нет более-менее адекватных способов запротектить написанные на них приложения. Адекватные протекторы совершают некоторую последовательность действий для защиты готового приложения от реверсинга: Вставляют в готовый бинарник готовые антиотладочные фрагменты кода (например, дешифрующий его в рантайме), путают секции, совершают хитрые джампы, в общем, совершают атомарные изменения (не меняющие поведение программы!) над исполняемым кодом, которые затрудняют отладку человеку. Человек - это reverse engineer, который открывает ваше приложение в IDA, зрительно выцепляет знакомые паттерны из дизассемблерного листинга, трейсит приложение, подменяя содержимое стека и патча это самое приложение в его рантайме. И основная цель этих защитных действий протектора - заставить реверсера сказать "Тьфу, пошло оно в ж*пу, задолбало", поскольку последовательность действий протектора всегда можно совершить в обратную сторону, был бы опыт и знания. Более серьезная защита, иногда останавливающая даже крутых и опытных реверсеров - это виртуализация кода приложения для того, чтобы исполнять его на собственной виртуальной машине (обычно сочетается со стандартным антиотладочными приемами, описанными выше). При этом виртуальная машина внедряется в бинарник вместе с некоторым IL-кодом, который может выглядеть абсолютно произвольно. Параметры создаваемой машины также можно варьировать в некоторых пределах - при желании можно и виртуалку с троичной логикой написать. Бонус такого подхода в том, что для полноценного реверсинга такого приложения необходимо каким-либо образом воспроизвести эту самую виртуальную машину. В такой ситуации (если виртуалка оказывается достаточно хитрой), большая часть стандартных тулзов и приемов реверсера перестает работать "из коробки". Что, опять же, приближает этого самого потенциального реверсера к состоянию "Спасибо, с меня хватит". С точностью до деталей таким образом протектит Themida. В соответствии с написанным выше - если есть желание запротектить свою программу более-менее достойно, то напишите свою виртуалку и сгенерируйте для нее IL-код из имеющихся объектных файлов вашего приложения. Это действие поможет отсеять большую часть реверсеров, которым недавно рассказали и показали, что такое IDA и как применять patch. Quick test - для того, чтобы понять, сможете ли вы создать более-менее адекватную защиту с виртуализацией, попробуйте сломать CrackMe от ESET. Если поймете, причем здесь SSE и общую механику проверки, то можно браться за дело. Всегда есть набор готовых public и private пакеров, которыми можно воспользоваться. Themida, ASProtect, FSG. Понятно также, что всегда найдется набор людей, которые эти или иные протекторы в состоянии снять.

вторник, 10 декабря 2019 г.

Обфускация .NET приложений

#net #обфускация


За счёт каких приемов в .NET достигается то, что нельзя восстановить декомпиляторами
исходный код на высокоуровневом языке (языке написания)?

Какие приемы используют современные обфускаторы?

Обратима ли обфускация?
    


Ответы

Ответ 1



Данная пост является почти полной копией статьи https://habrahabr.ru/post/74463/ Также рекомендую обзор обфускаторов для .NET. Несмотря на то, что статья 2010 года, она способна дать понимание, что в основном делают обфускаторы и в чём их недоработки. Методики обфускации Объединение сборок и пространств имён (Assembly Merge, Namespace Flatten) Данная методика сама по себе не задерживает злоумышленника ни на минуту, но очень полезна для дальнейшего его запутывания. Т.к. чем больше классов будет содержать результирующая сборка, тем сложнее без детального анализа будет в ней найти то, что надо. Опять же, при попытке украсть ваш код, злоумышленник получит вместо нескольких проектов-библиотек и одной программы только один проект, в котором все классы будут лежать в одной папке (и в одном неймспейсе). Для объединения сборок можно использовать утилиту ilmerge, либо встроенную в обфускатор функциональность. Пространства имён обычно объединяются во время обфускации имён классов (чтобы не было коллизий с одинаково названными классами из разных неймспейсов). Переименование классов, методов и т.д. Данный подход реализован почти во всех уважающих обфускаторах. Удаляются все «подсказки» которые может использовать злоумышленник для быстрого поиска классов, отвечающих за лицензирование, либо при «воровстве» кода будет очень тяжело разобраться в логике приложения, для чего и как создаются классы, и вызываются методы. Самый популярный вариант на данный момент — переименование в непечатные символы (или какую-нибудь «китайщину» типа 儽.凍::儽). Это немного осложняет просмотр сборки в рефлекторе, но на деобфускаторе никак не сказывается. К тому же из недостатков мы получаем труднопередаваемые сообщения об исключениях, которые могли бы произойти у конечного пользователя, и если он нам вышлет текст не в юникод-кодировке, то разобрать его будет практически невозможно. Аналогичный вариант — использование коротких, но печатных идентификаторов (a, b, c, …aa, ab, ac…). Для деобфускатора этот вариант полностью аналогичен предыдущему, но зато лишён указанного недостатка. Третий вариант именования — использование ключевых слов языка высокого уровня (C# или VB.net), либо невалидных идентификаторов для этого языка (например ?123?) — ничем не лучше двух предыдущих, но почему-то считается, что при «воровстве» кода не воспользуются деобфускатором, и на выходе получится некомпилируемый текст... Ещё существует куча «глупых» вариантов, которые конечно скрывают смысл исходных имён, но зачем делать их такими длинными? Интересным и ещё более запутывающим подходом является создание большого количества overload-методов с одним именем, которые имели до обфускации разные имена, и никак не были связаны. Также .net позволяет создавать override-методы, имена которых отличаются от имён методов, которые они перекрыли. Это сбивает с толку не только злоумышленников, но и добавляет лишние требования к деобфускатору. Изменение содержимого классов Некоторые обфускаторы могут объединять несколько классов в один, или делать из обычного класса вложенный. Но такая обфускация часто приводит к ошибкам в результирующей программе, и используется очень редко. Обфускация Control Flow На этом этапе меняется порядок инструкций в коде и даже меняются сами инструкции. Пожалуй, самый интересный и самый спорный этап. Данная методика позволяет ввести в заблуждение (а иногда и в полный ступор) большинство декомпиляторов языков высокого уровня. Что очень хорошо противодействует «воровству» кода. Также «запутывает» кракеров и авторов кейгенов. Обратная сторона медали — иногда сниженная производительность. Логично, что чем больше мы запутываем ход выполнения программы, тем дольше она выполняется. Особенно это относится к использованию исключений. В большинстве случаев код метода бьётся на блоки, эти блоки перемешиваются в случайном порядке и «склеиваются» с помощью безусловных переходов (инструкции br и br.s). В качестве примера: L_0034: br.s L_003a L_0036: nop L_0037: br.s L_0041 L_0039: nop L_003a: callvirt instance void [Aaa]Xxx.Yyy::Zzz() L_003f: br.s L_0036 L_0041: nop Бывают и случаи, когда метод очень короткий, и «перемешать» его хорошо не получается, в этом случае некоторые обфускаторы выдают переход на следующую инструкцию: L_0008: br.s L_000a L_000a: ldarg.0 Между инструкцией перехода, и её целью очень часто вставляется всякое «фуфло», типа выпадения в дебаггер, или просто невалидных инструкций: L_0000: br.s L_0003 L_0002: break L_0003: ldarg.0 Некоторые обфускаторы заменяют инструкции перехода (как оригинальные, так и вставленные) на загрузку константы и переход на switch: L_0000: br.s L_0023 L_0002: ldloc num3 L_0006: switch (L_005b, L_0068, L_00ce, L_00af, L_0047, L_007b) ... ... ... L_003c: ldc.i4 4 L_0041: stloc num3 L_0045: br.s L_0002 очевидно, что в данном примере инструкция со смещением L_0045 «в девичестве» была br L_0047, а если учесть предыдущие методики, то это вообще nop ;) Иногда можно встретить «переход на переход»: в одной из программ я видел цепочку из 6 (шести) таких переходов ;) Интересный подход — использование условных переходов для выражений, которые всегда верны (или неверны). Самый простой пример: L_0014: ldc.i4.1 L_0015: brtrue.s L_002e То же самое, но слегка более запутанное: L_0014: ldc.i4.1 L_0015: stloc.0 L_0016: br.s L_001c L_0018: nop L_0019: ldarg.1 L_001a: br.s L_002e L_001c: ldloc.0 L_001d: brtrue.s L_0018 Ещё вариант: if (5 < (5 - 6)) { // IL-мусор, или неверный код } в виде IL будет выглядеть примерно так: L_0000: ldc.i4.5 L_0001: dup L_0002: dup L_0003: ldc.i4.6 L_0004: sub L_0005: blt L_0001 Простое перемешивание некоторых инструкций, например: L_0000: ldc.i4 4 L_0005: stloc num L_0009: ldstr "\u5f03" L_000e: ldloc num компилятор иногда может выдавать код вида stloc X, ldloc X, когда требуется записать значение в локальную переменную, но не убирать его со стэка. В случае обфускаторов, эта переменная (num) добавлена искусственно, и нигде кроме данных двух инструкций больше не используется. Один из самых «жёстких» методов — всегда выбрасываемое внутри блока try—catch исключение. Данный подход используется очень редко, т.к. резко снижает производительность и может нарушить логику приложения при неверном использовании. Скриншот я не привожу т.к. он занимает много места. Invalid IL Тут всё очень просто. В участки кода, которые никогда не будут исполнены, вставляются не описанные в стандарте опкоды (т.е. невалидные инструкции). В рефлекторе вы увидите примерно такое: или, если переключится на IL: Данная методика обескураживает начинающих «хаксоров». Но не является чем-то сложным для обхода (данные опкоды просто заменяются на nop). Сокрытие строк Деобфускаторами это называется «шифрование строк», но назвать это шифрованием у меня не поворачивается язык. Обычно это делается каким-нибудь «детским» алгоритмом шифрования типа XOR на константу: public static string Decode(string str, int num) { int length = str.Length; char[] chArray = str.ToCharArray(); while (--length >= 0) chArray[length] = (char)(chArray[length] ^ num); return new string(chArray); } Иногда строки объединяются в одну, и потом происходит вызов метода Substring; иногда строки прячут в ресурсы. В любом случае «шифрование» представлено в виде статического метода с несколькими аргументами, обычно это строка и/или число. Никаких криптографических алгоритмов не применяется, что вполне логично: если применить здесь настоящее шифрование, то программа будет безбожно тормозить. Данный метод спасает от начинающих кракеров, которые будут искать по коду строки типа “Invalid serial number” или другие тексты сообщений. Специфичные атрибуты и баги декомпиляторов Самый часто встречаемый атрибут — [SuppressIldasm], который «вежливо просит» не работать на данной сборке официальный декомпилятор Microsoft — ildasm. Существуют также специфичные атрибуты для рефлектора и для коммерческих декомпиляторов. В качестве багов можно встретить как чисто технические недоработки декомпиляторов (например, рефлектор выпадает на инструкции ldfld string 儽.凍::儽, а большинство деобфускаторов на базе Mono.Cecil — на неправильных RVA), также и алгоритмичиские допущения: многие декомпиляторы высокого уровня отслеживают состояние стэка, но идут по методу не как по графу, а линейно, и радостно валятся на методах в которых после последней инструкции ret вставлен бесконечный цикл. Против плагина Reflexil хорошо «помогает» инструкция, переходящая сама на себя. Другие методы Иногда можно встретить очень похожий на скрытие строк подход, но для ресурсов. Также один из обфускаторов предлагает «V-Spot Elimination» (чем очень гордится) — создание прокси-классов для классов BCL, что замедляет анализ и слегка портит полученный декомпиляцией код. Также используется конвертирование managed в unmanaged .net-кода. Т.е. все пересобирается с пометками unmanaged. Практически вся функциональность в пределах домена сохраняется, но рефлектором код уже не посмотреть.

суббота, 30 ноября 2019 г.

Обфускатор Java байт-кода, способный не просто менять имена на “a.a.b”, а именно делать байт-код недекомпилируемым

#java #android #reverse_engineering #обфускация


Хотелось бы чтобы код именно невозможно было декомпилировать, то есть декомпилятор
при проходе по методу давал экскепшн, а еще лучше терял из виду какой-то блок кода
и вроде бы выдавал нормальный результат, но нужного блока в нем не было. В то же время,
код естественно должен быть валидным, не вызывать VerifyError.

Я думаю, что такое возможно, ведь в Java гораздо меньше свободы, чем в ее байт-коде,
поэтому не любой байт-код можно записать в виде кода Java.

Например, что, если в байт-коде получится вот так:

switch (test) {
case aaaa:
  break;
  :foo  <---------
  goto :foo2; ---|------
case bbbb:       |     |
  goto :foo; ----|     |
  break;               |
case cccc:             |
  break;               |
  :foo2  <--------------
  hidden_code();
}


Или вообще так:

switch (test) {
case aaaa:
  break;
  :foo3  <---------------
  hidden_code();        |
case bbbb:              |
  goto :foo -----|      |
  break;         |      |
case cccc:       |      |
  break;         |      |
  :foo  <---------      |
  goto :foo2 --------|  |
}                    |  |
                     |  |
switch (test2) {     |  |
case aaaa:           |  |
  break;             |  |
case bbbb:           |  |
  break;             |  |
case cccc:           |  |
  break;             |  |
  :foo2  <------------  |
  goto :foo3 -----------|
}


Ведь в Java нет goto, он есть только в опкодах JVM или DEX, и как такой байт-код
можно декомпилировать?

Важная деталь: if != switch в байт-коде Java.

Некоторые декомпиляторы просто не станут смотреть дальше break, все равно непонятно
куда девать этот блок.

А уж try-catch вообще прикольная штука.

В общем, не составляет большого труда придумать вот такие частные случаи, когда испытания
подтверждают, что код валиден с точки зрения Dalvik или JVM, но невалиден для многих
декомпиляторов.

Так вот есть ли обфускатор, который автоматически вставляет в любой байт-код такие
конструкции, чтобы он не декомпилировался?
    


Ответы

Ответ 1



SABLE описывает подобные подходы к обфускации в публикации о JBCO. Ссылка на публикацию (PDF, ~195KB). 6.7 Goto Instruction Augmentation (GIA) Java bytecode does have a goto instruction because it is necessary for simulating higher-level constructs such as loops. Therefore it is possible to insert an explicit goto within the bytecode. While it is very easily reversed using control flow graph analysis it can still cause many simple decompilers to fail. Также описывается ряд приемов, которые используют разницу между Java и байткодом в вызове конструкторов, switch и try/catch. В принципе, большинство обфускаторов Java производят байткод, который не полностью соответствует корректному коду. Даже при простом переименовании методов, возникают случаи когда метод переопределяется с разными возвращаемыми типами. Усиление безопасности от использования таких приемов имеет ограничения: Байткод остается в открытом виде. Даже если простой декомпилятор не справится с задачей, по инструкциям JVM можно отследить логику. Для большинства приложений достаточно рассмотреть ключевые классы/методы. Декомпиляторы (например, Dava от выше упомянутого SABLE) применяют анализ порядка выполнения и автоматизируют декомпиляцию. Стандартные классы не обфусцируются. При этом влияние модификаций байткода на производительность сложно оценить/предсказать. Подробный обзор подходов к защите Java-кода в статье Protect Your Java Code — Through Obfuscators And Beyond

среда, 31 октября 2018 г.

Обфусация кода, Защита от декомпиляции

Собственно, кто какие способы защиты от декомпиляции программы и кто какие способы обфуксации кода написанного на c# знает?


Ответ

Но я вижу, что здесь только издеваются, так что, закройте кто-нибудь тему @MadKeks никто над вами не издевается, не преувеличивайте. Дело в том, что необходимость в обфускации программ на C# сама по себе довольно сомнительна - очень существенная часть C# кода - это веб-приложения, веб-сервисы и прочий код, который работает на сервере, а следовательно, конечный пользователь его не видит и покопаться в нем не сможет даже с помощью ildasm'a или Reflector'a. Большая часть оставшегося софта - это программы, используемые очень ограниченным кругом лиц (внутрикорпоративные десктоп-приложения например), а следовательно, никому не придет в голову воровать их исходный код, не представляющий особой ценности. К примеру, на своей первой работе я получил в сопровождение кучу адского говнокода, нарушающего все возможные и невозможные каноны чистоты кода и в каккой-то степени благодаря этому уже являвшийся обфусцированным (справедливости ради, мои навыки на тот момент как раз того и стоили - из под моих пальцев вытекал точно такой же кривой говнокод). Так вот что тогда, что сейчас я бы не только не стал этот код воровать с помощью декомпиляции, я бы не стал брать его даже даром из исходников. В подобной защите, вероятно, нуждается серьезное проприетарное ПО, скажем, какой-нибудь Photoshop, но оно на C# обычно не пишется. Учитывая, что исходники практически на все случаи жизни можно отыскать на всяческих гитхабах и битбакетах, пляски с бубном для получения исходников становятся еще более сомнительными. Стоит также отметить вот что: на моей памяти 99% процентов случаев, когда программист волновался по этому поводу сводились к тому, что это был юный и неопытный разработчик, искренне уверенный в том, что его хелловорлды способны потрясти мир и произвести революцию в области софта, а потому срочно нуждаются в защите и сокрытии своих исходников. По мере профессионального взросления у них это обычно проходило. Впрочем, при желании материалов на эту тему можно найти достаточно. Например вот и вот UPD статья по обфускации в .NET обфускация в JS Стоит кстати заметить, что обфускация во многом зависит от средств конкретного языка

вторник, 30 октября 2018 г.

Скрытие кода *.exe-файла

Как можно скрыть код exe-файла (написаного, например, на Delphi) от расшифровки под дизассемблером и, впоследствии, от реверса.
Строки, например, можно просто XOR-ить или смещать каждый символ на k позиций по таблице ASCII (первое лучше, ИМХО). А вот как скрыть имена функций или даже целые блоки кода? Ведь многие разработчики (особенно вирусописатели) любят это делать, тем самым скрывая свои «творения» от исследования посторонними лицами. Таким же образом скрывается от антивирусов всем известный SpyEye. А как этого добиться мне?


Ответ

(Некоторое время не занимался реверсингом, поэтому информация может быть outdated) Если говорить о серьезной обфускации, то имеет смысл рассматривать только языки, которые компилируются в машинный код (C++, например). Языки с промежуточным слоем байт-кода обычно легко поддаются реверсингу и, судя по всему, пока нет более-менее адекватных способов запротектить написанные на них приложения. Адекватные протекторы совершают некоторую последовательность действий для защиты готового приложения от реверсинга: Вставляют в готовый бинарник готовые антиотладочные фрагменты кода (например, дешифрующий его в рантайме), путают секции, совершают хитрые джампы, в общем, совершают атомарные изменения (не меняющие поведение программы!) над исполняемым кодом, которые затрудняют отладку человеку. Человек - это reverse engineer, который открывает ваше приложение в IDA, зрительно выцепляет знакомые паттерны из дизассемблерного листинга, трейсит приложение, подменяя содержимое стека и патча это самое приложение в его рантайме. И основная цель этих защитных действий протектора - заставить реверсера сказать "Тьфу, пошло оно в ж*пу, задолбало", поскольку последовательность действий протектора всегда можно совершить в обратную сторону, был бы опыт и знания. Более серьезная защита, иногда останавливающая даже крутых и опытных реверсеров - это виртуализация кода приложения для того, чтобы исполнять его на собственной виртуальной машине (обычно сочетается со стандартным антиотладочными приемами, описанными выше). При этом виртуальная машина внедряется в бинарник вместе с некоторым IL-кодом, который может выглядеть абсолютно произвольно. Параметры создаваемой машины также можно варьировать в некоторых пределах - при желании можно и виртуалку с троичной логикой написать. Бонус такого подхода в том, что для полноценного реверсинга такого приложения необходимо каким-либо образом воспроизвести эту самую виртуальную машину. В такой ситуации (если виртуалка оказывается достаточно хитрой), большая часть стандартных тулзов и приемов реверсера перестает работать "из коробки". Что, опять же, приближает этого самого потенциального реверсера к состоянию "Спасибо, с меня хватит". С точностью до деталей таким образом протектит Themida. В соответствии с написанным выше - если есть желание запротектить свою программу более-менее достойно, то напишите свою виртуалку и сгенерируйте для нее IL-код из имеющихся объектных файлов вашего приложения. Это действие поможет отсеять большую часть реверсеров, которым недавно рассказали и показали, что такое IDA и как применять patch Quick test - для того, чтобы понять, сможете ли вы создать более-менее адекватную защиту с виртуализацией, попробуйте сломать CrackMe от ESET. Если поймете, причем здесь SSE и общую механику проверки, то можно браться за дело. Всегда есть набор готовых public и private пакеров, которыми можно воспользоваться. Themida, ASProtect, FSG. Понятно также, что всегда найдется набор людей, которые эти или иные протекторы в состоянии снять.

вторник, 16 октября 2018 г.

Обфускация .NET приложений

За счёт каких приемов в .NET достигается то, что нельзя восстановить декомпиляторами исходный код на высокоуровневом языке (языке написания)?
Какие приемы используют современные обфускаторы?
Обратима ли обфускация?


Ответ

Данная пост является почти полной копией статьи https://habrahabr.ru/post/74463/

Также рекомендую обзор обфускаторов для .NET. Несмотря на то, что статья 2010 года, она способна дать понимание, что в основном делают обфускаторы и в чём их недоработки.

Методики обфускации
Объединение сборок и пространств имён (Assembly Merge, Namespace Flatten)
Данная методика сама по себе не задерживает злоумышленника ни на минуту, но очень полезна для дальнейшего его запутывания. Т.к. чем больше классов будет содержать результирующая сборка, тем сложнее без детального анализа будет в ней найти то, что надо. Опять же, при попытке украсть ваш код, злоумышленник получит вместо нескольких проектов-библиотек и одной программы только один проект, в котором все классы будут лежать в одной папке (и в одном неймспейсе).
Для объединения сборок можно использовать утилиту ilmerge, либо встроенную в обфускатор функциональность. Пространства имён обычно объединяются во время обфускации имён классов (чтобы не было коллизий с одинаково названными классами из разных неймспейсов).
Переименование классов, методов и т.д.
Данный подход реализован почти во всех уважающих обфускаторах. Удаляются все «подсказки» которые может использовать злоумышленник для быстрого поиска классов, отвечающих за лицензирование, либо при «воровстве» кода будет очень тяжело разобраться в логике приложения, для чего и как создаются классы, и вызываются методы.
Самый популярный вариант на данный момент — переименование в непечатные символы (или какую-нибудь «китайщину» типа 儽.凍::儽). Это немного осложняет просмотр сборки в рефлекторе, но на деобфускаторе никак не сказывается. К тому же из недостатков мы получаем труднопередаваемые сообщения об исключениях, которые могли бы произойти у конечного пользователя, и если он нам вышлет текст не в юникод-кодировке, то разобрать его будет практически невозможно.

Аналогичный вариант — использование коротких, но печатных идентификаторов (a, b, c, …aa, ab, ac…). Для деобфускатора этот вариант полностью аналогичен предыдущему, но зато лишён указанного недостатка.

Третий вариант именования — использование ключевых слов языка высокого уровня (C# или VB.net), либо невалидных идентификаторов для этого языка (например ?123?) — ничем не лучше двух предыдущих, но почему-то считается, что при «воровстве» кода не воспользуются деобфускатором, и на выходе получится некомпилируемый текст...

Ещё существует куча «глупых» вариантов, которые конечно скрывают смысл исходных имён, но зачем делать их такими длинными?

Интересным и ещё более запутывающим подходом является создание большого количества overload-методов с одним именем, которые имели до обфускации разные имена, и никак не были связаны. Также .net позволяет создавать override-методы, имена которых отличаются от имён методов, которые они перекрыли. Это сбивает с толку не только злоумышленников, но и добавляет лишние требования к деобфускатору.

Изменение содержимого классов
Некоторые обфускаторы могут объединять несколько классов в один, или делать из обычного класса вложенный. Но такая обфускация часто приводит к ошибкам в результирующей программе, и используется очень редко.
Обфускация Control Flow
На этом этапе меняется порядок инструкций в коде и даже меняются сами инструкции. Пожалуй, самый интересный и самый спорный этап. Данная методика позволяет ввести в заблуждение (а иногда и в полный ступор) большинство декомпиляторов языков высокого уровня. Что очень хорошо противодействует «воровству» кода. Также «запутывает» кракеров и авторов кейгенов. Обратная сторона медали — иногда сниженная производительность. Логично, что чем больше мы запутываем ход выполнения программы, тем дольше она выполняется. Особенно это относится к использованию исключений. В большинстве случаев код метода бьётся на блоки, эти блоки перемешиваются в случайном порядке и «склеиваются» с помощью безусловных переходов (инструкции br и br.s). В качестве примера:
L_0034: br.s L_003a L_0036: nop L_0037: br.s L_0041 L_0039: nop L_003a: callvirt instance void [Aaa]Xxx.Yyy::Zzz() L_003f: br.s L_0036 L_0041: nop
Бывают и случаи, когда метод очень короткий, и «перемешать» его хорошо не получается, в этом случае некоторые обфускаторы выдают переход на следующую инструкцию:
L_0008: br.s L_000a L_000a: ldarg.0
Между инструкцией перехода, и её целью очень часто вставляется всякое «фуфло», типа выпадения в дебаггер, или просто невалидных инструкций:
L_0000: br.s L_0003 L_0002: break L_0003: ldarg.0
Некоторые обфускаторы заменяют инструкции перехода (как оригинальные, так и вставленные) на загрузку константы и переход на switch
L_0000: br.s L_0023 L_0002: ldloc num3 L_0006: switch (L_005b, L_0068, L_00ce, L_00af, L_0047, L_007b) ... ... ... L_003c: ldc.i4 4 L_0041: stloc num3 L_0045: br.s L_0002
очевидно, что в данном примере инструкция со смещением L_0045 «в девичестве» была br L_0047, а если учесть предыдущие методики, то это вообще nop ;)
Иногда можно встретить «переход на переход»:

в одной из программ я видел цепочку из 6 (шести) таких переходов ;)
Интересный подход — использование условных переходов для выражений, которые всегда верны (или неверны). Самый простой пример:
L_0014: ldc.i4.1 L_0015: brtrue.s L_002e
То же самое, но слегка более запутанное:
L_0014: ldc.i4.1 L_0015: stloc.0 L_0016: br.s L_001c L_0018: nop L_0019: ldarg.1 L_001a: br.s L_002e L_001c: ldloc.0 L_001d: brtrue.s L_0018
Ещё вариант:
if (5 < (5 - 6)) { // IL-мусор, или неверный код }
в виде IL будет выглядеть примерно так:
L_0000: ldc.i4.5 L_0001: dup L_0002: dup L_0003: ldc.i4.6 L_0004: sub L_0005: blt L_0001
Простое перемешивание некоторых инструкций, например:
L_0000: ldc.i4 4 L_0005: stloc num L_0009: ldstr "\u5f03" L_000e: ldloc num
компилятор иногда может выдавать код вида stloc X, ldloc X, когда требуется записать значение в локальную переменную, но не убирать его со стэка. В случае обфускаторов, эта переменная (num) добавлена искусственно, и нигде кроме данных двух инструкций больше не используется.
Один из самых «жёстких» методов — всегда выбрасываемое внутри блока try—catch исключение. Данный подход используется очень редко, т.к. резко снижает производительность и может нарушить логику приложения при неверном использовании. Скриншот я не привожу т.к. он занимает много места.
Invalid IL
Тут всё очень просто. В участки кода, которые никогда не будут исполнены, вставляются не описанные в стандарте опкоды (т.е. невалидные инструкции). В рефлекторе вы увидите примерно такое:

или, если переключится на IL:

Данная методика обескураживает начинающих «хаксоров». Но не является чем-то сложным для обхода (данные опкоды просто заменяются на nop).
Сокрытие строк
Деобфускаторами это называется «шифрование строк», но назвать это шифрованием у меня не поворачивается язык. Обычно это делается каким-нибудь «детским» алгоритмом шифрования типа XOR на константу:
public static string Decode(string str, int num) { int length = str.Length; char[] chArray = str.ToCharArray(); while (--length >= 0) chArray[length] = (char)(chArray[length] ^ num); return new string(chArray); }
Иногда строки объединяются в одну, и потом происходит вызов метода Substring; иногда строки прячут в ресурсы.
В любом случае «шифрование» представлено в виде статического метода с несколькими аргументами, обычно это строка и/или число. Никаких криптографических алгоритмов не применяется, что вполне логично: если применить здесь настоящее шифрование, то программа будет безбожно тормозить. Данный метод спасает от начинающих кракеров, которые будут искать по коду строки типа “Invalid serial number” или другие тексты сообщений.
Специфичные атрибуты и баги декомпиляторов
Самый часто встречаемый атрибут — [SuppressIldasm], который «вежливо просит» не работать на данной сборке официальный декомпилятор Microsoft — ildasm. Существуют также специфичные атрибуты для рефлектора и для коммерческих декомпиляторов.
В качестве багов можно встретить как чисто технические недоработки декомпиляторов (например, рефлектор выпадает на инструкции ldfld string 儽.凍::儽, а большинство деобфускаторов на базе Mono.Cecil — на неправильных RVA), также и алгоритмичиские допущения: многие декомпиляторы высокого уровня отслеживают состояние стэка, но идут по методу не как по графу, а линейно, и радостно валятся на методах в которых после последней инструкции ret вставлен бесконечный цикл. Против плагина Reflexil хорошо «помогает» инструкция, переходящая сама на себя.
Другие методы
Иногда можно встретить очень похожий на скрытие строк подход, но для ресурсов. Также один из обфускаторов предлагает «V-Spot Elimination» (чем очень гордится) — создание прокси-классов для классов BCL, что замедляет анализ и слегка портит полученный декомпиляцией код. Также используется конвертирование managed в unmanaged .net-кода. Т.е. все пересобирается с пометками unmanaged. Практически вся функциональность в пределах домена сохраняется, но рефлектором код уже не посмотреть.