Как написать шифровальщик файлов на c

Обновлено: 05.07.2024

  1. Хранить данную критическую информацию «зашитой» в исполняемый файл exe. Из плюсов – относительная надёжность хранения, ведь для получения доступа к этой информации из вне потребуется дизассемблировать/декомпилировать исполняемый файл, из минусов — необходимость постоянно перекомпилировать исполняемый файл при смене пароля в учетных записях и опять же хранение этих данных в открытом виде в исходном коде программы, где эта информация и может быть считана посторонним наблюдателем.
  2. Хранить информацию в настроечных файлах приложения, но при этом само приложение должно работать на сервере приложений с ограниченным доступом. Из плюсов – легкая настройка в случае изменения данных учетных записей, из минусов – легкий доступ к критической информации всем, кто может зайти на сервер приложений.
  3. Хранить информацию в настроечных файлах программы в зашифрованном виде. Из плюсов – легкая настройка приложения в случае изменения данных, программа может работать не только на сервере приложений, критически важная информация хранится в виде недоступном для постороннего наблюдателя. Из минусов – только необходимость модернизации ранее написанного ПО.

По совокупности плюсов и минусов выбираем третий вариант.

Теперь чуть –чуть теории. Что же такое шифрование и для чего оно нужно? Шифрование –это трансформация для ее сокрытия от не авторизованных лиц,

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

Выделяем основные способы шифрования

  • симметричное
  • асимметричное
  • хеширование

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

Асимметричное шифрование это способ при котором создаются два математически связанных ключа, один из которых передается открытым (доступных для всех) способом, а второй, приватный ключ — для расшифровывания

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

В итоге мы реализовали следующую схему. В динамически подключаемой библиотеке (dll) был создан статический класс инкапсулировавший в себе весь функционал требуемый нам для шифрования/расшифровывания данных. Вот пример функции выполняющей шифрование:

static public object Encrypt(byte[] data, string password) < SymmetricAlgorithm sa = null; try < sa = Rijndael.Create(); ICryptoTransform ct = sa.CreateEncryptor( (new PasswordDeriveBytes(password, null)).GetBytes(16), new byte[16]); MemoryStream ms = new MemoryStream(); CryptoStream cs = new CryptoStream(ms, ct, CryptoStreamMode.Write); cs.Write(data, 0, data.Length); cs.FlushFinalBlock(); return ms.ToArray(); >catch (Exception e) < return e; >> static public string Encrypt(string data, string password) < object tmp = Encrypt(Encoding.UTF8.GetBytes(data), password); if (!(tmp is Exception)) < return Convert.ToBase64String((byte[])tmp); >else return null; >

Теперь при создании новых программ, где требуется хранить зашифрованную информацию мы просто подключаем созданную нами dll и просто вызываем требуемые нам функции и получаем информацию для передачи в БД:

public string UserLogin < get < object tmp = dll.Crypt.Decrypt(userLogin, getKey()); return tmp is Exception ? "" : tmp.ToString(); >> При этом, в настроечном XML файле хранится информация в следующем виде: <Settings> <userLogin>p+esX621QNMR09YyY4plJQ==</userLogin> <userPassword>og6kLzB2woGVlnO0ZRisDA==</userPassword> </Settings>

Таким образом мы скрываем от постороннего наблюдателя критически важную информацию. При необходимости, учитывая достаточно слабую защищённость от дизассемблирования/декомпиляции программ, созданных на платформе. Net можно дополнительно обфусцировать исполняемый файл программы с целью сокрытия механизма получения ключа.

В этом пошаговом руководстве показано, как зашифровать и расшифровать содержимое. Пример кода предназначен для приложения Windows Forms. Это приложение не демонстрирует реальные сценарии, такие как использование смарт-карт. Вместо этого оно демонстрирует основные принципы шифрования и расшифровки.

В этом пошаговом руководстве использует следующие правила шифрования:

Используйте класс Aes с симметричным алгоритмом для шифрования и расшифровки данных при помощи автоматически созданных Key и IV.

Используйте RSA асимметричный алгоритм для шифрования и расшифровки ключа с данными, зашифрованными Aes . Асимметричные алгоритмы лучше подходят для небольших объемов данных, таких как ключ.

Если вы хотите защитить данные на компьютере вместо обмена зашифрованным содержимым с другими людьми, рассмотрите возможность использования ProtectedData класса.

В следующей таблице указаны задачи шифрования из этого раздела.

