Данная статья посвящена багу в Power Dependency Coordinator (CVE-2025-27736), запатченному Microsoft в апреле этого года.

В описании CVE сказано, что баг связан с раскрытием информации (адресов ядра).

Exposure of sensitive information to an unauthorized actor in Windows Power Dependency Coordinator allows an authorized attacker to disclose information locally.

https://msrc.microsoft.com/update-guide/vulnerability/CVE-2025-27736

Exploiting this vulnerability could allow the disclosure of certain memory address within kernel space. Knowing the exact location of kernel memory could be potentially leveraged by an attacker for other malicious activities.

В контексте изменений Windows 11 24H2 с ограничением NtQuerySystemInformation для определения адресов объектов, баги такого типа становятся еще более актуальны, поэтому мне стало интересно посмотреть пример такой уязвимости.

Из инструментов потребуются IDA Pro, BinDiff, WinDbg, для тестирования - Windows 11 или Windows 10 x64 с обновления до апреля 2025 (для тестирования работоспособности PoC) и актуальными обновлениями (для тестирования PoC после обновления).

Что такое Power Dependency Coordinator?

Power Dependency Coordinator (pdc.sys) связан с управлением электропитанием и он позволяет устройствам быстро выходить из состояния сна. Он появился в Windows 8.0.

Про этот драйвер сказано не очень много, например, во Внутреннем Уcтройстве Windows есть полезное упоминание этого компонента в главе, посвященной ALPC. Там сказано, что pdc.sys регистрирует callback для связи с клиентами по ALPC, в дальнейшем мы это увидим при реверсе драйвера.

Анализ патча

Как уже было сказано ранее, Power Dependency Coordinator – это драйвер pdc.sys

Скачиваем две версии драйвера с

https://winbindex.m417z.com/

и приступаем к анализу патча.

Необходимо скачать версию pdc.sys до апреля 2025 и апрельскую.

После сравнения BinDiff очевидно, что измененных функций не так много и они в основном связаны с активацией

Список измененных функций pdc.sys
Список измененных функций pdc.sys

Всего 7 измененных функций.

PdcpV2SendCallbackMessage
PdcpV2Deactivation
PdcpV2Renew
PdcpV2Activation
PdcpCompleteResiliencyOperation
PdcpCreateActivationInstance
PdcActivatorInitialize

В статье будут использоваться термины «активация» и «resiliency». Здесь я ориентируюсь на названия функций внутри pdc и его структур, важных для анализа. Например, PdcpV2Activation содержит в имени Activation, поэтому эта функция связана с активацией. PdcpV2Renew тоже подразумевается, что относится к активации, так как вызывается из функции PdcV2ActivatorReceive.

Если посмотреть, откуда вызываются функции PdcpV2Activation, PdcpV2Renew, PdcpV2Deactivation, то по цепочке вызовов довольно быстро находится функция PdcProcessMessage, эта функция вызывается из PdcpAlpcProcessMessages, а PdcpAlpcProcessMessages, в свою очередь, из PdcMessageCallback. Другие измененные функции также достижимы через PdcMessageCallback.
PdcMessageCallback — это callback, устанавливаемый ALPC порту через ZwAlpcSetInformation. Из предварительного анализа патча видно, что необходимо будет иметь дело с ALPC, чтобы вызывать интересующие функции. В связи с этим, ниже будет необходимый минимум информация об ALPC и его использовании в PDC.

Что такое ALPC?

ALPC (Advanced Local Procedure Call) - механизм межпроцессного взаимодействия (IPC). Это один из базовых механизмов Windows и многие системные компоненты используют ALPC для коммуникации (например, локальный RPC, csrss, lsass и другие).

Вообще, ALPC - недокументированный механизм и Microsoft не предполагает его использование напрямую и не предоставляет высокоуровневых API, поэтому для работы потребуется использовать функции из ntdll.

