Реверсинг .Net Framework приложений и компонентов (часть 1)

😁
Олдфаг
Статус
Оффлайн
Регистрация
27 Ноя 2016
Сообщения
2,091
Реакции[?]
2,025
Поинты[?]
6K
И так чем же отличается реверсинг .Net Framework приложений и компонентов(далее только .net) от обычных программ, на пример скомпилировынных компилятором C\C++(далее native)? Для ответа на этот вопрос нам понадобится немного теории. Программы написанные для платформы .net не компилируются в native, а компилируются в некий псевдо код называемый MSIL(Microsoft intermediate language), поэтому крэккеру для взлома .net необходимо быть знакомым с этим языком, пожалуй это основная сложность. Но псевдо код вовсе не означает, что IL выполняет некий интерпритатор, как например в программах VB6, откомпилированных с ключем P-CODE. IL код программы компилируется перед запуском, вернее компилируется метод помеченный атрибутом .entrypoint, остальной код компилируется перед его использованием. Эта технология называется JIT (just in time), компеляция во время выполнения. Однако есть возможность сделать эту компеляцию во время установки программы или вручную с помощью утилиты ngen, входящюю в дистрибутив .net. IL немного похож на асемблер, а похож тем что каждая команда имеет не более двух операндов и присутствует работа со стеком. Стек .net представляет собой типизированную реализацию стека в ассемблерном понимании, т.е. в стек можно записывать и считывать объекты определенно типа, нельзя записать в стек dword, а считать из него word :). И так если ты хочешь более подробно изучить IL, тогда тебе следует начать с прочтения доки от фаундера:
Пожалуйста, авторизуйтесь для просмотра ссылки.


Metadata
Метаданные в .net имеет ключевое значение, в метаданных содержится вся информация о классах, методах, свойствах и полях, включая их оригинальное название, т.е. то название которое для них определил разработчик. Именно такое положение вещей делает .net особо уязвимым к декомпиляции. На сегодняшний день написанно не мало декомпиляторов, которые в точности показывают исходный код программы. среди них пожалуй стоит выделить один Reflector. Рефлектор помогает быстро разобраться в логике программы на одном из .net языков, все же нельзя не отметить что разбирать программу проще на ЯВУ, нежеле непосредственно изучая IL. Для полного востановления исходного кода используют плагин Reflector.FileDisassembler. Но могу заверить тебя, что намного проще выявить места для модификации с помощью рефлектора и сделать патч в IL, нежели добиваться компилируемости декомпелированных исходников, т.е. я хочу сказать что декомпеляция все равно происходит не идеально и возникает куча нюансов, которым необходимо уделить время. Плюс ко всему не многие крэккеры имеют практические навыки в программировании под платформу .net