Задача Описание
Создание приложения Windows Forms Выводит список элементов управления, необходимых для запуска приложения.
Объявление глобальных объектов Объявляет, что строковые переменные пути CspParameters и RSACryptoServiceProvider имеют глобальный контекст класса Form.
Создание асимметричного ключа Создает пару значений открытого и закрытого асимметричного ключа и присваивает ей имя контейнера ключей.
Шифрование файла Отображает диалоговое окно, где можно выбрать шифруемый файл, и шифрует этот файл.
Расшифровка файла Отображает диалоговое окно, где можно выбрать зашифрованный файл, и выполняет расшифровку этого файла.
Получение закрытого ключа Возвращает полную пару ключей при помощи имени контейнера ключей.
Экспорт открытого ключа Сохраняет ключ в XML-файл только с открытыми параметрами.
Импорт открытого ключа Загружает ключ из XML-файла в контейнер ключей.
Тестирование приложения Список процедур для тестирования этого приложения.

Предварительные требования

Для выполнения этого пошагового руководства требуются следующие компоненты:

Создание приложения Windows Forms

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

Элемент Имя Текстовое свойство (при необходимости)
Button buttonEncryptFile Шифрование файла
Button buttonDecryptFile Расшифровка файла
Button buttonCreateAsmKeys Создание ключей
Button buttonExportPublicKey Экспорт открытого ключа
Button buttonImportPublicKey Импорт открытого ключа
Button buttonGetPrivateKey Получение закрытого ключа
Label label1 Ключ не задан
OpenFileDialog openFileDialog1
OpenFileDialog openFileDialog2

дважды щелкните кнопки в конструкторе Visual Studio, чтобы создать свои обработчики событий.

Объявление глобальных объектов

Добавьте следующий код как часть объявления класса Form1. Измените строковые переменные для среды и параметров.

Создание асимметричного ключа

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

Добавьте следующий код в качестве обработчика событий Click для кнопки Create Keys ( buttonCreateAsmKeys_Click ).

Шифрование файла

Эта задача включает два метода: метод обработчика событий для Encrypt File кнопки ( buttonEncryptFile_Click ) и EncryptFile метод. Первый метод отображает диалоговое окно для выбора файла и передает имя этого файла во второй метод, который выполняет шифрование.

Зашифрованное содержимое, ключ и вектор инициализации сохраняются в один FileStream, который называется пакетом шифрования.

Метод EncryptFile выполняет следующие действия:

Создает симметричный алгоритм Aes для шифрования содержимого.

Создает объект RSACryptoServiceProvider для шифрования ключа Aes.

Использует объект CryptoStream для чтения и шифрования исходного файла FileStream блоками байтов в конечный объект FileStream для зашифрованного файла.

Определяет длину зашифрованного ключа и вектора инициализации и создает массивы байтов со значениями их длин.

Записывает ключ, вектор инициализации и значения их длин в зашифрованный пакет.

Пакет шифрования использует следующий формат:

Длина ключа, байты 0–3

Длина вектора инициализации, байты 4–7

Вы можете использовать значения длины ключа и вектора инициализации для определения начальных точек и длин всех частей пакета шифрования, которые затем можно использовать для расшифровки файла.

Добавьте следующий код в качестве обработчика событий Click для кнопки Encrypt File ( buttonEncryptFile_Click ).

Добавьте следующий метод EncryptFile к форме:

Расшифровка файла

Эта задача включает в себя два метода: метод обработчика событий для кнопки Decrypt File ( buttonDecryptFile_Click ) и метод DecryptFile . Первый метод отображает диалоговое окно для выбора файла и передает имя этого файла во второй метод, который выполняет расшифровку.

Метод Decrypt выполняет следующие действия:

Создает Aes симметричный алгоритм для расшифровки содержимого.

Считывает первые восемь байтов FileStream зашифрованного пакета в массивы байтов, чтобы получить значения длин зашифрованного ключа и вектора инициализации.

Извлекает ключ и вектор инициализации из пакета шифрования в массивы байтов.

Создает объект RSACryptoServiceProvider для расшифровки ключа Aes.

Использует объект CryptoStream для чтения и расшифровки зашифрованного текста пакета шифрования FileStream блоками байтов в объект FileStream для расшифрованного файла. После завершения этой операции расшифровка завершается.

Добавьте следующий код в качестве обработчика событий Click для кнопки Decrypt File .

Добавьте следующий метод DecryptFile к форме:

Экспорт открытого ключа

Эта задача сохраняет ключ, созданный при помощи кнопки Create Keys , в файл. Она экспортирует только открытые параметры.

