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

😁
Олдфаг
Статус
Оффлайн
Регистрация
27 Ноя 2016
Сообщения
2,091
Реакции[?]
2,025
Поинты[?]
6K
Введение
Данная статья посвящается рассмотрению техники востановления исходного кода .Net программ. Так же мы рассмотрим проблемы, возникшие после восстановления исходного кода т.к. не редко восстановленный код имеет ошибки компеляции. В предыдущих своих статьях я уже упоминал об этом.

И так для чего же необходимо востанавливать исходный код? Исходный код востанавливают, в основном, по двум причинам. Первая причина это промышленный шпионаж, к примеру на мировом рынке имеется около 10 компаний, производящих Grid контрол. На сегодняшний день, 3-4 Компании это тройка лидеров, которые активно борятся за покупателей, привнося в свой Grid контрол, что то новое. Если ноухао оказалось на столько успешным, что один из конкурентов получает серьезные преимущества на этом рынке, тогда и возникает ситуация, которая соблазняет конкурентов проникнуть в дебри технологии, дабы внедрить ее в свой продукт. И так первая причина, это востановление исходного кода с целью кражи технологий. Вторая причина это кража самого продукта, что бы не платить за него деньги. На данный момент, мораль сей басни такова, что поменяв названия классов и namespace'ов, можно с легкостью утверждать, что компонент был написанн вами, и настоящие разработчики не смогут доказать своих авторских прав. Так почему же существуют эти причины? Они существуют потому, что украсть чужой труд, экономически намного выгоднее, т.к. при этом экономится время и деньги. Как это происходит, мы и будем рассматривать далее в этой статье.