Необходимые для понимания этой статьи основные понятия ALPC:

  1. ALPC порт - это ядерный объект Windows (как процесс, поток, event). ALPC построен по принципу клиент-сервер. Сервер создает ALPC порт, а клиенты могут подключаться к этому порту для обмена сообщениями. ALPC порт создается функцией NtAlpcCreatePort, клиент подключается к порту через NtAlpcConnectPort.
    Сервер может принять или отклонить запрос на соединение от клиента функцией NtAlpcAcceptConnectPort.

  2. ALPC порты разделены на три типа. Первый — Connection Port, он создается сервером (NtAlpcCreatePort) и к нему подключаются клиенты, при соединении клиента с сервером (NtAlpcConnectPort) создаются еще два порта — Server Communication Port и Client Communication Port для сервера и клиента соответственно.

  3. ALPC сообщение. Сообщение в специальном формате для обмена данными между ALPC клиентом и сервером. Сообщения отправляются и принимаются функцией NtAlpcSendWaitReceivePort. ALPC сообщения должны начинаться со структуры _PORT_MESSAGE.

На скрине ниже на примере процесса lsass показаны открытые ALPC порты

Открытые lsass ALPC порты
Открытые lsass ALPC порты

Вообще, про ALPC было написано достаточно много, поэтому нет смысла повторяться, интересующиеся могут посмотреть в источниках ниже

  1. Хорошая статья

  2. Внутреннее устройство Windows. Ключевые компоненты и возможности. 7-е издание (вторая часть) - Продвинутый локальный вызов процедур

  3. SyScan'14 Singapore: All About The Rpc, Lrpc, Alpc, And Lpc In Your Pc By Alex Ionescu

Коммуникация с клиентами Power Dependency Coordinator

Power Dependency Coordinator создает ALPC порт \PdcPort для коммуникации с клиентами.

В WinDbg можно посмотреть информацию о \PdcPort и вывести информацию о подключенных клиентах. Сначала надо найти адрес объекта командой !object, а затем вывести информацию, специфичную для ALPC командой !alpc

0: kd> !object \PdcPort
Object: ffffd783a9115cc0 Type: (ffffd783a57f44f0) ALPC Port
ObjectHeader: ffffd783a9115c90 (new version)
HandleCount: 1 PointerCount: 32606
Directory Object: ffffc48cd0206bb0 Name: PdcPort
0: kd> !alpc /p ffffd783a9115cc0
Port ffffd783a9115cc0
Type : ALPC_CONNECTION_PORT
CommunicationInfo : ffffc48cd05e41b0
ConnectionPort : ffffd783a9115cc0 (PdcPort), Connections
ClientCommunicationPort : 0000000000000000
ServerCommunicationPort : 0000000000000000
OwnerProcess : ffffd783a56d5040 (System), Connections
SequenceNo : 0x00000020 (32)
CompletionPort : 0000000000000000
CompletionList : 0000000000000000
ConnectionPending : No
ConnectionRefused : No
Disconnected : No
Closed : No
FlushOnClose : Yes
ReturnExtendedInfo : No
Waitable : No
Security : Static
Wow64CompletionList : No

Main queue is empty.

Direct message queue is empty.

Large message queue is empty.

Pending queue is empty.

Canceled queue is empty.

0: kd> !alpc /lpc ffffd783a9115cc0