Данная задача имитирует ситуацию, в которой Ольга предоставляет Дмитрию свой открытый ключ, чтобы он мог зашифровывать для нее файлы. Дмитрий и другие лица, имеющие этот открытый ключ, не смогут расшифровать данные, поскольку они не имеют полной пары ключей с закрытыми параметрами.

Добавьте следующий код в качестве обработчика событий Click для кнопки Export Public Key ( buttonExportPublicKey_Click ).

Импорт открытого ключа

Эта задача загружает ключ исключительно с открытыми параметрами, в том виде, в котором он был создан при помощи кнопки Export Public Key , и задает его в качестве имени контейнера ключей.

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

Добавьте следующий код в качестве обработчика событий Click для кнопки Import Public Key ( buttonImportPublicKey_Click ).

Получение закрытого ключа

Эта задача устанавливает в качестве имени контейнера ключей имя ключа, созданного при помощи кнопки Create Keys . Контейнер ключей будет содержать полную пару ключей с закрытыми параметрами.

Данная задача имитирует ситуацию, в которой Ольга использует свой закрытый ключ для расшифровки файлов, зашифрованных Дмитрием.

Добавьте следующий код в качестве обработчика событий Click для кнопки Get Private Key ( buttonGetPrivateKey_Click ).

Тестирование приложения

После сборки приложения необходимо выполнить следующие сценарии тестирования.

Создание ключей, шифрование и расшифровка

Изучите только что расшифрованный файл.

Закройте приложение и перезапустите его, чтобы протестировать извлечение сохраненных контейнеров ключей в следующем сценарии.

Шифрование при помощи открытого ключа

Этот сценарий показывает, как шифровать файлы для другого лица при наличии только открытого ключа. Обычно это лицо предоставляет вам только открытый ключ и утаивает закрытый ключ для расшифровки.

Расшифровка при помощи закрытого ключа

image

Автор: Кристиан Амманн (Christian Ammann)

Динамический шифратор (runtime-cryptor) шифрует двоичные исполняемые файлы с сохранением их функциональности. При запуске сначала расшифровывается тело файла, а затем исполняется код. Такой подход позволяет запускать вредоносные программы в защищенных средах и затрудняет детектирование по сигнатуре и блокирование подозрительных файлов антивирусными средствами. Зашифрованная копия содержит неизвестную сигнатуру и не может быть исследована при помощи эвристического анализа, тем самым оставаясь незаметной для антивируса.

Наименование

MSDOS-заголовок, MSDOS-заглушка, указатель на Image FileHeader

Image File Header

Размер опционального заголовка, количество секций

Image Optional Header

Адрес точки входа, база образа, размер образа

Указатели на таблицы импорта и экспорта

Список заголовков секций

Секции .code, .data и т. д.

Как и стандартные PE-файлы, NET-приложения содержат MZ-заглушку, Image File Header, Image Optional Header, Dada Directory и соответствующие секции. Детальное описание этих терминов выходит за рамки данной статьи. С более подробным объяснением этих понятий вы можете ознакомиться в документах [1] и [2]. При открытии NET-приложения в PE-редакторе (например, вLord PE) [8], можно увидеть следующую структуру:

  • Секция .text, содержащую таблицу импорта (import table), таблицу импортируемых адресов (import address table) и CIL-код.
  • Секция .rsrc, содержащую иконку файла (file icon).
  • Секцию .reloc, содержащую таблицу релокаций (relocation table). В этой таблице хранится только одна запись, инструкция точки входа (entry point instruction).

При запуске NET-приложение загружается в память как обычный исполняемый файл. Когда процесс загрузки окончен, адреса секций преобразуются к виртуальным адресам, и PE-загрузчик определяет, содержит ли файл собственный машинный код или CIL-код. Если файл содержит собственный машинный код, тогда загрузчик переходит к точке входа и далее приложение исполняется процессором. В случае с CIL-кодом управление передается среде CLR. Таким образом, массив указателей (data directory) каждого PE-файла содержит запись заголовка среды исполнения CLR (CLR Runtime Header), при помощи которого можно отличить стандартный исполняемый файл от NET-приложения. Если файл содержит байт-код CIL, тогда в секции CLRRuntime Header присутствует указатель на CIL-заголовок. В случае стандартного исполняемого файла этот указатель равен 0.

  1. CIL-заголовок.
  2. CIL-код и ресурсы.
  3. Заголовок метаданных (MetaData header).
  4. Потоки (Streams).
  5. Таблицы метаданных (MetaData tables).

CIL-заголовок содержит базовое описание NET-файла. В двух записях представлены предыдущая/последующая версии среды выполнения (runtime environment version), которые необходимы для запуска CIL-кода. Также CIL-заголовок содержит указатели на секцию с NET-ресурсами и заголовок метаданных.

