
Writeup técnico e PoC para CVE-2024-6769, encadeando DLL hijacking com envenenamento de cache de ativação para escalar de integridade média para alta em sistemas Windows.
Este blog aborda duas falhas encadeadas: A primeira etapa é um bug de sequestro de DLL causado pelo remapeamento da unidade ROOT, e a segunda etapa é um bug de Envenenamento do Cache de Ativação gerenciado pelo servidor CSRSS.
A primeira etapa foi apresentada em detalhes na Ekoparty 2023 na apresentação chamada "I'm High" por Nicolás Economou da BlueFrost Security. Ele explicou como explorar a vulnerabilidade que, na época, ainda não havia sido corrigida pela Microsoft. Isso permitia que um usuário de INTEGRIDADE MÉDIA fosse elevado a ter PRIVILÉGIOS ALTOS limitados, mas sem o acesso completo para ser um Administrador completo.
A segunda etapa não foi apresentada naquela conferência, embora algumas etapas tenham sido sugeridas para iniciar a pesquisa.
Para começar, revisaremos a primeira etapa para fornecer um contexto introdutório. A partir daí, mergulharemos na minha pesquisa sobre a segunda etapa, detalhando a obtenção da escalada completa de INTEGRIDADE ALTA limitada para Administrador completo. Isso inclui uma Prova de Conceito funcional completa para ambas as etapas para todas as versões do Windows, que foi testada com sucesso no Windows 10, Windows 11, Windows Server 2022 e Windows Server 2019 com todas as atualizações aplicadas.

O único requisito para esta etapa é que o processo inicial comece em um NÍVEL DE INTEGRIDADE MÉDIA e o usuário pertença ao grupo Administrador.
A primeira etapa da exploração pode ser resumida nos seguintes passos:
Por exemplo: remapear o disco de
"C:" para "C:\users\public"
Isso também remapeará a pasta "system32" de
"C:\windows\system32" para "C:\users\public\windows\system32"
Um desses programas afetados é o CTFMON, que é executado em um NÍVEL DE INTEGRIDADE ALTO, mas sem privilégios de Administrador.
Normalmente, ele tenta carregar o módulo chamado MsCtfMonitor.dll da pasta system32 real, mas como a unidade ROOT foi remapeada, ele procura por MsCtfMonitor.dll em nosso system32 falso controlado, onde podemos criar e colocar uma DLL manipulada com o mesmo nome.
Neste ponto, ao colocar nossa versão de MsCtfMonitor.dll na pasta system32 falsa, sua função DoMsCtfMonitor é chamada e executa nosso código em um NÍVEL DE INTEGRIDADE ALTO.




Ao mesmo tempo, podemos corroborar que o processo, apesar de estar em um NÍVEL DE INTEGRIDADE ALTO, não possui privilégios de Administrador:


Em sua apresentação na Ekoparty, Nicolas sugeriu os seguintes passos para completar a exploração:


Embora pareça simples, requer muito tempo de engenharia reversa e depuração.
Ao cavar um pouco mais nesta história do vetor de ataque, ficou claro que o envenenamento do Cache de Contexto de Ativação tem sido usado em algumas explorações. Consequentemente, vale a pena aprender como a exploração foi feita anteriormente para fornecer contexto e insights adicionais. Detalhes sobre esta exploração estão disponíveis no artigo do Zero Day Initiative, Activation Context Cache Poisoning: Exploiting CSRSS for Privilege Escalation.
O uso do cache de ativação ocorre quando um programa vai carregar uma biblioteca que requer uma versão específica.
Por exemplo, se um aplicativo vai carregar C:\Windows\System32\comctl32.dll, não há garantia de que a comctl32.dll naquele local seja a versão que o aplicativo precisa. Este é um caso de uso básico do Cache de Contextos de Ativação. O programa pode enviar uma solicitação ao servidor CSRSS para processar uma nova Entrada de Contexto de Ativação a ser inserida no cache, para que este programa possa carregar a versão específica da biblioteca necessária.
Para este propósito, é usado o chamado manifesto, que está em formato XML. Geralmente é incorporado como um recurso em um arquivo EXE ou DLL. Alternativamente, o Windows procurará um arquivo de manifesto na mesma pasta onde o executável do programa está localizado.
A URL mencionada acima tem alguns exemplos de arquivos de manifesto usados por explorações antigas, como enganar o sistema para carregar a biblioteca advapi32.dll de um diretório controlado pelo atacante que foi alcançado através da técnica de PATH TRAVERSAL.