Восстановление исходного кода, достигается путем анализа исполняемого файла. Целью анализа является поиск языковых конструкций исходного кода, характерных для данного участка, исполняемого кода. Данная технология называется декомпиляция. Однако декомпиляция обычно происходит в два прохода. При первом проходе анализатора происходит дизасембирование. Это необходимо для первоначального разбора исполняемого файла и составления структур для дальнейшего анализа кода. Дизасемблирование это перевод исполняемого кода, в код неких абстрактных команд. Некоторые путают эти два понятия. в .Net дизасемблирование, выдает листинг IL команд, а декомпиляция выдает исходный код на одном из .Net языков высокого уровня(ЯВУ). Для декомпиляции мы будем использовать Reflector(
Пожалуйста, авторизуйтесь для просмотра ссылки.
) и плагин Reflector.FileDisassembler(
Пожалуйста, авторизуйтесь для просмотра ссылки.
, он позволяет сохранять классы в отдельные файлы, конвертировать ресурсы в формат *.resx и создавать файл проекта.

(смотри картинку NicePanel.gif)
Нашим первым примером восстановления исходного кода будет выступать замечательный красивый контрол nicepanel. Его можно скачать тут
Пожалуйста, авторизуйтесь для просмотра ссылки.
. Мы будем декомпилировать данный контрол в C#, потому что этот контрол был написанн на нем. Декомпилировать в другой ЯВУ, несколько проблематично, т.к. существует разница в синтаксических конструкциях языков. Например в VB.Net не учитывается регистр символов имени классов, методов и т.д., а в C# одинаковые буквы в разных регистрах, считаются разными названиями, Reflector не учитывает это, поэтому при декомпиляции C#'ного кода в VB.Net, у нас будет куча ошибок. Первая ошибка которую выдал компилятор: PureComponents.NicePanel.Design\ActionMenuNative.cs(249): Небезопасный код может использоваться только при компиляции с параметром /unsafe. В компаненьте используются не безопастные конструкции с использованием указателей, как известно указатели могут указывать на не распределенную память или указывать совсем не туда куда он должен указывать. Это распрастраненная ошибка программирования с использованием указателей, поэтому в .Net осталась лишь поддержка этой возможности, для случаев где без указателей нельзя решить поставленную задачу. И так нужно зайти в опции проекта и установить параметр Allow Unsafe Code Blocks = True. После этого делаем еще раз buid, и видим что компелятор выдал нам 58 ошибок.хххх Верстальщику: вставить картинку CompileFirst.bmpхххх Начнем с проблемы которая ждала нас в методе PureComponents.NicePanel.NicePanel::OnPaintBackground(PaintEventArgs pevent). Компилятор сообщает об ошибке cs(416): Оператор '+' не может применяться к операндам типа 'PureComponents.NicePanel.PanelHeaderSize' и 'PureComponents.NicePanel.PanelHeaderSize', посмотрев на PanelHeaderSize мы видим, что это enum(перечисление).
Код:
    public enum PanelHeaderSize
    {
        Large  = 40,
        Medium = 24,
        Small  = 16
    }
После это становится понятно, что должно быть сложение элементов перечисления, для этого в C# используется оператор (|), поэтому заменив (+) на (|) мы избавляемся от девяти ошибок. Аналогичная ошибка есть еще в методе PureComponents.NicePanel.Design.NicePanelDesigner::OnRemoveAutoScrollPanel(object sender, EventArgs e). В этом же классе компилятор ругается в методе OnAddAutoScrollPanel(object sender, EventArgs e) строка 345: Оператор '-' не может применяться к операндам типа 'int' и 'PureComponents.NicePanel.PanelHeaderSize'. Вот эта самая строка: panel1.Height = (int)(((((PanelHeaderSize)this.m_NicePanel.Height) - this.m_NicePanel.Style.HeaderStyle.Size) - this.m_NicePanel.Style.FooterStyle.Size) - ((PanelHeaderSize)2)); Давай рассмотрим эту строку кода по ближе. Во первых хотел обратить внимание на приведение типа int к типу PanelHeaderSize: (PanelHeaderSize)this.m_NicePanel.Height, при этом происходит выравнивание к ближайшему элементу в перечислении PanelHeaderSize, если к примеру Height будет равен девятнадцати, тогда это преобразование округлит до PanelHeaderSize.Small(16), если Height будет равен двадцати одному тогда преобразование округлит до PanelHeaderSize.Medium(24). Автор таким преобразованием добивается дискретности размера некоего окна и поэтому размер окна в любом случае будет только одним из трех (40, 24, 16). Суть ошибки в этой строке в том, что когда два enum'а участвуют в разности, тогда результат автоматически преобразуется в тип int, и уже при следующем вычитании получается что из обьекта с типом int вычитается объект с типом PanelHeaderSize, компилятор видит не соответствие типов и выдает нам ошибку компиляции, следовательно для решения данной проблемы мы должны выделить каждую разность в отдельные скобки и сделать приведение результата разности к типу PanelHeaderSize. К сожалению Reflector неучитывает эту особенность, и если у тебя есть время, ты можешь написать аисту баг репорт :). В итоге эта строка кода должна выглядеть так: panel1.Height = (int)((PanelHeaderSize)((PanelHeaderSize)((PanelHeaderSize)((PanelHeaderSize)this.m_NicePanel.Height) - this.m_NicePanel.Style.HeaderStyle.Size) - this.m_NicePanel.Style.FooterStyle.Size) - ((PanelHeaderSize)2)); Остается еще две проблемы, связанные с этим несчастным перечислением :). Копилятор сообщает: PureComponents.NicePanel\NicePanel.cs: Оператор '/' не может применяться к операндам типа 'PureComponents.NicePanel.PanelHeaderSize' и 'PureComponents.NicePanel.PanelHeaderSize', это происходит в строках 1704 и 1900. Вот так будут правильно выглядеть эти строки: первая int num2 = (int)(PanelHeaderSize)((((PanelHeaderSize) (this.Height - num1)) - this.Style.FooterStyle.Size) + ((int)this.Style.FooterStyle.Size / 2)); и вторая int num3 = (int) (((PanelHeaderSize) num1) + ((this.Style.HeaderStyle.Size - ((PanelHeaderSize) 2)) / 2));

Далее следуют более каверзные ошибки, они синтаксические например в классе NicePanelDesigner строка 135: if(<PrivateImplementationDetails>.$$method0x60000d2-1 == null) тут сразу несколько ошибок знаки (>), ($) и (-) не могут использоватся в названиях методов, классов и т.д. Открыв эту сборку в рефлекторе, можно обнаружемть что метод $$method0x60000d2-1 действительно существует, кликнув на название этого метода, мы попадаем в интересный класс:
Код:
internal class <PrivateImplementationDetails>
{
      // Fields
      internal static $$struct0x6000067-1 $$method0x6000067-1; // data size: 176 bytes
      internal static Hashtable $$method0x60000d2-1;
      internal static Hashtable $$method0x60000d2-2;
      internal static $$struct0x6000157-1 $$method0x6000157-1; // data size: 512 bytes

      // Nested Types
      [StructLayout(LayoutKind.Explicit, Size=0xb0, Pack=1)]
      private struct $$struct0x6000067-1
      {
      }

      [StructLayout(LayoutKind.Explicit, Size=0x200, Pack=1)]
      private struct $$struct0x6000157-1
      {
      }
}
Причем этот класс находится в пространстве имен ("-"), это пространство имен создается автоматически компилятором, в него в ходят глобальные поля, методы и классы, но это не поддерживается в C#, это дает оснавания пологать, что использовался некий защитный механизм, тем более что из этого класса в проекте используется только одно поле $$method0x60000d2-1. Чтобы исправить данные глюки мы создадим класс Helper, в котором и создадим одно поле и подравим все обращения к ниму в строках 228, 148 и 135.
Код:
using System;
using System.Collections;

namespace PureComponents.NicePanel.Design
{
    public class Helper
    {
            public static Hashtable Hashtable1;
    }
}
Код:
using System;

using System.Collections;

namespace PureComponents.NicePanel.Design
{
    public class Helper
    {
            public static Hashtable Hashtable1;
    }
}
И последняя ошибка которую нам предстоит исправить: Сбой криптографических служб при создании подписи сборки 'nicePanelKey.snk' -- Не удается найти указанный файл. Это закрытый ключ который использовался для цифровой подписи этого компонента. что бы это исправить нужно зайти в файл AssemblyInfo.cs, здесь мы видим всякие атрибуты сборки, среди которых указанны: AssemblyVersion, AssemblyProduct, AssemblyCopyright, AssemblyCompany, AssemblyKeyFile и т.д. Значения этих параметров можно заменить на свои, или удалить их вовсе. На данном этапе мы добились компилируемости декомпилированных исходников, теперь нужно проверить работоспособность этих исходников ;), для этого мы добавляем в Solution любой из примеров поставляемых вместе с компонентом, я предпочел Showcase он более нагляден и показывает практически все способности компанента, так что если мы сделали ошибку тогда это будет сразу видно. Запустив пример я не увидел ни каких проблем, следовательно востановление исходного кода можно считать успешным :).