ffffd783a9115cc0('PdcPort') 0, 30 connections
ffffd783aa17f790 0 ->ffffd783aa17f9f0 0 ffffd783aa0020c0('svchost.exe')
ffffd783aa2d0070 0 ->ffffd783aa2d02d0 0 ffffd783aa0020c0('svchost.exe')
ffffd783aa2cf9a0 0 ->ffffd783aa2cfc00 0 ffffd783aa0020c0('svchost.exe')
ffffd783aa2cdd60 0 ->ffffd783aa2cf740 0 ffffd783aa0020c0('svchost.exe')
ffffd783aa2bbd80 0 ->ffffd783aa2cdb00 0 ffffd783aa0020c0('svchost.exe')
ffffd783a9768070 0 ->ffffd783a97682d0 0 ffffd783aa0020c0('svchost.exe')
ffffd783aa185070 0 ->ffffd783aa1852d0 0 ffffd783aa0020c0('svchost.exe')
ffffd783a5849790 0 ->ffffd783a58499f0 0 ffffd783aa4bb080('svchost.exe')
ffffd783a574c530 0 ->ffffd783a575c070 0 ffffd783aa4bb080('svchost.exe')
ffffd783aa7139f0 0 ->ffffd783a57d19f0 0 ffffd783aa65a080('svchost.exe')
ffffd783aa8b4dd0 0 ->ffffd783aa8b5dd0 0 ffffd783aa7d6080('svchost.exe')
ffffd783a9f4ede0 0 ->ffffd783aa735de0 0 ffffd783aa7d6080('svchost.exe')
ffffd783aa8ec9f0 0 ->ffffd783aa8ed9f0 0 ffffd783aa7d6080('svchost.exe')
ffffd783aa9e7070 0 ->ffffd783aa9e72d0 0 ffffd783aa8df080('svchost.exe')
ffffd783aa9e6530 0 ->ffffd783aa9e6790 0 ffffd783aa8df080('svchost.exe')
ffffd783aaa3cd60 0 ->ffffd783aaa3db00 0 ffffd783aaa19080('svchost.exe')
ffffd783aaa3ad60 0 ->ffffd783aaa3bb00 0 ffffd783aaa19080('svchost.exe')
ffffd783aaab22d0 0 ->ffffd783aaab2530 0 ffffd783aaaa0080('svchost.exe')
ffffd783aaab1c00 0 ->ffffd783aaab2070 0 ffffd783aaaa0080('svchost.exe')
ffffd783aaab1740 0 ->ffffd783aaab19a0 0 ffffd783aaaa0080('svchost.exe')
ffffd783ac1ee9f0 0 ->ffffd783ac1eec50 0 ffffd783aad4d080('svchost.exe')
ffffd783aac8b2d0 0 ->ffffd783aac8b530 0 ffffd783aad540c0('svchost.exe')
ffffd783ac7149f0 0 ->ffffd783ac714c50 0 ffffd783ac564080('svchost.exe')
ffffd783acb64070 0 ->ffffd783acb642d0 0 ffffd783acaf7080('svchost.exe')
ffffd783acb639f0 0 ->ffffd783acb63c50 0 ffffd783aad73080('MsMpEng.exe')
ffffd783acb5f530 0 ->ffffd783acb5f790 0 ffffd783ac7f0080('explorer.exe')
ffffd783acdb7b60 0 ->ffffd783acdb7dc0 0 ffffd783aae31080('svchost.exe')
ffffd783aca0b2d0 0 ->ffffd783aabd97d0 0 ffffd783ace5d080('msedge.exe')
ffffd783ad5c42d0 0 ->ffffd783ad5c4070 0 ffffd783aa654080('svchost.exe')
ffffd783ad80f070 0 ->ffffd783ad80f2d0 0 ffffd783a9f59080('svchost.exe')

Команда !alpc /lpc для Connection Port показывает подключенных к порту клиентов.
Как видно, \PdcPort активно используется в системе, среди подключенных клиентов svchost, msedge и explorer.

Теперь рассмотрим формат сообщений, который ожидает pdc.sys. Так как коммуникация происходит по ALPC, заголовок сообщения будет содержать _PORT_MESSAGE, а далее будут следовать данные специфичные для pdc. У pdc сообщений общий заголовок до смещения 0x38 в структуре _PDC_MESSAGE, за которым следуют данные, специфичные для каждого сообщения

1: kd> dt pdc!_PDC_MESSAGE
+0x000 Alpc : _PORT_MESSAGE
+0x028 Type : Uint4B
+0x028 MessageType : PDC_MESSAGE_TYPE
+0x02c ClientVersion : _PDC_CLIENT_VERSION
+0x02c Reserved : [2] Uint4B
+0x038 Register : _PDC_MSG_REGISTRATION
+0x038 Resiliency : _PDC_MSG_RESILIENCY
+0x038 Notification : _PDC_MSG_NOTIFICATION
+0x038 Activation : _PDC_MSG_ACTIVATION
+0x038 ActivationV2 : _PDC_MSG_ACTIVATION_V2
+0x038 ActivationRenewalV2 : _PDC_MSG_ACTIVATION_RENEWAL_V2
+0x038 ActivatorCallbackV2 : _PDC_MSG_ACTIVATOR_CALLBACK_V2
+0x038 DeactivationV2 : _PDC_MSG_ACTIVATION_DEACTIVATE_V2
+0x038 ActivatorCallbackV1 : _PDC_MSG_ACTIVATOR_CALLBACK_V1
+0x038 SuspendResume : _PDC_MSG_SUSPENDRESUME
+0x038 Task : _PDC_MSG_TASK
+0x038 SignalChange : _PDC_SIGNAL_CHANGE
+0x038 ProfileUpdate : _PDC_MSG_PPM_PROFILE_UPDATE
+0x038 SleepstudyHelperMessage : _PDC_SLEEPSTUDY_HELPER_MESSAGE