Заголовок метаданных содержит магическое число и, также как CIL-заголовок, хранит информацию о предыдущей/последующей версии среды выполнения (runtime version), которые, согласно [11], игнорируются загрузчиком. Важными элементами заголовка метаданных являются заголовки потоков (stream headers). Каждый такой заголовок содержит имя, размер и смещение (offset). По умолчанию в каждом NET-файле присутствую потоки Strings или Blob. Поток Strings содержит информацию о строках, а Blob – двоичные данные.

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

  • Имена классов, подклассов и т. п.
  • Имена переменных, типов и их начальные значения.
  • Имена констант и их значения.
  • Имена методов, параметры, тело метода, возвращаемые значения и т. п.

Следующий пример демонстрирует взаимосвязь CIL-кода, таблиц метаданных и потоков. Допустим, в NET-приложение объявлена константа со значением 1. Кроме того, там же присутствует класс B, в котором реализован метод c(). Идентификаторы a, B и c хранятся в потоке Strings, а значение константы в потоке Blob. В таблице метаданных хранятся записи класса, константы и метода. По сути, эти записи являются ссылками на соответствующие элементы потока. К тому же, в таблице присутствует запись-ссылка на CIL-код, который запускается при вызове функции. Еще раз обращаем внимание на то, что это лишь краткое описание формата, и за более подробной информацией рекомендуем обратиться к соответствующим источникам.

Теперь вам знакома структура NET-приложения, которая встроена в структуру PE-файла. Остался еще один аспект, который не описан – запуск CIL-кода по его загрузке в оперативную память. Для этого загрузчик сначала проверяет содержание ссылки на CIL-заголовок в data directory файла. Когда корректная ссылка найдена, в память загружается MsCorEE.dll, а затем вызывается функция _CorValidateImage(). Эта функция модифицирует PE-заголовок, перезаписывая точку входа. Новая точка входа является адресом функции _CorExeMain(), которая передает управление среде CLR.

3 Шифрование NET-файлов в реальном времени

В предыдущем разделе описан формат NET-приложений и механизм их запуска. В этом разделе будет рассмотрено два способа шифрования NET-файлов в реальном времени. Далее будет представлена базовая реализация шифратора Гиперион и рассмотрена проблема механизма загрузки зашифрованного файла в память. Затем эта проблема будет решена и представлена пилотная версия шифратора, которую можно использовать для реализации более изощренного алгоритма шифрования NET-файлов в реальном времени.

Для запуска процесса Windows-приложения используют функцию CreateProcess(). Один из параметров этой функции имя запускаемого файла. С точки зрения шифратора такой механизм запуска имеет недостаток, поскольку файл должен быть расшифрован и сохранен на диске перед вызовом функции CreateProcess(). К тому же, в Гиперионе и ему подобных динамических шифраторах реализован собственный PE-загрузчик, который работает с оперативной памятью, а не образом диска.

При помощи reflection API шифрование NET-приложений в реальном времени может быть реализовано следующим образом: динамический шифратор считывает входной файл, шифрует его, оставляя незашифрованную часть в виде исполняемого файла загрузчика (dropper executable) (например, в качестве ресурса). При запуске загрузчик расшифровывает основную программу в памяти и запускает ее при помощи функции Assembly.Load(). Данный механизм прост и понятен, но содержит следующие недостатки:

  • Assembly.Load() связана с CIL-кодом.
  • Антивирус может легко обнаружить вызов функции Assembly.Load() и затем запустить эвристический анализ.

Для устранения этих недостатков, используется еще один подход при реализации динамического шифратора. Согласно разделу 2, NET-файлы загружаются в память подобно обычным PE-файлам, а затем запускается функция _CorExeMain(). В соответствии с этим, Гиперион можно модифицировать так: когда файл находится в памяти, записи data directory могут быть проверены. В случае обнаружения CIL-заголовка вызывается функция _CorValidateImage(). В конце концов, загрузчик переходит к функции _CorExeMain() и выполняется CIL-код. Функции _CorExeMain() и _CorValidateImage() нужно динамически подгрузить, используя функцию GetProcAddress() и другие методы обфускации. Все это позволит создать эффективную защиту от детектирования исполняемого файла антивирусными средствами.

