
Подробный анализ и реализация эксплойта для Windows PrintNightmare (CVE-2021-1675/34527) с повышением привилегий на основе RPC и удаленным выполнением кода через установку вредоносного драйвера принтера.
= Print Nightmare 分析报告 :imagesdir: Figures :toc: :icons: font :figure-caption: 图 :xrefstyle: short :pdf-theme: basic-theme.yml
29 июня 2021 года была раскрыта очень серьезная уязвимость Windows Print Spooler как 0day, базовая оценка 8.8 балла, опубликована на GitHub (уже удалена). Эта уязвимость известна как PrintNightmare: CVE-2021-34527, она опаснее даже EternalBlue.
== Основная информация об уязвимости
Уязвимость 34527 затрагивает почти все версии после Windows 7 и Windows Server 2008, подробности см. <>.
С точки зрения ущерба, злоумышленник может использовать аутентификацию обычного пользователя для удаленного выполнения произвольного кода с правами администратора.
Что касается сложности эксплуатации, эта уязвимость очень легко эксплуатируется, поэтому она чрезвычайно опасна.
По характеристикам уязвимости, 34527 основана на уязвимости CVE-2021-1675. А уязвимость 1675 - это локальное повышение привилегий и удаленное выполнение кода, она очень похожа на 34527.
Прежде чем понять принцип работы уязвимости, мы должны иметь общее представление об архитектуре Windows Print Spooler, чтобы прояснить взаимосвязь между модулями, задействованными в уязвимости.
== Процесс вызова CVE-2021-1675
=== Архитектура Windows Print Spooler
Архитектуру spooler можно представить с помощью <<spooler_arch>>:
[[spooler_arch]] .Архитектура Print Spooler image::Print Spooler Architecture.png[]
В частности, диспетчер печати (spooler) используется для управления задачами печати и состоит из следующих компонентов:
winspool.drv:: динамически подключаемая библиотека, предоставляемая пользователю. Этот файл определяет Win32 API, связанные с spooler, для вызова пользователем. Все API в нем используют форму удаленного вызова процедур для получения услуг.
spoolsv.exe:: spoolsv.exe играет роль сервера в системе, как первая программа, обрабатывающая вызовы API. Такая конструкция позволяет диспетчеру печати обрабатывать как локальные, так и удаленные задания печати без различий.
spoolsv.dll:: программа маршрутизации. Она отправляет запросы печати, полученные spoolsv.exe, различным поставщикам печати и решает, какой поставщик печати в конечном итоге обработает запрос. Ее роль - различать удаленные и локальные задания печати. На удаленной машине она всегда назначает задание локальному поставщику печати.
localspl.dll:: локальный поставщик печати. Основная задача поставщика печати - удовлетворение потребностей управления задачами печати, и большинство API реализованы в этом модуле.
Продолжая исследование на основе вышеуказанной теории, например, при вызове функции AddPrinterDriverEx (CVE-2021-1675) происходит следующий процесс:
=== Выбор версии функции
Прежде всего, эта функция на самом деле является макросом, который выбирает версию Unicode (W) или Ansi (A) в зависимости от локальной среды компиляции, как показано в <>:
[[AddPrinterDriverEx]] .AddPrinterDriverEx image::AddPrinterDriverEx.png[]
Но независимо от версии с широкими или узкими символами, результат не отличается, поскольку строки ядра Windows используют кодировку Unicode, поэтому в конечном итоге вызовы версии Ansi преобразуются в вызовы версии Unicode, как показано в <>:
[[AnsiToUnicode]] .AnsiToUnicode image::AnsiToUnicode.png[]
Когда параметры функции Ansi преобразованы в версию Unicode, вызывается функция (<>):
[[AnsiCallUnicode]] .AnsiCallUnicode image::AnsiCallUnicode.png[]
И эта функция на самом деле является версией Unicode функции AddPrinterDriverEx (см. <>):
[[GetUnicodeProcAddress]] .GetUnicodeProcAddress image::GetUnicodeProcAddress.png[]
=== API функция отправляет RPC-запрос на сервер spooler
Внутри функции версии Unicode сначала выбирается тип параметра функции на основе значения Level:
image::pDriverInfo.png[]
В данной уязвимости мы установим Level равным 2, то есть выберем тип параметра pDriverInfo как структура DRIVER_INFO_2.
Затем Windows обрабатывает параметры функции, и после завершения обработки продолжает обработку API через удаленный вызов процедур:
image::set arguments.png[]
image::NdrClientCall3.png[]
=== Механизм MSRPC
Механизм удаленного вызова процедур Microsoft построен на основе стандарта DCE. Если объяснить просто, удаленный вызов процедур - это запуск процессов на удаленной системе, которые предопределены программистом или системой.
Конкретный метод RPC заключается в сериализации функции, которую необходимо вызвать удаленно, передаче ее по сети на удаленную систему, где она десериализуется и выполняется. В архитектуре Microsoft обычно выбираются протоколы TCP/IP и SMB для передачи вызовов RPC.
Чтобы использовать MSRPC, сначала необходимо определить IDL-описание интерфейса вызываемой функции, а затем с помощью инструмента MIDL сгенерировать соответствующие заглушки сериализации для клиента и сервера. Для некоторых Win32 API серверная заглушка уже определена, поэтому мы можем просто сгенерировать и использовать клиентскую заглушку.
MSRPC использует UUID для идентификации определенного типа протокола, например, MS-RPRN используется для описания протокола удаленной печати, все функции, связанные с удаленной печатью, являются частью этого протокола. MSPRC использует UUID 12345678-1234-ABCD-EF00-0123456789AB для идентификации этого протокола (см. <<rprn_uuid>>):
[[rprn_uuid]] .MS-RPRN UUID image::spoolss uuid.png[]
Затем на основе этого соединения можно использовать номер оператора (opnum) для идентификации функций внутри протокола и удаленного вызова. Например, AddPrinterDriverEx использует 89 для идентификации себя (см. <<addPrinterDriverEx_opnum>>):
[[addPrinterDriverEx_opnum]] .AddPrinterDriverEx Opnum image::AddPrinterDriverEx Opnum.png[]
При использовании MSRPC следует обратить внимание на два момента:
"As you can see in your output, the scripts are trying to connect to port 135 (endpoint mapper) in order to get the TCP/IP port where the DCOM endpoint is listening (that is a dynamic port)." -- SecureAuthCorp/impacket issue #412
=== Обработка API-запроса spoolsv.exe
[[call_flow]] .Поток вызова RpcAddPrinterDriverEx image::Function Calls.png[]
Из <<call_flow>> видно, что spoolsv.exe вызывает эти функции, а с точки зрения внутреннего анализа функций, в этом модуле не выполняется никаких операций, кроме инициализации.
В конце этот модуль вызывает функцию, на которую указывает pLocalProvidor, то есть функцию LocalAddPrinterDriverEx в модуле localspl.dll.
localspl, как локальный поставщик печати, действительно является модулем, реализующим функции API.
=== Логика реализации функций локального поставщика печати
[[LocalAddPrinterDriverEx]] .LocalAddPrinterDriverEx image::LocalAddPrinterDriverEx.png[]
Сначала <> показывает, что этот модуль проверяет, работает ли spooler нормально, а затем переходит к функции SplAddPrinterDriverEx.
[[SplAddPrinterDriverEx]] .SplAddPrinterDriverEx image::SplAddPrinterDriverEx.png[]
Внутри функции <> находится важное место для определения, может ли функция AddPrinterDriverEx выполниться успешно.
Первую часть можно не смотреть, так как WPP - это технология, связанная с журналированием, пропустим.
Во второй части Microsoft определила переменную v12, которая является флагом для определения, продолжать ли выполнение функции или сразу выйти.
[[bittest]] .bittest dwFileCopyFlags image::bittest in spl.png[]
Из <> видно, что есть два условия для продолжения выполнения: первое - v12 равно 0, то есть проверка bittest успешна, второе - Validate успешен.
А Validate - это проверка прав, которую невозможно легко обойти.