У pdc есть несколько типов сообщений, нас будут интересовать только некоторые из них – связанные с регистрацией клиента, активацией и resiliency. В рамках статьи будут рассмотрены первые два типа. Тип сообщения определяется полем MessageType. Для регистрации тип сообщения должен быть установлен в PdcRegisterMessage, для активации - Pdcv2ActivationMessage.

0: kd> dt PDC_MESSAGE_TYPE
PdcRegisterMessage = 0n0
PdcUnregisterMessage = 0n1
PdcNotificationMessage = 0n2
PdcControlMessage = 0n3
PdcActivationMessage = 0n4
PdcResiliencyMessage = 0n5
PdcSuspendResumeMessage = 0n6
PdcTaskMessage = 0n7
PdcSignalChangeMessage = 0n8
PdcPpmProfileMessage = 0n9
Pdcv2ActivationMessage = 0n10
Pdcv2ActivationRenewalMessage = 0n11
Pdcv2ActivationCallbackMessage = 0n12
Pdcv2DeactivationMessage = 0n13
Pdcv1ActivationCallbackMessage = 0n14
PdcSleepstudyHelperMessage = 0n15

Когда клиент соединяется с pdc функцией NtAlpcConnectPort он должен отправить регистрационное сообщение (_PDC_MSG_REGISTRATION), в котором будет указана информация о подключаемом клиенте. pdc будет проверять допустимость подключения этого клиента в несколько этапов. Если сообщение на регистрацию не проходит валидацию, то pdc отклоняет соединение. Если регистрационное сообщение было корректным и сервер принял соединение, то далее можно отправлять сообщения других типов.

pdc!_PDC_MSG_REGISTRATION
+0x000 ClientId : Uint4B
+0x004 ClientType : PDC_CLIENT_TYPE
+0x008 TriageContext : Uint8B
+0x010 ClientTypeData :
+0x0bc ModuleName : [64] Wchar

Рассмотрим более подробно процесс проверки регистрационного сообщения.
Здесь важное поле ClientId. У pdc есть предопределенные (статические) типы клиентов и в сообщении должен быть указан допустимый тип клиента. Статические типы клиентов прописаны в pdc.sys (массив pdc!PdcClientInfo).

Вообще, максимальное число клиентов на Windows 10 - 0x73, а на Win 11 – 0x7С.

Если проанализировать работу с массивом PdcClientInfo и его содержимое, то будет видно, что клиенты именованные и у них есть флаги. В некоторых случаях флаги могут быть важны, например, если установлен флаг 0x200, то клиент должен быть кернелмодный (отправка сообщения должна осуществляться из режима ядра).

Собрать список клиентов вместе с флагами можно скриптом для IDA Pro.

Вывод скрипта (для удобства в выводе сразу указано, кернелмодный или юзермодный клиент)

ClientIndex 0x0: flags 0 (UserMode) Client name "Invalid client"
ClientIndex 0x1: flags 6003 (UserMode) Client name "PLM"
ClientIndex 0x2: flags C4 (UserMode) Client name "NQM"
ClientIndex 0x3: flags 3 (UserMode) Client name "WNS"
ClientIndex 0x4: flags 246 (KernelMode) Client name "DAM"
ClientIndex 0x5: flags C4 (UserMode) Client name "WCM"
ClientIndex 0x6: flags 0 (UserMode) Client name "6"
ClientIndex 0x7: flags C3 (UserMode) Client name "NCSI"
ClientIndex 0x8: flags 1 (UserMode) Client name "DHCP"
ClientIndex 0x9: flags 341 (KernelMode) Client name "TCPIP"
...
ClientIndex 0x1E: flags 82 (UserMode) Client name "Test network service client"
ClientIndex 0x1F: flags 102 (UserMode) Client name "Test system service client"
ClientIndex 0x20: flags 7 (UserMode) Client name "Test client"
ClientIndex 0x21: flags 7 (UserMode) Client name "Test client"
ClientIndex 0x22: flags 42 (UserMode) Client name "Shell"
ClientIndex 0x23: flags 111 (UserMode) Client name "Maintenance Scheduler"
ClientIndex 0x24: flags 10 (UserMode) Client name "Sync Client"
ClientIndex 0x25: flags 1 (UserMode) Client name "Image Download Manager"
ClientIndex 0x26: flags 26041 (UserMode) Client name "Cortana Voice Activation"
...
ClientIndex 0x4A: flags 343 (KernelMode) Client name "Xbox System VM Quiescence Client"
ClientIndex 0x4B: flags 244 (KernelMode) Client name "Xbox Host VM Quiescence Client"
ClientIndex 0x4C: flags 41 (UserMode) Client name "Print Job Manager"
ClientIndex 0x4D: flags 41 (UserMode) Client name "Universal Telemetry Client"
ClientIndex 0x4E: flags 41 (UserMode) Client name "Windows Error Reporting"