Claro, alguns vetores de ataque usados foram corrigidos, e algumas novas técnicas foram descobertas. Além disso, na correção de outubro de 2022 para o Windows 11 22H2, uma nova verificação foi adicionada.
Após esta correção ser implementada, a verificação quando um Contexto de Ativação (ACTX) é registrado só pode ser contornada se o processo que adiciona a nova entrada no cache tiver o mesmo RID ou superior ao processo que a utilizará.
Em winnt.h podemos ver os valores RID:

A proposta para contornar esta verificação é criar uma solicitação com um Contexto de Ativação do processo CTFMON onde a DLL manipulada é executada. Esta DLL manipulada tem RID=0x3000 e após a entrada ser adicionada ao cache, TCMSETUP com RID=0x3000 carregará tapi32.dll.
Durante minha tentativa de seguir os passos, fiz todas as combinações possíveis para registrar o ACTX usando CreateActCtx. Isso se mostrou impossível, pois sempre havia uma verificação que evitava.
É importante notar que esta função está localizada no modo de usuário, exportada por kernel32.dll. As verificações podem ser evitadas corrigindo a DLL na memória, o que não é muito elegante, mas é possível e deve funcionar.

O slide da apresentação de Nicolas sugere o uso de BAIXO NÍVEL. No entanto, notando o rosto piscando, ficou claro que usar CreateActCtx não é a melhor opção ao explorar este bug sem uma correção.

Uma Chamada de Procedimento Local Avançada (ALPC) é um mecanismo de comunicação entre processos usado para enviar mensagens em alta velocidade dentro do sistema operacional Windows. Ao contrário da API padrão do Windows, ALPC não está disponível diretamente para aplicativos. Em vez disso, é um mecanismo interno que só pode ser acessado por componentes do sistema operacional Windows. (E por nós.)
Após pesquisar mais, notei que algumas explorações antigas de Envenenamento de Cache usavam ALPC para se comunicar diretamente com o servidor. Um exemplo pode ser visto no artigo de Philip Tsukerman, Activation Contexts—A Love Story.
A função CsrClientCallServer implementa a interface ALPC entre processos Win32 e o processo CSRSS.
Então, uma tentativa de chamada deve ser feita ao processo CSRSS que atua como servidor usando CsrClientCallServer.
Ao procurar exemplos em explorações antigas, encontrei uma página no Packet Storm sobre um problema de estouro de buffer de heap relevante.
Quando o servidor CSRSS é chamado com o pacote correto, ele é recebido na função BaseSrvSxsCreateActivationContextFromMessage, que pertence ao módulo sxssrv.dll.
A função tem apenas um argumento: o ponteiro para o pacote recebido. Para fazer engenharia reversa, criei uma estrutura TotalMessage personalizada.
O pacote da estrutura TotalMessage tem seus primeiros 0x40 bytes de CABEÇALHO, seguidos pela Mensagem de Contexto de Ativação incorporada, cuja estrutura é _BASE_SXS_CREATE_ACTIVATION_CONTEXT_MSG.
A estrutura TotalMessage pode ser vista abaixo:

E aqui está a estrutura _BASE_SXS_CREATE_ACTIVATION_CONTEXT_MSG:

Dentro desta estrutura existem seis UNICODE_STRINGS correspondentes ao idioma ou CultureFallbacks, AssemblyDirectory, TextualAssemblyIdentity, AssemblyName, e duas estruturas _BASE_MSG_SXS_STREAM que contêm uma UNICODE_STRING cada uma dentro.
Abaixo está a estrutura _BASE_MSG_SXS_STREAM:

Dada a dificuldade em criar um pacote válido aceito pelo servidor, vale a pena detalhar como fazê-lo.
O valor do campo Flags dentro de _BASE_SXS_CREATE_ACTIVATION_CONTEXT_MSG é muito importante, pois existem muitas combinações. Sem o valor de flag correto, o bug não pode ser explorado.
Por exemplo, pegue o código da minha MsCtfMonitor.dll. Após muitas tentativas, concluí que o único valor de flags correto para esta exploração de bug é 0x41:

A combinação de valores diferentes pode resultar em um valor de flag de caminho errado:

A mesma estrutura TotalMessage terá um cabeçalho com tamanho=0x40 bytes. Os 0x1f8 bytes restantes são reservados para a estrutura _BASE_SXS_CREATE_ACTIVATION_CONTEXT_MSG:
struct TotalMessage
{
signed __int64 pad[8];
_BASE_SXS_CREATE_ACTIVATION_CONTEXT_MSG message;
};
O tamanho para alocação é 0x40+0x1f8:

Em seguida, montei as strings e realizei um Contexto de Cache de Ativação para tapi32.dll. Esta é uma DLL muito raramente usada que é carregada por um processo chamado TCMSETUP. Ele tem um NÍVEL DE INTEGRIDADE DE PRIVILÉGIOS ALTOS (RID=0x3000) com os mesmos privilégios de um Administrador.

No meu código DLL, a função CaptureUnicodestring é chamada. Isso acaba chamando 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);
}
Esta etapa é necessária para preparar o pacote corretamente, permitindo que o sistema copie as strings do meu pacote para o processo CSRSS. Isso mantém as strings válidas e substitui meus ponteiros por ponteiros válidos em seu contexto.
Também adicionei um manifesto XML incorporado, com o idioma "Tasks" nele. Este é um idioma inexistente, mas será a chave para a exploração (Créditos ao Nico por isso):

Outro detalhe importante no meu código é quando CaptureBuffer é criado. A função CsrAllocateCaptureBuffer tem um argumento que define quantas UNICODE_STRINGS ela deve gerenciar e copiar para o servidor.
No meu caso, usei "4" strings:

O argumento com valor "4" é mostrado abaixo:

Para alcançar o servidor de ativação, a função CsrClientCallServer enviará meu pacote da minha MsCtfMonitor.dll com o mesmo ApiNumber 0x1001001E das explorações antigas mencionadas acima.
O blog de Geoff Chappell fornece mais detalhes sobre CsrClientCallServer:

Aqui está a chamada para CsrClientCallServer:

E aqui está o pacote a ser enviado, construído na minha DLL:

O valor Manifest.Offset aponta para meu manifesto XML incorporado:

Um comando interessante para registrar o processo de ativação é sxstrace, que é usado em um console de administrador dentro do alvo.
Este comando ativa o rastreamento e salva os resultados do log em sxstrace.etl. (Pressione ENTER para finalizar o rastreamento.)
sxstrace trace -logfile:sxstrace.etl
O arquivo sxstrace.etl bruto pode então ser convertido em um formato legível:
sxstrace parse -logfile:sxstrace.etl -outfile:sxstrace.txt
Se o pacote estiver correto, ele deve chegar à função BaseSrvSxsCreateActivationContextFromMessage no módulo sxssrv do processo csrss. Então, ao depurar o kernel remoto, o contexto precisa ser alterado para este processo. Em seguida, os símbolos do modo de usuário precisam ser recarregados para colocar um ponto de interrupção nele:

Usei IDA PRO para depurar o kernel com o plugin Windbg:

Uma vez que ele para em BaseSrvSxsCreateActivationContextFromMessage, RCX apontará para a estrutura TotalMessage:

Após os primeiros 0x40 bytes de CABEÇALHO (preenchidos pelo sistema com alguns valores como o PID do processo cliente, etc...), minha mensagem de ativação que pertence à estrutura _BASE_SXS_CREATE_ACTIVATION_CONTEXT_MSG pode ser vista:

Observe que os ponteiros para strings não têm o mesmo valor de quando os enviei:

Mas eles apontavam corretamente para as strings:

Quando o pacote foi enviado do cliente para o servidor, o sistema copiou as strings do meu processo para o processo CSRSS e alterou os ponteiros no meu pacote para serem válidos em seu contexto.
Depois disso, a função BaseSrvSxsCreateActivationContextFromMessage verifica se as strings são válidas.

Em um loop, ela verifica seis strings, mas passa perfeitamente na verificação. No meu caso, passei apenas quatro strings e as outras duas são zero.
Após outras verificações menores, ela chama BaseSrvSxsCreateActivationContextFromStructEx, que é a função mais importante no processo de ativação:

Ao chegar em BaseSrvSxsCreateActivationContextFromStructEx, r8 apontará para _BASE_SXS_CREATE_ACTIVATION_CONTEXT_MSG, que é a mensagem de ativação:

Ela avalia o valor das flags. No meu caso, o valor era 0x41 contra 0xD:

A função test pode ser contornada usando a opção de flag que corresponde a validar a arquitetura do processador (1).

Depois disso, ela obtém o RID do processo chamador e armazena para comparação posterior. Neste caso, o RID é 0x3000 pois CTFMON tem NÍVEL DE INTEGRIDADE ALTO.

A parte mais importante desta função é a chamada para BaseSrvActivationContextCacheLookupEntry:

Ela pesquisa o Cache de Contexto de Ativação para determinar se existe alguma entrada para tapi32.dll.
Ela chama uma função chamada BaseSrvActivationContextCacheCompareEntries, que compara certas partes da Mensagem de Ativação com todas as entradas existentes no cache:

Ela compara o valor LastWriteTime enviado no meu pacote com o mesmo valor em todas as entradas.
Eu havia calculado anteriormente este valor usando GetFileTime em tapi32.dll e o enviei dentro do meu pacote de ativação:

Como não há entrada para tapi32.dll, as comparações não corresponderão. Como esperado, ela retorna erro 0xC0000225. Depois disso, ela verificará meu ACTX para ver se é adequado para ser adicionado ao cache:

O servidor precisa ler meu manifesto XML incorporado, e o endereço Manifest.Offset que apontava para ele. No entanto, neste novo contexto, ainda não é um ponteiro válido. Vale a pena colocar um ponto de interrupção neste valor para ver como e quando meu manifesto XML incorporado é lido usando este valor.
Para verificar onde o CSRSS lê meu manifesto XML incorporado que foi enviado na minha solicitação ACTX, pontos de interrupção devem ser colocados em Manifest.Offset. Além disso, pontos de interrupção devem ser adicionados toda vez que ele parar, se copiar para outro endereço.

Ele para no ponto de interrupção ao ler o valor do endereço Manifest.Offset.
Ele usará este endereço para ler meu manifesto XML incorporado do processo CTFMON usando NtReadVirtualMemory, já que o endereço colocado no campo Manifest.Offset pertence àquele contexto:

Meu manifesto XML incorporado é lido e copiado para o buffer de destino:

Alterne para o contexto do processo CTFMON e verifique se meu Manifesto XML Incorporado está no endereço Manifest.Offset que enviei anteriormente. No meu caso, era 0x7ff93a261470.

O Manifesto XML Incorporado lido é chamado de SxSGenerateActivationContext. Como não é encontrado em nenhuma entrada válida no cache, ele tenta "gerá-lo" usando o manifesto incorporado:

A partir daí, ele começa a analisar meu Manifesto XML Incorporado.
Olhando para a última pilha de chamadas, decidi colocar um ponto de interrupção na chamada para RtlReadOutOfProcessMemoryStream para parar quando o buffer estivesse completamente preenchido.

Agora um ponto de interrupção pode ser colocado no acesso à string "Tasks" para parar quando for lida ou processada pelo servidor.

Aqui está a string tasks dentro do manifesto XML incorporado:

Ela para várias vezes lendo e copiando:

Ela para em CharEncoder::wideCharFromUtf8 quando converte a string “tasks” para caractere largo:

Ela então para no analisador XML:

Ela continua analisando os atributos XML, como o nome da função parseAttributes sugere.

Então, ela para em memcpy chamado de ValidateElementAttributes:

Outro ponto de interrupção pode ser colocado onde ela copia:

Ela valida o atributo de idioma, como o nome da função SxspValidateLanguageAttribute sugere:

Ela para novamente em memcpy, mas chamado de SxspCreateAssemblyIdentityfromIdentityElement:

Mais uma vez, ela para em memcpy, desta vez chamado de SxsInsertAssemblyIdentityAttribute+0xc48:
Em seguida, para em SxsInsertAssemblyIdentityAttribute:

Ele chama memcpy uma última vez, neste momento a partir de BufferedStream::prepairForInput:

Em seguida, lê a string tasks aqui:

Depois, lê-a a partir daqui:


Continua lendo a partir daqui:


Os nomes dessas funções chamaram minha atenção. No nome ProbingCandidate, ele inclui as mesmas palavras (probing manifests) usadas no arquivo de log de texto SXS.

Para novamente aqui:

Em seguida, usa GetFileAttributesExW para verificar se o primeiro arquivo mencionado no log de texto SXS existe. Como não existe, retorna zero.

A ordem da verificação do arquivo pode ser vista no arquivo de log:

O segundo arquivo não existe porque é o caminho na pasta tasks para tapi32.dll:

A partir daí, parece estar “procurando” por tapi32.manifest em tasks:

Então atinge CProbedAssemblyInformation::ProbeManifestExistence:


Verifica se meu arquivo de manifesto existe na pasta tasks. Como existe, retorna sem erro:

Bem, o tapi32.manifest na pasta “tasks” foi encontrado.
O servidor foi forçado a procurar um arquivo de manifesto na subpasta “tasks” de system32 pelo meu manifesto XML embutido com o valor de idioma “tasks” dentro:


Se os pontos de interrupção continuarem sendo colocados para ver onde ele usa o caminho, ele para em EncodingStream::Read onde o conteúdo do arquivo tapi32.manifest é lido.

Em seguida, fará o parsing do conteúdo do arquivo TAPI32.manifest. Se houver um erro, ele será mostrado no log SXS TRACE, o que facilita a correção.

Se meu arquivo TAPI32.manifest for analisado corretamente, ele retornará para BaseSrvSxsCreateActivationContextFromStructEx sem erro. Isso evita imprimir uma mensagem com a string FAILED.
No meu caso, a Geração do Contexto de Ativação foi bem-sucedida, usando meu arquivo TAPI32.manifest.


Então alcancei a chamada onde minha entrada será inserida no cache.
É passada sem qualquer problema, retornando zero. Este é o valor correto e a entrada com o TAPI32.manifest manipulado é inserida com sucesso.

Minha entrada é incluída no cache de ativação e o servidor retorna uma resposta OK para a chamada da DLL do CTFMON.
O arquivo de log txt mostra o processo completo.
Ele lê o manifesto XML embutido. Como seu idioma é "Tasks", ele procura um novo arquivo de manifesto na subpasta "Tasks" de system32, exatamente como procuraria um manifesto na subpasta de system32 chamada "en-us" se o idioma estivesse definido como "en-us."

O arquivo de log txt do SXS mostra a mensagem “Geração do Contexto de Ativação bem-sucedida”!

Após minha entrada ACTX ser adicionada ao cache, se tcmsetup.exe for executado, ele carregará tapi32.dll e deverá usar meu arquivo de manifesto para carregar imm32.dll.
No entanto, não é tão simples, pois não pode carregar imm32.dll porque existem algumas verificações que podem impedir seu carregamento.
As verificações são feitas em uma chamada posterior para a mesma função BaseSrvSxsCreateActivationContextFromStructEx, então remova todos os pontos de interrupção e deixe apenas um nela.
A partir daí, podemos executar TCMSETUP.EXE de um console, embora meu PoC execute TCMSETUP a partir de MsCtfMonitor.dll após o envenenamento do Cache de Ativação ser concluído:

Para no ponto de interrupção muitas vezes. Cada vez que para, observe a estrutura apontada por r8 para ver se corresponde a uma solicitação relacionada a tapi32.dll.

Após muitas paradas para outros módulos, aparece uma solicitação para TCMSETUP.exe:

Vemos na pilha de chamadas que vem do momento em que o processo é criado. É chamado para verificar se tem alguma entrada no cache de ativação para TCMSETUP.
Continue executando até que a chamada chegue para TAPI32.dll. Antes disso, haverá várias chamadas para TCMSETUP.

Finalmente, o pacote que chega deve ser muito semelhante ao feito anteriormente pela minha DLL quando inseri a entrada no cache. No entanto, agora ele para quando TCMSETUP tenta carregar TAPI32.dll.

Neste ponto, notei alguns valores importantes neste pacote.

Estendendo-se do início da estrutura _BASE_SXS_CREATE_ACTIVATION_CONTEXT_MSG 0x40 bytes para cima, atribua a estrutura TotalMessage. O PID do processo que faz a solicitação para TAPI32.dll é TCMSETUP, pois quer carregar a DLL.

Alterando o contexto para o processo TCMSETUP, o valor de Manifest.Offset pode ser visto apontando para algum manifesto XML embutido.


Abra tapi32.dll no NOTEPAD para ver que o manifesto XML embutido recebido é o mesmo que o incluído no arquivo.
TCMSETUP lê o recurso do arquivo anteriormente para ler o manifesto e colocá-lo no pacote como o manifesto XML embutido.
Depois disso, a comparação é realizada novamente pela função BaseSrvActivationContextCacheCompareEntries, que é chamada de BaseSrvSxsCreateActivationContextFromStructEx. Agora minha entrada para tapi32.dll também está no cache.

BaseSrvActivationContextCacheCompareEntries é chamada dentro de um ciclo para comparar a solicitação real com cada entrada do Cache de Contexto de Ativação (como a minha).
No início, compara ambos os valores de LastWriteTime, como são iguais, continua comparando mais valores.
Este valor LastWriteTime é crucial. Se houver valores diferentes, descartará minha entrada em cache e minha imm32.dll não será carregada.
Segue adiante e para na próxima verificação.

Agora, verifica o valor ResourceName que deve ser 0x7c em ambos.

Em seguida, compara o idioma do pacote ACTX real, que é "en-us", com o idioma da minha entrada em cache. O idioma da minha entrada em cache também é "en-us".

Meu pacote tem o mesmo valor de idioma:

Em seguida, compara a arquitetura do processador, que neste caso será 9 em ambos os casos:


Então, compara ambos Manifest.path.

Construí o mesmo caminho sem hardcoding usando o valor do Diretório do Sistema:

Então compara o AssemblyDirectory, que também é o mesmo:


Se todas as comparações estiverem corretas, retorna zero. Isso significa que encontrou minha entrada no cache de ativação e ela será usada.
Lembre-se de que quando enviei minha solicitação pela primeira vez para adicionar a entrada, a comparação retornou um erro, pois não havia entrada no cache para TAPI32.dll. Como minha entrada foi adicionada anteriormente, agora retorna zero.
Depois disso, compara os RIDs de TCMSETUP com CTFMON, como ambos têm RID = 0x3000 o processo continua.
Uma explicação completa do patch de RID está disponível em um blog da Zero Day Initiative.

Este é o código para este patch:

R15 tem o RID do chamador TCMSETUP = 0x3000 e o buffer armazenou o RID=0x3000 do processo CTFMON.
Como foi dito anteriormente, a Microsoft adicionou este patch de verificação de RID em outubro de 2022.
Após esse patch ser implementado, se você tentar adicionar a entrada tapi32.dll ao cache usando o mesmo MsCtfMonitor.dll de um PROCESSO DE NÍVEL DE INTEGRIDADE MÉDIO (0x2000), a entrada será adicionada ao cache, mas falhará. Isso ocorre porque o RID do processo chamador 0x2000 é armazenado e quando você tenta executar TCMSETUP com RID=0x3000 para carregar imm32, os RIDs são comparados e a entrada é removida.
Nesse caso hipotético, R15 terá o RID=0x3000 do processo TCMSETUP que solicitou o carregamento de tapi32.dll, a variável “buffer” terá armazenado o RID=0x2000 do processo que adicionou a entrada ao cache que tem um NÍVEL DE INTEGRIDADE MÉDIO.

