От редакции
Мы решили в качестве эксперимента начать знакомить читателей с лучшими мировыми исследованиями. Далее — близкий к тексту пересказ поста Джеймса Форшоу из блога Google Project Zero. Эта публикация доступна без платной подписки.
Объектно ориентированные технологии удаленного взаимодействия, такие как DCOM и .NET Remoting, позволяют без особых заморочек организовать доступ к сервисам, пересекающим границы процессов и уровни безопасности. Фишка в том, что они поддерживают не только встроенные объекты, но и любые другие, которые можно передавать удаленно.
Например, захотелось тебе пробросить XML-документ через клиент‑серверный барьер? Не проблема — берешь готовую COM- или .NET-библиотеку, заворачиваешь объект и отправляешь клиенту. По умолчанию объект маршализуется по ссылке. Это значит, что сам он остается на сервере, а клиент лишь управляет им дистанционно.
Но за такую гибкость приходится платить, и именно об этом пойдет речь — о классе багов, который я назову «ловушка для объектов». Дело в том, что не все объекты, которые можно передавать удаленно, безопасно так передавать. Например, те же XML-библиотеки в COM и .NET поддерживают выполнение произвольного кода в контексте XSLT-документа. А теперь представь: если XML-объект доступен за пределами процесса, клиент может запустить код прямо внутри серверного окружения. Итог — повышение привилегий или даже удаленное выполнение кода. Красиво? Да. Опасно? Еще как!
Сценариев, которые могут привести к этой уязвимости, хватает. Самый частый — когда небезопасный объект случайно становится доступным для удаленного использования. Классический пример — CVE-2019-0555.
Этот баг появился, когда разработчики Windows Runtime захотели работать с XML-документами. Они не стали изобретать велосипед, а просто прикрутили нужные интерфейсы к уже существующему COM-объекту XML DOM Document v6. Казалось бы, все четко: новые интерфейсы не поддерживали выполнение XSLT-скриптов, значит, передавать объект через границы привилегий безопасно. Но не тут‑то было! Злой хакер мог спокойно запросить старый интерфейс IXMLDOMDocument
, который все еще оставался доступным, и с его помощью выполнить XSLT-скрипт, вырвавшись из песочницы. Итог — дырка в безопасности и повышение привилегий.
Еще один сценарий — использование асинхронного маршалинга. Это когда объект может передаваться как по значению, так и по ссылке, но платформа по умолчанию выбирает передачу по ссылке.
Например, в .NET классы FileInfo и DirectoryInfo сериализуемые, значит, их можно передавать в .NET Remoting по значению. Но есть нюанс: они также наследуются от MarshalByRefObject, что позволяет передавать их и по ссылке. Тут‑то атакующий и может сыграть на особенностях маршалинга. Он отправляет на сервер сериализованный объект, который при десериализации создает новый экземпляр внутри серверного процесса.
Дальше — хитрый ход: если атакующий может обратно получить этот объект, рантайм автоматически отправит его по ссылке, оставляя его «запертым» в серверном окружении. Итог? Злоумышленник сможет спокойно вызывать методы этого объекта, например создавать файлы, причем с привилегиями сервера. И вот тебе элегантное повышение прав.
www
Этот трюк я реализовал в своем инструменте ExploitRemotingService.
Последний (и, пожалуй, самый интересный) сценарий — это злоупотребление встроенными механизмами поиска и создания объектов в системе удаленного взаимодействия. Здесь мы заставляем сервер порождать неожиданные объекты, которые можно использовать в своих интересах.
Например, в COM достаточно найти способ вызвать CoCreateInstance с произвольным CLSID, получить объект в ответ — и вот у нас уже выполняется произвольный код на сервере. Классический пример — CVE-2017-0211. Эта бага позволяла передавать через границу безопасности объект Structured Storage, который поддерживал интерфейс IPropertyBag. А вот этот интерфейс уже можно было использовать для создания любого COM-объекта в контексте сервера.
Что можно было провернуть? Все тот же XML DOM Document! Создаем его на сервере, получаем обратно по ссылке и... включаем XSLT-магию для выполнения произвольного кода. Вуаля — повышение привилегий на ровном месте.
А что насчет IDispatch?
IDispatch — это часть OLE Automation, одной из первых фич COM, созданной для удобной автоматизации. С его помощью клиент может динамически вызывать методы объекта без строгой типизации — удобно для скриптовых языков типа VBA и JScript.
Фишка в том, что IDispatch прекрасно работает даже через границы процессов и уровней привилегий. Хотя чаще его используют внутри одного процесса, например в компонентах ActiveX. Но если такой объект вдруг окажется доступен в удаленном взаимодействии, можно ожидать веселых последствий...
Чтобы клиент мог динамически вызывать методы объекта COM, сервер должен как‑то сообщить ему, какие параметры передавать и как их упаковывать. Для этого используется type library — файл с описанием типов, который хранится на диске. Клиент может запросить эту информацию через метод GetTypeInfo интерфейса IDispatch.
Теперь начинается самое интересное. В COM интерфейс для работы с библиотекой типов маршализуется по ссылке. То есть, когда клиент получает ITypeInfo, он на самом деле управляет объектом, который остается на сервере. А это значит, что все вызовы методов на этом интерфейсе выполняются в контексте сервера.
Если этот механизм использовать хитро, можно заставить сервер делать вещи, о которых его разработчики и не подозревали.
Интерфейс ITypeInfo открывает две заманчивые дверцы для атакующего: методы Invoke и CreateInstance.
С Invoke облом — он не поддерживает удаленный вызов, потому что требует загрузки type library в текущий процесс. А вот CreateInstance
— совсем другая история. Он вполне ремотабельный и вызывает CoCreateInstance
для создания COM-объекта по CLSID.
И тут самое вкусное: создаваемый объект будет жить в серверном процессе, а не у клиента. То есть, если атакующий провернет этот трюк, он сможет заспаунить на сервере любой COM-объект, который ему нужен. Дальше — дело техники.
Но стоп, если заглянуть в документацию CreateInstance, там нигде не видно параметра CLSID. Так как же тогда type library понимает, какой объект создать?
Фишка в том, что ITypeInfo
описывает любой тип, который есть в type library. Когда клиент вызывает GetTypeInfo
, он получает только информацию об интерфейсе, который хочет использовать. Если потом попробовать дернуть CreateInstance
, ничего не выйдет — просто ошибка.
Но тут на сцену выходит CoClass. В type library можно хранить не только интерфейсы, но и CoClass-объекты, а вот они уже содержат CLSID нужного COM-объекта. И если CreateInstance
вызывается на CoClass, все срабатывает: создается объект с заданным CLSID прямо внутри серверного процесса.
Как же нам превратить объект с информацией об интерфейсе в объект, представляющий класс? Все просто: у ITypeInfo
есть метод GetContainingTypeLib
, который возвращает ссылку на ITypeLib
— интерфейс библиотеки типов. А уже через него можно перебрать все классы, определенные в type library.
И вот тут начинается веселье: среди этих классов может оказаться что‑то совершенно небезопасное при удаленном использовании. Если такой объект можно создать удаленно, это открывает дорогу к повышению привилегий или даже запуску кода на сервере.
Давай разберем это на практике! Сначала с помощью моего PowerShell-модуля OleView.NET найдем подходящие COM-сервисы, которые поддерживают IDispatch
. Если среди них окажется что‑то уязвимое... Ну, ты знаешь, что делать!
PS>
PS>
PS>
Select Name, Clsid
Name Clsid----
WaaSRemediation 72566e27-1abb-4eb3-b4f0-eb431cb1cb32
Search Gathering Manager 9e175b68-f52a-11d8-b9a5-505054503030
Search Gatherer Notification 9e175b6d-f52a-11d8-b9a5-505054503030
AutomaticUpdates bfe18e9c-6d87-4450-b37c-e02f0b373803
Microsoft.SyncShare.SyncShareFactory Class da1c0281-456b-4f14-a46d-8ed2e21a866f
Флаг -Service
в Get-ComClass
позволяет вытащить COM-классы, которые работают в локальных сервисах. Дальше мы запрашиваем у каждого класса список поддерживаемых интерфейсов. Вывод этой команды нам не нужен — вся инфа уже сохраняется в свойстве Interfaces
.
Теперь самое интересное: отфильтруем только те классы, которые поддерживают IDispatch
. В итоге у нас остается пять кандидатов.
Возьмем первого — WaaSRemediation
— и заглянем в его type library. Кто знает, может, там затаилось что‑то интересное...
PS>
PS>
PS>
ProcessId ProcessName---------
27020 svchost.exePS>
PS>
Name Version TypeLibId----
WaaSRemediationLib 1.0 3ff1aab8-f3d8-11d4-825d-00104b3646c0PS>
Name Uuid----
WaaSRemediationAgent 72566e27-1abb-4eb3-b4f0-eb431cb1cb32
WaaSProtectedSettingsProvider 9ea82395-e31b-41ca-8df7-ec1cee7194df
Скрипт сначала создает COM-объект, а затем с помощью Import-ComTypeLib
получает интерфейс библиотеки типов.
Чтобы убедиться, что объект реально работает вне процесса, мы маршализуем его через Get-ComObjRef
и достаем инфу о процессе. Ожидаемо объект живет внутри svchost.
— общего исполняемого файла для сервисов Windows.
Разбираться с type library вручную — то еще удовольствие, поэтому упрощаем себе жизнь: используем Parse
, чтобы превратить ее в удобную объектную модель. Теперь можно спокойно вывалить список классов и посмотреть, есть ли среди них что‑нибудь интересное (и опасное).
Облом! В случае с этим COM-объектом все классы в type library уже зарегистрированы для работы в сервисе, так что ничего нового мы не получили.
Но не беда — нам нужен другой вариант: класс, который зарегистрирован только для работы в локальном процессе, но при этом описан в type library удаленного сервиса.
Такое бывает, потому что type library может использоваться одновременно и для локальных (in-process) компонентов, и для сервисов, работающих вне процесса. Если мы найдем такой класс, то сможем заставить удаленный сервис создать его у себя — и тут уже возможны очень интересные сценарии.
Четыре оставшихся COM-класса тоже не дали ничего полезного (один вообще оказался криво зарегистрирован и даже не экспортируется сервисом). На этом можно было бы сдаться... Но нет!
Дело в том, что type library может ссылаться на другие библиотеки типов, а их тоже можно просканировать с помощью тех же интерфейсов. Так что скрытые классы все‑таки есть — просто они не лежат на поверхности.
Давай копнем глубже и посмотрим, что откопается.
PS>
Name Version TypeLibId----
stdole 2.0 00020430-0000-0000-c000-000000000046PS>
Name Uuid----
StdFont 0be35203-8f91-11ce-9de3-00aa004bb851
StdPicture 0be35204-8f91-11ce-9de3-00aa004bb851PS>
PS>
Key Value
--- -----
InProcServer32 C:\Windows\System32\oleaut32.dll
В этом примере мы используем свойство ReferencedTypeLibs
, чтобы посмотреть, какие библиотеки подтягиваются автоматически при разборе type library. В нашем случае видим единственную запись — stdole
, которая почти всегда импортируется.
Но если повезет, можно найти и другие подключенные библиотеки, которые стоит исследовать. Давай разберем stdole
и посмотрим, какие классы она экспортирует. Выясняется, что там всего два класса и один из них — StdFont
.
Если посмотреть, где этот класс может быть создан, то окажется, что он зарегистрирован только для работы в локальном процессе. Отлично! Это наша цель для поиска уязвимостей.
Теперь, чтобы получить удаленный интерфейс к stdole
, нам нужен тип, который на нее ссылается. Причина проста: базовые интерфейсы типа IUnknown
и IDispatch
определены именно в этой библиотеке, а значит, нужно найти объект, который их использует.
Погнали, попробуем создать объект StdFont
прямо в удаленном COM-сервисе!
PS>
PS>
PS>
PS>
PS>
PS>
Name Version TypeLibId----
stdole 2.0 00020430-0000-0000-c000-000000000046PS>
PS>
PS>
ProcessId ProcessName---------
27020 svchost.exePS>
Name IID HasProxy HasTypeLib----
...
IFont bef6e002-a874-101a-8bba-00aa00300cab True False
IFontDisp bef6e003-a874-101a-8bba-00aa00300cab True True
Чтобы добраться до нужной библиотеки, мы используем хитрую комбинацию методов: GetRefTypeOfImplType и GetRefTypeInfo. Они позволяют вытащить базовый тип у существующего интерфейса. Затем с помощью GetContainingTypeLib
получаем интерфейс подключенной библиотеки типов.
Разбираем ее и убеждаемся, что это действительно stdole
. Все идет по плану. Теперь достаем type info для класса StdFont
и вызываем CreateInstance
.
Дальше самое важное: проверяем, где же создался объект. И бинго! Он заперт внутри процесса сервиса. В качестве финальной проверки запрашиваем его интерфейсы — и точно, это объект шрифта. Осталось понять, как заставить его делать что‑нибудь интересное.
Нам нужно найти способ эксплуатировать один из этих классов. Но есть проблема: доступным остается только StdFont
. Второй класс, StdPicture
, имеет встроенную проверку, которая блокирует его использование вне процесса.
Я не нашел явных уязвимостей в StdFont
, но, честно говоря, не копал слишком глубоко. Так что если у кого‑то чешутся руки — дерзайте! Вполне возможно, там прячется какой‑нибудь баг, который можно использовать для повышения привилегий или удаленного выполнения кода.
Исследование системы сервисов зашло в тупик — по крайней мере, в плане атак на системные COM-сервисы. Возможно, есть COM-сервер, доступный из песочницы, но беглый анализ AppContainer не дал очевидных целей.
Однако, немного поразмыслив, я понял: этот метод все еще может быть полезен — для внедрения кода в процесс с тем же уровнем привилегий.
Как? Очень просто: перехватываем COM-регистрацию для StdFont
! Достаточно поменять его CLSID в реестре, используя ключ TreatAs, и указать на любой другой класс, который можно эксплуатировать. Например, загружаем движок JScript в целевой процесс и исполняем произвольный скрипт.
Получается элегантный способ внедрить код в доверенный процесс, даже если нет прямых уязвимостей.
Я обычно не публикую техники инъекций — это уже ближе к теме малвари. Но есть один крайне интересный сценарий, где такой трюк может иметь серьезные последствия для безопасности.
Что, если с его помощью можно внедриться в Windows Protected Process?
По иронии только что разобранный нами WaaSRemediationAgent
может оказаться идеальным пропуском в защищенный процесс. Давай разберемся, как это можно провернуть.
PS>
PS>
WindowsLight
Когда мы проверили уровень защиты хостового сервиса, оказалось, что он работает на уровне PPL-Windows (Protected Process Light — Windows)!
Это значит, что сервис запущен в защищенном режиме и просто так в него не влезть. Но если мы сможем как‑то использовать наши находки, то получится внедрить код в PPL-процесс, что открывает очень интересные возможности.
Давай попробуем выжать максимум из этого исследования!
Инъекция в защищенный процесс
Я уже писал (и рассказывал на конференциях) про инъекции в Windows Protected Processes. Если хочешь разобраться глубже, советую перечитать тот пост — там подробно разбираются предыдущие атаки.
Но вот ключевой момент: в Microsoft не считают PPL жесткой границей безопасности. Это значит, что такие уязвимости обычно не закрываются срочными патчами. Вместо этого их могут исправить только в новой версии Windows, если вообще обратят на них внимание. А это открывает интересные возможности для атак.
Идея до безобразия проста: мы подменяем регистрацию класса StdFont
, чтобы он указывал на другой класс. Когда мы создаем его через type library, объект будет исполняться в защищенном процессе (PPL).
Почему StdFont
? Он универсален. Даже если WaaSRemediationAgent
исчезнет в будущих обновлениях, этот метод все равно будет работать с другим COM-сервером.
Осталось только найти подходящий класс, который позволит нам запустить произвольный код и не сломается внутри PPL. Если такой найдется, тогда это уже не просто уязвимость, а шикарный механизм обхода защиты Windows.
К сожалению, скриптовые движки типа JScript сразу отпадают. Если перечитать мой предыдущий пост, узнаешь, что Code Integrity блокирует их загрузку в Protected Process. Так что этот путь закрыт.
Значит, нужен класс, который, во‑первых, доступен вне процесса, во‑вторых, разрешен для загрузки в защищенный процесс.
Тут у меня появилась идея: .NET DCOM!
Я уже писал, что .NET-компоненты в DCOM — это дырявая конструкция, которой не стоит пользоваться. Но в этом случае нам как раз нужна вся эта кривизна! Если мы сможем загрузить класс .NET COM внутрь PPL, то получим мощный вектор для выполнения произвольного кода.
Дальше — ищем нужный класс .NET COM и запускаем веселье.
Я уже как‑то рассказывал про атаки через сериализацию в .NET, но оказалось, что есть вариант гораздо проще и мощнее.
Фишка в том, что через DCOM можно создать объект System.Type. А имея доступ к объекту Type
, можно делать произвольные рефлексивные вызовы — вызываем любые методы, загружаем любые классы.
А знаешь, какой самый жирный трюк? Загрузка сборки прямо из массива байтов! Это обходит проверку цифровой подписи и позволяет загружать любой код в защищенный процесс (PPL).
Итог: полный контроль над защищенным процессом без эксплоитов в самом PPL. Просто хакерская магия с DCOM и .NET.
Да, в Microsoft закрыли эту дыру, но оставили бэкдор — параметр AllowDCOMReflection
. Если его включить, старая уязвимость возвращается, как ни в чем не бывало.
Поскольку мы не повышаем привилегии, а уже работаем с админскими правами (иначе нельзя изменить регистрацию COM-класса), то просто записываем в реестр
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETFramework\AllowDCOMReflection = 1 (DWORD)
И загружаем .NET Framework внутрь защищенного процесса (PPL).
Чтобы успешно внедрить код в защищенный процесс, делаем следующее:
- Включаем DCOM Reflection в реестре.
- Подменяем COM-регистрацию
StdFont
, добавляя ключTreatAs
, чтобы он указывал наSystem.
.Object - Создаем объект
WaaSRemediationAgent
, чтобы работать через его type library. - Получаем
TypeInfo
для классаStdFont
через type library. - Создаем объект
StdFont
черезCreateInstance
, но реально это загрузит .NET Framework и вернет нам экземплярSystem.
.Object - Используем .NET Reflection для вызова System.Reflection.Assembly::Load(byte[]), чтобы загрузить кастомную сборку без проверки подписи.
- Создаем объект из загруженной сборки, чтобы сразу запустить произвольный код в защищенном процессе (PPL).
- Чистим реестр, убираем изменения, чтобы замести следы.
Итог: мы получили полный контроль над защищенным процессом Windows, используя хитрую комбинацию DCOM, .NET Reflection и COM-редиректа.
Важно написать этот эксплоит не на .NET-языке, иначе механизмы сериализации пересоздадут reflection-объекты в вызывающем процессе и инъекция не сработает. Оптимальный вариант — C++, но можно попробовать Python с pywin32 или ctypes. В своем PoC я использовал C++, и он схож с эксплоитом, который я писал для CVE-2014-0257, где подробно показано, как использовать DCOM Reflection.
Еще один нюанс — по умолчанию объекты .NET COM запускаются через .NET Framework v2, который больше не устанавливается в новых версиях Windows. Можно либо настроить запуск через .NET v4, что сложнее, либо просто установить v2 через Windows Components Installer, как сделал я. В итоге получаем C++ PoC, который позволяет выполнить произвольный код в защищенном процессе Windows, используя комбинацию DCOM, .NET Reflection и манипуляций с COM-регистрацией.
Мой PoC сработал с первого раза на Windows 10, но, к сожалению, на Windows 11 24H2 затея провалилась. Мне удалось создать .NET-объект, но при вызове любого метода возникала ошибка TYPE_E_CANTLOADLIBRARY
.
Можно было бы на этом остановиться, ведь сам факт уязвимости уже доказан, но мне стало интересно, что именно ломается на Windows 11. Давай разберемся, почему это происходит, и посмотрим, можно ли что‑то сделать, чтобы обойти это ограничение и заставить PoC работать на последней версии Windows.
Проблема с Windows 11
Я смог доказать, что ошибка связана именно с защищенными процессами (PPL). Если изменить настройки сервиса и запустить его без защиты, PoC снова начинает работать. Значит, что‑то конкретно блокирует загрузку библиотек в защищенном режиме.
При этом type libraries в целом загружаются нормально, например stdole
работает без проблем. Значит, ограничение касается именно .NET, а не всех COM-библиотек. Теперь нужно разобраться, что именно программисты Microsoft поменяли в обработке .NET внутри PPL на Windows 11.
Анализ работы PoC с Process Monitor показал, что во время выполнения загружается библиотека mscorlib.
. Она используется для создания stub-класса на сервере. Однако по какой‑то причине загрузка не срабатывает, из‑за чего stub не создается, а это, в свою очередь, ломает любые вызовы к объекту.
Тут у меня появилась догадка. В одном из прошлых постов я рассказывал, как атаковать процесс NGEN COM: модификация type library приводила к type-confusion, что позволяло перезаписать хендл KnownDlls и подгрузить произвольную DLL в память.
Но в Microsoft в последние годы серьезно занялись защитой KnownDlls — об этом писал Клемент Лабро и другие исследователи. Я заподозрил, что Windows 11 получила еще одно исправление, блокирующее атаку через подмену type library.
Теперь надо выяснить, как именно это ограничение работает и можно ли его обойти.
Погрузившись в oleaut32.
, я нашел фикс, который нам мешает. Это метод VerifyTrust
. Вот как он выглядит:
NTSTATUS VerifyTrust(LoadInfo *load_info) { PS_PROTECTION protection; BOOL is_protected; CheckProtectedProcessForHardening(&is_protected, &protection); if (!is_protected) return SUCCESS; ULONG flags; BYTE level; HANDLE handle = load_info->Handle; NTSTATUS status = NtGetCachedSigningLevel(handle, &flags, &level, NULL, NULL, NULL); if (FAILED(status) || (flags & 0x182) == 0 || FAILED(NtCompareSigningLevels(level, 12))) { status = NtSetCachedSigningLevel(0x804, 12, &handle, 1, handle); } return status;}
Этот метод вызывается во время загрузки type library и проверяет уровень подписи файла (signing level). Это снова связано с механизмом кешированного уровня подписи.
Если у файла нет уровня подписи 12 (Windows Signing Level), код пытается принудительно установить его через NtSetCachedSigningLevel
. Если это не удается, система считает, что файл нельзя загрузить в защищенный процесс, и выдает ошибку. В итоге type library не загружается и PoC ломается.
Кстати, похожий фикс блокирует атаку через Running Object Table, где можно было ссылаться на out-of-process type library. Но в данном случае нас интересует именно защита PPL. Теперь вопрос: можно ли как‑то обойти проверку подписи и заставить Windows загрузить нужную библиотеку?
Судя по выводу Get-AuthenticodeSignature
, файл mscorlib.
действительно подписан, хоть и каталожной подписью. Сертификат — Microsoft Windows Production PCA 2011, тот же самый, что и у .NET Runtime DLL. Логично, что у него должен быть Windows Signing Level, так что что‑то тут не сходится.
Попробуем обойти систему! Достанем мой NtObjectManager для PowerShell и вручную установим кешированный уровень подписи. Может, это даст какие‑то подсказки, что именно идет не так. Если получится подделать подпись — дорога к инъекции снова открыта.
PS>
PS>
Exception
%hs
original
PS>
00
-----------------------------------------------------------------------------
00000000:
00000010:
00000020:
00000030:
Попытка вручную установить уровень подписи обломилась с ошибкой STATUS_INVALID_IMAGE_FORMAT
. Заглядываем в первые 64 байта mscorlib.
и обнаруживаем, что это сырая type library, а не PE-файл. А вот это уже проблема.
Обычно, даже если файл имеет расширение .TLB, он все равно запакован в PE как ресурс. Но тут, похоже, не тот случай. Windows, судя по всему, просто не умеет устанавливать кешированный уровень подписи файлам, которые не являются PE-образами.
Короче говоря, если мы не сможем заставить Windows поверить, что этот файл подписан, то он не загрузится в защищенный процесс. А без него мы не сможем создать stub-класс и вызвать .NET-интерфейсы через DCOM.
Не повезло! Но всегда есть обходные пути. Вопрос только в том, какой из них сработает.
Забавный момент: у меня есть виртуалка с Windows 11, где та же самая type library в не DLL-формате все же принимает кешированный уровень подписи. То есть в одном окружении Windows блокирует загрузку, а в другом — спокойно ее пропускает.
Очевидно, я каким‑то образом изменил конфигурацию этой VM, что позволило системе принимать подпись даже для raw type library. Но вот что именно я поменял — понятия не имею.
Рыться дальше мне, если честно, уже лень. Просто фиксирую факт: если бы удалось повторить эту магию на боевой системе, то защита снова бы сломалась.
Можно было бы попытаться найти старую версию type library, которая и подписана правильно, и запакована в PE, но ковыряться в архивах не хочется.
Конечно, наверняка есть и другой COM-объект, который можно загрузить вместо .NET и получить удаленное выполнение кода. Но мне принципиально хотелось добить именно этот способ.
В итоге решение оказалось проще, чем я думал. По какой‑то причине 32-битная версия type library (которая лежит в Framework
, а не Framework64
) упакована в DLL. А раз так, на нее можно установить кешированный уровень подписи — и вуаля, защита больше не мешает!
PS>
PS>
00
-----------------------------------------------------------------------------
00000000:
00000010:
00000020:
00000030:
PS>
PS>
Flags :
SigningLevel :
Thumbprint :
ThumbprintBytes : {
ThumbprintAlgorithm :
Значит, чтобы провернуть атаку на Windows 11 24H2, просто меняем путь в реестре для регистрации type library — вместо 64-битной версии подсовываем 32-битную. После этого запускаем эксплоит заново.
Функция VerifyTrust
сама автоматически установит кешированный уровень подписи, так что никаких дополнительных танцев с бубном реестром или подписыванием не требуется — все просто работает.
Хотя формально это другая версия type library, на практике это ничего не меняет. Генератор stub-классов продолжает работать без проблем, а значит, наша атака остается полностью рабочей. Ребята из Microsoft, хотите закрыть лазейку? Попробуйте еще раз.
Выводы
В этом исследовании я разобрал интересный класс уязвимостей в Windows, который, впрочем, актуален для любого объектно ориентированного механизма удаленного взаимодействия между процессами. Мы увидели, как можно запереть COM-объект в более привилегированном процессе, используя особенности OLE Automation, в частности интерфейс IDispatch и библиотеки типов.
Этот трюк позволяет обходить ограничения, создавая объекты внутри защищенных процессов и затем управляя ими удаленно. Это не просто локальный баг, а целый класс атак, который Microsoft закрывает уже не в первый раз... но мы снова нашли лазейку.
Хотя мне не удалось продемонстрировать повышение привилегий, я показал, как можно использовать интерфейс IDispatch в классе WaaSRemediationAgent
, чтобы внедрить код в процесс с уровнем защиты PPL-Windows. Это, конечно, не максимальный уровень защиты, но дает доступ к большинству защищенных процессов, включая LSASS.
Мы увидели, что в Microsoft действительно стараются закрывать возможности для атак, например через подмену type library, но в нашем случае их защита не должна была мешать — ведь мы не изменяли саму type library.
Да, этот конкретный эксплоит требует администраторских привилегий. Но сама техника в общем случае не зависит от прав администратора. Если удастся найти подходящий COM-сервер, который экспортирует IDispatch, можно провести атаку от имени обычного пользователя, просто изменив локальную регистрацию COM и .NET. Вопрос лишь в том, какие сервисы еще остались открытыми для этой техники.