ClientIndex 0x70: flags 80000 (UserMode) Client name "PDC User Mode Sleepstudy Helper Library Client"
ClientIndex 0x71: flags 80200 (KernelMode) Client name "PDC Kernel Mode Sleepstudy Helper Library Client"
ClientIndex 0x72: flags 142 (UserMode) Client name "Container Manager user mode notification client"

Чтобы pdc принял соединение от клиента, регистрационное сообщение должно пройти несколько проверок, сначала pdc!PdcSanitizeClientMessage проверит размер сообщения (допустимо 0x300 или 0x320) и ClientVersion.

Возможные значения ClientVersion

0: kd> dt pdc!_PDC_CLIENT_VERSION
PdcClientVersionInvalid = 0n0
PdcClientVersionRs1 = 0n1
PdcClientVersionRs3 = 0n2
PdcClientVersionRs4 = 0n3
PdcClientVersionRs5 = 0n4
PdcClientVersion19H1 = 0n5
PdcClientVersionCurrent = 0n5

Далее pdc!PdcValidateClient проверит ClientIndex – индекс клиента в массиве pdc!PdcClientInfo.
Как было упомянуто выше, для каждого клиента в данном массиве содержатся флаги. Для валидации важен флаг 0x200, он означает, что это клиент режима ядра, и pdc будет проверять KTHREAD.PreviousMode текущего треда. Для kernel mode клиентов вызов должен быть из ядра и KTHREAD.PreviousMode должен быть установлен в 0.

Значит, нам необходимо указать индекс такого клиента из pdc!PdcClientInfo, который не будет кернелмодным, иначе мы не пройдем валидацию. Для срабатывания уязвимости использовался клиент 0x26 "Cortana Voice Activation".

Другое важное поле — ClientType (тип клиента) оно имеет тип PDC_CLIENT_TYPE.

1: kd> dt pdc!PDC_CLIENT_TYPE
PdcNotificationClient = 0n0
PdcResiliencyClient = 0n1
PdcActivator = 0n2
PdcSuspendResumeClient = 0n3
PdcTaskClient = 0n4
PdcSignalClient = 0n5
PdcPpmProfileClient = 0n6
Pdcv2Activator = 0n7
PdcSleepstudyClient = 0n8
PdcInvalidType = 0n9

Для активации оно должно иметь значение PdcActivator (2) или Pdcv2Activator (7). Так как измененные функции имеют в имени v2, в нашем случае подойдет Pdcv2Activator. PdcAllocateUserClient ориентируется на значение ClientType при проведении инициализации клиента. Если все валидации прошли успешно pdc также выделит специальную структуру (pdc_client) вызовом PdcAllocateUserClient, с информацией о подключенном клиенте.

Далее клиент может отправлять сообщения других типов, например, для активации формат сообщения будет следующий

1: kd> dt _PDC_MSG_ACTIVATION_V2
pdc!_PDC_MSG_ACTIVATION_V2
+0x000 OperationStatus : Int4B
+0x004 ErrorDetail : _PDC_ACTIVATOR_ERROR_DETAIL
+0x008 PdcActivationInstanceHandle : Uint8B
+0x010 ActivityType : PDC_ACTIVITY_TYPE
+0x018 Flags : Uint8B
+0x020 StartTimeTolerance : Uint4B
+0x024 ExpectedMaximumDuration : Uint4B
+0x028 Task : [128] Wchar
+0x128 SubTask : [128] Wchar
+0x228 ExtraActivationParameters : _PDC_EXTRA_ACTIVATION_PARAMETERS
+0x240 DiagnosticContext : _PDC_DIAGNOSTIC_CONTEXT64