Strong key
Strong Key (сильное имя далее SN) это крипто стойкая(RSA1024) цифровая подпись .net программ, которую разработчик может внедрить в свою программу. Она служит для удостоверение факта того, что программа не была модифицирована кем либо. Если программа была подписанна тогда в дизасемблерном листинге среди прочих атрибутов сборки можно будет увидеть следующее:
Код:
 .custom instance void [mscorlib]System.Reflection.AssemblyKeyFileAttribute::.ctor(string) =
  ( 01 00 7B 43 3A 5C 44 6F 63 75 6D 65 6E 74 73 20   // ..{C:\Documents
    61 6E 64 20 53 65 74 74 69 6E 67 73 5C D0 90 D0   // and Settings\...
    B4 D0 BC D0 B8 D0 BD D0 B8 D1 81 D1 82 D1 80 D0
    B0 D1 82 D0 BE D1 80 5C D0 9C D0 BE D0 B8 20 D0   // .......\...... .
    B4 D0 BE D0 BA D1 83 D0 BC D0 B5 D0 BD D1 82 D1
    8B 5C 56 69 73 75 61 6C 20 53 74 75 64 69 6F 20   // .\Visual Studio
    50 72 6F 6A 65 63 74 73 5C 4D 79 41 70 70 5C 62   // Projects\MyApp\b
    69 6E 5C 44 65 62 75 67 5C 31 2E 6B 65 79 00 00 ) // in\Debug\1.key..

  .publickey = (00 24 00 00 04 80 00 00 94 00 00 00 06 02 00 00   // .$..............
                00 24 00 00 52 53 41 31 00 04 00 00 01 00 01 00   // .$..RSA1........
                2D C3 3A 4A E0 FA EF 05 D5 FC 1C 9D 08 7D 67 5A   // -.:J.........}gZ
                B0 48 EB 1A D4 D6 E4 E2 B7 93 11 A6 D8 68 5F 1B   // .H...........h_.
                7E D7 E3 3C 2C 25 86 1F 34 26 F6 86 26 59 2D 8E   // ~..<,%..4&..&Y-.
                F7 0B B3 DC 74 C1 3D 8E 00 79 06 68 06 82 C3 F2   // ....t.=..y.h....
                E9 91 CB F1 F3 4E 87 CD 4A CB 55 CE 57 DE DF 4E   // .....N..J.U.W..N
                93 64 42 5E A6 86 54 43 D8 25 D0 AD BF 49 F6 9B   // .dB^..TC.%...I..
                53 9D 3B A7 7A C4 0F 5D A4 53 8E 7F 9A EB A5 E9   // S.;.z..].S......
                92 09 B0 F5 C5 9B E5 33 CF C7 6E A5 5A 20 C5 C4 ) // .......3..n.Z ..
В редких случаях после модификации тебе потребуется подписать сборку(Solution еще одно название .net программ) своим SN. Для этого есть утилита sn.exe входящая в дистрибутив .net framework. Для генерации ключа: sn -k 1.key, для подписи: sn -i MyApp.exe 1.key

O Формате PE
Обзац, для маньяков-любителей ковырять все в HEX едиторах, посвещается. Разбор .net программ необходимо начинать со структуры CliHeader, Rva которой записанн в DataDirectory(ENTRY_COM_DESCRUIPTOR), для тех кто не помнит напомню, что DataDirectory(ENTRY_COM_DESCRUIPTOR) идет сразу после DataDirectory(ENTRY_IAT). Структуры C#:

Код:
struct DataDirectory
{
    uint Rva;
    int Size;
}
struct CliHeader
{
    uint Size;
    ushort MajorRuntimeVersion;
    ushort MinorRuntimeVersion;
    DataDirectory File;
    uint Flags;
    uint EntryPointToken;
    DataDirectory Resources;
    DataDirectory StrongNameSignature;
    DataDirectory CodeManagerTable;
    DataDirectory VTableFixups;
    DataDirectory ExportAddressTableJumps;
    DataDirectory ManagedNativeHeader;
}
CliHeader.File.Rva содержит адрес MetadataStartOffset. Дальнейшее изучение PE ты можешь начать с изучение документов

Методы защиты .NET от взлома
Прежде чем говорить о взломе, нужно сказать не много о светлой стороне силы. Почему не много? да потому, что большего она пока не заслужила :). Итак современная protect .net индустрия добилась выпуска двух технологий обфускации и упаковки\криптования. Принцип обфускации .net заключается в простом переименовании названий классов, методов, свойств и полей, на названия типа "_1_1", "_1_2" и т.д. Это якобы затрудняет анализ программы, но не редко обфускация помогает анализировать программы. Например разработчики могут давать страшные названия классам содержащие слова Protected или Security, сей факт заставляет особо бдительно просматривать код, на что тратится лишнее время но потом оказывается, что разработчик решает весьма мирные задачи, используя пространство имен System.Security.Permissions или System.Security.Policy, это конечно не имеет ни какого отношения к защите кода. Также для защиты .net существуют несколько горе-пакеров, которые не тянут даже на средний уровень защиты. Они бережно пакуют и криптуют .net программу, но они вероятно не знают, что все что нам нужно это подождать распаковки ;). Ни о каких современных техниках защиты с применением драйверов или с переносом когда на псевдо код для виртуальной машины протектора, речи быть не может. Это связанно с архитектурными особенностями платформы .net. Оригинальный(не криптованный\упакованный) модуль может потребоваться платформе в любой момент, например если нужно определить некий атрибут кода или когда программисты решая свои прикладные задачи используют пространство имен System.Reflection, я уже не говорю про сериализацию и ремоутинг, где рефлекция используется повсеместно. В заключение можно лишь сказать что МС сделала отличную современную платформу для разрботки, но которую нельзя толком защитить ни в статике, ни в динамике.