(смотри картинки junus1.jpg junus2.jpg)
Вторым примером нашей статьи будет Grid компонент от компании Janus (
Пожалуйста, авторизуйтесь для просмотра ссылки.
), тело контрола было обфусцированно, но только та часть кода которая была помеченна атрибутами доступа private и internal. Зато все public методы и классы остались в первозданном виде :), это связанно с тем что разработчики должны, используя этот контрол, видеть нормальные названия классов и полей классов, а не обфусцированные, это нам наруку. Первые ошибки которые можно наблюдать после декомпиляции, это множественные ошибки ресурсов. Это связанно с тем что декомпилятор создает отдельные папки для каждого пространства имен и классы, которые входят в них, он складывает в эти папки, но ресурсы он складывать в них почему то забывает. Те классы которые являются производными от System.Windows.Forms.Control или System.Windows.Forms.Form могут иметь свой файл ресурса и он должен распологаться в той же папке где и сам класс. Поэтому все что нужно сделать это разложить файлы ресурсов в соответствующие папки. Например ресурс Janus.Windows.GridEX.EditControls.Calendar.JNSAB.resx нужно положить в папку \Janus\Windows\GridEX\EditControls\Calendar, относительно корневой папки проэкта, а сам ресурс переименовать в JNSAB.resx. После исправления этих ошибок перед нами возникает другая куча ошибок. Они уже связанны с обфускацией кода. Пример ошибки: private void l(object, EventArgs args1), здесь пропущенно название первого параметра. Наверно этот эффект достигается путем переименования названия параметра в пробел, а если параметров два тогда переименовывается в два пробела, и т.д. Такой способ обфускации мне был неизвестен. Сначало я решил исправлять эти ошибки вручную, но исправив одну ошибку появились десять таких же, коварная студия почему то не показывает все ошибки сразу, а выдает их порциями, складывается даже впечатление, что они создаются в процессе :). Вобщем этих ошибок в коде не менее пятисот, и править их руками дело утомительное, поэтому я решил автоматизировать данный процесс, написав макрос для исправления данной ошибки.

Код:
Imports EnvDTE
Imports System.Diagnostics
Imports System.Collections