Таким образом, были рассмотрены особенности взаимодействия с pdc, необходимые для дальнейшего анализа патча.

Более детальный анализ патча

Как уже было сказано выше, измененных функций несколько. Рассмотрим одну из них - PdcpV2Activation.

Сравнение изменений в функции PdcpV2Activation
Сравнение изменений в функции PdcpV2Activation

При анализе в BinDiff (слева уязвимый файл, справа после патча) можно сделать вывод, что баг как-то связан с вызовом функции PdcpReplyActivatorClient (на новой версии PdcpV2ReplyActivation вызвает те же PdcpPrintActivationMessage и PdcpReplyActivatorClient), так как в новой версии pdc.sys ее параметры проходят дополнительную валидацию. В старой версии pdc.sys PdcpReplyActivatorClient передается указатель на структуру pdc_client, у которой по смещению 0x190 помещается значение, которое вернула функция PdcpCreateActivationInstance.

Рассмотрим данную функцию более подробно. PdcpCreateActivationInstance выделяет блок памяти (activation_instance) в NonPagedPoolNx размера 0x348 на Win 11 и 0x340 на Win10, заполняет данный блок памяти и возвращает на него указатель.

Это уже очень похоже на суть бага. Теперь подробнее рассмотрим что PdcpReplyActivatorClient дальше делает с сохраненным ядерным указателем. Данная функция предназначена для отправки ответа клиенту pdc.sys. PdcpReplyActivatorClient может отправлять ответ клиенту из пользовательского режима (тогда управление уйдет в PdcSendUserMessage) или из режима ядра (PdcSendKernelMessage). Нас интересует первый вариант, поэтому рассмотрим функцию PdcSendUserMessage. Здесь также важно сказать, что PdcpReplyActivatorClient смещает указатель, на структуру, где располагается ядерный указатель, на 0x150 перед ее передачей в PdcSendUserMessage. Таким образом, ядерный указатель теперь будет по смещению 0x40.

Функция PdcpReplyActivatorClient
Функция PdcpReplyActivatorClient

Внутри PdcSendUserMessage после подготовки сообщения для отправки ZwAlpcSendWaitReceivePort, указатель остается в неизменном виде. Теперь надо проверить это на практике.

Срабатывание бага

Как выяснилось, для срабатывания бага было достаточно отправить v2 activation message (_PDC_MSG_ACTIVATION_V2), таким образом вызвав PdcpV2Activation, тогда pdc.sys в ответном сообщении вернет адрес из NonPagedPoolNx.

Для успешного срабатывания надо было присоединиться к порту \PdcPort с корректным регистрационным сообщением, отправить активационное сообщение и получить ответное NtAlpcSendWaitReceivePort.

Таким образом, PdcpCreateActivationInstance выделяет блок памяти размера 0x348 на Win 11 и этот адрес будет помещен в ответное сообщение ALPC клиенту (в данном случае, нашему).

Ниже стек вызовов из PoC до PdcpV2Activation