Взлом
И так переходим к расмотрению техники взлома. Первое что надо сделать это дизассемблирование: ildasm MyApp.exe /out:MyApp.h на выходе будет файл MyApp.h в котором содержится IL листинг программы, почему *.h? потому что эти файлы понимает визуал студия, которая подсвечивает некоторый синтаксис и есть возможность закоментировать сразу несколько строк кода, это удобно. Коментарии в IL тоже // и /* */. После изучения и модификации кода необходимо сделать ассемблирование: ilasm MyApp.h Особо нужно заметить, что после дизассемлирования в каталоге, где находится MyApp.h, создаются и файлы ресурсов, которые при ассемблировании не нужно перечислять в командной строке ilasm т.к. они уже перечисленны в файле MyApp.h в виде атрибутов: .mresource public MyApp.Form1.resources. Если в файле присутствует SN, тогда его необходимо удалить из листинга, просто удалив атрибуты .AssemblyKeyFileAttribute и .publickey, пример которых показан выше. Точка входа в программу это метод помеченный атрибутом .entrypoint, именно с него стоит начинать исследование. Так же в каждом .net классе есть точка входа которая помеченна атрибутом .ctor или .cctor(также эти методы называют конструкторы), в 99.9% случаях интересный для реверсинга код находится именно в этих методах. Конструкторы вызываются в момент создания объекта из класса, пример: newobj instance void MyApp.Form1::.ctor(). Для отладки необходимо в теле метода .entrypoint поставить команду break, остальные брейки можно раставить непосредственно в отладчике. После этого компилировать нужно с генерацией отладочных файлов *.pdb, делается это так: ilasm MyApp.h /debug. Если у тебя установлена студия, тогда после запуска программы появится окошко где нужно выбрать отладчик, выбрать нужно Microsoft CLR Debugger.