В предыдущем разделе было рассмотрено и проанализировано два подхода к созданию динамического шифратора. На основе сделанных выводов, мы выбрали второй алгоритм, который будет реализован в Гиперионе. Базовая версия шифратора работает так: сначала расшифровывается основная часть программы путем подбора шифр-ключа, после чего расшифрованный файл загружается в память согласно содержанию PE-заголовков и заголовков секций. В конце происходит переход к точке входа, и управление передается распакованному файлу.


Следующий участок кода показывает, как Гиперион переходит к точке входа после расшифровки файла:

01. ;.
02. mov edx,[image_base]
03. mov eax,[edx+IMAGE_DOS_HEADER.e_lfanew]
04. add eax,edx
05. add eax,4
06. ;image file header now in eax
07. add eax,sizeof.IMAGE_FILE_HEADER
08. mov eax,[eax+IMAGE_OPTIONAL_HEADER32.AddressOfEntryPoint]
09. add eax,[image_base]
10. ;entry point of original exe is now in eax
11. jmp eax

Рассмотрим листинг подробнее. Базовый образ (base image) расшифрованного файла хранится в регистре edx. Далее вычисляется и сохраняется в регистре eax адрес PE-заголовка. PE-заголовок используется для получения опционального заголовка, который содержит соответствующую точку входа. После перехода к точке входа происходит запуск исполняемого файла. Для NET-файлов вышеприведенный листинг добавляется небольшая модификация: в строке 1 появляется функция, которая получает в качестве параметра адрес загрузки расшифрованного файла и разбирает PE-заголовки для получения массива указателей (data directory). Если data directory содержит указатель на CIL-заголовок, вызывается функция _CorValidateImage(), которая изменяет адрес точки входа, и Гиперион переходит к функции _CorExeMain().

Первое, что приходит на ум – в Гиперионе произошла ошибка, и файл некорректно загрузился/расшифровался в памяти. После анализа образа расшифрованного файла был сделан вывод, что загрузка и расшифровка произошла успешно. Таким образом, текущая ситуация такова:

  • Поскольку файл был расшифрован, значит, NET-приложение работает корректно, и правильная рабочая версия доступна в системе.
  • После расшифровки файла, среда исполнения не может прочитать CIL-заголовок, даже в случае корректной загрузки файла в память.

Далее была запущена отладка зашифрованного файла с использованием Immunity [12] и точкой останова на функции _CorExeMain() (которая является точкой входа среды CLR) и пошагово пройден файл. Результаты отладки показаны на рис. 2 (выделено красным прямоугольником).


Рис. 2. Процесс отладки NET-приложения.

CIL framework открывает файл при помощи CreateFileW(), обращаясь к образу диска. Такое поведение странно, поскольку в нем нет необходимости, так как файл уже загружен в память PE-загрузчиком. Дальнейшие исследования выявили следующую картину:

  • Вызываются функции CreateFileW, CreateFileMapping and MapViewOfFile() для мапирования образа диска в память
  • Далее происходит разбор и обработка PE-заголовка файла образа диска

3.2 Частичное шифрование NET-файлов

Исследование в предыдущем разделе показало, что CIL framework обращается к файлу образа диска даже в том случае, когда файл уже загружен в память PE-загрузчиком. Следовательно, при загрузке исполняемых файлов NET, зашифрованных Гиперионом, возникает ошибка, поскольку среда CLR не может считать CIL-заголовок. В этом разделе будет представлена пилотная реализация шифрования в режиме реального времени для NET-приложений, где CLI-заголовок остается незашифрованным. Новый метод шифрования разделен на две части:

  • Шифровщик: шифруется только CIL-код; PE-заголовок, CIL-заголовок и метаданные остаются нетронутыми.
  • Загрузчик: стартует зашифрованный файл при помощи функции CreateProcess(). Главный поток находится в состоянии ожидания. Загрузчик расшифровывает CIL-код и возвращается в главный поток.

Само собой, при такой реализации приложение не защищено от статического анализа антивирусами, поскольку остаются незашифрованные места. В разделе 4 будет реализовано расширение для Гипериона, которое будет шифровать все NET-файлы.

3.2.1 Шифровщик

На входе шифровщик получает входной файл и копирует его в память. Далее происходит разбор заголовков файла, чтобы определить местонахождение CIL-кода и зашифровать его.

Каждый PE-файл начинается с MZ-заголовка, содержащего указатель на заголовок COFF. Размер заголовка COFF является постоянным, за ним следуют опциональные стандартные заголовки (optional standard headers) и windows-заголовки (optional windows headers), которые также фиксированного размера. После опциональных windows-заголовков находится data directory, размер которого определяется переменной NumberOfRvaAndSizes, являющейся элементом опционального windows-заголовка. Поскольку размер data directory может быть равным нулю, рекомендуется высчитывать размер опционального заголовка через переменную SizeOfOptionalHeader, а не обращаться к переменной NumberOfRvaAndSizes. Data directory представляет из себя массив структурImageDataDirectory:

01. struct ImageDataDirectory f
02. uint32_t VirtualAddress;
03. uint32_t Size;
04. g;

Каждая запись содержит относительный виртуальный адрес (relative virtual address, rva) и относительный адрес. Если rva равен нулю, то эта запись не используется в PE-файле. Следующий код проверяет присутствие в файле CIL-кода:

01. ImageCor20Header* rva_cil_header = (ImageCor20Header*)
02. data_directory [CLR_RUNTIME_HEADER]).VirtualAddress;
03.
04. if(rva_cil_header==0)f
05. printf("no clr runtime header found");
06. return false;
07. g

CIL-заголовок представлен структурой ImageCor20Header, которая имеет такой формат:

01. struct ImageCor20Header
02. f
03. uint32_t cb;
04. uint16_t MajorRuntimeVersion;
05. uint16_t MinorRuntimeVersion;
06. struct ImageDataDirectory MetaData;
07. uint32_t Flags;
08. uint32_t EntryPoint;
09. struct ImageDataDirectory Resources;
10. struct ImageDataDirectory StrongNameSignature;
11. struct ImageDataDirectory CodeManagerTable;
12. struct ImageDataDirectory VTableFixups;
13. struct ImageDataDirectory ExportAddressTableJumps;
14. struct ImageDataDirectory ManagedNativeHeader;
15. g;

Для реализации шифровщика важным элементом является указатель на метаданные, представленный структурой ImageDataDirectory. Как только получен CIL-заголовок, шифровщик считывает следующую информацию:

  • Размер и относительный адрес CIL-заголовка.
  • Размер и относительный адрес заголовка метаданных.

Здесь упущен один аспект: входной файл смапирован в память по определенному адресу загрузки (image base). Заголовки PE-файлов содержат указатели, являющие относительными виртуальными адресами (rva). Rva должен быть трансформирован в соответствующий физический адрес (raw address), после чего будет доступна запись и чтение данных. Для преобразования адреса используется функция:

01. unsigned char* rvaToOffset(unsigned char* base,

02. struct SectionHeader* sections,
03. uint16_t sections_size, uint32_t rva)
04. f
05. unsigned char* ret = 0;
06. for(int i=0;i<sections_size;i++)f
07. if(rva >= sections[i].VirtualAddress &&
08. rva < sections[i].VirtualAddress + sections[i].VirtualSize)f
09. ret = base + sections[i].PointerToRawData +
10. (rva - sections[i].VirtualAddress);
11. g
12. g
13. return ret;
14. g

Метод rvaToOffset() на входе получает следующие параметры: адрес загрузки файла в память, указатель на секцию заголовков, число секций заголовков и относительный адрес, который необходимо преобразовать. В начале работы происходит поиск секции, содержащей относительный виртуальный адрес. Далее к адресу загрузки файла добавляется смещение секцииPointerToRawData и разность между относительным виртуальным адресом и виртуальным адресом секции VirtualAddress, а затем возвращается итоговое значение преобразованного адреса.

3.2.2 Загрузчик

В предыдущем разделе был рассмотрен процесс шифровки CIL-кода NET-приложения. Загрузчик запускает зашифрованный файл, используя метод CreateProcess(). При этом главный поток приостанавливается. После дешифровки CIL-кода загрузчик вновь возвращается в главный поток.

01. STARTUPINFOA startupinfo;
02. memset(&startupinfo, 0, sizeof(startupinfo));
03. startupinfo.cb = sizeof(STARTUPINFOA);
04. PROCESS_INFORMATION process_information;
05. memset(&process_information, 0, sizeof(process_information));
06. CreateProcessA(application_name_and_path, 0, 0, 0,
07. false, NORMAL_PRIORITY_CLASS | CREATE_SUSPENDED, 0, 0, &startupinfo,
08. &process_information);

Метод CreateProcess() использует две структуры: STARTUPINFO и PROCESS INFORMATION. В вышеприведенном листинге сначала обе структуры инициализированы нулевыми байтами, а затем CreateProcess() запускает приложение. Информация о созданном процессе хранится в структуре process_information. CIL-код расшифровывается при помощи следующего алгоритма:

  • Метод VirtualProtectEx() делает участок памяти процесса доступной для записи
  • Метод ReadProcessMemory() копирует участок память процесса в буфер. Туда же попадает выше заполненная структура process_information
  • Дешифруется CIL-код
  • Обновленный буфер копируется обратно в память процесса с использованием метода WriteProcessMemory()
  • VirtualProtectEx() восстанавливает прежние атрибуты участка памяти процесса

