Как обмануть NET.Reflector

    Сегодня я задумался о том, как обфускаторы скрывают код методов от утилит деобфускации вроде NET.Reflector. Как ни странно, но я не нашел в интернете никакой полезной информации по этому вопросу (возможно, плохо искал) и поэтому пришлось провести маленькое исследования самостоятельно. Под катом краткая заметка о результатах. В примере кода будет снова использоваться Mono.Cecil и генерация кода, так что не забудьте прочитать мою первую статью.



    Начнем с теории. Каждый код MSIL инструкции может быть представлен одним или двумя байтами. Например, инстукция nop представляется как 0x00. Таким образом мы имеем 65536 различных вариантов. На текущий момент некоторая часть этого пространства занята валидными кодами MSIL инструкций, однако большая часть свободна. Переход JIT компилятора на инструкцию с некорректным кодом приведет к аварийному завершению приложения. NET.Reflector, натыкаясь на некорректную инструкцию прекращает разбор кода метода и выводит сообщение «Invalid method body», что нам и требуется.

    Таким образом, наша цель — вставить в метод некорректную инструкцию, но так, чтобы переход на нее не мог произойти ни при каких условиях. Для этого можно использовать безусловный переход:

    goto MethodCode;
    // здесь некорректная инструкция
    MethodCode:
    // основной код метода
    


    Reflector не будет анализировать достижимость кода и при анализе плохой инструкции споткнется и дальше метод анализировать не станет. Приложение же будет работать нормально, так как перехода на плохую инструкцию никогда не произойдет. Дело несколько осложняется тем, что Mono.Cecil не даст нам так просто вставить плохую инструкцию — все валидные коды представлены в виде перечисления и добавить свой стандартными средствами нельзя. Конечно, всегда можно поменять исходники Mono.Cecil, благо система с открытым кодом, но мне хотелось, чтобы работало на стандартной сборке. После получаса анализа исходников Mono.Cecil я нашел, как вставить некорректную инструкцию 0x0024 так, чтобы Mono.Cecil пропустил ее и не выдал исключения. Посмотрим на код:

    static void ProtectMethod(string path, string methodName)
        {
          var assembly = AssemblyDefinition.ReadAssembly(path);
          foreach (var typeDef in assembly.MainModule.Types)
          {
            foreach (var method in typeDef.Methods)
            {
              if (method.Name == methodName)
              {
                var ilProc = method.Body.GetILProcessor();
                // здесь получаем internal конструктор для класса OpCode
                var constructor = typeof(OpCode).GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, null, new Type[] { typeof(int), typeof(int) }, null);
                // в Mono.Cecil инструкции создаются оригинальным способом - в конструктор передается два 4х-битных(int) числа, из которых операциями побитового сдвига получается 8 байт, а каждый байт отвечает за определенный параметр MSIL иструкции. Соответственно, такими же операциями побитового сдвига мы превращаем 8 байт в 2 числа. Каждый байт отвечает за определенную характеристику OpCode, но нам важны только первые два. Остальные тоже имеют некоторое значение для нашей задачи, так как если задать их абы как, Mono.Cecil может не допустить такую инструкцию и выкинет Exception, но я не буду останавливаться на подробностях.
                int x = 
                  0xff << 0  | //это первый байт IL инстуркции
                  0x24 << 8  | //это второй байт IL инструкции
                  0x00 << 16 | 
                  (byte) FlowControl.Next << 24;
                // дальнейшее не имеет отношения к нашей цели, однако необходимо для того, чтобы Mono.Cecil корректно обработал нашу инстукцию
                int y = (byte) OpCodeType.Primitive << 0   | 
                        (byte) OperandType.InlineNone << 8 | 
                        (byte) StackBehaviour.Pop0 << 16   | 
                        (byte) StackBehaviour.Push0 << 24;
                
                var badOpCode = (OpCode) constructor.Invoke(new object[] {x, y});
                // создаем плохую инструкцию
                Instruction badInstruction = Instruction.Create(badOpCode);
                Instruction firstInstruction = ilProc.Body.Instructions[0];
                // вставляем инструкцию безусловного перехода на реальный код метода
                ilProc.InsertBefore(firstInstruction, Instruction.Create(OpCodes.Br_S, firstInstruction));
                // вставляем плохую инструкцию перед началом кода метода
                ilProc.InsertBefore(firstInstruction, badInstruction);
              }
            }
          }
          assembly.Write(path);
        }
    


    Рассмотрим результат работы этого метода на некотором методе нашей сборки, который мы хотим защитить от просмотра

    Метод до обработки:
    image
    image

    Метод в рефлекторе после обработки:
    image
    image

    При этом после обработки приложение запускается без проблем. Требуемый результат достигнут.

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

    Похожие публикации

    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 28

      +1
      Это статья уже более реальнее )
      1. Нужно будет так изменять каждый защищаемый метод?
      2. Как все это дело автоматизировать? Может написать свой обфускатор (применяя изложенные методы в заметке) и есть ли такие уже готовые?
        +3
        Все уже давно написано.
        +1
        1) Если в методе убрать параметр «string methodName» и проверку «if (method.Name == methodName)», то он обработает все методы целевой сборки.

        2) Конечно, уже есть готовые утилиты, применяющие этот и многие другие методы, но я не знаю хорошего бесплатного обфускатора (но что касается именно указанного в заметке метода — его применяют некоторые бесплатные обфускаторы). Также, как я уже описал в заметке, не стоит слишком полагаться на этот метод, так как разработчик, который знает, как его обойти, обойдет его без проблем. Автоматизировать это можно например так — указать в PostBuild событии Visual Studio для целевого проекта путь к консольному приложению с параметром в виде пути к вашей сборке, используя соответствующие макросы VS (до этого конечно нужно обернуть описанный выше метод в консольное приложение). Тогда после каждого успешного построения все методы вашей сборки будут автоматически обрабатываться. Другой способ — подключиться к MSBuild.
          0
          я не знаю хорошего бесплатного обфускатора

          Можете попробовать Babel .NET, мы им пользуемся для обфускации, довольны.
          0
          Перевод в C#, VB,… можно также отрубить, вызывая Exception в Reflector путем введения в стек лишних значений и перемешивания кода.
            0
            Очень интересная тема. Спасибо.

            Читателям добавлю: Разработчики, применяйте обфускацию, хотя бы в коммерческих проектах. Этим самым обезопасите себя во многих направлениях.
              +4
              От чего обезопасим «защитив» код? И какой обфускатор посоветуете (методы обфускации). Можно даже купить, если стоимость не будет равна годовому заработку :)
                0
                К сожалению самому пока не доводилось писать приложения, которые распростараняются, а только работающие внутри компании. Как следствие, не занимался в плотную обфускацией. Посоветовать к сожалению не могу.

                За то регулярно удивляюсь, сколько «незащищенных» коммерческих приложений выпущено в свет. Иногда за 15 минут можно пройтись рефлектором по всем библиотекам приложения, и создать рабочий солюшн с несколькими проектами в нем.

                От лицензирования подобный софт отучается на раз два. Нет ни какой гарантии, что этими исходниками не воспользуются со стороны. Кроме того этим облегчается поиск уязвимостей (в большей степени актуально для серверных решений).
                  0
                  Такова природа NET приложений на данный момент.
                    0
                    Ну к серверным решениям вы сначала доступ получите, а потом рефлектором смотрите :)
                      0
                      Я имею виду случай, если продукт продается с сайта и доступен для свободного скачивания
                +2
                насколько я понимаю, инструкций не 65538, а несколько меньше.
                ведь раз инструкция либо однобайтная, либо двухбайтная, значит один бит уходит на указание того, сколько байт в инструкции.
                значит остается 128 однобайтных и 32768 двухбйтных инструкций.
                всего 32896.
                ну это так, придираясь по мелочи. )
                  0
                  Все верно, но сути не меняет :)
                  +1
                  Про стек еще напишите.
                    0
                    Не понял, что именно вам интересно про стек. Некоторая информация об этом есть в моей первой статье.
                      0
                      Я имел ввиду манипуляции со стеком, которые так же приводят к «обвалу» рефлектора.
                    0
                    А можно сделать обратно, когда с помощью Mono.Cecil удалить весь некорректный код, который не обрабатывается в .NET Reflector, да и вообще никогда не выполняется?
                      +1
                      Конечно можно, поэтому я и говорю, что не стоит ни в какой мере надеяться на указанную в топике защиту (правда при этом сборка потеряет свое строгое имя, однако это не является проблемой). Простые методы обфускации (запутывание потока вызовов, переименование методов, вставка некорректных инструкций и т.д.) не могут в полной мере защитить NET сборку от дизассемблирования профессионалом. Более надежные методы обфускации, например запуск кода под виртуальной машиной с уникальной (виртуальной) архитектурой процессора (и соответственно ассемблера) могут помочь, но они дороги и далеко не всегда могут быть применены.
                      –1
                      спасибо за статью,
                      может быть стоит ее открыть из под замка?
                        –1
                        Сторонники OperSource сообщества будут в ярости от увиденного =)
                          –1
                          У статьи 39 плюсов, она в конце ленты на 3 странице. Думаю, что боятся уже ничего не стоит
                            –1
                            Подумал, что вы правы и раз статья на 3ей странице уже, то проблем быть не должно :) Открыл.
                              0
                              Даже комментарии минусуют :) Что им не нравится?
                          0
                          Open* конечно же, за правописание двойку мне
                            0
                            Когда я открыл ее, сразу начали минусовать :) Я так думаю это потому, что она засоряла главную страницу тем, кому NET впринципе не интересен, но возможно я чего-то не понимаю.
                            0
                            Спасибо за статью.
                            Хорошо бы было написать как именно обходится такая обфускация.
                            Есть неплохой проект "Simple Assembly Explorer" там можно в два клика мышкой пофиксить или метод или всю сборку на предмет таких трюков, чтобы рефлектор мог показать потом.
                              0
                              Еще вариант — поправить некоторые вещи в CLR-заголовке или PE-заголовке, тогда рефлектор может не обнаружить что это дотнетовская сборка вообще, а работать все еще будет. Чем поправить — например CFF Explorer полезная тулза.
                                0
                                Будет ли такой вариант работать на WPF приложениях?

                                Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                Самое читаемое