Microsoft подвергла своих пользователей
В качестве PoC (proof-of-concept) я изолировал Windows Defender, а сейчас выкладываю свой код в открытый доступ как
В статье я опишу процесс и результаты создания этого инструмента, а также выскажу свои мысли о Rust на Windows.
Первый шаг к изолированию Windows Defender — возможность запустить AppContainers. Я бы хотел опять использовать
Но затем меня
Создание песочницы
Несколько месяцев спустя, после беглого изучения учебников по Rust и написания своего первого кода, у меня появилось три главные опоры для запуска AppContainers на Rust: это SimpleDacl, Profile и WinFFI.
Далее нужно было понять, как установить интерфейс со сканирующим компонентом Windows Defender. Пример реализации на С и инструкции для начала сканирования MsMpEng были в репозитории
Базовая архитектура Flying Sandbox Monster
Наш пример
Этого недостаточно для полноценной работы PoC, ибо Malware Protection Engine не хотел инициализироваться внутри AppContainer. Сначала я думал, что это проблема контроля доступа. Но после тщательной сверки отличий в ProcMon (сравнивая отличия исполнения в AppContainer и не в AppContainer), я понял, что проблема может быть в определении версии Windows. Код Тэвиса всегда отчитывался как версия Windows XP. Мой код сообщал реальную версию хостовой системы: в моём случае это Windows 10. Проверка в WinDbg доказала, что проблема инициализации действительно в этом. Нужно было соврать MpEngine о хостовой версии Windows. В С/C++ я бы использовал перехват функций с помощью Detours. К сожалению, для Rust под Windows нет эквивалентной библиотеки для перехвата функций (несколько библиотек для перехвата функций оказались гораздо «тяжеловеснее», чем мне надо). Естественно, я реализовал
Представление AppJailLauncher-rs
Поскольку я уже реализовал ключевые компоненты AppJailLauncher на Rust, почему бы не закончить работу и не обернуть это всё в сервер Rust TCP? Я это сделал, и теперь рад представить вам «вторую версию» AppJailLauncher —
AppJailLauncher был TCP-сервером, который прослушивал определённый порт и запускал процесс AppContainer для каждого принятого соединения TCP. Я не хотел изобретать велосипед, но
TcpServer отвечает за асинхронный TCP-сервер и клиентский сокет, совместимый с перенаправлением STDIN/STDOUT/STDERR. Созданные вызовом socket сокеты не могут перенаправлять стандартные потоки ввода-вывода. Для правильного стандартного перенаправления ввода-вывода нужны «нативные» сокеты (как те, которые создаются через WSASocket). Чтобы разрешить перенаправление, TcpServerсоздаёт эти «нативные» сокеты и не запрещает явно для них наследование.
Мой опыт работы с Rust
В целом, мой опыт работы с Rust оказался очень приятным, несмотря на некоторые небольшие шероховатости. Позвольте упомянуть некоторые функции этого языка программирования, которые я особенно приметил во время разработки AppJailLauncher.
Cargo. Управление зависимостями в C++ под Windows — реально утомительное и сложное дело, особенно со ссылками на сторонние библиотеки. Rust ловко решает эту проблему с помощью пакетного менеджера cargo. Там большой набор пакетов, которые решают многие типичные проблемы вроде разбора аргументов (clap-rs), Windows FFI (winapi-rs и др.) и обработки широких строк (widestring).
Встроенное тестирование. Юнит-тесты для приложений C++ требуют применения сторонней библиотеки и большой ручной работы. Вот почему их редко пишут для маленьких проектов, вроде первой версии AppJailLauncher. В Rust юнит-тестирование встроено в систему cargo, где существует вместе с основной функциональностью.
Система макросов. В Rust система макросов работает на уровне абстрактного синтаксического дерева, в отличие от простого движка замены в С/C++. Хотя здесь нужно немного обучиться, но макросы Rust полностью лишены раздражающих свойств макросов C/C++, вроде коллизий именований и области.
Отладка. Отладка Rust под Windows работает как надо. Rust генерирует WinDbg-совместимые символы отладки (файлы PDB), которые обеспечивают беспрепятственную отладку исходников.
Интерфейс внешних функций. Windows API написаны на C/С++ и подразумевается, что таким образом и надо к нему обращаться. Другие языки, как и Rust, должны использовать интерфейс внешних функций (FFI), чтобы обратиться к Windows API. Rust FFI для Windows (winapi-rs) по большей части готов. Там есть ключевые API, но не хватает некоторых не так часто используемых подсистем вроде API для изменения списка контроля доступа.
Аттрибуты. Установка аттрибутов довольно обременительна, поскольку они применяются только для следующей строчки.
Borrow Checker. Концепция собственности — то, как Rust достигает безопасности памяти. Попытки понять, как работает Borrow Checker, сопровождаются загадочными, уникальными ошибками и требуют часов чтения документации и учебников. В конце концов оно того стоит: когда у меня «щёлкнуло» и я усвоил концепцию, мои навыки программирования кардинально продвинулись.
Векторы. В C++ контейнер std::vector может выставить свой поддерживающий буфер другому коду. Оригинальный вектор остаётся валидным, даже если поддерживающий буфер изменили. В случае Vec в Rust это не так. Здесь Vec требует создания нового объекта из «заготовок» старого Vec.
Типы Option и Result. Нативные типы Option и Result должны были упростить проверку ошибок, но на самом деле сделали её как будто более подробной. Можно сделать вид, что ошибок не существует, и просто вызвать unwrap, но это приведёт к сбою рантайма, когда неизбежно вылезет Error (или None).
Другие типы и слайсы. К принадлежащим типам (owned types) и сопутствующим слайсам (например, String/str, PathBuf/Path) нужно немного привыкнуть. Они идут парами с похожими именами, но ведут себя по-разному. В Rust принадлежащий тип представляет расширяемый, изменчивый объект (обычно строку). Слайс — это вид неизменяемого буфера символов (тоже обычно строка).
Будущее
Экосистема Rust для Windows ещё растёт. Можно ещё создавать новые библиотеки Rust, упрощающие разработку ПО для безопасности под Windows. Я сделал начальные версии нескольких библиотек Rust для сэндбоксинга в Windows, разбора PE и перехвата IAT. Надеюсь, они будут полезны возникающему сообществу Rust под Windows.
С помощью Rust и AppJailLauncher я изолировал в песочнице Windows Defender, флагманский антивирусный продукт Microsoft. Моё достижение одновременно и замечательное, и немного постыдное. Замечательно то, что надёжный механизм песочницы Windows доступен для стороннего софта. Постыдно то, что Microsoft сама не изолировала Defender. Microsoft
Пожалуйста,
Вход
или
Регистрация
для просмотра содержимого URL-адресов!
, когда выпустила Windows Defender вне песочницы. Меня это удивило. Песочница — одна из самых эффективных техник усиления безопасности. Почему Microsoft изолировала в песочнице другие высоковероятные цели атаки, вроде кода JIT в Microsoft Edge, но оставила Windows Defender без защиты?В качестве PoC (proof-of-concept) я изолировал Windows Defender, а сейчас выкладываю свой код в открытый доступ как
Пожалуйста,
Вход
или
Регистрация
для просмотра содержимого URL-адресов!
. Основа Flying Sandbox Monster — это Пожалуйста,
Вход
или
Регистрация
для просмотра содержимого URL-адресов!
, фреймворк на Пожалуйста,
Вход
или
Регистрация
для просмотра содержимого URL-адресов!
для помещения ненадёжных приложений в Пожалуйста,
Вход
или
Регистрация
для просмотра содержимого URL-адресов!
. Он также позволяет вынести I/O приложения за TCP-сервер, чтобы приложение в песочнице работало на полностью другой машине. Это дополнительный уровень изоляции.В статье я опишу процесс и результаты создания этого инструмента, а также выскажу свои мысли о Rust на Windows.
Пожалуйста,
Вход
или
Регистрация
для просмотра содержимого URL-адресов!
два года назад?Первый шаг к изолированию Windows Defender — возможность запустить AppContainers. Я бы хотел опять использовать
Пожалуйста,
Вход
или
Регистрация
для просмотра содержимого URL-адресов!
, но здесь была проблема. Оригинальный AppJailLauncher был написан как демонстрационный пример. Если бы я тогда знал, то написал бы его на Пожалуйста,
Вход
или
Регистрация
для просмотра содержимого URL-адресов!
, чтобы не мучиться с управлением памятью. За последние два года я пытался переписать его на С++, но неудачно (почему зависимости всегда такая головная боль?).Но затем меня
Пожалуйста,
Вход
или
Регистрация
для просмотра содержимого URL-адресов!
. Почему бы не переписать код запуска AppContainer на Rust?Создание песочницы
Несколько месяцев спустя, после беглого изучения учебников по Rust и написания своего первого кода, у меня появилось три главные опоры для запуска AppContainers на Rust: это SimpleDacl, Profile и WinFFI.
- Пожалуйста, Вход или Регистрация для просмотра содержимого URL-адресов!— это обобщённый класс, который берёт на себя добавление и удаление простых дискреционных записей контроля доступа ACE (access control entries) под Windows. Хотя SimpleDacl работает и с файлами, и с директориями, у него есть несколько недостатков. Во-первых, он полностью переписывает существующую ACL и преобразует унаследованные элементы ACE в «нормальные». Кроме того, он пренебрегает теми элементами ACE, которые не может парсить (например, всё, кроме AccessAllowedAce и AccessDeniedAce. Примечание: мы не поддерживаемПожалуйста, Вход или Регистрация для просмотра содержимого URL-адресов!иПожалуйста, Вход или Регистрация для просмотра содержимого URL-адресов!записи контроля доступа).
- Пожалуйста, Вход или Регистрация для просмотра содержимого URL-адресов!реализует создание профилей и процессов AppContainer. Из профиля мы можем получить SID, который можно использовать для создания ACE для ресурсов, к которым AppContainer должен иметь доступ.
- Пожалуйста, Вход или Регистрация для просмотра содержимого URL-адресов!даёт нам функции и структуры, которые вПожалуйста, Вход или Регистрация для просмотра содержимого URL-адресов!реализованы не так хорошо, как в полезных служебных классах/функциях. Я приложил много усилий, чтобы обернуть каждый исходный HANDLE и указатель в объекты Rust для управления временем их работы.
Далее нужно было понять, как установить интерфейс со сканирующим компонентом Windows Defender. Пример реализации на С и инструкции для начала сканирования MsMpEng были в репозитории
Пожалуйста,
Вход
или
Регистрация
для просмотра содержимого URL-адресов!
Тэвиса Орманди. Портирование структур и прототипов функций на Rust несложно автоматизировать, хотя я поначалу забыл о полях-массивах и указателях функций, из-за чего возникло много разных проблем; однако благодаря встроенной в Rust функциональности тестирования я быстро решил все свои ошибки портирования — и вскоре у меня был минимальный тестовый вариант, который Пожалуйста,
Вход
или
Регистрация
для просмотра содержимого URL-адресов!
.Базовая архитектура Flying Sandbox Monster
Наш пример
Пожалуйста,
Вход
или
Регистрация
для просмотра содержимого URL-адресов!
состоит из враппера песочницы и Malware Protection Engine (MpEngine). У единственного исполняемого файла два режима: родительский и дочерний процессы. Режим определяется присутствием переменных окружения, которые содержат HANDLEs для сканируемого файла, и коммуникацией между процессами. Родительский процесс устанавливает два этих значения HANDLEs перед созданием дочернего процесса AppContainer. Теперь изолированный дочерний процесс загружает библиотеку антивирусного движка и сканирует входящий файл на предмет вирусов.Этого недостаточно для полноценной работы PoC, ибо Malware Protection Engine не хотел инициализироваться внутри AppContainer. Сначала я думал, что это проблема контроля доступа. Но после тщательной сверки отличий в ProcMon (сравнивая отличия исполнения в AppContainer и не в AppContainer), я понял, что проблема может быть в определении версии Windows. Код Тэвиса всегда отчитывался как версия Windows XP. Мой код сообщал реальную версию хостовой системы: в моём случае это Windows 10. Проверка в WinDbg доказала, что проблема инициализации действительно в этом. Нужно было соврать MpEngine о хостовой версии Windows. В С/C++ я бы использовал перехват функций с помощью Detours. К сожалению, для Rust под Windows нет эквивалентной библиотеки для перехвата функций (несколько библиотек для перехвата функций оказались гораздо «тяжеловеснее», чем мне надо). Естественно, я реализовал
Пожалуйста,
Вход
или
Регистрация
для просмотра содержимого URL-адресов!
(только для 32-битной Windows PE).Представление AppJailLauncher-rs
Поскольку я уже реализовал ключевые компоненты AppJailLauncher на Rust, почему бы не закончить работу и не обернуть это всё в сервер Rust TCP? Я это сделал, и теперь рад представить вам «вторую версию» AppJailLauncher —
Пожалуйста,
Вход
или
Регистрация
для просмотра содержимого URL-адресов!
.AppJailLauncher был TCP-сервером, который прослушивал определённый порт и запускал процесс AppContainer для каждого принятого соединения TCP. Я не хотел изобретать велосипед, но
Пожалуйста,
Вход
или
Регистрация
для просмотра содержимого URL-адресов!
, компактная I/O библиотека для Rust, просто не подходила. Во-первых, её TcpClient не обеспечивал доступа к исходным HANDLEs сокетам под Windows. Во-вторых, эти сокеты не наследовались дочерним процессом AppContainer. Из-за этого приходится представить ещё одну «опору» для поддержки appjaillauncher-rs: Пожалуйста,
Вход
или
Регистрация
для просмотра содержимого URL-адресов!
.TcpServer отвечает за асинхронный TCP-сервер и клиентский сокет, совместимый с перенаправлением STDIN/STDOUT/STDERR. Созданные вызовом socket сокеты не могут перенаправлять стандартные потоки ввода-вывода. Для правильного стандартного перенаправления ввода-вывода нужны «нативные» сокеты (как те, которые создаются через WSASocket). Чтобы разрешить перенаправление, TcpServerсоздаёт эти «нативные» сокеты и не запрещает явно для них наследование.
Мой опыт работы с Rust
В целом, мой опыт работы с Rust оказался очень приятным, несмотря на некоторые небольшие шероховатости. Позвольте упомянуть некоторые функции этого языка программирования, которые я особенно приметил во время разработки AppJailLauncher.
Cargo. Управление зависимостями в C++ под Windows — реально утомительное и сложное дело, особенно со ссылками на сторонние библиотеки. Rust ловко решает эту проблему с помощью пакетного менеджера cargo. Там большой набор пакетов, которые решают многие типичные проблемы вроде разбора аргументов (clap-rs), Windows FFI (winapi-rs и др.) и обработки широких строк (widestring).
Встроенное тестирование. Юнит-тесты для приложений C++ требуют применения сторонней библиотеки и большой ручной работы. Вот почему их редко пишут для маленьких проектов, вроде первой версии AppJailLauncher. В Rust юнит-тестирование встроено в систему cargo, где существует вместе с основной функциональностью.
Система макросов. В Rust система макросов работает на уровне абстрактного синтаксического дерева, в отличие от простого движка замены в С/C++. Хотя здесь нужно немного обучиться, но макросы Rust полностью лишены раздражающих свойств макросов C/C++, вроде коллизий именований и области.
Отладка. Отладка Rust под Windows работает как надо. Rust генерирует WinDbg-совместимые символы отладки (файлы PDB), которые обеспечивают беспрепятственную отладку исходников.
Интерфейс внешних функций. Windows API написаны на C/С++ и подразумевается, что таким образом и надо к нему обращаться. Другие языки, как и Rust, должны использовать интерфейс внешних функций (FFI), чтобы обратиться к Windows API. Rust FFI для Windows (winapi-rs) по большей части готов. Там есть ключевые API, но не хватает некоторых не так часто используемых подсистем вроде API для изменения списка контроля доступа.
Аттрибуты. Установка аттрибутов довольно обременительна, поскольку они применяются только для следующей строчки.
Borrow Checker. Концепция собственности — то, как Rust достигает безопасности памяти. Попытки понять, как работает Borrow Checker, сопровождаются загадочными, уникальными ошибками и требуют часов чтения документации и учебников. В конце концов оно того стоит: когда у меня «щёлкнуло» и я усвоил концепцию, мои навыки программирования кардинально продвинулись.
Векторы. В C++ контейнер std::vector может выставить свой поддерживающий буфер другому коду. Оригинальный вектор остаётся валидным, даже если поддерживающий буфер изменили. В случае Vec в Rust это не так. Здесь Vec требует создания нового объекта из «заготовок» старого Vec.
Типы Option и Result. Нативные типы Option и Result должны были упростить проверку ошибок, но на самом деле сделали её как будто более подробной. Можно сделать вид, что ошибок не существует, и просто вызвать unwrap, но это приведёт к сбою рантайма, когда неизбежно вылезет Error (или None).
Другие типы и слайсы. К принадлежащим типам (owned types) и сопутствующим слайсам (например, String/str, PathBuf/Path) нужно немного привыкнуть. Они идут парами с похожими именами, но ведут себя по-разному. В Rust принадлежащий тип представляет расширяемый, изменчивый объект (обычно строку). Слайс — это вид неизменяемого буфера символов (тоже обычно строка).
Будущее
Экосистема Rust для Windows ещё растёт. Можно ещё создавать новые библиотеки Rust, упрощающие разработку ПО для безопасности под Windows. Я сделал начальные версии нескольких библиотек Rust для сэндбоксинга в Windows, разбора PE и перехвата IAT. Надеюсь, они будут полезны возникающему сообществу Rust под Windows.
С помощью Rust и AppJailLauncher я изолировал в песочнице Windows Defender, флагманский антивирусный продукт Microsoft. Моё достижение одновременно и замечательное, и немного постыдное. Замечательно то, что надёжный механизм песочницы Windows доступен для стороннего софта. Постыдно то, что Microsoft сама не изолировала Defender. Microsoft
Пожалуйста,
Вход
или
Регистрация
для просмотра содержимого URL-адресов!
то, что Пожалуйста,
Вход
или
Регистрация
для просмотра содержимого URL-адресов!
, в 2004 году. В те времена такие баги и архитектурные просчёты были неприемлемы, но объяснимы. За прошедшие Microsoft создала отличную организацию по разработке систем безопасности, для обычного тестирования и фаззинга. Она изолировала в песочнице критические части Internet Explorer. Каким-то образом Windows Defender застрял в 2004 году. Вместо использования методов Project Zero и непрерывного указания на симптомы этого врождённого недостатка, давайте перенесём Windows Defender обратно в будущее.