Public Module Module1

    Sub CheckErrors()
        Dim al As ArrayList = ListProj()

        For i As Integer = 0 To al.Count - 1
            Dim pr As ProjectItem = al(i)

            Dim n As String = pr.Name

            For j As Integer = 1 To pr.FileCodeModel.CodeElements.Count
                Dim code As CodeElement = pr.FileCodeModel.CodeElements.Item(j)

                Dim ep As EditPoint = code.StartPoint.CreateEditPoint()
                Dim str As String = ep.GetText(code.EndPoint)

                ParseCode(CType(code, CodeNamespace).Members)
            Next

        Next
    End Sub

    Sub ParseCode(ByVal elem As CodeElements)
        For i As Integer = 1 To elem.Count
            Dim code As CodeElement = elem.Item(i)

            If code.IsCodeType() Then
            End If

            Dim ep As EditPoint = code.StartPoint.CreateEditPoint()
            Dim str As String = ep.GetText(code.EndPoint)

            If TypeOf code Is CodeClass Or _
                TypeOf code Is CodeStruct Or _
                TypeOf code Is CodeInterface Then

                ParseCode(CType(code, CodeType).Members)
            ElseIf TypeOf code Is CodeFunction Then
                ParseParameters(CType(code, CodeFunction).Parameters)
            End If
        Next
    End Sub

    Sub ParseParameters(ByVal elem As CodeElements)
        For i As Integer = 1 To elem.Count
            Dim code As CodeParameter = elem.Item(i)

            Dim ep As EditPoint = code.StartPoint.CreateEditPoint()
            Dim str As String = ep.GetText(code.EndPoint)

            If str.Split(" ".ToCharArray()).Length < 2 Then
                ep.WordRight()
                ep.Insert(" __Param" + i.ToString())
            End If
        Next
    End Sub

    Function ListProj() As ArrayList
        Dim list As New ArrayList

        Dim proj As Project = DTE.ActiveSolutionProjects(0)
        Dim win As Window = DTE.Windows.Item(Constants.vsWindowKindCommandWindow)
        ListProjAux(proj.ProjectItems(), list)

        Return list
    End Function

    Sub ListProjAux(ByVal projitems As ProjectItems, ByVal list As ArrayList)
        For Each projitem As ProjectItem In projitems
            If GetExt(projitem) = "cs" Then list.Add(projitem)
            If Not projitem.ProjectItems Is Nothing Then
                ListProjAux(projitem.ProjectItems, list)
            End If
        Next
    End Sub

    Function GetExt(ByVal pi As ProjectItem) As String
        Dim str() As String = pi.Name.Split(".".ToCharArray())
        Return str(str.Length - 1)
    End Function

End Module
Что бы вставить этот макрос нужно курить меню Tools->Macros->Macro IDE. Ранее я ни когда не писал макросы для студии. Поэтому не удивляйся от того что макрос написан на vb .net, это язык по умолчанию для Macro IDE и разбиратся с этим мне было лениво :) Собственно запустив макрос, можно успеть попить кофе, прежде чем он закончит свою работу. Макрос вставляет названия параметров в формате " __Param" + NumberOfItemParameter. Закончив работу макрос исправит все эти ошибки. Но на смену им приходят более коварные ошибки. Все дело в том что в il коде допускаются методы которые могут быть похоже количеством параметров и с одинаковыми типами параметров, но такие методы должны различатся типом возвращаемого значения. Этих ошибок в коде не меньше чем предыдущих, но они тоже решается с помощью макроса. Далее остаются только две ошибки, как и в предыдущем примере, связанные с присутствием двух классов в пространстве имен ("-"), названия которых a и b. Члены этих классов не используются в проекте поэтому их нужно просто исключить из проекта или удалить. Наконецто мы избавились от всех ошибок компеляции. Берем пример, поставляемый с данным компонентом, запускаем и видим что все работает без ошибок.

И так теперь ты знаешь, как потратив максимум час времени востановить работоспобные исходники двух компонентов, на написание которых ты бы потратил не менее двух месяцев, а то и больше, или потратил бы кучу зелени. Теперь включив этот исходный код непосредственно в свой проект и обфусцировав его, ни кто не будет в состоянии доказать, что это не ты написал эту часть своей программы ;). В свое время осознав это я понял, что весь мир работает на меня :).
 
Сверху Снизу