
Отравление кэша активации для повышения целостности с среднего до высокого уровня (CVE-2024-6769)
В этом блоге рассказывается о двух связанных ошибках: первая стадия — это DLL-хайекинг, вызванный переопределением ROOT-диска, а вторая стадия — это отравление кэша активации (Activation Cache Poisoning), управляемое сервером CSRSS.
Первая стадия была подробно представлена на Ekoparty 2023 в докладе под названием "I'm High" Николаса Экономо из BlueFrost Security. Он объяснил, как эксплуатировать уязвимость, которая на тот момент ещё не была исправлена Microsoft. Это позволяло пользователю с MEDIUM INTEGRITY повысить свои привилегии до ограниченных HIGH PRIVILEGES, но без полного доступа к функциям администратора.
Вторая стадия не была представлена на этой конференции, хотя были предложены некоторые шаги для начала её исследования.
Для начала мы рассмотрим первую стадию, чтобы дать вводный контекст. Затем мы углубимся в моё исследование второй стадии, детально разберём полное повышение с ограниченного HIGH INTEGRITY до полного администратора. Это включает полностью рабочее PoC для обеих стадий для всех версий Windows, успешно протестированное на Windows 10, Windows 11, Windows Server 2022 и Windows Server 2019 со всеми установленными обновлениями.

Единственное требование для этой стадии — чтобы начальный процесс имел MEDIUM INTEGRITY LEVEL, а пользователь принадлежал к группе Администраторов.
Первую стадию эксплуатации можно обобщить следующими шагами:
Например: переопределение диска с
"C:" на "C:\users\public"
Это также переопределит папку "system32" с
"C:\windows\system32" на "C:\users\public\windows\system32"
Одной из таких затронутых программ является CTFMON, которая работает на HIGH INTEGRITY LEVEL, но без прав Администратора.
Обычно она пытается загрузить модуль MsCtfMonitor.dll из реальной папки system32, но, так как ROOT-диск переопределён, она ищет MsCtfMonitor.dll в нашей фальшивой папке system32, где мы можем создать и разместить поддельную DLL с тем же именем.
На этом этапе, поместив нашу версию MsCtfMonitor.dll в фальшивую папку system32, будет вызвана её функция DoMsCtfMonitor, которая выполнит наш код с HIGH INTEGRITY LEVEL.




В то же время мы можем подтвердить, что процесс, несмотря на HIGH INTEGRITY LEVEL, не имеет прав Администратора:


В своём докладе на Ekoparty Николас предложил следующие шаги для завершения эксплуатации:


Хотя это выглядит просто, требуется много времени на реверс-инжиниринг и отладку.
При более глубоком изучении этой истории векторов атак стало ясно, что отравление кэша контекста активации (Activation Context Cache) использовалось в некоторых эксплойтах. Следовательно, стоит изучить, как ранее проводилась эксплуатация, чтобы получить дополнительный контекст и понимание. Подробности об этой эксплуатации доступны в отчёте Zero Day Initiative Activation Context Cache Poisoning: Exploiting CSRSS for Privilege Escalation.
Использование кэша активации происходит, когда программа собирается загрузить библиотеку, требующую определённой версии.
Например, если приложение собирается загрузить C:\Windows\System32\comctl32.dll, нет гарантии, что comctl32.dll в этом расположении — это та версия, которая нужна приложению. Это базовый пример использования кэша контекстов активации. Программа может отправить запрос серверу CSRSS для обработки новой записи контекста активации для внесения в кэш, чтобы программа могла загрузить нужную версию библиотеки.
Для этого используется так называемый манифест в формате XML. Обычно он встроен как ресурс в EXE или DLL-файл. Альтернативно, Windows ищет файл манифеста в той же папке, где находится исполняемый файл программы.
В упомянутом выше URL есть несколько примеров файлов манифеста, использовавшихся в старых эксплойтах, например, обман системы с целью загрузки библиотеки advapi32.dll из папки, контролируемой атакующим, с помощью техники PATH TRAVERSAL.

