Вступление

Ходит слух, будто некоторые программисты не очень хотят, чтобы сигнатурный анализ продуктов деятельности других кодеров шарил по сокровенным местам их творений. Чтобы их скрыть, они пользуются крипторами, которые традиционно написаны на языках более-менее низкого уровня. А что, если попробовать сишарп? В конце концов, 2013 год на дворе!

 

WARNING!

Вся информация представлена исключительно в образовательных целях. Автор и редакция не несут никакой ответственности за ее противозаконное использование!

Для начала вкратце освежим в памяти теорию. Для этого дадим определения тому, с чем мы имеем дело.

  • Native — машинный код, представитель C++, программа calc.exe.
  • .NET — общеязыковая среда исполнения CLR, представитель C#, программа — наш криптор.
  • Криптор — программа для криптозащиты, используемая преимущественно для маскировки программного обеспечения. Обеспечивает защиту от распространенных антивирусных методов поиска по сигнатурам.
  • Стаб — получаемый на выходе файл, содержит защищенную программу в шифрованном виде и код для ее дешифрования с последующим запуском.

Выделяют следующие виды крипторов:

  • Static (стаб-код запуска одинаков всегда);
  • Polymorph (стаб-код запуска всегда разный).

Запуск в них может производиться двумя способами:

  • Scantime (запись расшифрованного файла на HDD, запуск);
  • Runtime (расшифровка и запуск производятся в памяти).

В данной статье речь пойдет о Static-крипторе на языке C Sharp, тип запуска Scantime и Runtime. Если у тебя еще не установлена среда разработки, то самое время скачать и установить бесплатный пакет Visual Studio Express с сайта Microsoft, в своих примерах я использовал версию 2010. Код каждого из них с подробными комментариями есть на нашем DVD, здесь же я проиллюстрирую только самые интересные моменты. Внутри проектов создан текстовый файл Source («Проект -> Свойства -> Ресурсы -> Файлы»), он же стаб, копия которого хранится в классе Test.cs для контроля над синтаксисом.

Ну что, теорию повторили, направление выбрали, поехали...

 

Scantime Crypter

Итак, первый на очереди Scantime, который для запуска программы вынужден предварительно записать ее на HDD. Запускаем Visual Studio и открываем проект из архива Csharp_ScanTime_Temp.rar. Два раза нажимаем на Form1.cs и смотрим на красивый GUI-интерфейс нашей программы (рис. 1), код обработчиков доступен по двойному нажатию на элементы управления.

Рис. 1. Шикарный дизайн нашего приложения. Готов побеждать в государственных тендерах!
Рис. 1. Шикарный дизайн нашего приложения. Готов побеждать в государственных тендерах!

Теперь познакомимся поближе с внутренностями кнопки «Crypt». Начинается все с создания экземпляра класса System.Resources.ResourceWriter("res.resources"), потом методом AddResource мы задаем имя нового вложенного ресурса file и сразу же шифруем исходный файл по указанному пути алгоритмом RC4 и заданным через запятую ключом RC4KEY. Затем в дело вступает CodeDom, в котором мы указываем требуемые параметры компиляции через CompilerParameters (GenerateExecutable = true, OutputAssembly = "File.exe", ReferencedAssemblies.Add("System.dll"), EmbeddedResources.Add("res.resources"), CompilerOptions += "/t:winexe"). Параметры выходного файла указаны в CompilerResults. Чтобы не мусорить на своем HDD, подчищаем временные файлы удалением File.Delete("res.resources") и, наконец, проверяем свежеиспеченный боевой стаб на наличие ошибок при помощи CodeDom.Compiler.CompilerError. На этом, в принципе, и все, взяли файл, зашифровали, записали в ресурсы. Рассмотрим типы запуска.

 

Всемогущий Temp

Сделаем так, чтобы при открытии нашей программы криптованный файл попадал в папку временных файлов %temp%. Открываем стаб, а точнее — текстовый файл Source из архива Csharp_ScanTime_Temp.rar. Окидываем беглым взглядом код и понимаем, что в целом мы производим практически те же (за некоторым исключением) действия, но в обратном порядке. У нас вызывается ResourceManager, задается имя вложенного ресурса и используемый алгоритм шифрования с ключом RC4KEY. Результат этих действий сохраняется в переменную массива байтов b, он и становится тем самым файлом, защиту которого мы организовывали. Осталось записать его и запустить. Для этого воспользуемся классом Path.GetTempPath(), который вернет нам актуальный для данной машины путь к временной папке в переменную string "nameA", а для удобства сразу допишем подходящее нам имя будущего файла Your_File.exe. Байты есть, полный путь готов (включая имя), давай же скорее писать! Для этого вызываем метод WriteAllBytes класса File, передавая ранее указанные параметры в виде аргументов. Последней строчкой кода будет Process.Start(nameA), который и запустит наш процесс.

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

 