00 fffff80615a92071 : ffffa10c2b0d0ea0 ffffa10c2b0d0f00 ffffa10c2b0d12b8 0000000000000000 : pdc!PdcpV2Activation+0x136 01 fffff80615aadf96 : ffffd783a9115cc0 ffffa10c00000000 ffffc48cdd0f8c20 ffffa10c2b0d0e30 : pdc!PdcV2ActivatorReceive+0x11
02 fffff80615aad642 : ffffc48cd07a0fc0 ffffa10c2b0d0f00 ffffa10c2b0d12b8 0000000000000000 : pdc!PdcProcessReceivedUserMessage+0x62 03 fffff80615aad8b4 : ffffffff80000194 ffffd783a5ff7101 ffffd783a5ff7101 ffffd783a5ffe648 : pdc!PdcProcessMessage+0x50a
04 fffff80615aad129 : ffffd783a5ff7190 ffffd783a5ffe650 0000000000000000 ffff840101361e90 : pdc!PdcpAlpcProcessMessages+0x94 05 fffff80683a7dd77 : ffffc48cdd6b20c0 ffffd783a9115cc0 7ffffffffffffffc 0000000000000000 : pdc!PdcMessageCallback+0x9
06 fffff80683f1b366 : ffffc48cdd6b20c0 ffffd783a9115cc0 ffffd783a5ffe640 0000000000000000 : nt!ExNotifyCallback+0xb7 07 fffff80683f1a44d : ffffc48cdd0f8c20 ffffd78300000000 0000000000000004 0000000000000000 : nt!AlpcpCompleteDispatchMessage+0xdd6
08 fffff80683f1e1d2 : ffffd78300000004 ffffd783b204ea08 0000000000000004 00000000000000f8 : nt!AlpcpDispatchNewMessage+0x2ed 09 fffff80683eab3ae : 0000000000000000 0000000000000000 000002026c3d0000 0000000000000006 : nt!AlpcpSendMessage+0x982
0a fffff80683cfd355 : ffffa10c2b0d1b60 ffffd783b204e080 000000443094f738 0000000000000000 : nt!NtAlpcSendWaitReceivePort+0x24e 0b 00007ffb5cc808d4 : 00007ff7b13e1357 0000000000000000 000002026c3cf4c0 0000000000000000 : nt!KiSystemServiceCopyEnd+0x25
0c 00007ff7b13e1357 : 0000000000000000 000002026c3cf4c0 0000000000000000 000000000000014c : ntdll!NtAlpcSendWaitReceivePort+0x14 0d 00007ff7b13e1b2c : 0000000000000000 0000000000000000 0000000000000001 0000000000000001 : CVE_2025_27736!wmain+0x297

Если протрейсить PdcpV2Activation до PdcpCreateActivationInstance и PdcpReplyActivatorClient до ZwAlpcSendWaitReceivePort, то будет видно, что в ответном сообщении по смещению 0x40 будет адрес, который вернула PdcpCreateActivationInstance.

Патч и причины уязвимости

Если посмотреть, что происходит в исправленной версии драйвера в функции PdcpV2Activation, то станет видно, что вместо адреса, возвращенного из PdcpCreateActivationInstance (activation_instance) теперь по тому же offset 0x190 помещается значение, взятое по адресу activation_instance + 0x340.

Фрагмент обновленной PdcpV2Activation
Фрагмент обновленной PdcpV2Activation

Чтобы понять, что теперь передается клиенту вместо адреса, необходимо посмотреть функцию PdcpCreateActivationInstance. Значение по смещению 0x340 устанавливается из структуры pdc_client+0x480 и теперь смысл этого поля — число вызовов активации клиентом (по всей видимости).

Обновленная PdcpCreateActivationInstance

Фрагмент обноленной PdcpCreateActivationInstance
Фрагмент обноленной PdcpCreateActivationInstance

Кстати, поле pdc_client + 0x480 инициализируется в 1 в измененной PdcActivatorInitialize

Если проверить PoC на обновленной системе, то все подтвердится, вместо адреса теперь будет 1 для первого вызова активации.

В заключение рассмотрим другие измененные функции, относящиеся к активации.

PdcpV2Renew – не представляет интереса, так как там изменена работа с хендлом активации. Вообще, ядерный адрес, который возвращается в сообщении, это и есть хендл активации и данная функция его ожидает в принимаемом сообщении. Раньше функция просто извлекала хендл активации из сообщения и далее использовала этот адрес, теперь же стала вызываться функция PdcpDecodeActivationInstanceHandle. Она, кстати, новая в исправленной версии драйвера

Изменения в функции PdcpV2Renew
Изменения в функции PdcpV2Renew

PdcpV2Deactivation содержит изменение, аналогичное PdcpV2Renew.

Что касается причин бага, у меня есть предположение, как в структуру, которая передается в юзермод, мог попасть ядерный адрес. Вероятно, это может быть связано с тем, что pdc обрабатывает и ядерных и юзермодных клиентов, и изначально ядерный адрес был нужен ядерным клиентам. В пользу этого предположения говорит то, что обоим типам клиентов PdcpReplyActivatorClient передаются одинаковые данные.

Итоги

Был рассмотрен интересный баг в редком (с точки зрения встречаемости в патчах) компоненте Power Dependency Coordinator. Конечно же, осталось много вопросов по работе драйвера, по возможностям бага, не затронутые в статье.

Комментарии (0)