Пример
Наш пример сегодня посвящен обфускатору Decompiler.NET $550 с гордой надписью в документации: "Decompiler.NET protects itself". Пожалуй стоит признать храбрость или глупость автора ;). Почему же был выбран именно этот обфускатор? ). Но тем неменее я очень хотел, что бы примером данной статьи был обфускатор: Dotfuscator Professional Edition for .NET $1495. По двум причинам. Во первых цена слишком борзая на мой взгляд, а во вторых я целый месяц не могу получить триал версию. что бы получить триал, нужно заполнить специальную форму, после заполнения которой появляется надпись: "Note: Acceptance of registration is not guaranteed." т.е. посмотреть триал могут только избранные. Но причина на самом деле другая, они боятся показывать даже триал, получается они не уверенны в своем обфускаторе и они продают людям свою неуверенность за полтора килобакса!!
И так вернемся к Decompiler.NET. Этот обфускатор по совместительству еще дизасемблер, декомпелятор, оптимизатор, рефактор и Show MSIL :). Автор защиты один из тех, кто верит, что если устроить по настоящему мсил-шоу, тогда никто ни в чем не сможет разобраться. Однако первое что можно увидеть, открыв файл в рефлекторе, это два не обфусцированных неймспейса jungle.Deploy.NET.Launcher и jungle.Deploy.NET.Launcher.Licensing, что само по себе кажется очень подозрительным, это привлекает внимание как оазис в пустыне. Не поддаваясь провокации и чтя традиции, исследование началось, как обычно с метода помеченного атрибутом .entrypoint в нем небыло ни чего инересного. Через определенное количество вызовов функций(.entrypoint->ab.a3::ah1(string[])->ab.a3::ah0(string[])) мы попадаем в ag0. в этом методе есть вызов метода string a4.a8::aba(int32), просмотрев код стало понятно, что эта функция извлекает из колекции зашифрованную строку под номером int32, переданном в параметре, и расшифровывает ее. В связи с этим я сделал фунцию(DumpCryptedStrings), которая переберает все строки в коллекции, расшифровывает их и печатает в отладчике в окошке Output. Эту функцию ты сможешь посмотреть в файле Decompiler.NET.h на диске. Ее вызов закоментированн в .entrypoint, так же этот файл содержит более подробные коментарии относительно анализа кода.
Далее мы попадаем в функцию ab.az::ag2(string, bool, string, object[]). Первое что привлекает здесь внимание это создание дополнительных доменов приложения System.AppDomain::CreateDomain (читаем матчасть). Это говорит о том, что с большой вероятностью, кроме того, что код обфусцирован, мы имеем дело еще с пакером\криптером. Схема простая в определенный момент ресурс программы расшифровывается, потом распаковывается и динамически загружается в память. Наша задача найти, этот момент и сделать дамп. Так же в этой функции есть вызов: L_00e9: callvirt instance bool jungle.Deploy.NET.Launcher.IConfiguration::get_CheckLicenseSignature() его необходимо закоментировать, и поправить далее L_00ee: brfalse.s L_0126
на L_00ee: brtrue.s L_0126, нам необходимо что бы код выполнился внутри этого условия. Сразу после условия идет вызов ab.'at'::get_afy(), в этой функции ничего интересного нет поэтому интересует серия переходов (ab.'at'::get_afy()->ab.au::af6(bool, bool)->ab.au::af8(class jungle.Deploy.NET.Launcher.Licensing.License, bool, bool)) посмотри внимательно эту функцию в рефлекторе, не знаю что увидел ты, но мне первое что увиделось это: byte[] buffer2 = reader2.ReadBytes(num1); :) вот и первая хитрая функция которая извлекает из ресурсов стандартную лицензию, ее я тоже запишу на диск.
Всего в этом загрузчике три такие функции, для извлечения лицензии, конфигурации и шифрованной програмы, им соответствуют ресурсы: jungle.Deploy.NET.Launcher.License.resources, jungle.Deploy.NET.Launcher.Configuration.resources, jungle.Deploy.NET.Launcher.Archive.resources и сответсвуют функции для расшифровки: ab.au::af8(class jungle.Deploy.NET.Launcher.Licensing.License, bool, bool)), ab.au::af5(bool __0, bool __1), ab.ax::agz(Hashtable __0), остальные функции я нашел бональным поиском текста "MemoryStream stream3" т.к. врятли бы автор стал писать разный алгоритм для этих трех функций, копипаст нам в помощь товарищи :). Как я узнал, что всего три функции? Дело в том, что эти три ресурса имееют нестандартный формат, и эдиторы ресурсов .net ругаются на них, поэтому я изначально это предположил. Когда знаешь, что ищешь тогда обязательно найдешь. Так же из крипто коллекции строк, в этих функциях извлекаются названия ресурсов с которыми они работают, это еще раз подтверждало, что я правильно определил их. Вернемся к необфусцированному классу Licensing, как я и предпологал, этот класс просто мусор, его патчинг не дал положительных результатов, хотя этот класс и используется в загрузчике, делается это в холостую. Поэтому прежде чем я распаковал оригинальный exe, я попытался найти главную форму в загрузчике, что бы пропатчить в ней триальные ограничения, тогда у меня еще не было полной уверенности, что это загрузчик а не сама программа. После распаковки оказалось, что в ....Archive.resources три файла, оригинальный exe и три dll. Для распаковки я создал метод DecryptResurceLauncherArchive, он доступен в том же месте, что и DumpCryptedStrings.
После распаковки все, что нам нужно сделать это пропатчить функцию (уже в распакованном файле конечно;) jungle.Decompiler.NET.Licensing::.ctor(string __0, class jungle.Decompiler.NET.Licensing.License __1). Пропатчить нужно всего в шести местах по меткам: IL_0043, IL_004a, IL_0051, IL_00f4, IL_00f6, IL_0102, заменив ldc.i4.0 на ldc.i4.1, это флаги класса License: fIsLicenseKeyValid, fIsSignatureValid и fIsLicenseKeyValidComputed. Все готово, обфускатор теперь работает как надо: notrial.png