Nas versões mais recentes do Windows, o envenenamento de cache não funcionará se o processo que solicita a adição da entrada for inferior ao executor e a entrada for removida. Versões anteriores lançadas antes deste patch funcionarão sem problemas com qualquer RID.

Voltando a este caso, a verificação de RID é aprovada e ambos os processos têm o mesmo RID=0x3000. Consequentemente, a entrada não é excluída e continua sem erros.
O servidor retorna a resposta para TCMSETUP. Quando carrega tapi32.dll, usará minha entrada com o arquivo tapi32.manifest, que carregará imm32.dll da pasta tasks.
Este é o caminho completo desde LoadLibrary até onde TCMSETUP faz a solicitação ao cache de ativação
ao carregar tapi32.dll.

BasepCreateActCtx é quem faz sua solicitação ao servidor CSRSS. É necessário tentar ver quando ele acaba carregando o módulo IMM32.dll.
Olhando para kernel32.dll, ele chama CsrBasepCreateActCtxCommon. Dentro, há uma chamada de servidor semelhante à feita pela minha DLL para inserir minha entrada no cache.

Ele usa o mesmo ApiNumber que o meu.
Ao executar TCMSETUP, um ponto de interrupção pode ser colocado lá quando retornar do servidor, após meu arquivo tapi32.manifest ser aceito.

Esta é toda a pilha de chamadas até que a chamada ao servidor em CsrBasepCreateActCtxCommon seja produzida.

Pontos de interrupção são colocados no retorno de algumas funções da pilha de chamadas.

Quando para, pode-se observar que imm32.dll foi carregada da pasta "tasks":

A validação pode ser alcançada usando PROCESS MONITOR que TCMSETUP carrega IMM32.dll da pasta "tasks".

O processo CMD recém-executado tem privilégios ALTOS.

Além disso, tem os mesmos privilégios de Administrador.
Com esses privilégios, agora podemos instalar qualquer programa que precise de elevação para administrador e escrever em qualquer pasta. Por exemplo, escrever em SYSTEM32 ou em qualquer pasta de instalação de programa, como pode ser visto na DEMONSTRAÇÃO EM VÍDEO abaixo.
Aqui estão os privilégios antes da exploração (Nível de Integridade Médio, não Administrador):

E agora aqui estão os privilégios após a exploração (Nível de Integridade Alto, Administrador TOTAL):

Neste ponto, é uma boa oportunidade para elevar facilmente para SYSTEM, colocando alguma DLL manipulada em uma pasta do sistema.


Assista ao vídeo aqui e a Prova de Conceito funcional está aqui
Enviei uma mensagem ACTX manipulada para o servidor CSRSS.
Esta mensagem ACTX tinha um Manifesto XML Embutido com um offset que apontava para ele.
Quando o servidor a recebeu, usou esse offset para ler o manifesto XML embutido do contexto do processo CTFMON.
O manifesto XML embutido foi analisado. Se aceito, tentaria carregar um segundo manifesto externo de uma pasta externa.
A pasta para leitura dependia do campo de idioma no manifesto XML embutido controlado por mim.
No meu caso, o manifesto XML embutido tinha “tasks” como idioma. Por essa razão, ele procurou no subdiretório “tasks” de system32 por um manifesto externo e o encontrou.
Ele analisou o arquivo tapi32.manifest criado por mim e o aceitou, permitindo carregar a IMM32.dll externa da mesma pasta “tasks”.
Agradeço a Nicolas Economou, pois sua apresentação foi o ponto de partida para minha pesquisa e a publicação deste blogpost.
Ricardo Narvaja