Конечно, некоторые использованные векторы атак были исправлены, а также обнаружены новые техники. Кроме того, в октябрьском обновлении 2022 года для Windows 11 22H2 была добавлена новая проверка.
После внедрения этого исправления проверка при регистрации Activation Context (ACTX) может быть обойдена только в том случае, если процесс, добавляющий новую запись в кэш, имеет тот же или более высокий RID, чем процесс, который будет её использовать.
В winnt.h можно увидеть значения RID:

Предложение для обхода этой проверки — создать запрос с Activation Context из процесса CTFMON, где выполняется поддельная DLL. Эта поддельная DLL имеет RID=0x3000, и после добавления записи в кэш процесс TCMSETUP с RID=0x3000 загрузит tapi32.dll.
В ходе моих попыток следовать шагам я перепробовал все возможные комбинации для регистрации ACTX с помощью CreateActCtx. Это оказалось невозможным, так как всегда существовала проверка, которая это предотвращала.
Важно отметить, что эта функция находится в пользовательском режиме и экспортируется из kernel32.dll. Проверки можно обойти, исправив DLL в памяти, что не очень элегантно, но возможно и должно сработать.

Слайд из презентации Николаса предлагает использовать LOW LEVEL. Однако, обращая внимание на смайлик-подмигивание, было ясно, что использование CreateActCtx не является лучшим вариантом при эксплуатации этой ошибки без исправления.

Advanced Local Procedure Call (ALPC) — это механизм межпроцессного взаимодействия, используемый для отправки сообщений на высокой скорости внутри операционной системы Windows. В отличие от стандартного Windows API, ALPC напрямую недоступен для приложений. Вместо этого это внутренний механизм, доступный только компонентам операционной системы Windows. (И нам.)
В ходе дальнейших исследований я заметил, что некоторые старые эксплойты для отравления кэша использовали ALPC для прямого общения с сервером. Пример можно увидеть в статье Филипа Цукермана Activation Contexts—A Love Story.
Функция CsrClientCallServer реализует интерфейс ALPC между процессами Win32 и процессом CSRSS.
Итак, следует предпринять попытку вызова процесса CSRSS, который действует как сервер, с помощью CsrClientCallServer.
Ища примеры в старых эксплойтах, я нашёл страницу на Packet Storm с соответствующим переполнением буфера в куче.
Когда сервер CSRSS вызывается с правильным пакетом, он принимается в функции BaseSrvSxsCreateActivationContextFromMessage, которая принадлежит модулю sxssrv.dll.
Функция имеет только один аргумент: указатель на полученный пакет. Для реверс-инжиниринга я создал пользовательскую структуру TotalMessage.
Структура пакета TotalMessage имеет первые 0x40 байт ЗАГОЛОВКА, за которыми следует встроенное Сообщение контекста активации, структура которого — _BASE_SXS_CREATE_ACTIVATION_CONTEXT_MSG.
Структура TotalMessage показана ниже:

А вот структура _BASE_SXS_CREATE_ACTIVATION_CONTEXT_MSG:

Внутри этой структуры находятся шесть UNICODE_STRINGS, соответствующих языку или CultureFallbacks, AssemblyDirectory, TextualAssemblyIdentity, AssemblyName и две структуры _BASE_MSG_SXS_STREAM, каждая из которых содержит по одному UNICODE_STRING.
Ниже представлена структура _BASE_MSG_SXS_STREAM:

Учитывая сложность создания действительного пакета, принимаемого сервером, стоит подробно описать, как это сделать.
Значение поля Flags внутри _BASE_SXS_CREATE_ACTIVATION_CONTEXT_MSG очень важно, так как существует множество комбинаций. Без правильного значения флага невозможно воспользоваться ошибкой.
Например, возьмём мой код MsCtfMonitor.dll. После множества попыток я пришёл к выводу, что единственное правильное значение flags для этой эксплуатации — 0x41:

Комбинация других значений может привести к неправильному значению флага пути:

Та же структура TotalMessage будет иметь заголовок размером 0x40 байт. Оставшиеся 0x1f8 байт зарезервированы для структуры _BASE_SXS_CREATE_ACTIVATION_CONTEXT_MSG:
struct TotalMessage
{
signed __int64 pad[8];
_BASE_SXS_CREATE_ACTIVATION_CONTEXT_MSG message;
};
Размер для выделения — 0x40+0x1f8:

Затем я собрал строки и выполнил контекст кэша активации для tapi32.dll. Это очень редко используемая DLL, которая загружается процессом TCMSETUP. Он имеет HIGH PRIVILEGES INTEGRITY LEVEL (RID=0x3000) с теми же привилегиями, что и Администратор.

В моём коде DLL вызывается функция CaptureUnicodestring. В итоге она вызывает CsrCaptureMessageString:
NTSTATUS CaptureUnicodeString(LPVOID CaptureBuffer, PSTR OutputString,
PCWSTR String, ULONG Length = 0) {
if (Length == 0) {
Length = lstrlenW(String);
}
return CsrCaptureMessageString(CaptureBuffer, (PCSTR)String, Length * 2,
Length * 2 + 2, OutputString);
}
Этот шаг необходим для правильной подготовки пакета, позволяя системе скопировать строки моего пакета в процесс CSRSS. Это сохраняет строки действительными и заменяет мои указатели на действительные указатели в его контексте.
Я также добавил встроенный XML-манифест с языком "Tasks". Это несуществующий язык, но он станет ключом к эксплуатации (спасибо Нико за это):

Ещё одна важная деталь в моём коде — это когда создаётся CaptureBuffer. Функция CsrAllocateCaptureBuffer имеет аргумент, который определяет, сколько UNICODE_STRINGS она должна обработать и скопировать на сервер.
В моём случае я использовал строки в количестве "4":

Аргумент со значением "4" показан ниже:

Чтобы достичь сервера активации, функция CsrClientCallServer отправляет мой пакет из моей MsCtfMonitor.dll с тем же ApiNumber 0x1001001E, что и в упомянутых выше старых эксплойтах.
В блоге Джеффа Чаппелла содержится больше информации о CsrClientCallServer:

Вот вызов CsrClientCallServer:

А вот пакет для отправки, построенный в моей DLL:

Значение Manifest.Offset указывает на мой встроенный XML-манифест:

Интересная команда для журналирования процесса активации — sxstrace, которая используется в консоли администратора на целевой системе.
Эта команда включает трассировку и сохраняет результаты журнала в sxstrace.etl. (Нажмите ENTER для завершения трассировки.)
sxstrace trace -logfile:sxstrace.etl
Затем сырой файл sxstrace.etl можно преобразовать в читаемый формат:
sxstrace parse -logfile:sxstrace.etl -outfile:sxstrace.txt
Если пакет корректен, он должен достичь функции BaseSrvSxsCreateActivationContextFromMessage в модуле sxssrv процесса csrss. Поэтому при отладке удалённого ядра контекст необходимо переключить на этот процесс. Затем необходимо перезагрузить символы пользовательского режима, чтобы установить на неё точку останова:

Я использовал IDA PRO для отладки ядра с плагином Windbg:

Как только выполнение останавливается на BaseSrvSxsCreateActivationContextFromMessage, RCX указывает на структуру TotalMessage:

После начальных 0x40 байт ЗАГОЛОВКА (заполненных системой некоторыми значениями, такими как PID клиентского процесса и т.д.) видно моё сообщение активации, относящееся к структуре _BASE_SXS_CREATE_ACTIVATION_CONTEXT_MSG:

Обратите внимание, что указатели на строки имеют не те же значения, которые я отправлял:

Но они корректно указывают на строки:

Когда пакет отправлялся от клиента к серверу, система скопировала строки из моего процесса в процесс CSRSS и изменила указатели в моём пакете, чтобы они были действительными в его контексте.
После этого функция BaseSrvSxsCreateActivationContextFromMessage проверяет, являются ли строки допустимыми.

В цикле она проверяет шесть строк, но они успешно проходят проверку. В моём случае я передал только четыре строки, а остальные две равны нулю.
После других незначительных проверок вызывается BaseSrvSxsCreateActivationContextFromStructEx, которая является наиболее важной функцией в процессе активации:

Когда мы доходим до BaseSrvSxsCreateActivationContextFromStructEx, r8 указывает на _BASE_SXS_CREATE_ACTIVATION_CONTEXT_MSG, то есть на сообщение активации:

Она оценивает значение флагов. В моём случае значение было 0x41 по сравнению с 0xD:

Функция test может быть обойдена с помощью опции флага, которая соответствует проверке архитектуры процессора (1).

После этого она получает RID вызывающего процесса и сохраняет для дальнейшего сравнения. В данном случае RID равен 0x3000, так как CTFMON имеет HIGH INTEGRITY LEVEL.

Самая важная часть этой функции — вызов BaseSrvActivationContextCacheLookupEntry:

Она ищет в кэше контекста активации, есть ли какая-либо запись для tapi32.dll.
Она вызывает функцию BaseSrvActivationContextCacheCompareEntries, которая сравнивает определённые части записи сообщения активации со всеми существующими записями в кэше:

Она сравнивает значение LastWriteTime, отправленное в моём пакете, с теми же значениями во всех записях.
Ранее я вычислил это значение с помощью GetFileTime для tapi32.dll и отправил его внутри моего пакета активации:

Так как записи для tapi32.dll нет, сравнения не совпадут. Как и ожидалось, возвращается ошибка 0xC0000225. После этого она проверяет мой ACTX, чтобы определить, подходит ли он для добавления в кэш:

Серверу необходимо прочитать мой встроенный XML-манифест и адрес Manifest.Offset, который на неё указывает. Однако в новом контексте это ещё не действительный указатель. Стоит установить точку останова на этом значении, чтобы увидеть, как и когда мой встроенный XML-манифест читается с использованием этого значения.
Чтобы проверить, где CSRSS читает мой встроенный XML-манифест, отправленный в моём запросе ACTX, следует установить точки останова на Manifest.Offset. Кроме того, следует добавлять точки останова каждый раз, когда выполнение останавливается, если оно копируется в другой адрес.

Выполнение останавливается на точке останова при чтении значения адреса Manifest.Offset.
Оно будет использовать этот адрес для чтения моего встроенного XML-манифеста из процесса CTFMON с помощью NtReadVirtualMemory, так как адрес, помещённый в поле Manifest.Offset, принадлежит этому контексту:

Мой встроенный XML-манифест читается и копируется в целевой буфер:

Переключаемся на контекст процесса CTFMON и проверяем, что мой встроенный XML-манифест находится по адресу Manifest.Offset, который я отправил ранее. В моём случае это было 0x7ff93a261470.

Чтение встроенного XML-манифеста вызывается из SxSGenerateActivationContext. Так как в кэше не найдено ни одной действительной записи, он пытается «сгенерировать» её с использованием встроенного манифеста:

Оттуда начинается разбор моего встроенного XML-манифеста.
Глядя на последний стек вызовов, я решил установить точку останова на вызов RtlReadOutOfProcessMemoryStream, чтобы остановиться, когда буфер будет полностью заполнен.

Теперь можно установить точку останова на доступ к строке "Tasks", чтобы остановиться, когда она будет прочитана или обработана сервером.

Вот строка tasks внутри встроенного XML-манифеста:

Выполнение останавливается несколько раз при чтении и копировании:

Оно останавливается в CharEncoder::wideCharFromUtf8 при преобразовании строки "tasks" в широкие символы:

Затем оно останавливается в XML-парсере:

Он продолжает разбор атрибутов XML, как следует из названия функции parseAttributes.