Хитрости
Еще я хотел рассказать о хитростях, которые автор использовал для затруднения анализа кода. Выше я уже говорил, что в методе ab.az::ag2 используется динамическое создание доменов. Так вот автор определил класс ProxyControl, посредством которого происходили вызовы методов оригинального ехе из загрузчика и наоборот. Технология называется Remouting. Это дало замусоревание CallStack, я в отладчике не мог понять откуда вызываются методы, адреса возврата уходили кудато в неуправляемый код, особенно меня удивило, когда в процессе ковыряния загрузчика, неожиданно выскачила форма licencewarning.png, которая была определена в самом загрузчике ab.ap.cs . Лечится это просто удаляются функции создания динамических доменов System.AppDomain::CreateDomain и удаляются сопутствующие им динамические создания объектов (C#): this.ag5 = (av) domain1.CreateInstanceAndUnwrap(type1.Assembly.GetName().Name, type1.FullName); в место этого лучше написать просто this.ag5 = new type1. Или на крайний случай написать так System.AppDomain::CreateInstance(type1.Assembly.GetName().Name, type1.FullName); тогда объект будет создан в текущем домене и все заморочки исчезнут. Вторая хитрость заключается в хитром вызове функций, компрометируищие названия которых,привлекает внимание. Предварительно создается поле с типом Type, класс Type поддерживает любые динамические махинации, например вызов метода или чтение поля класса. Это можно наблюдать в a4.a8::.cctor, создается Type a8.ai4 = Type.GetType("System.Security.Cryptography.TripleDESCryptoServiceProvider"); потом создается объект описывающий конкретный метод: MethodBase a8.ai0 = a8.ai3.GetGetMethod(true); а вызывается этот метот вот так: a8.ai0.Invoke(). Эта технология называется рефлекция, пространство имен System.Reflection. Но у нее есть большой недостаток, это очень не производительный способ вызова методов, поэтому годится он только на этапе инициализации, что в принципе устраивает автора защиты. Следующая хитрость это метод хранения лицензии. Классы помеченные атрибутом [Serializable] могут быть сохранены в потоке(stream) или востановлены из него. При этом сохраняются значения полей класса и информация о сборке. Но есть маленький нюанс, который и позволяет использовать его в защите. При изменении версии сборки сохраненный ранее объект уже не может быть восстановлен. Поэтому совершив маленькую оплошность, можно потратить немало времени чтобы ее выявить. Автор использует данную технологию по отношению к некоторым классам, в том числе и к классу Licensing.

Обфускация
Decompiler.NET защищал обфускаций человек, который является специалистом в этой области. И это один из лучших обфусцированных кодов, которые мне доводилось видеть, но и это дало коду только три часа стойкости. Что же можно сказать, про защиту обфускатором, которую использует обычный програмист? Ответ: Эта защита врятли проживет больше 15ти-20ти минут. Это подтверждение моих слов, в начале статьи, об уязвимости .net программ.

Домашнее задание:
Decompiler.NET после обфускации оставляет некоторые сигнатуры, что бы не декомпилировать\дизасемблировать сборки, защищенные им. Это и есть, то задание, которое предстоит тебе выполнить для закрепления материала. При открытии такой сборки, выдается сообщение, что ты не имеешь разрешения на это действие. Нажав в отладчике бряк, в этот момент, ты попадешь в отправную точку для этого исследования. Твоя задача устранить сей недостаток. Удачи ;)
 
Сверху Снизу