Котфускация исполняемого .net кода

    (пятница)

    Обычно развернутое приложение в файловой системе выглядит как-то так:



    Совершенно незащищенное от инструментов типа рефлектора или IlSpy, но что если оно станет таким:



    По крайней мере легкий ступор хакеру-неофиту обеспечен. Приятно смотрится, и антивирусы не заинтересуются.

    Коротко


    На картинках присутствует модуль Booster.exe, о нем и поговорим, это совсем небольшая программка и умеет она следующее:
    • запаковать набор dll'ок в набор картинок, при этом сжав и зашифровав
    • загрузить из набора картинок dll'ки, найти в них типы с методом Main и запустить их

    При этом учитывается что сборки могут друг друга использовать (для этого используется событие AssemblyResolve у AppDomain).

    Если убрать все проверки, код запуска на исполнение выглядит так:
    AssemblyProvider.LoadAssemblies(imagesFolder).CallMain();
    


    Код упаковки так:
    var config = CommandLineParser.Parse(args);
    AssemblyProvider.PackAssemblies(config["img"], config["imgout"], config["asm"]);
    


    Упаковка


    Начнем с упаковки, метод PackAssemblies принимает путь к каталогу с картинками, путь к каталогу куда нужно сложить картинки с кодом, и путь к каталогу со сборками. Первым делом собираем информацию о картинках, нам нужно знать какой объем данных можно в них хранить:
    Dictionary<string, long> imagesVolume = new Dictionary<string, long>();
    var imageFiles = Directory.GetFiles(imagesFolder, "*.*").Where(file => file.ToLower().EndsWith("bmp") || 
    file.ToLower().EndsWith("png") || file.ToLower().EndsWith("jpg") || file.ToLower().EndsWith("jpeg")) .ToList();
    foreach (string file in imageFiles)
                    imageSet.Append(file);
    
    //ImageSet
    internal void Append(string imageFile)
            {
                using (Image img = Image.FromFile(imageFile))
                {
                    _imagesVolume.Add(imageFile, img.Width * img.Height - 4);
                    _images.Add(imageFile);
                }
            }
    

    Формула проста, ширину умножаем на высоту и отнимаем 4 байта под размер данных. ImageSet дополнительно следит за закольцованным пробеганием по картинкам (чтобы были разные), и за поиск картинки способной вместить размер сборки.

    Теперь остается пробежаться по сборкам, загрузить каждую в бинарном виде, и слить с картинкой, способной вместить размер данных. Вкратце для отдельной сборки так:
    byte[] packed = AssemblyPacker.Pack(file);
    string imageFile = imageSet.FindImage(packed.Length);
    if (imageFile != null)
    {
          using (Image img = Image.FromFile(imageFile))
          {
                  Bitmap bmp = SteganographyProvider.Injection(img, packed);
                  //Save
           }
    }
    


    AssemblyPacker сжимает массив байт используя GZipStream, т.к. размер сборки выравнивается по блокам, это может значительно сократить размер.
    Пример окончания файла сборки:

    , выделена часть добитая нулями, и ниже еще сотни две нулей, что отлично сожмется.
    Затем сжатый массив 'шифруется', я не стал применять криптопровайдеры, массив просто xor'ится с набором псевдослучайных чисел, при этом генератором служит размер массива. Т.е. при повторении операции шифрования получаем исходный массив. Алгоритм очень простой, но дает результат похожий на белый шум.

    Рапределение данных в необработанной сборке:

    Распределение после обработки близко к нормальному


    Получив запакованную сборку переходим к самой интересной части, слияние с картинкой. Где и натыкаемся на первые проблемы. .NET при сохранении картинки использует WinApi метод GdipSaveImageToFile (можно тут посмотреть), который принимает помимо ссылки на изображение и имени файла, еще и ссылку на обработчики(encoder'ы) изображения, среди которых могут архиваторы, оптимизаторы и т.п. Каждый из которых может изменить значения пикселей, что повлечет повреждение хранимых данных. Самым простым решением казалось бы не передавать обработчики, но метод GdipSaveImageToFile и сам по себе умный, кроме передаваемого списка обработчиков он еще ориентируется и на формат изображения (clsid, третий параметр метода), при этом, например для png, он вообще может забить на наш список обработчиков и решить что применять самостоятельно. Мне так и не удалось найти сочетание при котором работало бы сжатие изображения без потерь, поэтому использую следующее решение:
    Bitmap bmp = SteganographyProvider.Injection(img, packed);
    FieldInfo fi = bmp.GetType().GetField("nativeImage", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
    object d = fi.GetValue(bmp);
    IntPtr nativeImage = (IntPtr)d;
    Guid clsid = FindEncoder(ImageFormat.Bmp.Guid).Clsid;
    GdipSaveImageToFile(new HandleRef(bmp, nativeImage), outImagefile, ref clsid, new HandleRef(null, IntPtr.Zero));
    


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

    Разобравшись с хранением, переходим к слиянию. Для 32-хбитных изображений каждый пиксель кодируется четырмя байтами (argb), для 24-хбитных тремя (rgb), первоначально алгоритм ориентировался на запись в 4 байта с использованием альфа канала, что позволяло получать изображения на глаз почти неотличимые от оригинала, но по итогу придя к bmp, от альфа канала отказался.

    Итак, каждый байт из нашего массива сопоставляем с одним пикселем, при этом, байт разбиваем на три числа, количество единиц, десяток и сотен, т.е. для байта 158 получим три числа, (1, 5, 8), после чего заменяем единицы в rgb компонентах на получившиеся числа, например пиксель (7,155,72) превратится в (1, 155, 78).

     pix = MixByteAndPixel(pix, ByteDecomposition(data[index], false));
    ((byte*)bmd.Scan0)[offset] = pix.blue;
    ((byte*)bmd.Scan0)[offset + 1] = pix.green;
    ((byte*)bmd.Scan0)[offset + 2] = pix.red;
    


    Алгоритм слияния получается следующий, в начало массива с данными добавляем 4 байта, в которых записана длина массива. Проходим по изображению и сливаем байты с пикселями, если в изображении остаются свободные пиксели, сливаем со случайными значениями (иначе на конечном изображении будет четко прослеживаться граница где закончились данные, как на CD/DVD болванках видна пустая область). Сохраняем полученное изображение.

    Оригинал (слева) и картинка со сборкой внутри (справа):


    Запуск


    Для старта нам понадобится Booster.exe и итоговые изображения. Достаточно поместить Booster.exe в каталог с картинками и запустить его. Или запустить с параметром img=путь_к_картинкам.

    При этом для картинок применяются все операции упаковки в обратном порядке:
    1. Считываем значения пикселей, у каждого пикселя из rgb компонент берем единицы, и восстанавливаем исходный байт.
    2. После первых четырех байт получаем размер оставшихся данных.
    3. Считываем исходный массив с запакованной и зашифрованной сборкой. Остаток изображения игнорируем.
    4. Расшифровываем исходный массив.
    5. Разархивируем массив и получаем непосредственно сборку в raw виде.
    6. Загружаем сборку
    7. При удачной загрузке регистрируем сборку в AssemblyResolver'е, на случай если сборки используют друг друга


     internal static AssembliesSet LoadAssemblies(string imagesFolder)
            {
                AssembliesSet set = new AssembliesSet();
                foreach (string file in Directory.GetFiles(imagesFolder, "*.bmp"))
                {
                    byte[] data = null;
                    using (Image img = Image.FromFile(file))
                    {
                        data = SteganographyProvider.Extraction(img);
                    }
                    data = AssemblyPacker.UnPack(data);
                    set.TryAppendAssembly(data);
                }
                return set;
            }
    //AssembliesSet 
    internal void TryAppendAssembly(byte[] rawAssembly)
            {
                Assembly asm;
                try
                {
                    asm = Assembly.Load(rawAssembly);
                    AssembliesResolver.Register(asm);
                    _assemblies.Add(asm);
                }
                catch { }
            }
    


    И заключительная часть, ищем в загруженных сборах по доступным типам метод Main, и вызываем его:
    internal void CallMain()
            {
                foreach (var type in CollectExportedTypes())
                {
                    MethodInfo main = type.GetMethod("Main");
                    if (main != null)
                    {
                        ParameterInfo[] paramsInfo = main.GetParameters();
                        object[] parameters = new object[paramsInfo.Length];
                        for (int i = 0; i < paramsInfo.Length; i++)
                        {
                            parameters[i] = GetDefaultValue(paramsInfo[i].ParameterType);
                        }
                        main.Invoke(null, parameters);
                    }
                }
            }
    


    Тестируем


    Сделаем проект с тремя сборками, A, B и С, при этом A и B будут использовать сборку C. Вот так, например:
    /*картинка в комментарии ниже*/

    Пакуем в картинки, и запускаем:
    /*картинка в комментарии ниже*/

    Как видно по выводу, все сборки загрузились и код исполнился, в том числе работает зависимость сборок друг от друга (вызов метода Run класса CommonClass).

    Заключение


    Изначально очень хотелось использовать png формат, почему то был уверен что он использует алгоритмы сжатия без потерь, однако оказалось потери есть, незначительные для изображения, но критичные для стеганографии. Если кто-то знает как можно сохранить Bitmap в png без потерь, прошу отписать.

    Надеюсь было интересно. Скачать и поиграться можно тут.

    P.S. Что-то странное твориться с картинками на Хабре, стабильно теряет ссылки.

    UPD. Спасибо хабраюзеру mayorovp за комментарий насчет PNG, действительно можно стандартными средствами сохранять без потерь. Теперь алгоритм учитывает наличие альфа канала, и при его наличии раскладывает каждый байт на 4 составляющих для снижения искажений на выходе. На примере байта равного 158, разложение будет следующим:

    На первом шаге разделим сотни, десятки и единицы, получаем вектор (a1, a2, a3, a4) со значениями (1, 5, 8, 0), затем находим a4 в зависимости от условий:
    if (a2 >= 5 && a3 >= 5)
    {
          a4 = 2;      a2 -= 5;      a3 -= 5;
    }
    else if (a2 >= 5)
    {
         a4 = 3;       a2 -= 5;
    }
    else if (a3 >= 5)
    {
          a4 = 4;      a3 -= 5;
    }
    else
          a4 = 5;
    

    Конечный вектор будет таким (1, 0, 3, 2).

    Таким образом, все значения единиц rgba компонент будут в пределах 0-5, что по идее должно сгладить картинку. Можно придумать и более оптимальную кодировку.
    Поделиться публикацией
    Комментарии 42
      +11
      Хабр удаляет ссылки на изображения в конце статьи. Дублирую тут:

      Сделаем проект с тремя сборками, A, B и С, при этом A и B будут использовать сборку C. Вот так, например:


      Пакуем в картинки, и запускаем:
        +25
        По крайней мере для меня это выглядит настолько гениально, что сложно сказать что-нибудь вразумительное :) Автор — молодец!
          +13
          На самом деле вы купились на котят.
          +6
          Замечательно, красиво и пятнично! Порадовали :)
            +4
            Почему-то эти фаршированные котики меня пугают
              +10
              Можно заменить на картинки фаршированных индеек, но it и котики неразрывно связаны.
              +2
              Я думаю картинки с котами ещё больше мотивируют хакера докопаться до оригинала.
                +32
                А потом пришел строгий админ и удалил картинки с котиками. Потому что бухгалтер не должен хранить своих любимых котиков в рабочих директориях. /лирика
                  +3
                  Сразу вспомнился rarjpeg
                    +3
                    Там все таки нет смешения с картинкой, просто дозапись в конец. Но идея интересная.
                    +1
                    Расскажите подробнее, почему формат PNG не подошел. Он же поддерживает и хранение chroma и alpha-данных как индексированными, так и прямым числовым представлением. Во втором случае отклонения значений в каналах в процессе сжатия/распаковки исключены. Чем вы жали PNG?
                      +1
                      Берем png картинку. Например, такую, от хабраюзера:


                      И выполняем следующий код:
                      string pngFile = @"O:\cat.png";
                      string pngOutFile = @"O:\catnew.png";
                      using (Image img = Image.FromFile(pngFile))
                      {
                          var bmp = new Bitmap(img.Width, img.Height, PixelFormat.Format32bppArgb);
                          using (Graphics gr = Graphics.FromImage(bmp))
                          {
                              gr.DrawImage(img, new Rectangle(0, 0, img.Width, img.Height));
                          }
                          bmp.Save(pngOutFile, ImageFormat.Png);
                      }
                      

                      Формат идентичный для оригинала и результата — Format32bppArgb, никаких манипуляций нет, просто перезаписал. В итоге оригинал 26Кб, результат 21Кб. Можно даже не сравнивать HEX редактором… Соответственно данные будут битыми.

                      Использую стандартный GDI+.
                        +4
                        Вы делаете предположение о битости данных только на основе размера файла?.. Это может быть просто лучший алгоритм сжатия. Или выкинутые метаданные.
                          0
                          Это для быстрого примера привел, конечно я пробовал сравнивать. Данные отличаются, на 1-2 бита, и не все, но отличаются. И изменение происходит именно в методе GdipSaveImageToFile. Вполне вероятно что есть другие библиотеки, используя которых можно без потерь работать, пока не было времени поизучать.

                          Тут описана эта проблема в GDI+
                            +2
                            То, что размер файла на выходе получился меньше говорит о том, что коэффициент сжатия оказался выше или были отброшены дополнительныеметаданные. Значения пикселей хранятся в числовом виде вне зависимости от того, какая гамма у отображающего устройства (т.е., грубо говоря, если вы сохраните файл, в котором знчения по каналам = (1,0,0), то на выходе получите файл, залитый таким же красным цветом, т.е. (1,0,0).

                            Судя по всему, проблема из-за того, что GDI+, написанный «на отъе*ись», меняет при сохранении значения в каналах. Чудеса. 2014-й год на дворе, а .NET до сих пор работает с изображениями будто мы только выбрались из ФИДО…
                          +1
                          Попробуйте теперь этот же файл (21 кб) так обработать. Там может быть разница в энкодерах.
                            0
                            Ответил выше.
                            +2
                            Много кода
                            using System;
                            using System.Drawing;
                            using System.Drawing.Imaging;
                            using System.IO;
                            using System.Linq;
                            
                            class Program
                            {
                                static void Main(string[] args)
                                {
                                    var orig = File.ReadAllBytes("9c63b8a4e0f74341b6484d481d434a02.png");
                                    var modified = Modify(orig);
                                    Console.WriteLine("Packed sizes: original = {0}, modified = {1}", orig.Length, modified.Length);
                            
                                    Compare(orig, modified);
                            
                                    orig = Unpack(orig);
                                    modified = Unpack(modified);
                                    Console.WriteLine("Unpacked sizes: original = {0}, modified = {1}", orig.Length, modified.Length);
                            
                                    Compare(orig, modified);
                            
                                    if (Enumerable.SequenceEqual(orig, modified))
                                        Console.WriteLine("Unpacked data is equal");
                                    else
                                        Console.WriteLine("Unpacked data not equal");
                                }
                            
                                private static byte[] Modify(byte[] orig)
                                {
                                    var ms = new MemoryStream();
                                    using (var image = Image.FromStream(new MemoryStream(orig)))
                                        image.Save(ms, ImageFormat.Png);
                                    /*using (var bmp = new Bitmap(image.Width, image.Height, PixelFormat.Format32bppArgb))
                                    {
                                        using (Graphics gr = Graphics.FromImage(bmp))
                                            gr.DrawImage(image, new Rectangle(0, 0, image.Width, image.Height));
                                        bmp.Save(ms, ImageFormat.Png);
                                    }*/
                                    return ms.GetBuffer();
                                }
                            
                                private static byte[] Unpack(byte[] orig)
                                {
                                    var ms = new MemoryStream();
                                    using (var image = Image.FromStream(new MemoryStream(orig)))
                                        image.Save(ms, ImageFormat.Bmp);
                                    return ms.GetBuffer();
                                }
                            
                                private static void Compare(byte[] orig, byte[] modified)
                                {
                                    using (var img1 = (Bitmap)Image.FromStream(new MemoryStream(orig)))
                                    using (var img2 = (Bitmap)Image.FromStream(new MemoryStream(modified)))
                                    {
                                        int h = img1.Height, w = img1.Width;
                                        if (h != img2.Height || w != img2.Width)
                                        {
                                            Console.WriteLine("Image sizes is different");
                                            return;
                                        }
                            
                                        for (int x = 0; x<w; x++)
                                            for (int y=0; y<h; y++)
                                                if (img1.GetPixel(x, y) != img2.GetPixel(x, y))
                                                {
                                                    Console.WriteLine("\tat ({0},{1}): {2:x8} != {3:x8}", x, y, img1.GetPixel(x, y), img2.GetPixel(x, y));
                                                    return;
                                                }
                                    }
                            
                                    Console.WriteLine("Images are equal");
                                }
                            }
                            

                            Как показал эксперимент, виноват во всем DrawImage — именно он искажает картинку. Вероятно, дело в том, что он рисует картинку с учетом возможных аффинных трансформаций или просто масштабирования — и даже в случае, когда этого не требуется, всплывают мелкие ошибки округления. Сами по себе методы сохранения и загрузки работают корректно.

                            Packed sizes: original = 27314, modified = 21121
                            Images are equal
                            Unpacked sizes: original = 262198, modified = 262198
                            Images are equal
                            Unpacked data not equal
                            


                            Следует также отметить, что сравнивать изображения нужно по-пиксельно, а не по-байтово, потому что последний метод находит ложные отличия (отличия в метаданных).
                              0
                              Спасибо, опробую и отпишу.
                                +1
                                Заработало c PNG. Дополнил конец статьи. Спасибо! Изображение, кстати, обрабатываю способом отсюда, таким способом отрабатывает тоже правильно.
                                  0
                                  А если DrawImageUnscaled?
                                    0
                                    Я пытался его использовать — но что-то тоже не срослось. Сейчас уже не помню, но вроде бы он пытается подогнать физические размеры картинки в соответствии с установленным dpi. А значит, ошибки округления никуда не деваются.
                              +4
                              Если есть желание доводить до ума, советую выкинуть вообще GDI и работать напрямую с файлами. BMP — довольно простой формат.
                                +11
                                Напомнило случай, когда ребятам, участвовавшим в конкурсе 10k apart, не хватало тех самых 10кб для js-кода. Тогда они (вдохновившись наработками других иностранных коллег) написали небольшой скрипт, который кодировал байты js-кода в точки для png-картинки:

                                image
                                jquery.1.8.2.min.js — 91кб до сжатия, 34кб в виде png-файла

                                Эту картинку можно легко нарисовать на canvas-е и считать js-код обратно, выполнив его eval-ом. Совместив эту идею с вашей котофускацией, можно сделать неплохой обфускатор js файлов, особенно если запутать код самого дешифратора.
                                  0
                                  Никак не могу найти ни английскую статью с js-кодером, ни аналогичной статьи на русском. Не поделитесь ссылками? Желательно на англоязычный ресурс
                                  +2
                                  Немного не понял вот этот момент: «после чего заменяем единицы в rgb компонентах на получившиеся числа»:
                                  как при этом (7,155,72) превратится в (1, 155, 78)?
                                    +1
                                    Есть байт: 158, раскладываем его на 1, 5 и 8. Обозначим a1, a2, a3, то есть чтобы получить исходный байт: b = a1*100 + a2*10 + a3
                                    Есть пиксель, его rgb значения (7, 155, 72),

                                    Получаем новые значения по формуле:
                                    REDnew = (REDold/10)*10 + a1 = (7/10)*10 + 1 = 0+1 = 1
                                    GREENnew = (GREENold/10)*10 + a2 = (155/10)*10 + 5 = 150 + 5 = 155
                                    BLUEnew = (BLUEold/10)*10 + a3 = (72/10)*10 + 8 = 70 + 8 = 78

                                    И получим новое значение пикселя (1, 155, 78). Заменив для каждой компоненты RGB значения единиц.
                                      +1
                                      О, спасибо, теперь ясно. Просто заменяем последний значащий десятичный символ на наш.
                                        +1
                                        А что делаете, если значения RGB пикселя находятся в диапазоне 250-255, а в единицах надо закодировать 6, 7, 8 или 9?
                                          0
                                          В коде проверка есть, в этом случае перед суммированием значение RGB компоненты для которой может быть переполнение предварительно уменьшается на 10.
                                            0
                                            Да, спасибо за разъяснение!
                                            +1
                                            логично просто вычесть десять, и из 257 получить 247, на глаз все равно незаметно.
                                            +3
                                            А не проще было отбрасывать последние 4 бита канала и писать туда 4 бита данных?
                                          +1
                                          Вопрос автору (мои поздравления с удачным пожатием PNG!) или людям сведущим:
                                          Я сам только игры программировал, со сборками .net не работал особо. Вопрос такой: если этот лоадер (Booster) или хотя бы именно дешифровщик написать, скажем, на плюсах, можно ли каким-то образом расшифрованные массивы байтов с содержимым IL-сборок передать из сишного лоадера второму лоадеру, но уже написанному на .net, чтобы уже тот запускал, собственно, приложение?
                                          По идее это еще увеличило бы крутость решения, т.к. декомпилить лоадер, скомпиленный в нативный код — задача не в пример сложнее, чем ковырять байткод в ILSpy.
                                            +3
                                            Можно написать на плюсах дешифровку и никуда не передавать, а запустить непосредственно из нативного кода. Ниже реализация:

                                            Реализация хостинга в нативном коде
                                            // .h
                                            #pragma once
                                            #include <tchar.h>
                                            #include <windows.h>
                                            #include <metahost.h>
                                            #pragma comment(lib, "mscoree.lib")
                                            
                                            class DotNetHostDispatcher
                                            {
                                            private:
                                            	ICLRMetaHost *pMetaHost;
                                            	ICLRRuntimeInfo *pRuntimeInfo;
                                            	ICLRRuntimeHost *pClrRuntimeHost;
                                            	PCWSTR pszStaticMethodName;
                                            	PCWSTR pszStringArg;
                                            	DWORD dwLengthRet;
                                            public:
                                            	HRESULT hr;
                                            	// Samples of pszVersion: L"v2.0.50727", L"v4.0.30319"
                                            	DotNetHostDispatcher(PCWSTR pszVersion);
                                            	// pszAssemblyPath - path to .NET dll
                                            	void StartMain(PCWSTR pszAssemblyPath, PCWSTR pszClassName, PCWSTR pszStringArg);
                                            	// Release
                                            	~DotNetHostDispatcher();
                                            };
                                            
                                            //.cpp
                                            #include "DotNetHostDispatcher.h"
                                            
                                            DotNetHostDispatcher::DotNetHostDispatcher(PCWSTR pszVersion)
                                            {
                                            	pMetaHost = NULL;
                                            	pRuntimeInfo = NULL;
                                            	pClrRuntimeHost = NULL;
                                            	pszStaticMethodName = L"Main";
                                            	pszStringArg = L"--start";
                                            
                                            	hr = CLRCreateInstance(CLSID_CLRMetaHost, IID_PPV_ARGS(&pMetaHost));
                                            	if (FAILED(hr)) throw L"CLRCreateInstance failed w/hr 0x%08lx";
                                            	hr = pMetaHost->GetRuntime(pszVersion, IID_PPV_ARGS(&pRuntimeInfo));
                                            	if (FAILED(hr)) throw L"ICLRMetaHost::GetRuntime failed w/hr 0x%08lx";
                                            	BOOL fLoadable;
                                            	hr = pRuntimeInfo->IsLoadable(&fLoadable);
                                            	if (FAILED(hr)) throw L"ICLRRuntimeInfo::IsLoadable failed w/hr 0x%08lx";
                                            	if (!fLoadable) throw L".NET runtime cannot be loaded";
                                            	hr = pRuntimeInfo->GetInterface(CLSID_CLRRuntimeHost, IID_PPV_ARGS(&pClrRuntimeHost));
                                            	if (FAILED(hr)) throw L"ICLRRuntimeInfo::GetInterface failed w/hr 0x%08lx";
                                            	hr = pClrRuntimeHost->Start();
                                            	if (FAILED(hr)) throw L"CLR failed to start w/hr 0x%08lx";
                                            }
                                            
                                            void DotNetHostDispatcher::StartMain(PCWSTR pszAssemblyPath, PCWSTR pszClassName, PCWSTR pszStringArg)
                                            {
                                            	hr = pClrRuntimeHost->ExecuteInDefaultAppDomain(pszAssemblyPath, pszClassName, pszStaticMethodName, pszStringArg, &dwLengthRet);
                                            }
                                            
                                            DotNetHostDispatcher::~DotNetHostDispatcher()
                                            {
                                            	if (pMetaHost)
                                            	{
                                            		pMetaHost->Release();
                                            		pMetaHost = NULL;
                                            	}
                                            	if (pRuntimeInfo)
                                            	{
                                            		pRuntimeInfo->Release();
                                            		pRuntimeInfo = NULL;
                                            	}
                                            	if (pClrRuntimeHost)
                                            	{
                                            		pClrRuntimeHost->Release();
                                            		pClrRuntimeHost = NULL;
                                            	}
                                            }
                                            




                                            И пример вызова:
                                            #include "DotNetHostDispatcher.h"
                                            
                                            int _tmain(int argc, _TCHAR* argv[])
                                            { 
                                                    // Сборку будет исполнять вторым фреймворком
                                            	DotNetHostDispatcher * dispatcher = new DotNetHostDispatcher(L"v2.0.50727");
                                                    // Хостинг сборки и вызов метода Main с передачей параметра
                                            	dispatcher->StartMain(L"DotNetAppLib.dll", L"DotNetAppLib.DotNetClass", L"--start");
                                            	dispatcher->~DotNetHostDispatcher();
                                            	return 0;
                                            }
                                            


                                            P.S. я плюсы уже плохо помню, код может быть не особого качества.
                                              0
                                              Класс, спасибо за инфу.
                                            0
                                            Когда-то давно писал скрипт для преобразования файл-картинка, чтобы хостить файлы в виде картинок.
                                            Можно потрогать
                                              0
                                              Статью читал по диагонали, поэтом заранее прошу извинить, если вопрос глупый.

                                              Если сдампить запущенный процесс, то весь ли MSIL в нём будет и все ли метаданные, или только часть?
                                                0
                                                Будут все данные.
                                                  0
                                                  Тогда какой в этом смысл? Чем это лучше обычной обфускации?
                                                    0
                                                    По крайней мере легкий ступор хакеру-неофиту обеспечен.
                                                      +1
                                                      Это больше пятничный пост, он не предлагает замену стандартной обфускации. Собственно я в посте нигде не делаю сравнения способов обфускации друг с другом и с показанным методом. Просто делюсь решением запуска проекта хранящегося в виде картинок.

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

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