NTFS-потоки

Открываем второй архив под именем Csharp_SanTime_NTFS.rar, смотрим файл Source. И видим почти ту же самую картину, за исключением того, что размер стаба увеличился более чем в два раза. А все потому, что там используются API-функции и их полный код обязан присутствовать в листинге. Разберем все по этапам:

  1. Вместо одного полного имени файла мы задаем еще одно. Это имя нашего потока, которое будет записано через двоеточие, что в итоге даст такой результат: JustTempFile.tmp:YourFile.exe.
  2. Запись осуществляется при помощи класса PInvokeWin32API.WriteAlternateStreamBytes(nameA, NTFSName, b), в качестве аргументов он принимает полное имя файла, имя потока и сами байты, которые требуется записать.
  3. Запуск Process.Start(),т которым здесь не отделаешься, поскольку данный способ не сработает на Windows 7 и выше (висту не тестировал). Выход из ситуации предоставил могучий API, вызывается он строкой StartNTFSProcess.Start(nameA + ":" + NTFSName), где и происходит обращение к именованному потоку.
  4. Выдерживаем секундную паузу и удаляем файл File.Delete(nameA), для глаза эта операция незаметна, и папка %temp% кажется нетронутой. Процесс работает исправно, но на диске уже ничего нет.

Минус этого подхода аналогичен первому — запись «чистого» файла на HDD. Этот способ сработает только для native-приложений и файловой системы NTFS. Для просмотра потоков я использовал бесплатную программу AlternateStreamView.

Рис. 2. Папка temp из способа с NTFS-потоками
Рис. 2. Папка temp из способа с NTFS-потоками
 

Runtime Crypter

В двух предыдущих примерах зашифрованное приложение было записано в ресурсы выходного файла и запускалось с HDD. Сейчас же мы разместим его в коде стаба при помощи Base64-кодировки и перезаписи Source каждый раз при нажатии кнопки «Crypt». Рассмотренные примеры актуальны только для .NET-приложений. Вот как это выглядит:

byte[] filebytes = RC4EncryptDecrypt(System.IO.File.ReadAllBytes(textBox1.Text), "RC4KEY");
string NewSource = Properties.Resources.Source;
NewSource = NewSource.Replace("$FILE$", Convert.ToBase64String(filebytes));

Мы также шифруем байты, но при этом создаем еще одну переменную string, в которую записываем весь Source, и, используя метод Replace, заменяем заранее заданные метки на новые данные. Корректируем CodeDom.Compiler.CompilerResults, и дальше без изменений. Но теперь при открытии нашего стаба в том же .NET Reflector вложенных ресурсов мы не обнаружим, и выудить файл станет сложнее. Перейдем к запуску.

 

Как делают многие

Открываем файл проекта из архива Csharp_RunTime_Simple.rar, смотрим Source.

static void Main() {
    byte[] betyFile = RC4EncryptDecrypt(Convert.FromBase64String("$FILE$"), "RC4KEY");
    System.Reflection.Assembly.Load(betyFile).EntryPoint.Invoke(null, null);
}

Этот код запустит наше приложение из памяти, минуя HDD и сигнатурный анализ антивируса. Протестируем на работающем антивирусе… ого, не сработало! В чем проблема? Хм... проблема в пароле, точнее, в том, что он хранится в открытом и доступном для статического анализа виде. А это значит, что мы подошли к самому интересному и ключевому моменту статьи...

 

Как сделаем мы

Открываем проект файла из архива Csharp_RunTime_Hard.rar и наблюдаем большие изменения в программе.

Рис. 3. Результат работы криптора
Рис. 3. Изменён интерфейс - добавлен новый функционал

Теперь мы можем менять сведения о сборке и наконец-то дописали код для иконки. Проблема решена, пароль в стабе не видно! Хе-хе.

Вот так выглядит работа усложненного стаба: берем файл, шифруем, делим на части, шифруем части разными паролями и переворачиваем некоторые из них. Сохраняем все это в файл Source. При запуске такого стаба он проделает все эти операции наоборот и практически мгновенно, благодаря многопоточности и хеш-суммам для контроля правильной расшифровки байт. Вот как это выглядит:

Globals.randomPasswd = RandomPassNewGlobal();
byte[] filebytes = RC4EncryptDecrypt(System.IO.File.ReadAllBytes(textBox1.Text), Globals.randomPasswd);
int a = filebytes.Length / 4;
byte[] partofthebytes = new byte[a];
Array.Copy(filebytes, 0, partofthebytes, 0, a);
Globals.partOneHash = md5Hash(Convert.ToBase64String(partofthebytes));
Globals.partOneKey = RandomPassNew();
partofthebytes = RC4EncryptDecrypt(partofthebytes, Globals.partOneKey);
Globals.filePartOne = Convert.ToBase64String(partofthebytes);

public static string RandomPassNew() {
    Random rnd = new Random();
    uint rOne = (uint) rnd.Next(52345, 52348);
    uint rTwo = (uint) rnd.Next(39327, 39329);
    uint rMul = rOne * rTwo;
    return rMul.ToString();
}

Первой строкой получаем случайный мастер-пароль, затем шифруем им выбранный файл, делим его длину на 4 (если он нечетный — дописываем один пустой байт). Создаем массив, равный размеру одной этой части, и при помощи Array.Copy() поэтапно копируем в него шифрованные байты. Вычисляем MD5-хеш Convert.ToBase64String() и повторно шифруем этот кусок новым паролем. После чего он готов к записи в стаб.

Самое главное в крипторе посмотрели, теперь рассмотрим краткую выдержку из файла стаба:

Thread threadpartone = new Thread(ThreadFourMethod);
threadpartone.Start();
private static void ThreadFourMethod() {
    string partFourHash = "$partFourHash$";
    byte[] ByteFileFour = Convert.FromBase64String("$baseFour$");
    while (true) {
        string rndpasswd = RandomPassNew();
        byte[] befoRndDecOne = RC4EncryptDecrypt(ByteFileFour, rndpasswd);
        string bufferForParts = md5Hash(Convert.ToBase64String(befoRndDecOne));
        if (partFourHash == bufferForParts) {
            bytefromthreadfour = befoRndDecOne;
            break;
        }
    }
}
Array.Reverse(bytefromthreadtwo);
Array.Copy(bytefromthreadone, 0, FileHere, 0, bytefromthreadone.Length);
System.Reflection.Assembly.Load(RC4EncryptDecrypt(testa, (testb + testccc).ToString())).EntryPoint.Invoke(null, null);

В результате работы этого кода мы создаем новый поток, всего их будет пять. Первые четыре выполняют расшифровку записанных частей, для чего они каждый раз, подбирая пароль, сверяют полученный хеш строки с правильным. По окончании этого процесса управление возвращается главному потоку, где далее происходит переворачивание некоторых байт классом Array.Reverse() и склейка в Array.Copy(). Все, байты готовы, для запуска используется финальный пятый поток, выполняющий System.Reflection.Assembly.Load(), как и в первом случае (если стаб не стартует, криптуем файл заново).

 

Проверяем VirusTotal’ом

Для проверки нашего криптора я взял старый ReverseSocksBot, написанный на .NET, и загрузил его на VT (исключительно для наглядности). Его результат составил 26/46 (рис. 4). Затем я закриптовал файл и загрузил его повторно. Показатель значительно улучшился и составил 1/46 (рис. 5), что очень неплохо!

Рис. 4. VirusTotal до
Рис. 4. VirusTotal до
Рис. 5. VirusTotal после
Рис. 5. VirusTotal после
 

Заключение

.NET — интересная и быстро развивающаяся платформа, опасаться отсутствия Framework на целевой машине практически не приходится, и в будущем ситуация только улучшится. «Сила» применяемого алгоритма шифрования не самое главное в крипторе, гораздо важнее (и перспективнее) найти новую комбинацию его применения.

  • Подпишись на наc в Telegram!

    Только важные новости и лучшие статьи

    Подписаться

  • Подписаться
    Уведомить о
    0 комментариев
    Межтекстовые Отзывы
    Посмотреть все комментарии