Затем оно останавливается в memcpy, вызываемой из ValidateElementAttributes:

Можно установить ещё одну точку останова там, где происходит копирование:

Он проверяет атрибут языка, как следует из названия функции SxspValidateLanguageAttribute:

Оно снова останавливается в memcpy, но на этот раз вызывается из SxspCreateAssemblyIdentityfromIdentityElement:

Ещё раз остановка в memcpy, на этот раз вызванной из SxsInsertAssemblyIdentityAttribute+0xc48:
Затем она останавливается в SxsInsertAssemblyIdentityAttribute:

Она вызывает memcpy последний раз, в данном случае из BufferedStream::prepairForInput:

Затем она считывает строку tasks здесь:

Затем она считывает её отсюда:


Она продолжает считывание отсюда:


Названия этих функций привлекли моё внимание. В названии ProbingCandidate содержатся те же слова (probing manifests), которые используются в текстовом лог-файле SXS.

Она снова останавливается здесь:

Далее она использует GetFileAttributesExW для проверки, существует ли первый файл, упомянутый в текстовом логе SXS. Так как его нет, она возвращает ноль.

Порядок проверки файлов можно увидеть в лог-файле:

Второй файл не существует, потому что это путь к tapi32.dll в папке tasks:

Отсюда, похоже, происходит "probing" (зондирование) для tapi32.manifest в tasks:

Затем она доходит до CProbedAssemblyInformation::ProbeManifestExistence:


Она проверяет, существует ли мой файл манифеста в папке tasks. Так как он существует, она возвращает без ошибки:

Итак, tapi32.manifest в папке "tasks" был найден.
Сервер был вынужден искать файл манифеста в подпапке "tasks" папки system32 благодаря моему встроенному XML-манифесту со значением языка "tasks" внутри:


Если продолжать ставить точки останова, чтобы увидеть, где используется путь, она останавливается в EncodingStream::Read, где считывается содержимое файла tapi32.manifest.

Далее она будет разбирать содержимое файла TAPI32.manifest. Если возникнет ошибка, она покажет её в логе SXS TRACE, что упрощает её исправление.

Если мой файл TAPI32.manifest будет разобран правильно, он вернётся в BaseSrvSxsCreateActivationContextFromStructEx без ошибки. Это позволяет избежать вывода сообщения со строкой FAILED.
В моём случае создание контекста активации прошло успешно, используя мой файл TAPI32.manifest.


Затем я дошёл до вызова, где моя запись будет вставлена в кэш.
Он проходит без каких-либо проблем, возвращая ноль. Это правильное значение, и запись с созданным TAPI32.manifest успешно вставляется.

Моя запись включается в кэш активации, и сервер возвращает ответ OK на вызов от
DLL из CTFMON.
Текстовый лог-файл показывает весь процесс.
Он считывает встроенный XML-манифест. Поскольку его язык — "Tasks", он ищет новый файл манифеста в подпапке "Tasks" папки system32, точно так же, как искал бы манифест в подпапке system32 с именем "en-us", если бы язык был установлен в "en-us".

Файл лога SXS показывает сообщение «Activation Context generation succeeded» (Создание контекста активации успешно)!

После того как моя запись ACTX добавлена в кэш, если запустить tcmsetup.exe, он загрузит tapi32.dll и должен использовать мой файл манифеста для загрузки imm32.dll.
Однако всё не так просто, так как он не может загрузить imm32.dll, потому что есть некоторые проверки, которые могут помешать его загрузке.
Проверки выполняются при последующем вызове той же функции BaseSrvSxsCreateActivationContextFromStructEx, поэтому удалите все точки останова и оставьте только одну на ней.
Оттуда мы можем запустить TCMSETUP.EXE из консоли, хотя мой PoC запускает TCMSETUP из MsCtfMonitor.dll после завершения отравления кэша активации:

Она останавливается на точке останова много раз. При каждой остановке смотрите на структуру, на которую указывает r8, чтобы увидеть, соответствует ли она запросу, связанному с tapi32.dll.

После множества остановок для других модулей появляется запрос для TCMSETUP.exe:

Мы видим в стеке вызовов, что он исходит из момента создания процесса. Он вызывается, чтобы проверить, есть ли какая-либо запись в кэше активации для TCMSETUP.
Продолжайте выполнение, пока вызов не придёт для TAPI32.dll. До этого будет несколько вызовов для TCMSETUP.

Наконец, прибывший пакет должен быть очень похож на тот, что был сделан ранее из моей DLL, когда я вставлял запись в кэш. Однако теперь он останавливается, когда TCMSETUP пытается загрузить TAPI32.dll.

В этот момент я заметил некоторые важные значения в этом пакете.

Расширяясь от начала структуры _BASE_SXS_CREATE_ACTIVATION_CONTEXT_MSG на 0x40 байт вверх, присвойте структуру TotalMessage. PID процесса, который делает запрос для TAPI32.dll, — это TCMSETUP, так как он хочет загрузить DLL.

Переключив контекст на процесс TCMSETUP, можно увидеть значение Manifest.Offset, указывающее на какой-то встроенный XML-манифест.


Откройте tapi32.dll в БЛОКНОТЕ, чтобы увидеть, что полученный встроенный XML-манифест совпадает с тем, что включён в файл.
TCMSETUP считывает ресурс файла предварительно, чтобы прочитать манифест и поместить его в пакет как встроенный XML-манифест.
После этого снова выполняется сравнение функцией BaseSrvActivationContextCacheCompareEntries, которая вызывается из BaseSrvSxsCreateActivationContextFromStructEx. Теперь моя запись для tapi32.dll также находится в кэше.

BaseSrvActivationContextCacheCompareEntries вызывается в цикле для сравнения фактического запроса с каждой записью кэша контекста активации (включая мою).
Сначала она сравнивает оба значения LastWriteTime, так как они равны, она продолжает сравнивать другие значения.
Это значение LastWriteTime критично. Если значения различаются, моя кэшированная запись будет отброшена, и моя imm32.dll не будет загружена.
Она идёт дальше и останавливается на следующей проверке.

Теперь она проверяет значение ResourceName, которое должно быть 0x7c в обоих случаях.

Затем она сравнивает язык фактического пакета ACTX, который равен "en-us", с языком моей кэшированной записи. Язык моей кэшированной записи также "en-us".

Мой пакет имеет то же значение языка:

Затем она сравнивает архитектуру процессора, которая в данном случае будет равна 9 в обоих случаях:


Затем она сравнивает оба пути Manifest.path.

Я построил тот же путь без жёсткого кодирования, используя значение системного каталога:

Затем она сравнивает AssemblyDirectory, который также совпадает:


Если все сравнения верны, она возвращает ноль. Это означает, что моя запись найдена в кэше активации и будет использована.
Помните, что когда я впервые отправил свой запрос для добавления записи, сравнение вернуло ошибку, так как в кэше не было записи для TAPI32.dll. Поскольку моя запись была добавлена ранее, теперь она возвращает ноль.
После этого она сравнивает RID процессов TCMSETUP и CTFMON. Так как оба имеют RID = 0x3000, выполнение продолжается.
Полное объяснение патча RID доступно в блоге инициативы Zero Day.

Это код этого патча:

R15 содержит RID вызывающего процесса TCMSETUP = 0x3000, а buffer хранит RID=0x3000 процесса CTFMON.
Как было сказано ранее, Microsoft добавила этот патч проверки RID в октябре 2022 года.
После реализации этого патча, если вы попытаетесь добавить запись tapi32.dll в кэш, используя ту же MsCtfMonitor.dll из ПРОЦЕССА СРЕДНЕГО УРОВНЯ ЦЕЛОСТНОСТИ (0x2000), запись будет добавлена в кэш, но это не сработает. Это связано с тем, что RID вызывающего процесса 0x2000 сохраняется, и когда вы попытаетесь выполнить TCMSETUP с RID=0x3000 для загрузки imm32, RID сравниваются, и запись удаляется.
В этом гипотетическом случае R15 будет содержать RID=0x3000 процесса TCMSETUP, запросившего загрузку tapi32.dll, переменная “buffer” будет хранить RID=0x2000 процесса, добавившего запись в кэш, который имеет СРЕДНИЙ УРОВЕНЬ ЦЕЛОСТНОСТИ.