После отработки алгоритма загрузчик возвращается в потом, используя конструкцию ResumeThread(process information.hThread);.

4 Заключение и направление дальнейшей работы

  • Используется простое XOR-шифрование, в то время как Гиперион может использовать алгоритм AES-128.
  • Шифруется только CIL-код, а заголовок и метаданные остаются уязвимыми для статического анализа антивирусными средствами.

В дальнейшем функционал шифровщика будет доработан, и Гиперион сможет шифровать все NET-файлы по следующему алгоритму: сначала происходит перехват (hook) методов CreateFileW(),CreateFileMapping() and MapViewOfFile() в модуле kernel32.dll. Перехватчик проверяет, пытается ли среда исполнения обратиться к файлу образа диска. Если такое происходит, тогда возвращается указатель образа ранее дешифрованного файла и среда CLR получает доступ к CIL-заголовку. Это решает проблему, описанную в разделе 3.1, что позволяет полностью зашифровать NET-файлы.

5 Благодарности

Мы хотим выразить благодарность Крису Каулингу (Chris Cowling), Яну Квисту (Ian Qvist) и Томасу Педерсену (Thomas Pedersen) за помощь при работе над данной статьей. Кроме того, мы хотим сказать спасибо всей команде Nullsecurity за плодотворные беседы и (что более важно) за прекрасную вечеринку на Berlin-Sides 2012.

[3] Information Technology Laboratory (National Institute of Standards and Technology). Announcing

the Advanced Encryption Standard (AES) [electronic resource]. Computer Security Division, Information Technology Laboratory, National Institute of Standards and Technology, Gaithersburg, MD :, 2001.

[9] Microsoft Cooperation. Standard ECMA-335 - Common Language Infrastructure (CLI).

Как написать локер, шифровальщик и вирус на Python

Почему кому-то может прийти в голову писать малварь на Python? Мы сделаем это, чтобы изучить общие принципы вредоносостроения, а заодно вы попрактикуетесь в использовании этого языка и сможете применять полученные знания в других целях. К тому же вредонос на Python таки попадается в дикой природе, и далеко не все антивирусы обращают на него внимание.

Чаще всего Python применяют для создания бэкдоров в софте, чтобы загружать и исполнять любой код на зараженной машине. Так, в 2017 году сотрудники компании Dr.Web обнаружили Python.BackDoor.33, а 8 мая 2019 года был замечен Mac.BackDoor.Siggen.20. Другой троян — RAT Python крал пользовательские данные с зараженных устройств и использовал Telegram в качестве канала передачи данных.

Мы же создадим три демонстрационные программы: локер, который будет блокировать доступ к компьютеру, пока пользователь не введет правильный пароль, шифровальщик, который будет обходить директории и шифровать все лежащие в них файлы, а также вирус, который будет распространять свой код, заражая другие программы на Python.

Как написать локер, шифровальщик и вирус на Python

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

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

Настройка среды

Итак, первым делом нам, конечно, понадобится сам Python, причем третьей версии. Не буду детально расписывать, как его устанавливать, и сразу отправлю вас скачивать бесплатную книгу «Укус питона» (PDF). В ней вы найдете ответ на этот и многие другие вопросы, связанные с Python.

Дополнительно установим несколько модулей, которые будем использовать:

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

Создание локера

Идея — создаем окно на полный экран и не даем пользователю закрыть его.

Теперь возьмемся за основную часть программы.

Здесь pyautogui.FAILSAFE = False — защита, которая активируется при перемещении курсора в верхний левый угол экрана. При ее срабатывании программа закрывается. Нам это не надо, поэтому вырубаем эту функцию.

Чтобы наш локер работал на любом мониторе с любым разрешением, считываем ширину и высоту экрана и по простой формуле вычисляем, куда будет попадать курсор, делаться клик и так далее. В нашем случае курсор попадает в центр экрана, то есть ширину и высоту мы делим на два. Паузу (sleep) добавим для того, чтобы пользователь мог ввести код для отмены.

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

Создадим функцию для ввода ключа:

Тут всё просто. Если ключ не тот, который мы задали, программа продолжает работать. Если пароли совпали — тормозим.

Последняя функция, которая нужна для работы окна-вредителя:

На этом наш импровизированный локер готов.

Создание шифровальщика

