В моем предыдущем блоге я обсуждал технику, которая объединила в себе многочисленные проблемы, о которых я ранее сообщал в Microsoft, для внедрения произвольного кода в процесс PPL-WindowsTCB. Представленные методы не подходят для использования старых, более сильных защищенных процессов (PP) по нескольким причинам. Этот блог пытается исправить это упущение и предоставить подробную информацию о том, как я смог захватить полный процесс PP-WindowsTCB, не требуя прав администратора. Это в основном академическое упражнение, чтобы увидеть, смогу ли я заставить код выполняться в полном PP.
В качестве краткого обзора предыдущей атаки я смог идентифицировать процесс, который будет работать как PPL, который также предоставил COM-сервис. В частности, это была "Служба оптимизации времени выполнения .NET", которая поставляется с платформой .NET и использует PPL на уровне CodeGen для применения уровней кэшированной подписи к заранее скомпилированным библиотекам DLL, чтобы их можно было использовать с целостностью кода пользовательского режима. (UMCI). Изменив конфигурацию прокси-сервера COM, можно было вызвать путаницу в типах, что позволило мне загрузить произвольную DLL путем перехвата конфигурации KnownDlls. Запустив код внутри PPL, я мог злоупотребить ошибкой в функции кэшированной подписи, чтобы создать DLL, подписанную для загрузки в любой PPL, и через это перейти на уровень PPL-WindowsTCB.
В поисках новой цели
Моей первой мыслью об использовании полного PP было бы использование дополнительного доступа, который нам предоставил запуск кода в PPL-WindowsTCB. Вы можете предположить, что можете злоупотребить кэшированной подписанной DLL, чтобы обойти проверки безопасности для загрузки в полный PP. К сожалению, модуль целостности кода ядра игнорирует кешированные уровни подписи для полного PP. Как насчет KnownDlls в целом? Если у нас есть права администратора и код, работающий в PPL-WindowsTCB, мы можем напрямую писать в каталог объектов KnownDlls и попытаться заставить PP загружать произвольную DLL. К сожалению, как я уже упоминал в предыдущем блоге, это тоже не работает, поскольку полный PP игнорирует KnownDll. Даже если он загрузил KnownDll, я не хочу требовать прав администратора для внедрения кода в процесс.
Я решил, что имеет смысл перезапустить мой сценарий PowerShell из предыдущего блога, чтобы узнать, какие исполняемые файлы будут работать как полный PP и на каком уровне. В Windows 10 1803 имеется значительное количество исполняемых файлов, которые работают на уровне PP-Authenticode, однако только четыре исполняемых файла будут запускаться с более привилегированным уровнем, как показано в следующей таблице.
Поскольку у меня нет известного маршрута от уровня PP-Windows до уровня PP-WindowsTCB, как у меня с PPL, интерес представляют только два из четырех исполняемых файлов, WerFaultSecure.exe и SgrmBroker.exe. Я сопоставил эти два исполняемых файла с известными регистрациями служб COM, которые не дали никаких результатов. Это не означает, что эти исполняемые файлы не открывают поверхность для атаки COM, исполняемый файл .NET, которым я злоупотреблял в прошлый раз, также не регистрирует свою службу COM, поэтому я также выполнил некоторые базовый реверсинг в поисках использования COM.
Исполняемый файл SgrmBroker ничего не делает, он представляет собой оболочку вокруг изолированного приложения в пользовательском режиме для реализации аттестации системы во время выполнения как часть System Guard в Защитнике Windows и не вызывает никаких COM API. WerFaultSecure также, похоже, не вызывает COM, однако я уже знал, что WerFaultSecure может загружать COM-объекты, поскольку Алекс Ионеску использовал мою исходную атаку выполнения кода сценария COM, чтобы получить уровень PPL-WindowsTCB, перехватив загрузку COM-объекта в WerFaultSecure. Даже несмотря на то, что WerFaultSecure не предоставлял сервис, если он мог инициализировать COM, возможно, есть что-то, чем я мог бы злоупотребить, чтобы получить выполнение произвольного кода? Чтобы понять поверхность атаки COM, нам необходимо понять, как COM реализует внепроцессные COM-серверы и удаленное взаимодействие COM в целом.
Копаемся во внутренностях COM Remoting
Связь между COM-клиентом и COM-сервером осуществляется по протоколу MSRPC, который основан на протоколе DCE/RPC Open Group. Для локального обмена данными используются порты Advanced Local Procedure Call (ALPC). На высоком уровне связь происходит между клиентом и сервером согласно следующей схеме:
Чтобы клиент мог найти местоположение сервера, процесс регистрирует конечную точку ALPC в активаторе DCOM в RPCSS [1]. Эта конечная точка регистрируется вместе с идентификатором экспортера объектов (OXID) сервера, который представляет собой 64- битное случайно сгенерированное число, присвоенное RPCSS. Когда клиент хочет подключиться к серверу, он должен сначала попросить RPCSS преобразовать значение OXID сервера в конечную точку RPC [2]. Зная конечную точку ALPC RPC, клиент может подключаться к серверу и вызывать методы COM-объекта [3].
Значение OXID обнаруживается либо из результата активации COM вне процесса (OOP), либо через структуру маршалированной ссылки на объект (OBJREF).
Под капотом клиент вызывает метод ResolveOxid в интерфейсе RPC IObjectExporter RPCSS. Прототип ResolveOxid выглядит следующим образом:
В прототипе мы видим, что OXID для разрешения передается в параметре pOxid, и сервер возвращает массив Dual String Bindings, которые представляют конечные точки RPC для подключения для этого значения OXID. Сервер также возвращает две другие части информации: подсказку об уровне аутентификации (pAuthnHint), которую мы можем игнорировать, и IPID интерфейса IRemUnknown (pipidRemUnknown).
IPID - это значение GUID, называемое идентификатором процесса интерфейса. Он представляет собой уникальный идентификатор для COM-интерфейса внутри сервера, и он необходим для связи с правильным COM-объектом, поскольку он позволяет одной конечной точке RPC мультиплексировать несколько интерфейсов через одно соединение. Интерфейс IRemUnknown - это COM-интерфейс по умолчанию, который должен реализовать каждый COM-сервер, поскольку он используется для запроса новых IPID для существующего объекта (с использованием RemQueryInterface) и поддержания счетчика ссылок удаленного объекта (с помощью методов RemAddRef и RemRelease). Поскольку этот интерфейс должен существовать всегда, независимо от того, экспортируется ли фактический COM-сервер, а IPID может быть обнаружен путем разрешения OXID сервера, мне было интересно, какие другие методы поддерживает интерфейс на случай, если есть что-то, что я мог бы использовать для выполнения кода.
Код времени выполнения COM поддерживает базу данных всех IPID, поскольку ему нужно искать объект сервера, когда он получает запрос на вызов метода. Если мы знаем структуру этой базы данных, мы могли бы обнаружить, где реализован интерфейс IRemUnknown, проанализировать его методы и выяснить, какие другие функции он поддерживает. К счастью, я выполнил работу по реверсингу формата базы данных в моем инструменте OleViewDotNet, в частности, с помощью команды Get-ComProcess в модуле PowerShell. Если мы запустим команду против процесса, который использует COM, но на самом деле не реализует COM-сервер (например, блокнот), мы можем попытаться определить правильный IPID.
В этом примере снимка экрана фактически экспортированы два IPID: IRundown и интерфейс Windows.Foundation. Интерфейс Windows.Foundation можно смело игнорировать, но IRundown выглядит интереснее. Фактически, если вы выполните ту же проверку для любого COM-процесса, вы обнаружите, что у них также есть экспортированные интерфейсы Irundown. Но разве мы не ожидаем интерфейса IRemUnknown? Если мы передадим параметры ResolveMethodNames и ParseStubMethods в Get-ComProcess, команда попытается проанализировать параметры метода для интерфейса и имен поиска на основе общедоступных символов. С помощью проанализированных данных интерфейса мы можем передать объект IPID команде Format-ComProxy, чтобы получить базовое текстовое представление интерфейса Irundown. После очистки интерфейс IRundown выглядит следующим образом:
Этот интерфейс является надмножеством IRemUnknown, он реализует такие методы, как RemQueryInterface, а затем добавляет еще несколько дополнительных методов для хорошей оценки. Что меня действительно интересовало, так это методы DoCallback и DoNonreentrantCallback, они звучат так, как будто могут выполнить какой-то обратный вызов. Может быть, мы можем злоупотреблять этими методами? Давайте посмотрим на реализацию DoCallback на основе небольшого количества RE (DoNonreentrantCallback просто делегирует DoCallback внутренне, поэтому нам не нужно обрабатывать его специально):
Этот метод очень интересен, он принимает структуру, содержащую указатель на вызываемый метод и произвольный параметр, и выполняет указатель. Единственные ограничения на вызов произвольного метода - вы должны заранее знать случайно сгенерированное значение GUID, секрет процесса и адрес контекста сервера. Проверка случайного значения для каждого процесса является обычным шаблоном безопасности в COM API и обычно используется для ограничения функциональности только внутрипроцессными вызывающими объектами. Я злоупотреблял чем-то похожим в Free-Threaded Marshaler еще в 2014 году.
Какова цель DoCallback? Среда выполнения COM создает новый интерфейс IRundown для каждой инициализированной COM среды. Это на самом деле важно, поскольку для вызова методов между апартаментами, скажем, для вызова объекта STA из MTA, вам необходимо вызвать соответствующие методы IRemUnknown в правильной комнате. Поэтому, пока разработчики были там, они добавили еще несколько методов, которые были бы полезны для вызова между квартирами, в том числе общий метод "вызовите что угодно". Это используется внутренними компонентами среды выполнения COM и предоставляется косвенно через такие методы, как CoCreateObjectInContext. Чтобы предотвратить злоупотребление методом DoCallback ООП, проверяется секрет каждого процесса, что должно ограничивать его только вызывающими внутри процесса, если только внешний процесс не может прочитать секрет из памяти.
Злоупотребление DoCallback
У нас есть примитив для выполнения произвольного кода в любом процессе, инициализировавшем COM, путем вызова метода DoCallback, который должен включать PP. Для успешного вызова произвольного кода нам необходимо знать четыре части информации:
Получить порт ALPC и IPID легко, если процесс предоставляет COM-сервер, так как оба будут предоставлены во время разрешения OXID. К сожалению, WerFaultSecure не предоставляет доступ к COM-объекту, который мы можем создать, чтобы этот угол был закрыт для нас, оставляя нас перед проблемой, которую мы должны решить. Извлечение секрета процесса и значения контекста требует чтения содержимого памяти процесса. Это еще одна проблема, одна из преднамеренных функций безопасности PP предотвращает чтение памяти процессом PP процессом, отличным от PP. Как мы собираемся решить эти две проблемы?
Обсуждая это с Алексом из Recon, мы придумали возможную атаку, если у вас есть права администратора. Даже будучи администратором, вы не можете читать память непосредственно из процесса PP. Мы могли бы загрузить драйвер, но это полностью сломало бы PP, поэтому мы решили, как это сделать, не выполняя код ядра.
Первое и самое простое, порт ALPC и IPID могут быть извлечены из RPCSS. Служба RPCSS не запускается с защитой (даже PPL), поэтому можно обойтись без каких-либо хитрых уловок, кроме знания того, где значения хранятся в памяти. Для указателя контекста мы должны иметь возможность перебрать местоположение, поскольку, вероятно, будет только узкий диапазон ячеек памяти для тестирования, что немного упростит, если мы будем использовать 32-разрядную версию WerFaultSecure.
Извлечь секрет несколько сложнее. Секрет инициализируется в доступной для записи памяти и, следовательно, попадает в рабочий набор процесса после его изменения. Поскольку страница не заблокирована, она будет доступна для разбиения на страницы, если условия памяти правильные. Следовательно, если бы мы могли принудительно выгрузить страницу, содержащую секрет, на диск, мы могли бы прочитать ее, даже если она была получена из процесса PP. Как администратор, мы можем выполнить следующие действия, чтобы украсть секрет:
После того, как я придумал эту атаку, мне показалось, что это слишком тяжелая работа и требуются права администратора, которых я хотел избежать. Мне нужно было найти альтернативное решение.
Использование WerFaultSecure по прямому назначению
До этого момента я обсуждал WerFaultSecure как процесс, которым можно злоупотреблять для запуска произвольного кода внутри PP/PPL. Я не совсем объяснил, почему процесс может выполняться на максимальных уровнях PP/PPL. WerFaultSecure используется службой отчетов об ошибках Windows для создания аварийных дампов защищенных процессов. Поэтому он должен работать с повышенными уровнями PP, чтобы гарантировать, что он может сбрасывать любой возможный PP в пользовательском режиме. Почему мы не можем просто заставить WerFaultSecure создать аварийный дамп, который приведет к утечке содержимого памяти процесса и позволит нам извлечь любую необходимую нам информацию?
Причина, по которой мы не можем использовать WerFaultSecure, заключается в том, что он шифрует содержимое аварийного дампа перед записью его на диск. Шифрование выполняется таким образом, чтобы позволить Microsoft только расшифровать аварийный дамп, используя асимметричное шифрование для защиты случайного сеансового ключа, который может быть предоставлен веб-службе Microsoft WER. За исключением слабых мест в реализации Microsoft или новой криптографической атаки на используемые примитивы, получение зашифрованных данных кажется неудачным.
Однако так было не всегда. В 2014 году Алекс представил на NoSuchCon PPL и рассказал об обнаруженной им ошибке в том, как WerFaultSecure создает зашифрованные файлы дампа. Он использовал двухэтапный процесс: сначала он записал аварийный дамп в незашифрованном виде, а затем зашифровал аварийный дамп. Возможно, вы заметите недостаток? Удалось украсть незашифрованный аварийный дамп. Из-за того, как WerFaultSecure назывался, он принимал два дескриптора файлов, один для незашифрованного дампа и один для зашифрованного дампа. При прямом вызове WerFaultSecure незашифрованный дамп никогда не будет удален, а это означает, что вам даже не нужно гонять процесс шифрования.
Есть одна проблема, она была исправлена в 2015 году в MS15-006. После этого исправления WerFaultSecure зашифровал аварийный дамп напрямую, он никогда не попадает на диск в незашифрованном виде. Но это заставило меня задуматься, хотя они могли исправить ошибку в будущем, что мешает нам взять старую уязвимую версию WerFaultSecure из Windows 8.1 и запустить ее в Windows 10? Я загрузил ISO для Windows 8.1 с веб-сайта Microsoft (ссылка), извлек двоичный файл и протестировал его с предсказуемыми результатами:
Мы можем взять уязвимую версию WerFaultSecure из Windows 8.1, и она будет вполне успешно работать в Windows 10 на уровне PP-WindowsTCB. Почему? Неясно, но из-за способа защиты PP все доверие основано на подписанном исполняемом файле. Поскольку подпись исполняемого файла все еще действительна, ОС просто верит, что ее можно запустить на запрошенном уровне защиты. Предположительно должен быть какой-то способ, которым Microsoft может блокировать определенные исполняемые файлы, хотя, по крайней мере, они не могут просто отозвать свои собственные сертификаты подписи. Возможно, двоичные файлы ОС должны иметь в сертификате EKU, указывающее, для какой версии они предназначены? В конце концов, Microsoft уже добавила новый EKU при переходе с Windows 8 на 8.1, чтобы блокировать атаки перехода на более раннюю версию для обхода подписи WinRT UMCI, поэтому обобщение может иметь некоторый смысл, особенно для определенных уровней PP.
После небольшого количества RE и ссылки на презентацию Алекса я смог выработать различные параметры, которые мне нужно было передать процессу WerFaultSecure для выполнения дампа PP:
Это дает нам все необходимое для завершения эксплойта. Нам не нужны права администратора, чтобы запустить старую версию WerFaultSecure как PP-WindowsTCB. Мы можем заставить его сделать дамп еще одной копии WerFaultSecure с инициализированным COM и использовать аварийный дамп для извлечения всей необходимой нам информации, включая порт ALPC и IPID, необходимые для связи. Нам не нужно писать собственный синтаксический анализатор аварийного дампа, так как можно использовать API Debug Engine, который поставляется вместе с Windows. Как только мы извлекли всю необходимую информацию, мы можем вызвать DoCallback и вызвать произвольный код.
Собираем все вместе
Для завершения эксплойта нам еще нужны две вещи: как заставить WerFaultSecure запустить COM и что мы можем вызвать, чтобы получить полностью произвольный код, работающий внутри процесса PP-WindowsTCB.
Давайте займемся первой частью, как начать работу с COM. Как я упоминал ранее, WerFaultSecure не вызывает напрямую какие-либо методы COM, но Алекс явно использовал его раньше, поэтому, чтобы сэкономить время, я просто спросил его. Хитрость заключалась в том, чтобы заставить WerFaultSecure выгружать процесс AppContainer, это приводит к вызову метода CCrashReport::ExemptFromPlmHandling внутри FaultRep DLL, что приводит к загрузке CLSID {07FC2B94-5285-417E-8AC3-C2CE5240B0FA}, который разрешается в недокументированный COM-объект. Важно только то, что это позволяет WerFaultSecure инициализировать COM.
К сожалению, я не совсем правдиво описал настройку удаленного взаимодействия через COM. Простая загрузка COM-объекта не всегда достаточна для инициализации интерфейса IRundown или конечной точки RPC. Это имеет смысл, если все вызовы COM предназначены для кодирования в одной и той же комнате, тогда зачем вообще инициализировать весь код удаленного взаимодействия для COM. В этом случае, хотя мы можем заставить WerFaultSecure загрузить COM-объект, он не соответствует условиям для настройки удаленного взаимодействия. Что мы можем сделать, чтобы убедить среду выполнения COM в том, что мы действительно хотим ее инициализировать? Одна из возможностей - изменить регистрацию COM с внутрипроцессного класса на класс ООП. Как показано на скриншоте ниже, регистрация COM сначала запрашивается у HKEY_CURRENT_USER, что означает, что мы можем захватить его, не требуя прав администратора.
Код передает флаг CLSCTX_INPROC_SERVER в CoCreateInstance. Этот флаг ограничивает поисковый код в среде выполнения COM только поиском внутрипроцессных регистраций классов. Даже если мы заменим регистрацию на регистрацию для класса ООП, среда выполнения COM просто проигнорирует это. К счастью, есть другой способ: код инициализирует COM-квартиру текущего потока как STA, используя флаг COINIT_APARTMENTTHREADED с CoInitializeEx. Если посмотреть на регистрацию COM-объекта, его потоковая модель установлена на "Оба". На практике это означает, что объект поддерживает вызов непосредственно из STA или MTA.
Однако, если модель потоковой передачи вместо этого была установлена на "Free", тогда объект поддерживает только прямые вызовы из MTA, что означает, что среда выполнения COM должна будет включить удаленное взаимодействие, создать объект в MTA (используя что-то похожее на DoCallback), а затем маршалировать звонки на этот объект из исходной Комнаты. Как только COM начинает удаленное взаимодействие, он инициализирует все удаленные функции, включая Irundown. Поскольку мы можем захватить регистрацию сервера, мы можем просто изменить модель потоковой передачи, это заставит WerFaultSecure запустить удаленное взаимодействие COM, которое мы теперь можем эксплуатировать.
А как насчет второй части, что мы можем вызвать внутри процесса для выполнения произвольного кода? Все, что мы вызываем с помощью DoCallback, должно соответствовать следующим критериям, чтобы избежать неопределенного поведения:
Поскольку WerFaultSecure не делает ничего особенного, то как минимум любая экспортируемая функция DLL должна быть допустимой целью косвенного вызова. LoadLibrary явно соответствует нашим критериям, поскольку принимает единственный параметр, который является указателем на путь к DLL, и нас не заботит возвращаемое значение, поэтому усечение не имеет значения. Мы не можем просто загрузить любую DLL, поскольку она должна быть правильно подписана, но как насчет перехвата KnownDll?
Подождите, разве я не говорил, что PP не может загружаться из KnownDlls? Да, они не могут, но только потому, что значение глобальной переменной LdrpKnownDllDirectoryHandle всегда устанавливается в NULL во время инициализации процесса. Когда загрузчик DLL проверяет наличие известной библиотеки DLL, если дескриптор равен NULL, проверка немедленно возвращается. Однако, если дескриптор имеет значение, он будет выполнять обычную проверку, и, как и в PPL, дополнительные проверки безопасности не выполняются, если процесс отображает образ из существующего объекта раздела. Следовательно, если мы можем изменить глобальную переменную LdrpKnownDllDirectoryHandle, чтобы она указывала на объект каталога, унаследованный от PP, мы можем заставить его загружать произвольную DLL.
Последняя часть головоломки - это поиск экспортируемой функции, которую мы можем вызвать для записи произвольного значения в глобальную переменную. Это оказалось сложнее, чем ожидалось. Идеальной функцией была бы функция, которая принимает единственный аргумент значения указателя и записывает в это место без каких-либо других побочных эффектов. После ряда неудачных запусков (в том числе попыток использования get) я остановился на паре SetProcessDefaultLayout и GetProcessDefaultLayout в USER32. Функция set принимает единственное значение, которое представляет собой набор флагов, и сохраняет его в глобальном местоположении (фактически в ядре, но достаточно хорошо). Затем метод get запишет это значение в произвольный указатель. Это не идеально, поскольку значения, которые мы можем установить и, следовательно, записать, ограничены числами 0-7, однако, смещая указатель в вызовах get, мы можем записать значение в форме 0x0?0?0?0? где ? может быть любым значением от 0 до 7. Поскольку значение просто должно относиться к дескриптору внутри процесса, находящегося под нашим контролем, мы можем легко создать дескриптор в соответствии с этими строгими требованиями.
Заключение
В заключение, чтобы получить выполнение произвольного кода внутри PP-WindowsTCB без прав администратора, мы можем сделать следующее:
Этот процесс работает во всех поддерживаемых версиях Windows 10, включая 1809. Стоит отметить, что вызов DoCallback может использоваться с любым процессом, в котором вы можете читать содержимое памяти, и процесс инициализировал удаленное взаимодействие COM. Например, если у вас есть уязвимость произвольного раскрытия памяти в привилегированной службе COM, вы можете использовать эту атаку для преобразования произвольного чтения в произвольное выполнение. Поскольку я не склонен искать уязвимости, связанные с повреждением памяти/раскрытием памяти, возможно, это поведение будет более полезно для других.
На этом я завершаю серию атак на защищенные Windows процессы. Я думаю, это демонстрирует, что предотвращение атаки пользователем процессов, которые совместно используют ресурсы, такие как реестр и файлы, в конечном итоге обречены на неудачу. Вероятно, поэтому Microsoft не поддерживает PP/PPL в качестве границы безопасности. Изолированный пользовательский режим кажется более сильным примитивом, хотя он требует дополнительных ресурсов, которых PP/PPL по большей части не удовлетворяет. Я не удивлюсь, если более новые версии Windows 10, под которыми я подразумеваю после версии 1809, попытаются каким-то образом защититься от этих атак, но вы почти наверняка сможете найти обходной путь.
Источник: https://googleprojectzero.blogspot.com/2018/11/injecting-code-into-windows-protected.html
Автор перевода: yashechka
Переведено специально для https://xss.pro
В качестве краткого обзора предыдущей атаки я смог идентифицировать процесс, который будет работать как PPL, который также предоставил COM-сервис. В частности, это была "Служба оптимизации времени выполнения .NET", которая поставляется с платформой .NET и использует PPL на уровне CodeGen для применения уровней кэшированной подписи к заранее скомпилированным библиотекам DLL, чтобы их можно было использовать с целостностью кода пользовательского режима. (UMCI). Изменив конфигурацию прокси-сервера COM, можно было вызвать путаницу в типах, что позволило мне загрузить произвольную DLL путем перехвата конфигурации KnownDlls. Запустив код внутри PPL, я мог злоупотребить ошибкой в функции кэшированной подписи, чтобы создать DLL, подписанную для загрузки в любой PPL, и через это перейти на уровень PPL-WindowsTCB.
В поисках новой цели
Моей первой мыслью об использовании полного PP было бы использование дополнительного доступа, который нам предоставил запуск кода в PPL-WindowsTCB. Вы можете предположить, что можете злоупотребить кэшированной подписанной DLL, чтобы обойти проверки безопасности для загрузки в полный PP. К сожалению, модуль целостности кода ядра игнорирует кешированные уровни подписи для полного PP. Как насчет KnownDlls в целом? Если у нас есть права администратора и код, работающий в PPL-WindowsTCB, мы можем напрямую писать в каталог объектов KnownDlls и попытаться заставить PP загружать произвольную DLL. К сожалению, как я уже упоминал в предыдущем блоге, это тоже не работает, поскольку полный PP игнорирует KnownDll. Даже если он загрузил KnownDll, я не хочу требовать прав администратора для внедрения кода в процесс.
Я решил, что имеет смысл перезапустить мой сценарий PowerShell из предыдущего блога, чтобы узнать, какие исполняемые файлы будут работать как полный PP и на каком уровне. В Windows 10 1803 имеется значительное количество исполняемых файлов, которые работают на уровне PP-Authenticode, однако только четыре исполняемых файла будут запускаться с более привилегированным уровнем, как показано в следующей таблице.
| Path | Signing Level |
| C:\windows\system32\GenValObj.exe | Windows |
| C:\windows\system32\sppsvc.exe | Windows |
| C:\windows\system32\WerFaultSecure.exe | WindowsTCB |
| C:\windows\system32\SgrmBroker.exe | WindowsTCB |
Поскольку у меня нет известного маршрута от уровня PP-Windows до уровня PP-WindowsTCB, как у меня с PPL, интерес представляют только два из четырех исполняемых файлов, WerFaultSecure.exe и SgrmBroker.exe. Я сопоставил эти два исполняемых файла с известными регистрациями служб COM, которые не дали никаких результатов. Это не означает, что эти исполняемые файлы не открывают поверхность для атаки COM, исполняемый файл .NET, которым я злоупотреблял в прошлый раз, также не регистрирует свою службу COM, поэтому я также выполнил некоторые базовый реверсинг в поисках использования COM.
Исполняемый файл SgrmBroker ничего не делает, он представляет собой оболочку вокруг изолированного приложения в пользовательском режиме для реализации аттестации системы во время выполнения как часть System Guard в Защитнике Windows и не вызывает никаких COM API. WerFaultSecure также, похоже, не вызывает COM, однако я уже знал, что WerFaultSecure может загружать COM-объекты, поскольку Алекс Ионеску использовал мою исходную атаку выполнения кода сценария COM, чтобы получить уровень PPL-WindowsTCB, перехватив загрузку COM-объекта в WerFaultSecure. Даже несмотря на то, что WerFaultSecure не предоставлял сервис, если он мог инициализировать COM, возможно, есть что-то, чем я мог бы злоупотребить, чтобы получить выполнение произвольного кода? Чтобы понять поверхность атаки COM, нам необходимо понять, как COM реализует внепроцессные COM-серверы и удаленное взаимодействие COM в целом.
Копаемся во внутренностях COM Remoting
Связь между COM-клиентом и COM-сервером осуществляется по протоколу MSRPC, который основан на протоколе DCE/RPC Open Group. Для локального обмена данными используются порты Advanced Local Procedure Call (ALPC). На высоком уровне связь происходит между клиентом и сервером согласно следующей схеме:
Чтобы клиент мог найти местоположение сервера, процесс регистрирует конечную точку ALPC в активаторе DCOM в RPCSS [1]. Эта конечная точка регистрируется вместе с идентификатором экспортера объектов (OXID) сервера, который представляет собой 64- битное случайно сгенерированное число, присвоенное RPCSS. Когда клиент хочет подключиться к серверу, он должен сначала попросить RPCSS преобразовать значение OXID сервера в конечную точку RPC [2]. Зная конечную точку ALPC RPC, клиент может подключаться к серверу и вызывать методы COM-объекта [3].
Значение OXID обнаруживается либо из результата активации COM вне процесса (OOP), либо через структуру маршалированной ссылки на объект (OBJREF).
Под капотом клиент вызывает метод ResolveOxid в интерфейсе RPC IObjectExporter RPCSS. Прототип ResolveOxid выглядит следующим образом:
C:
interface IObjectExporter {
// ...
error_status_t ResolveOxid(
[in] handle_t hRpc,
[in] OXID* pOxid,
[in] unsigned short cRequestedProtseqs,
[in] unsigned short arRequestedProtseqs[],
[out, ref] DUALSTRINGARRAY** ppdsaOxidBindings,
[out, ref] IPID* pipidRemUnknown,
[out, ref] DWORD* pAuthnHint
);
В прототипе мы видим, что OXID для разрешения передается в параметре pOxid, и сервер возвращает массив Dual String Bindings, которые представляют конечные точки RPC для подключения для этого значения OXID. Сервер также возвращает две другие части информации: подсказку об уровне аутентификации (pAuthnHint), которую мы можем игнорировать, и IPID интерфейса IRemUnknown (pipidRemUnknown).
IPID - это значение GUID, называемое идентификатором процесса интерфейса. Он представляет собой уникальный идентификатор для COM-интерфейса внутри сервера, и он необходим для связи с правильным COM-объектом, поскольку он позволяет одной конечной точке RPC мультиплексировать несколько интерфейсов через одно соединение. Интерфейс IRemUnknown - это COM-интерфейс по умолчанию, который должен реализовать каждый COM-сервер, поскольку он используется для запроса новых IPID для существующего объекта (с использованием RemQueryInterface) и поддержания счетчика ссылок удаленного объекта (с помощью методов RemAddRef и RemRelease). Поскольку этот интерфейс должен существовать всегда, независимо от того, экспортируется ли фактический COM-сервер, а IPID может быть обнаружен путем разрешения OXID сервера, мне было интересно, какие другие методы поддерживает интерфейс на случай, если есть что-то, что я мог бы использовать для выполнения кода.
Код времени выполнения COM поддерживает базу данных всех IPID, поскольку ему нужно искать объект сервера, когда он получает запрос на вызов метода. Если мы знаем структуру этой базы данных, мы могли бы обнаружить, где реализован интерфейс IRemUnknown, проанализировать его методы и выяснить, какие другие функции он поддерживает. К счастью, я выполнил работу по реверсингу формата базы данных в моем инструменте OleViewDotNet, в частности, с помощью команды Get-ComProcess в модуле PowerShell. Если мы запустим команду против процесса, который использует COM, но на самом деле не реализует COM-сервер (например, блокнот), мы можем попытаться определить правильный IPID.
В этом примере снимка экрана фактически экспортированы два IPID: IRundown и интерфейс Windows.Foundation. Интерфейс Windows.Foundation можно смело игнорировать, но IRundown выглядит интереснее. Фактически, если вы выполните ту же проверку для любого COM-процесса, вы обнаружите, что у них также есть экспортированные интерфейсы Irundown. Но разве мы не ожидаем интерфейса IRemUnknown? Если мы передадим параметры ResolveMethodNames и ParseStubMethods в Get-ComProcess, команда попытается проанализировать параметры метода для интерфейса и имен поиска на основе общедоступных символов. С помощью проанализированных данных интерфейса мы можем передать объект IPID команде Format-ComProxy, чтобы получить базовое текстовое представление интерфейса Irundown. После очистки интерфейс IRundown выглядит следующим образом:
C:
[uuid("00000134-0000-0000-c000-000000000046")]
interface IRundown : IUnknown {
HRESULT RemQueryInterface(...);
HRESULT RemAddRef(...);
HRESULT RemRelease(...);
HRESULT RemQueryInterface2(...);
HRESULT RemChangeRef(...);
HRESULT DoCallback([in] struct XAptCallback* pCallbackData);
HRESULT DoNonreentrantCallback([in] struct XAptCallback* pCallbackData);
HRESULT AcknowledgeMarshalingSets(...);
HRESULT GetInterfaceNameFromIPID(...);
HRESULT RundownOid(...);
}
Этот интерфейс является надмножеством IRemUnknown, он реализует такие методы, как RemQueryInterface, а затем добавляет еще несколько дополнительных методов для хорошей оценки. Что меня действительно интересовало, так это методы DoCallback и DoNonreentrantCallback, они звучат так, как будто могут выполнить какой-то обратный вызов. Может быть, мы можем злоупотреблять этими методами? Давайте посмотрим на реализацию DoCallback на основе небольшого количества RE (DoNonreentrantCallback просто делегирует DoCallback внутренне, поэтому нам не нужно обрабатывать его специально):
C:
struct XAptCallback {
void* pfnCallback;
void* pParam;
void* pServerCtx;
void* pUnk;
void* iid;
int iMethod;
GUID guidProcessSecret;
};
HRESULT CRemoteUnknown::DoCallback(XAptCallback *pCallbackData) {
CProcessSecret::GetProcessSecret(&pguidProcessSecret);
if (!memcmp(&pguidProcessSecret,
&pCallbackData->guidProcessSecret, sizeof(GUID))) {
if (pCallbackData->pServerCtx == GetCurrentContext()) {
return pCallbackData->pfnCallback(pCallbackData->pParam);
} else {
return SwitchForCallback(
pCallbackData->pServerCtx,
pCallbackData->pfnCallback,
pCallbackData->pParam);
}
}
return E_INVALIDARG;
}
Этот метод очень интересен, он принимает структуру, содержащую указатель на вызываемый метод и произвольный параметр, и выполняет указатель. Единственные ограничения на вызов произвольного метода - вы должны заранее знать случайно сгенерированное значение GUID, секрет процесса и адрес контекста сервера. Проверка случайного значения для каждого процесса является обычным шаблоном безопасности в COM API и обычно используется для ограничения функциональности только внутрипроцессными вызывающими объектами. Я злоупотреблял чем-то похожим в Free-Threaded Marshaler еще в 2014 году.
Какова цель DoCallback? Среда выполнения COM создает новый интерфейс IRundown для каждой инициализированной COM среды. Это на самом деле важно, поскольку для вызова методов между апартаментами, скажем, для вызова объекта STA из MTA, вам необходимо вызвать соответствующие методы IRemUnknown в правильной комнате. Поэтому, пока разработчики были там, они добавили еще несколько методов, которые были бы полезны для вызова между квартирами, в том числе общий метод "вызовите что угодно". Это используется внутренними компонентами среды выполнения COM и предоставляется косвенно через такие методы, как CoCreateObjectInContext. Чтобы предотвратить злоупотребление методом DoCallback ООП, проверяется секрет каждого процесса, что должно ограничивать его только вызывающими внутри процесса, если только внешний процесс не может прочитать секрет из памяти.
Злоупотребление DoCallback
У нас есть примитив для выполнения произвольного кода в любом процессе, инициализировавшем COM, путем вызова метода DoCallback, который должен включать PP. Для успешного вызова произвольного кода нам необходимо знать четыре части информации:
- Порт ALPC, который прослушивает COM-процесс.
- IPID интерфейса IRundown.
- Инициализированное значение секрета процесса.
- Адрес допустимого контекста, в идеале то же значение, которое GetCurrentContext возвращает для вызова в том же потоке RPC.
Получить порт ALPC и IPID легко, если процесс предоставляет COM-сервер, так как оба будут предоставлены во время разрешения OXID. К сожалению, WerFaultSecure не предоставляет доступ к COM-объекту, который мы можем создать, чтобы этот угол был закрыт для нас, оставляя нас перед проблемой, которую мы должны решить. Извлечение секрета процесса и значения контекста требует чтения содержимого памяти процесса. Это еще одна проблема, одна из преднамеренных функций безопасности PP предотвращает чтение памяти процессом PP процессом, отличным от PP. Как мы собираемся решить эти две проблемы?
Обсуждая это с Алексом из Recon, мы придумали возможную атаку, если у вас есть права администратора. Даже будучи администратором, вы не можете читать память непосредственно из процесса PP. Мы могли бы загрузить драйвер, но это полностью сломало бы PP, поэтому мы решили, как это сделать, не выполняя код ядра.
Первое и самое простое, порт ALPC и IPID могут быть извлечены из RPCSS. Служба RPCSS не запускается с защитой (даже PPL), поэтому можно обойтись без каких-либо хитрых уловок, кроме знания того, где значения хранятся в памяти. Для указателя контекста мы должны иметь возможность перебрать местоположение, поскольку, вероятно, будет только узкий диапазон ячеек памяти для тестирования, что немного упростит, если мы будем использовать 32-разрядную версию WerFaultSecure.
Извлечь секрет несколько сложнее. Секрет инициализируется в доступной для записи памяти и, следовательно, попадает в рабочий набор процесса после его изменения. Поскольку страница не заблокирована, она будет доступна для разбиения на страницы, если условия памяти правильные. Следовательно, если бы мы могли принудительно выгрузить страницу, содержащую секрет, на диск, мы могли бы прочитать ее, даже если она была получена из процесса PP. Как администратор, мы можем выполнить следующие действия, чтобы украсть секрет:
- Убедитесь, что секрет инициализирован, а страница изменена.
- Заставьте процесс обрезать свой рабочий набор, это должно гарантировать, что измененная страница, содержащая секрет, будет выгружена на диск (в конечном итоге).
- Создайте файл аварийного дампа памяти ядра с помощью системного вызова NtSystemDebugControl. Аварийный дамп может быть создан администратором без включения отладки ядра и будет содержать всю оперативную память ядра. Обратите внимание, что на самом деле это не приводит к сбою системы.
- Проанализировать аварийный дамп для записи таблицы страниц страницы, содержащей секретное значение. PTE должен раскрывать, где в файле подкачки на диске расположены выгружаемые данные.
- Откройте том, содержащий файл подкачки, для доступа на чтение, проанализируйте структуры NTFS, чтобы найти файл подкачки, а затем найдите выгружаемые данные и извлеките секрет.
После того, как я придумал эту атаку, мне показалось, что это слишком тяжелая работа и требуются права администратора, которых я хотел избежать. Мне нужно было найти альтернативное решение.
Использование WerFaultSecure по прямому назначению
До этого момента я обсуждал WerFaultSecure как процесс, которым можно злоупотреблять для запуска произвольного кода внутри PP/PPL. Я не совсем объяснил, почему процесс может выполняться на максимальных уровнях PP/PPL. WerFaultSecure используется службой отчетов об ошибках Windows для создания аварийных дампов защищенных процессов. Поэтому он должен работать с повышенными уровнями PP, чтобы гарантировать, что он может сбрасывать любой возможный PP в пользовательском режиме. Почему мы не можем просто заставить WerFaultSecure создать аварийный дамп, который приведет к утечке содержимого памяти процесса и позволит нам извлечь любую необходимую нам информацию?
Причина, по которой мы не можем использовать WerFaultSecure, заключается в том, что он шифрует содержимое аварийного дампа перед записью его на диск. Шифрование выполняется таким образом, чтобы позволить Microsoft только расшифровать аварийный дамп, используя асимметричное шифрование для защиты случайного сеансового ключа, который может быть предоставлен веб-службе Microsoft WER. За исключением слабых мест в реализации Microsoft или новой криптографической атаки на используемые примитивы, получение зашифрованных данных кажется неудачным.
Однако так было не всегда. В 2014 году Алекс представил на NoSuchCon PPL и рассказал об обнаруженной им ошибке в том, как WerFaultSecure создает зашифрованные файлы дампа. Он использовал двухэтапный процесс: сначала он записал аварийный дамп в незашифрованном виде, а затем зашифровал аварийный дамп. Возможно, вы заметите недостаток? Удалось украсть незашифрованный аварийный дамп. Из-за того, как WerFaultSecure назывался, он принимал два дескриптора файлов, один для незашифрованного дампа и один для зашифрованного дампа. При прямом вызове WerFaultSecure незашифрованный дамп никогда не будет удален, а это означает, что вам даже не нужно гонять процесс шифрования.
Есть одна проблема, она была исправлена в 2015 году в MS15-006. После этого исправления WerFaultSecure зашифровал аварийный дамп напрямую, он никогда не попадает на диск в незашифрованном виде. Но это заставило меня задуматься, хотя они могли исправить ошибку в будущем, что мешает нам взять старую уязвимую версию WerFaultSecure из Windows 8.1 и запустить ее в Windows 10? Я загрузил ISO для Windows 8.1 с веб-сайта Microsoft (ссылка), извлек двоичный файл и протестировал его с предсказуемыми результатами:
Мы можем взять уязвимую версию WerFaultSecure из Windows 8.1, и она будет вполне успешно работать в Windows 10 на уровне PP-WindowsTCB. Почему? Неясно, но из-за способа защиты PP все доверие основано на подписанном исполняемом файле. Поскольку подпись исполняемого файла все еще действительна, ОС просто верит, что ее можно запустить на запрошенном уровне защиты. Предположительно должен быть какой-то способ, которым Microsoft может блокировать определенные исполняемые файлы, хотя, по крайней мере, они не могут просто отозвать свои собственные сертификаты подписи. Возможно, двоичные файлы ОС должны иметь в сертификате EKU, указывающее, для какой версии они предназначены? В конце концов, Microsoft уже добавила новый EKU при переходе с Windows 8 на 8.1, чтобы блокировать атаки перехода на более раннюю версию для обхода подписи WinRT UMCI, поэтому обобщение может иметь некоторый смысл, особенно для определенных уровней PP.
После небольшого количества RE и ссылки на презентацию Алекса я смог выработать различные параметры, которые мне нужно было передать процессу WerFaultSecure для выполнения дампа PP:
| Parameter | Description |
| /h | Enable secure dump mode. |
| /pid {pid} | Specify the Process ID to dump. |
| /tid {tid} | Specify the Thread ID in the process to dump. |
| /file {handle} | Specify a handle to a writable file for the unencrypted crash dump |
| /encfile {handle} | Specify a handle to a writable file for the encrypted crash dump |
| /cancel {handle} | Specify a handle to an event to indicate the dump should be cancelled |
| /type {flags} | Specify MIMDUMPTYPE flags for call to MiniDumpWriteDump |
Это дает нам все необходимое для завершения эксплойта. Нам не нужны права администратора, чтобы запустить старую версию WerFaultSecure как PP-WindowsTCB. Мы можем заставить его сделать дамп еще одной копии WerFaultSecure с инициализированным COM и использовать аварийный дамп для извлечения всей необходимой нам информации, включая порт ALPC и IPID, необходимые для связи. Нам не нужно писать собственный синтаксический анализатор аварийного дампа, так как можно использовать API Debug Engine, который поставляется вместе с Windows. Как только мы извлекли всю необходимую информацию, мы можем вызвать DoCallback и вызвать произвольный код.
Собираем все вместе
Для завершения эксплойта нам еще нужны две вещи: как заставить WerFaultSecure запустить COM и что мы можем вызвать, чтобы получить полностью произвольный код, работающий внутри процесса PP-WindowsTCB.
Давайте займемся первой частью, как начать работу с COM. Как я упоминал ранее, WerFaultSecure не вызывает напрямую какие-либо методы COM, но Алекс явно использовал его раньше, поэтому, чтобы сэкономить время, я просто спросил его. Хитрость заключалась в том, чтобы заставить WerFaultSecure выгружать процесс AppContainer, это приводит к вызову метода CCrashReport::ExemptFromPlmHandling внутри FaultRep DLL, что приводит к загрузке CLSID {07FC2B94-5285-417E-8AC3-C2CE5240B0FA}, который разрешается в недокументированный COM-объект. Важно только то, что это позволяет WerFaultSecure инициализировать COM.
К сожалению, я не совсем правдиво описал настройку удаленного взаимодействия через COM. Простая загрузка COM-объекта не всегда достаточна для инициализации интерфейса IRundown или конечной точки RPC. Это имеет смысл, если все вызовы COM предназначены для кодирования в одной и той же комнате, тогда зачем вообще инициализировать весь код удаленного взаимодействия для COM. В этом случае, хотя мы можем заставить WerFaultSecure загрузить COM-объект, он не соответствует условиям для настройки удаленного взаимодействия. Что мы можем сделать, чтобы убедить среду выполнения COM в том, что мы действительно хотим ее инициализировать? Одна из возможностей - изменить регистрацию COM с внутрипроцессного класса на класс ООП. Как показано на скриншоте ниже, регистрация COM сначала запрашивается у HKEY_CURRENT_USER, что означает, что мы можем захватить его, не требуя прав администратора.
C:
HRESULT CCrashReport::ExemptFromPlmHandling(DWORD dwProcessId) {
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
IOSTaskCompletion* inf;
HRESULT hr = CoCreateInstance(CLSID_OSTaskCompletion,
NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&inf));
if (SUCCEEDED(hr)) {
// Open process and disable PLM handling.
}
}
Код передает флаг CLSCTX_INPROC_SERVER в CoCreateInstance. Этот флаг ограничивает поисковый код в среде выполнения COM только поиском внутрипроцессных регистраций классов. Даже если мы заменим регистрацию на регистрацию для класса ООП, среда выполнения COM просто проигнорирует это. К счастью, есть другой способ: код инициализирует COM-квартиру текущего потока как STA, используя флаг COINIT_APARTMENTTHREADED с CoInitializeEx. Если посмотреть на регистрацию COM-объекта, его потоковая модель установлена на "Оба". На практике это означает, что объект поддерживает вызов непосредственно из STA или MTA.
Однако, если модель потоковой передачи вместо этого была установлена на "Free", тогда объект поддерживает только прямые вызовы из MTA, что означает, что среда выполнения COM должна будет включить удаленное взаимодействие, создать объект в MTA (используя что-то похожее на DoCallback), а затем маршалировать звонки на этот объект из исходной Комнаты. Как только COM начинает удаленное взаимодействие, он инициализирует все удаленные функции, включая Irundown. Поскольку мы можем захватить регистрацию сервера, мы можем просто изменить модель потоковой передачи, это заставит WerFaultSecure запустить удаленное взаимодействие COM, которое мы теперь можем эксплуатировать.
А как насчет второй части, что мы можем вызвать внутри процесса для выполнения произвольного кода? Все, что мы вызываем с помощью DoCallback, должно соответствовать следующим критериям, чтобы избежать неопределенного поведения:
- Принимает только один параметр размером с указатель.
- Только младшие 32 бита вызова возвращаются как HRESULT, если нам это нужно.
- Callite охраняется CFG, поэтому это должно быть что-то, что является допустимой целью косвенного вызова.
Поскольку WerFaultSecure не делает ничего особенного, то как минимум любая экспортируемая функция DLL должна быть допустимой целью косвенного вызова. LoadLibrary явно соответствует нашим критериям, поскольку принимает единственный параметр, который является указателем на путь к DLL, и нас не заботит возвращаемое значение, поэтому усечение не имеет значения. Мы не можем просто загрузить любую DLL, поскольку она должна быть правильно подписана, но как насчет перехвата KnownDll?
Подождите, разве я не говорил, что PP не может загружаться из KnownDlls? Да, они не могут, но только потому, что значение глобальной переменной LdrpKnownDllDirectoryHandle всегда устанавливается в NULL во время инициализации процесса. Когда загрузчик DLL проверяет наличие известной библиотеки DLL, если дескриптор равен NULL, проверка немедленно возвращается. Однако, если дескриптор имеет значение, он будет выполнять обычную проверку, и, как и в PPL, дополнительные проверки безопасности не выполняются, если процесс отображает образ из существующего объекта раздела. Следовательно, если мы можем изменить глобальную переменную LdrpKnownDllDirectoryHandle, чтобы она указывала на объект каталога, унаследованный от PP, мы можем заставить его загружать произвольную DLL.
Последняя часть головоломки - это поиск экспортируемой функции, которую мы можем вызвать для записи произвольного значения в глобальную переменную. Это оказалось сложнее, чем ожидалось. Идеальной функцией была бы функция, которая принимает единственный аргумент значения указателя и записывает в это место без каких-либо других побочных эффектов. После ряда неудачных запусков (в том числе попыток использования get) я остановился на паре SetProcessDefaultLayout и GetProcessDefaultLayout в USER32. Функция set принимает единственное значение, которое представляет собой набор флагов, и сохраняет его в глобальном местоположении (фактически в ядре, но достаточно хорошо). Затем метод get запишет это значение в произвольный указатель. Это не идеально, поскольку значения, которые мы можем установить и, следовательно, записать, ограничены числами 0-7, однако, смещая указатель в вызовах get, мы можем записать значение в форме 0x0?0?0?0? где ? может быть любым значением от 0 до 7. Поскольку значение просто должно относиться к дескриптору внутри процесса, находящегося под нашим контролем, мы можем легко создать дескриптор в соответствии с этими строгими требованиями.
Заключение
В заключение, чтобы получить выполнение произвольного кода внутри PP-WindowsTCB без прав администратора, мы можем сделать следующее:
- Создайте поддельный каталог KnownDlls, дублируя дескриптор, пока он не встретит шаблон, подходящий для записи через Get/SetProcessDefaultLayout. Отметьте дескриптор как наследуемый.
- Создайте захват COM-объекта для CLSID {07FC2B94-5285-417E-8AC3-C2CE5240B0FA} с ThreadingModel, установленным на "Free".
- Запустите Windows 10 WerFaultSecure на уровне PP-WindowsTCB и запросите аварийный дамп из процесса AppContainer. Во время создания процесса необходимо добавить поддельные KnownDll, чтобы гарантировать, что они унаследованы от нового процесса.
- Подождите, пока COM инициализируется, затем используйте Windows 8.1 WerFaultSecure для сброса памяти процесса целевого объекта.
- Проанализируйте аварийный дамп, чтобы обнаружить секрет процесса, указатель контекста и IPID для IRundown.
- Подключитесь к интерфейсу IRundown и используйте DoCallback с Get/SetProcessDefaultLayout, чтобы изменить глобальную переменную LdrpKnownDllDirectoryHandle на значение дескриптора, созданное в 1.
- Вызовите DoCallback еще раз, чтобы вызвать LoadLibrary с именем для загрузки из наших поддельных KnownDll.
Этот процесс работает во всех поддерживаемых версиях Windows 10, включая 1809. Стоит отметить, что вызов DoCallback может использоваться с любым процессом, в котором вы можете читать содержимое памяти, и процесс инициализировал удаленное взаимодействие COM. Например, если у вас есть уязвимость произвольного раскрытия памяти в привилегированной службе COM, вы можете использовать эту атаку для преобразования произвольного чтения в произвольное выполнение. Поскольку я не склонен искать уязвимости, связанные с повреждением памяти/раскрытием памяти, возможно, это поведение будет более полезно для других.
На этом я завершаю серию атак на защищенные Windows процессы. Я думаю, это демонстрирует, что предотвращение атаки пользователем процессов, которые совместно используют ресурсы, такие как реестр и файлы, в конечном итоге обречены на неудачу. Вероятно, поэтому Microsoft не поддерживает PP/PPL в качестве границы безопасности. Изолированный пользовательский режим кажется более сильным примитивом, хотя он требует дополнительных ресурсов, которых PP/PPL по большей части не удовлетворяет. Я не удивлюсь, если более новые версии Windows 10, под которыми я подразумеваю после версии 1809, попытаются каким-то образом защититься от этих атак, но вы почти наверняка сможете найти обходной путь.
Источник: https://googleprojectzero.blogspot.com/2018/11/injecting-code-into-windows-protected.html
Автор перевода: yashechka
Переведено специально для https://xss.pro