В новейших версиях Windows отравление кэша не сработает, если процесс, запрашивающий добавление записи, имеет более низкий уровень, чем процесс-исполнитель, и запись удаляется. Предыдущие версии, выпущенные до этого патча, будут работать без проблем с любым RID.

Возвращаясь к нашему случаю, проверка RID пройдена, и оба процесса имеют одинаковый RID=0x3000. Следовательно, запись не удаляется, и выполнение продолжается без ошибок.
Сервер возвращает ответ TCMSETUP. Когда он загружает tapi32.dll, он будет использовать мою запись с файлом tapi32.manifest, которая загрузит imm32.dll из папки tasks.
Это полный путь от LoadLibrary до того места, где TCMSETUP делает запрос в кэш активации
при загрузке tapi32.dll.

Именно BasepCreateActCtx отправляет запрос на сервер CSRSS. Нужно попытаться увидеть, когда в итоге загружается модуль IMM32.dll.
Глядя на kernel32.dll, она вызывает CsrBasepCreateActCtxCommon. Внутри находится вызов сервера, подобный тому, что был сделан из моей DLL для вставки моей записи в кэш.

Она использует тот же ApiNumber, что и у меня.
При выполнении TCMSETUP можно установить точку останова там, когда она возвращается от сервера после принятия моего файла tapi32.manifest.

Это полный стек вызовов до момента, когда происходит вызов сервера в CsrBasepCreateActCtxCommon.

Точки останова устанавливаются на возврате из некоторых функций стека вызовов.

Когда она останавливается, можно заметить, что imm32.dll была загружена из папки "tasks":

Проверить это можно с помощью PROCESS MONITOR, что TCMSETUP загружает IMM32.dll из папки "tasks".

Только что выполненный процесс CMD имеет привилегии HIGH.

Кроме того, он имеет те же привилегии, что и Администратор.
С этими привилегиями теперь мы можем установить любую программу, требующую повышения до администратора, и записывать в любую папку. Например, запись в SYSTEM32 или любую папку установки программы, как показано в ВИДЕО-ДЕМОНСТРАЦИИ ниже.
Вот привилегии до эксплуатации (Уровень целостности Medium, не Администратор):

А вот привилегии после эксплуатации (Уровень целостности High, ПОЛНЫЙ Администратор):

В этот момент это хорошая возможность легко повысить привилегии до SYSTEM, поместив созданную DLL в системную папку.


Посмотреть видео здесь а функциональное доказательство концепции здесь
Я отправил специально сформированное сообщение ACTX на сервер CSRSS.
Это сообщение ACTX содержало встроенный XML-манифест со смещением, указывающим на него.
Когда сервер получал его, он использовал это смещение для чтения встроенного XML-манифеста из контекста процесса CTFMON.
Встроенный XML-манифест был разобран. Если он принимался, он пытался загрузить второй внешний манифест из внешней папки.
Папка для чтения зависела от поля языка в встроенном XML-манифесте, которое я контролировал.
В моём случае встроенный XML-манифест имел язык "tasks". По этой причине он искал внешний манифест в подкаталоге "tasks" папки system32 и нашёл его.
Он разобрал созданный мной файл tapi32.manifest и принял его, разрешив загрузку внешней IMM32.dll из той же папки "tasks".
Спасибо Николасу Экономиу, так как его презентация стала отправной точкой для моего исследования и публикации этого сообщения в блоге.
Рикардо Нарвая