Этот вирус мы напишем при помощи только одной сторонней библиотеки — pyAesCrypt. Идея — шифруем все файлы в указанной директории и всех директориях ниже. Это важное ограничение, которое позволяет не сломать операционку. Для работы создадим два файла — шифратор и дешифратор. После работы исполняемые файлы будут самоудаляться.

Сначала запрашиваем путь к атакуемому каталогу и пароль для шифрования и дешифровки:

Дальше мы будем генерировать скрипты для шифрования и дешифровки. Выглядит это примерно так:

Переходим к файлам, которые мы будем использовать в качестве шаблонов. Начнем с шифратора. Нам потребуются две стандартные библиотеки:

Пишем функцию шифрования (все по мануалу pyAesCrypt):

Вместо str(password) скрипт-генератор вставит пароль.

Важные нюансы. Шифровать и дешифровать мы будем при помощи буфера, таким образом мы избавимся от ограничения на размер файла (по крайней мере, значительно уменьшим это ограничение). Вызов os.remove(file) нужен для удаления исходного файла, так как мы копируем файл и шифруем копию. Можно настроить копирование файла вместо удаления.

Теперь функция, которая обходит папки. Тут тоже ничего сложного.

В конце добавим еще две строки. Одна для запуска обхода, вторая — для самоуничтожения программы.

Здесь снова будет подставляться нужный путь.

Вот весь исходник целиком.

Теперь «зеркальный» файл. Если в шифровальщике мы писали encrypt, то в дешифраторе пишем decrypt. Повторять разбор тех же строк нет смысла, поэтому сразу финальный вариант.

Создание вируса

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

На этот раз нам не потребуются никакие сторонние библиотеки, нужны только модули sys и os. Подключаем их.

Функция, которая сообщает об атаке:

Сразу вызовем ее, чтобы понять, что программа отработала:

Обход директорий похож на тот, что мы делали в шифровальщике.

В теории мы могли бы таким же образом отравлять исходники и на других языках, добавив код на этих языках в файлы с соответствующими расширениями. А в Unix-образных системах скрипты на Bash, Ruby, Perl и подобном можно просто подменить скриптами на Python, исправив путь к интерпретатору в первой строке.

Вирус будет заражать файлы «вниз» от того каталога, где он находится (путь мы получаем, вызвав os.getcwd()).

В начале и в конце файла пишем вот такие комментарии:

Чуть позже объясню зачем.

Дальше функция, которая отвечает за саморепликацию.

Теперь, думаю, стало понятнее, зачем нужны метки «старт» и «стоп». Они обозначают начало и конец кода вируса. Сперва мы читаем файл и построчно просматриваем его. Когда мы наткнулись на стартовую метку, поднимаем флаг. Пустую строку добавляем, чтобы вирус в исходном коде начинался с новой строки. Читаем файл второй раз и записываем построчно исходный код. Последний шаг — пишем вирус, два отступа и оригинальный код. Можно поиздеваться и записать его как-нибудь по-особому — например, видоизменить все выводимые строки.

Создание исполняемого файла

Как запустить вирус, написанный на скриптовом языке, на машине жертвы? Есть два пути: либо как-то убедиться, что там установлен интерпретатор, либо запаковать созданный нами шифровальщик вместе со всем необходимым в единый исполняемый файл. Этой цели служит утилита PyInstaller. Вот как ей пользоваться.

И вводим команду

Немного ждем, и у нас в папке с программой появляется куча файлов. Можете смело избавляться от всего, кроме экзешников, они будет лежать в папке dist.

Говорят, что с тех пор, как начали появляться вредоносные программы на Python, антивирусы стали крайне нервно реагировать на PyInstaller, причем даже если он прилагается к совершенно безопасной программе.

Я решил проверить, что VirusTotal скажет о моих творениях. Вот отчеты:

  • файл Crypt.exe не понравился 12 антивирусам из 72;
  • файл Locker.exe — 10 антивирусам из 72;
  • файл Virus.exe — 23 антивирусам из 72.

Худший результат показал Virus.exe — то ли некоторые антивирусы обратили внимание на саморепликацию, то ли просто название файла не понравилось. Но как видите, содержимое любого из этих файлов насторожило далеко не все антивирусы.

Итого

Итак, мы написали три вредоносные программы: локер, шифровальщик и вирус, использовав скриптовый язык, и упаковали их при помощи PyInstaller.

Безусловно, наш вирус — не самый страшный на свете, а локер и шифровальщик еще нужно как-то доставлять до машины жертвы. При этом ни одна из наших программ не общается с C&C-сервером и я совсем не обфусцировал код.

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

Читайте также: