Skip to content
KitploitKITPLOIT
FerramentasBlog
Enviar
FerramentasBlog
Enviar

Ferramentas de Hacking, PenTest e Cibersegurança para o seu Arsenal de Segurança!

Kitploit é um diretório de ferramentas de hacking, cibersegurança e pentesting. Descubra as últimas atualizações de projetos para encontrar vulnerabilidades, analisar sistemas, automatizar testes e fortalecer sua segurança.

··Feeds·Contato·Privacidade·© 2026 Kitploit

Diretório de Ferramentas

Categorias

Ver todas as categorias
Loading categories
CVE-2022-37969 — Exploit de prova de conceito para CVE-2022-37969, uma escalada de privilégio local do driver do Windows Common Log File System. Demonstra heap spray, roubo de token e gravação arbitrária no kernel para obter privilégios de SYSTEM. | Kitploit
Ferramentas/GitHubGitHub/fortra/cve-2022-37969
Escalada de PrivilégiosForensia de MemóriaAnálise de VulnerabilidadesExploraçãoExploração de Binários
GitHubfortra/cve-2022-37969

CVE-2022-37969

Exploit de prova de conceito para CVE-2022-37969, uma escalada de privilégio local do driver do Windows Common Log File System. Demonstra heap spray, roubo de token e gravação arbitrária no kernel para obter privilégios de SYSTEM.

Ver Repositório
135381há 3 anosRevisado pelo Kitploit

Mais Populares

Ver todos →

Descubra as ferramentas mais usadas pela nossa comunidade.

Explore todas as ferramentas

Navegue pela nossa coleção de ferramentas

Ver todas as ferramentas →
Compartilhar

CVE-2022-37969 Windows Local Privilege Escalation PoC

autores: Ricardo Narvaja & Daniel Kazimirow (Solid)

Apenas para fins de demonstração. O exploit completo funciona em sistemas Windows 11 21H2 vulneráveis.

PoC funcional baseado em informações publicadas anteriormente pela Zscaler

Confira o writeup Understanding the CVE-2022-37969 Windows Common Log File System Driver Local Privilege Escalation.

Uso

Understanding the CVE-2022-37969 Windows Common Log File System Driver Local Privilege Escalation.

Passo a passo da exploração:

  • Criando o arquivo de log BLF inicial
    • Criando vários arquivos de log BLF aleatórios
    • Elaborando o arquivo de log inicial
    • Realizando um Heap Spray controlado
    • Preparando os métodos CreatePipe() / NtFsControlFile()
    • Uma vez que a memória foi preparada, irá acionar a vulnerabilidade
    • Lendo o Token do Sistema
    • Validando o token
    • Sobrescrever o token do nosso processo com o do sistema
    • Executando o processo como sistema
    • Revertendo o Patch: Analisando as estruturas
    • Corrompendo o ponteiro "pContainer"
    • Revisitando o Patch
    • Corrompendo o SignatureOffset
    • Corrompendo mais valores
    • Controlando as funções que permitem ler o token SYSTEM
    • Escrever nosso próprio processo para alcançar a elevação de privilégio local
    • Código fonte do PoC

O cenário utilizado aqui foi Windows 11 21H2 (OS Build 22000.918) clfs.sys v10.0.22000.918

Criando o arquivo de log BLF inicial

O primeiro passo é criar um arquivo chamado MyLog.blf na pasta pública (%public%), usando a função CreateLogFile():

Criando vários arquivos de log BLF aleatórios

Em seguida, cria vários arquivos de log com nomes aleatórios usando um loop.

E dentro do loop, chama nossa função getBigPoolInfo():

Ele chama a função NtQuerySystemInformation(), com 0x42 (66 decimal) como primeiro argumento, isso retornará em v5 as informações sobre os raids feitos no bigpool, cuja estrutura é do tipo SYSTEM_BIGPOOL_INFORMATION.

Temos que chamar essa função duas vezes. A primeira retornará um erro, mas nos dará o tamanho correto do buffer para chamar a segunda vez e obter as informações desejadas.

Interface gráfica do usuário, Aplicação Descrição gerada automaticamente

v5 receberá as informações da estrutura SYSTEM_BIG_POOL_INFORMATION.

O número de alocações no bigpool é armazenado no primeiro campo chamado Count, no segundo campo há um array de estruturas SYSTEM_BIGPOOL_ENTRY.

Então percorremos todas as estruturas em busca da tag "Clfs" e do tamanho 0x7a00.

Armazena em um array chamado kernelAddrArray o VirtualAddress, que é o primeiro campo de cada estrutura que possui a tag CLFS e tamanho 0x7a00. A partir de agora, os pools que atendem ambas as condições serão chamados de “pools certos”.

Além de armazenar cada pool certo no array, armazena o último pool certo encontrado no conteúdo da variável a2, que é usada como argumento da função.

Interface gráfica do usuário, Texto, Aplicação Descrição gerada automaticamente

Dessa forma, a2 sempre aponta para o último pool certo com a tag CLFS e tamanho 0x7a00 criado.

A variável v26 sempre armazena o pool certo anterior encontrado, pois é igual a v24 (v26=v24), antes de chamar getBigPoolinfo(), mas v24 é atualizado ao sair dessa chamada com o último pool certo encontrado, e v26 permanece com o pool certo anterior encontrado.

Interface gráfica do usuário, Texto, Aplicação Descrição gerada automaticamente

Em seguida, subtrai ambos os endereços e, caso o resultado seja negativo, inverte os operandos para que seja sempre positivo.

Interface gráfica do usuário, Texto, Aplicação Descrição gerada automaticamente

Dessa forma, em v32 será armazenada a diferença entre o VirtualAddress dos dois últimos pools certos encontrados.

Depois faz algo semelhante; neste caso, v23 é inicialmente zero, então faz v23 = v32 na primeira vez.

Na próxima vez no loop, v23 ainda tem o mesmo valor e não é zero, então interrompe e vai para cá.

V32 tem a última diferença e v23 a anterior; se forem iguais, sai e incrementa um, mas redefine o contador para zero.

A ideia é encontrar 6 comparações consecutivas de tags CLFS e tamanho 0x7a00 cujas diferenças sejam iguais, e essa diferença será 0x11000. Veremos ao executar que quando encontrar 6 (pois começa do zero) consecutivos com distâncias iguais, dará esse valor de diferença entre eles.

Texto Descrição gerada automaticamente

Ali vemos que ele encontrou 6 consecutivos e saiu do loop de criação de arquivos de log.

Na pasta "public" podemos ver os arquivos criados

Elaborando o arquivo de log inicial:

Nossa função craftFile() abre o arquivo original (MyLog.blf) e o modifica para acionar o bug.

Após modificar o arquivo, é necessário alterar o CRC32, caso contrário, receberemos um erro de arquivo corrompido.

Esse valor está localizado no offset 0x80C do arquivo.

Realizando um Heap Spray controlado

Em seguida, realiza um HeapSpray, usando a função VirtualAlloc() para alocar memória, em endereços arbitrários 0x10000 e 0x5000000 respectivamente, e salvando na segunda alocação (0x10000), o valor 0x5000000, a cada 0x10 bytes.

Preparando os métodos CreatePipe() / NtFsControlFile()

Usa CreatePipe() para criar um pipe anônimo e chama NtFsControlFile() usando 0x11003c como argumento para adicionar um atributo; posteriormente, você pode chamar essa mesma função com o argumento 0x110038 para lê-lo.

Mais detalhes sobre este método podem ser encontrados AQUI

Ali vemos o buffer de entrada que é o atributo que estamos adicionando; se chamarmos NtFsControlFile() novamente com o argumento 0x11038 na saída, deve retornar este mesmo atributo.

Procurando no pool pela tag do atributo criado (NpAt)

E quando encontra, salva em v30.Pointer o VirtualAddress deste pool.

V30.pointer+24 aponta para o AttributeValueSize no pool do kernel e o salva em um dos HeapSprays que fizemos anteriormente.

A ideia é escrever nesse endereço de kernel+8, para sobrescrever o AttributeValue.

Texto Descrição gerada automaticamente

A estrutura PipeAttribute tem como primeiro campo uma LIST_ENTRY que possui 16 bytes, depois um ponteiro para o nome do atributo que tem 8 bytes e então vem em 0x18 (24 decimal) o campo AttributeValueSize, que é o que estamos armazenando no HeapSpray.

Depois disso, carregamos CLFS.sys e ntoskrnl no modo de usuário e, usando GetProcAddress(), encontramos os endereços das funções ClfsEarlierLsn() e SeSetAccessStateGenericMapping().

Em seguida, chamamos a função FindKernelModulesBase() que encontrará a base do kernel de ambos os módulos usando NtquerySystemInformation(), desta vez com o argumento SystemModuleInformation para retornar as informações sobre todos os módulos.

Dessa forma, podemos calcular o offset de cada função e, em seguida, obtê-los no kernel

Uma vez que a memória foi preparada, irá acionar a vulnerabilidade:

A função pipeArbitraryWrite() é chamada duas vezes; há uma flag que inicialmente é zero para a primeira chamada e, quando na segunda chamada o valor é 1, alterará os valores do HeapSpray.

Texto Descrição gerada automaticamente

Na primeira chamada, no endereço de memória 0x5000000, os seguintes valores são localizados

Lembre-se de que esse valor, além de alocar nesse endereço, é armazenado em nosso HeapSpray.

Assim fica a memória após a primeira chamada, como dissemos, no endereço por volta de 0x5000000

E no HeapSpray, a partir da memória 0x10000, armazenará o ponteiro para AttributeValueSize a cada 0x10 bytes, além do ponteiro para 0x5000000.

Lendo o Token do Sistema:

Esta sequência acionará o bug:

CreateLogFile() é chamado novamente no arquivo manipulado e em outro com um nome aleatório.

Em seguida, AddLogContainer() é chamado usando os handles desses arquivos.

A função NtSetinformationFile() é chamada, e os handles são fechados, com os quais o ponteiro é corrompido (será explicado mais tarde)

Interface gráfica do usuário, Aplicação Descrição gerada automaticamente com confiança média

O HeapSpray impede que ocorra um BSOD neste ponto:

Definindo um breakpoint lá, podemos ver que o ponteiro está corrompido e aponta para nosso HeapSpray, com o qual podemos manipular as próximas duas chamadas de função da vtable.

Interface gráfica do usuário, Texto, Aplicação Descrição gerada automaticamente

Texto Descrição gerada automaticamente

RAX assume o valor 0x5000000 e pula primeiro para a função localizada em 0x5000000+18 e depois para 0x5000000+8.

Texto Descrição gerada automaticamente com confiança média

Imagem que contém Interface gráfica do usuário Descrição gerada automaticamente

Então, primeiro salta para fnClfsEarlierLsn() e depois para fnSeSetAccessStateGenericMapping().

Traçamos a partir do breakpoint e vemos que ele atinge CLFS!ClfsEarlierLsn().

Texto Descrição gerada automaticamente

Esta função é chamada exclusivamente porque, ao retornar, define EDX para 0xFFFFFFFF

Interface gráfica do usuário Descrição gerada automaticamente com confiança média

No endereço 0xFFFFFFFF, havíamos armazenado o resultado do SYSTEM _EPROCESS & 0xFFFFFFFFFFFFFFF000

Interface gráfica do usuário, Texto, Aplicação Descrição gerada automaticamente

Como mencionamos, ao retornar de CLFS!ClfsEarlierLsn(), o valor de RDX é 0x00000000FFFFFFFF

Texto Descrição gerada automaticamente

Chegamos à segunda função nt!SeSetAccessStateGenericMapping()

Interface gráfica do usuário, Aplicação Descrição gerada automaticamente

Esta função é útil, pois RCX aponta para nosso HeapSpray, e RDX vale 0xFFFFFFFF, cujo conteúdo controlamos

Interface gráfica do usuário, Aplicação, Teams Descrição gerada automaticamente

Texto Descrição gerada automaticamente

O conteúdo de RCX+0x48 possui o ponteiro para AttributeValueSize que foi armazenado em v30.Pointer+24

Interface gráfica do usuário, Texto Descrição gerada automaticamente

Interface gráfica do usuário, Texto Descrição gerada automaticamente com confiança média

Interface gráfica do usuário, Texto Descrição gerada automaticamente

Esse valor do ponteiro de AttributeValueSize é movido para RAX, então lê o conteúdo do endereço 0xFFFFFFFF onde havíamos armazenado o endereço do SYSTEM _EPROCESS & 0xFFFFFFFFFFFFFFF000.

Interface gráfica do usuário, Texto, Aplicação Descrição gerada automaticamente

Em seguida, sobrescreve em RAX+8 o próximo campo que é o AttributeValue()

Texto Descrição gerada automaticamente

Texto Descrição gerada automaticamente

Claro, o AttributeValue normalmente apontaria no kernel para o atributo que adicionamos.

Calendário Descrição gerada automaticamente

E agora sobrescreveremos com um ponteiro do resultado do system _EPROCESS & 0xFFFFFFFFFFFFFFF00.

Isso significará que, quando chamarmos a função NtFsControlFile() novamente, desta vez com o argumento 0x110038 para ler o atributo, em vez de retornar os "A" que eram apontados pelo ponteiro AttributeValue, agora lerá a partir de _EPRROCESS & 0xFFFFFFFFFFFFFFFFF000 o número de bytes solicitado e o retornará no buffer de saída, com o qual podemos obter na primeira chamada o valor do SYSTEM TOKEN.

Texto Descrição gerada automaticamente

v9b é o endereço inicial do Output Buffer onde o conteúdo do resultado do System EPROCESS & 0xFFFFFFFFFFFFFFF000 foi copiado.

A isso ele adiciona v14, que são os últimos 3 bytes do System EPROCESS, e então adiciona 0x4b8 que é o offset do Token para esta versão do Windows 11; então encontra o conteúdo desse endereço que terá salvo o valor do System Token.

Interface gráfica do usuário, Texto, Aplicação Descrição gerada automaticamente

Texto Descrição gerada automaticamente

Validando o token

Texto Descrição gerada automaticamente com confiança baixa

Lembre-se de que os últimos 4 bits foram alterados, não é significativo, então o valor ainda corresponde.

Sobrescrever o token do nosso processo com o do sistema

Na segunda chamada, o valor da Flag é 1, pois foi incrementado ao final da primeira chamada.

Interface gráfica do usuário, Texto, Aplicação, Correio eletrônico Descrição gerada automaticamente

Ali vemos a ordem em que os valores são armazenados

Interface gráfica do usuário, Texto Descrição gerada automaticamente

O endereço 0xFFFFFFFF com o valor que acabamos de encontrar do System Process Token.

Interface gráfica do usuário, Texto Descrição gerada automaticamente

Texto Descrição gerada automaticamente

E no HeapSpray está o valor do endereço do Token do meu processo ao qual subtraio 8. Esse valor mais oito será usado como alvo; lembre-se de que você escreveu no endereço apontado por RAX+8.

Interface gráfica do usuário, texto Descrição gerada automaticamente

Texto Descrição gerada automaticamente

No endereço de memória começando em 0x5000000

Interface gráfica do usuário Descrição gerada automaticamente

Também vemos que ele usa o nome de outro container, pois o anterior está sendo usado pelo processo do sistema e não pode ser aberto novamente ou excluído.

Então o bug é acionado pela segunda vez da mesma forma que na primeira tentativa.

Ele chega novamente a CLFS!ClfsEarlierLsn().

Interface gráfica do usuário, Texto, Aplicação Descrição gerada automaticamente

definindo RDX para 0xFFFFFFFF

Uma captura de tela de um celular Descrição gerada automaticamente

Em seguida, chega a nt!SeSetAccessStateGenericMapping()

Texto Descrição gerada automaticamente

Lê o endereço do Token do meu processo menos 8, onde irá escrever

Texto Descrição gerada automaticamente

Então lê o SYSTEM TOKEN

Texto Descrição gerada automaticamente

E escreve no endereço do Token do meu processo (adiciona 8), o System Token

E dessa forma meu processo fica com o System Token

Texto Descrição gerada automaticamente

Uma vez que o token é escrito, iniciamos um processo para verificar os privilégios; neste caso, lançamos o Notepad.exe

Executando o processo como sistema:

Texto Descrição gerada automaticamente

Interface gráfica do usuário, Aplicação, Word Descrição gerada automaticamente

Texto Descrição gerada automaticamente com confiança baixa

Lembre-se de que este PoC funciona apenas no Windows 11; no Windows 10, produzirá um BSOD, portanto, você deve fazer algumas modificações para funcionar corretamente, o que não é explicado neste blogpost.

Revertendo o Patch:

Analisando as estruturas

As estruturas e a maior parte da documentação sobre o formato do arquivo CLFS foram retiradas do excelente trabalho de IONESCU sobre CLFS Internals.

Podemos ver que uma verificação foi adicionada na função ClfsBaseFilePersisted::LoadContainerQ

Texto Descrição gerada automaticamente com confiança média

Os valores que realizam uma adição pertencem à estrutura _CLFS_BASE_RECORD_HEADER.

Escala de tempo Descrição gerada automaticamente com confiança média

Observe que o Base Block começa no offset 0x800 do arquivo e termina no offset 0x71FF, correspondendo os primeiros 0x70 bytes ao Log Block Header

Como boa prática, podemos adicionar a estrutura _CLF_LOG_BLOCK_HEADER no IDA

struct _CLFS_LOG_BLOCK_HEADER

{

UCHAR MajorVersion;

UCHAR MinorVersion;

UCHAR Usn;

char ClientId;

USHORT TotalSectorCount;

USHORT ValidSectorCount;

ULONG Padding;

ULONG Checksum;

ULONG Flags;

CLFS_LSN CurrentLsn;

CLFS_LSN NextLsn;

ULONG RecordOffsets[16];

ULONG SignaturesOffset;

};

Em seguida, temos o Base Record Header (_CLFS_BASE_RECORD_HEADER) que começa no offset 0x870 do início do arquivo e tem 0x1338 bytes de comprimento.

Interfaz de usuário gráfica, Texto, Aplicação, Correio eletrónico Descrição gerada automaticamente

Se você quiser importá-lo para o IDA, antes deve adicionar os seguintes tipos e estruturas faltantes

typedef GUID CLFS_LOG_ID;
typedef UCHAR CLFS_LOG_STATE;

struct _CLFS_METADATA_RECORD_HEADER

{

ULONGLONG ullDumpCount;

};

Agora está pronto para ser adicionado:

typedef struct _CLFS_BASE_RECORD_HEADER

{

CLFS_METADATA_RECORD_HEADER hdrBaseRecord;

CLFS_LOG_ID cidLog;

ULONGLONG rgClientSymTbl[0x0b];

ULONGLONG rgContainerSymTbl[0x0b];

ULONGLONG rgSecuritySymTbl[0x0b];

ULONG cNextContainer;

CLFS_CLIENT_ID cNextClient;

ULONG cFreeContainers;

ULONG cActiveContainers;

ULONG cbFreeContainers;

ULONG cbBusyContainers;

ULONG rgClients[0x7c];

ULONG rgContainers[0x400];

ULONG cbSymbolZone;

ULONG cbSector;

USHORT bUnused;

CLFS_LOG_STATE eLogState;

UCHAR cUsn;

UCHAR cClients;

} CLFS_BASE_RECORD_HEADER, *PCLFS_BASE_RECORD_HEADER;

Interfaz de usuário gráfica, Texto Descrição gerada automaticamente

Depois de incluir as estruturas, notamos que realiza uma adição entre o cbSymbolZone e o endereço onde termina a _CLFS_BASE_RECORD_HEADER. (start + 1338h)Texto, Aplicação Descrição gerada automaticamente

Lembre-se que o cbSymbolZone foi modificado no ficheiro de log criado de 0x000000F8 para 0x0001114B.

(offset 0x1b98 do ficheiro)

0x800(offset do início do Bloco Base) + 0x70 (logBlockHeader) + 0x1328 (cbsymbolZone)

0x800+0x70+0x1328 = 0x1b98

cbsymbolZone manipulado no ficheiro MyLog.blf:

Tabela Descrição gerada automaticamente

Imagem que contém Texto Descrição gerada automaticamente

Como o patch está na função CClfsBaseFilePersisted::LoadContainerQ, temos que dar uma olhada no objeto CClfsBaseFilePersisted.

Colocando um breakpoint em CLFS!CClfsBaseFilePersisted::LoadContainerQ e quando CreateLogFile é chamado com o handle do ficheiro manipulado, ele irá parar.

Interfaz de usuário gráfica, Texto, Aplicação Descrição gerada automaticamente

Chame a função CClfsBaseFile::GetBaseLogRecord para obter o endereço do Base Log Record (_CLFS_BASE_RECORD_HEADER)

Graphical user interface, application, timeline Description automatically generated

RAX apontará para o endereço do _CLFS_BASE_RECORD_HEADER

Interfaz de usuário gráfica, Texto, Aplicação, Correio eletrónico Descrição gerada automaticamente

Observe a estrutura _CLFS_BASE_RECORD_HEADER na memória e o campo cbsymbolZone 0x1328

bytes à frente

Imagem que contém Texto Descrição gerada automaticamente

Texto Descrição gerada automaticamente

r14 armazena a estrutura correspondente ao "this", que é CClfsBaseFilePersisted, uma vez que é o this da função CClfsBaseFilePersisted::LoadContainerQ.

Interfaz de usuário gráfica, Aplicação Descrição gerada automaticamente

A estrutura CClfsBaseFilePersisted na memória:

Texto Descrição gerada automaticamente

Então, vamos criar uma estrutura com comprimento 0x21c0 para completar seus campos enquanto fazemos engenharia reversa (é uma estrutura não documentada) e chamá-la de struct_CClfsBaseFilePersisted

Tabela Descrição gerada automaticamente com confiança média

Dentro da função CClfsBaseFile::GetBaseLogRecord() obtém o ponteiro para _CLFS_BASE_RECORD_HEADER. e sabemos que o "this" nessa função é a estrutura: struct_CClfsBaseFilePersisted.

Escala de tempo Descrição gerada automaticamente com confiança média

Leia dois campos (offset 0x28 e 0x30)

Interfaz de usuário gráfica, Aplicação, Tabela Descrição gerada automaticamente

O campo 0x28 é uma word e tem o valor 6, então mudamos o tipo para word na estrutura.

Texto Descrição gerada automaticamente

Texto Descrição gerada automaticamente

Interfaz de usuário gráfica, Texto, Aplicação Descrição gerada automaticamente

Por enquanto, renomeamos para constante 6 (const_6)

Texto Descrição gerada automaticamente

Interfaz de usuário gráfica, Texto, Aplicação Descrição gerada automaticamente

De acordo com a documentação, 6 seria o número de blocos CLFS_METADATA_BLOCK_COUNT. O campo pode se referir a este valor.

E esse ponteiro está no offset 0x30.

Tabela Descrição gerada automaticamente

Observe que o tamanho mostrado lá inclui o cabeçalho com comprimento 0x10

Texto Descrição gerada automaticamente

Forma Descrição gerada automaticamente com confiança média

Quando a função ExAllocatePoolWithTag é chamada, alguns bytes são solicitados, mas o cabeçalho não está incluído, portanto, 0x90 bytes (0xa0 – 0x10) serão solicitados na chamada.

Procurando por texto +30h], as instruções que escrevem no offset 0x30 encontramos uma longa lista, mas filtrando a lista pelo tipo de objeto CClfsBaseFilePersisted ficamos com poucos resultados e imediatamente encontramos onde esse tamanho é alocado, e a mesma tag. (Dica: Nomes de funções Create e Initialize, são sempre os primeiros a olhar)

Texto Descrição gerada automaticamente com confiança baixa

Interfaz de usuário gráfica, Texto, Aplicação Descrição gerada automaticamente

Como ainda não sabemos o nome, vamos colocar pool_0x90, que é outra estrutura não documentada, e vamos criar uma estrutura desse tamanho.

Texto Descrição gerada automaticamente

Interfaz de usuário gráfica, Tabela Descrição gerada automaticamente

A pool_0x90 na memória tem outro ponteiro em seu próprio offset 0x30.

Aplicação, Tabela Descrição gerada automaticamente com confiança média

Este outro ponteiro aponta para o bloco base no ficheiro (Bloco Base começa no offset 0x800)

Forma Descrição gerada automaticamente

A picture containing calendar Description automatically generated

Imagem retirada do blogpost da Zscaler:

Graphical user interface, application, email Description automatically generated

A alocação é enorme, porque contém todo o bloco base.

Text Description automatically generated

Graphical user interface, text, application Description automatically generated

Então, vamos criar uma nova estrutura de tamanho 0x7a00 e chamá-la de BASE_BLOCK

Graphical user interface, text, application Description automatically generated

Os primeiros 70 bytes já sabemos que correspondem a _CLFS_LOG_BLOCK_HEADER e os seguintes 0x1338 a _CLFS_BASE_RECORD_HEADER.

Text Description automatically generated

Então, adicionando o início do Bloco Base com o offset para o próximo registo (que é 0x70), obtemos a _CLFS_BASE_RECORD_HEADER

Application Description automatically generated with low confidence

A _CLFS_BASE_RECORD_HEADER na memória.

Calendar Description automatically generated

Olhando para outros métodos do mesmo objeto CClfsBaseFilePersisted, em CClfsBaseFilePersisted::AddContainer obtém-se com CClfsBaseFile::GetBaseLogRecord também o endereço de _CLFS_BASE_RECORD_HEADER.

Text Description automatically generated

Em seguida, chama CClfsBaseFile::OffsetToAddr usando cbOffset, obtém o endereço de _CLFS_CONTAINER_CONTEXT, e armazena cboffset no array rgbcontainers que está no offset 0x328 da _CLFS_BASE_RECORD_HEADER.

Graphical user interface, application Description automatically generated

A função CClfsBaseFile::OffsetToAddr é usada para encontrar endereços de estruturas a partir do offset

Graphical user interface, text, application Description automatically generated

Neste ponto, o offset do contentor que será armazenado em 0x328 ainda é 0, porque ainda não adicionámos um contentor.

Background pattern Description automatically generated with low confidence

o PoC chama CreateLogFile duas vezes, a primeira vez com o ficheiro malformado MyLog.blf e a segunda vez com o ficheiro normal MyLogxxx.blf, então devemos parar a depuração duas vezes em todos os lugares acima e anotar no bloco de notas os endereços das estruturas acima para ambos os ficheiros.

Text Description automatically generated

Vamos avançar um pouco para CLFS!CClfsLogFcbPhysical::AllocContainer definindo um breakpoint nela e executando até lá.

Quando a função AddLogContainer() é alcançada no POC, paramos no breakpoint.

A picture containing application Description automatically generated

Vamos também definir um breakpoint em CClfsBaseFilePersisted::AddContainer+176 onde vimos antes que encontrará o offset e o ponteiro para a estrutura _CLFS_CONTAINER_CONTEXT.

A screenshot of a computer Description automatically generated with medium confidence

Graphical user interface, text, application Description automatically generated

Quando o depurador para, podemos ver que o offset é 0x1468.

Text Description automatically generated

Em RAX retornará o endereço da estrutura _CLFS_CONTAINER_CONTEXT.

Graphical user interface, text, application, table Description automatically generated

a estrutura ainda está vazia porque o contentor ainda não foi adicionado.

Text Description automatically generated

Observe que o valor SignatureOffset=0x50 que escrevemos no offset 0x868 do ficheiro malformado, subtraindo o 0x800 do início do bloco base, estará na estrutura _CLFS_LOG_BLOCK_HEADER no offset 0x68.

Graphical user interface, text, application Description automatically generated

Text, letter Description automatically generated

Quando o PoC chama a função AddLogContainer() usando o ficheiro malformado, no offset 0x68 de _CLFS_LOG_BLOCK_HEADER, em vez do valor 0x50 que escrevemos lá, atualmente está 0xFFFF0050 na memória.

A picture containing text Description automatically generated

Em algum momento, esse valor foi alterado pelo programa, para ver quando aconteceu, na próxima execução, vamos definir um memory breakpoint de escrita.

O offset está armazenado em r15 + 0x328 (r15 aponta para a estrutura _CLFS_BASE_RECORD_HEADER)

Text Description automatically generated with medium confidence

Graphical user interface Description automatically generated with low confidence

RBX armazena o offset 0x1468.

A picture containing calendar Description automatically generated

Então, no endereço do Bloco Base + 0x70 + o offset 0x1468 que descobrimos, estará o endereço do contentor CLFS_CONTAINER_CONTEXT.

Text Description automatically generated

Na estrutura CLFS_CONTAINER_CONTEXT no offset 0x18 estará o ponteiro pContainer que será armazenado lá, podemos definir um breakpoint de escrita e ver quando é escrito.

Text Description automatically generated

A picture containing text Description automatically generated

Este é o ponteiro que devemos corromper, pois na função onde está a vulnerabilidade, primeiro lê o CLFS_CONTAINER_CONTEXT, depois move para r15 e em seguida lê o valor de r15+18, que é este ponteiro que acabámos de definir o Breakpoint de escrita.

Graphical user interface, application, table Description automatically generated

Graphical user interface, application, Word Description automatically generated

ele armazena o pContainer no offset 0x1c0 da estrutura struct_CClfsBaseFilePersisted.

Text, application, whiteboard Description automatically generated

Após várias vezes que para, alcançamos o momento em que é corrompido. O topo do endereço do ponteiro foi alterado de FFs para zero.

Calendar Description automatically generated

Isso acontece quando o segundo AddLogContainer() do ficheiro malformado é chamado, o ponteiro do MyLogxxx anterior é corrompido.

O problema ocorre porque o SignaturesOffset, que deveria ser 0x50, agora é 0xFFFF0050, então permite escrever fora dos limites no memset que se segue.

Graphical user interface, text, application Description automatically generated

Graphical user interface, text Description automatically generated

Corrompendo o ponteiro “pContainer”:

A função memset() vai corromper a estrutura _CLFS_CONTAINER_CONTEXT que está abaixo, esta estrutura corresponde ao ficheiro MyLogxxx, pois quando foram criados, localizava-os a 0x11000 bytes de distância um do outro.

Desta forma, calcula exatamente onde escrever na próxima estrutura e zera o topo do ponteiro, de modo que aponta para o heap do usuário onde o HeapSspray foi criado.

a estrutura do bloco base do ficheiro malformado está apenas 0x11000 antes da do ficheiro MyLogxxx.

Malformado:

Shape Description automatically generated

MyLogxxx

A picture containing text Description automatically generated

RCX é menor que RDX pois 0xFFFF0050 foi adicionado a, em vez de 0x50 como deveria ser.

Graphical user interface, text, application Description automatically generated

e chegamos à função memset(), para definir a quantidade de 0xb0 bytes com Zeroes, com RCX apontando para a estrutura CLFS_CONTAINER_CONTEXT do ficheiro MyLogxxx, especificamente para os cinco bytes altos do pContainer.

Text Description automatically generated

Este ponteiro será corrompido ao sobrescrever os primeiros bytes:

Text Description automatically generated

permanecendo a apontar para um endereço de memória previamente controlado por nós através do HeapSpray

Text Description automatically generated

A picture containing text Description automatically generated

Em seguida, o handle do ficheiro MyLogxxx será fechado, e chega-se a CClfsBaseFilePersisted::RemoveContainer, a vulnerabilidade é finalmente acionada.

Text, application Description automatically generated with medium confidence

Revisitando o Patch

Agora que temos mais informações, notamos que aqui lê o Base_Block.LOG_BLOCK_HEADER.SignaturesOffset e o Base_Block.LOG_BLOCK_HEADER.TotalSectorCount

Na primeira parte do patch, o SignaturesOffset não deve ser maior que 0x7a00, no nosso era originalmente 0x50, se chegasse com um valor maior que 0x7a00 nos expulsaria.

A picture containing diagram Description automatically generated

Executando o PoC na máquina corrigida, ele compara 0x50 com 0x7a00 e como é menor, continua.

Text Description automatically generated

No bloco seguinte, o cbSymbolZone malformado é adicionado ao valor do endereço final de _CLFS_BASE_RECORD_HEADER e esta soma é armazenada em result_1.

Text Description automatically generated

Em seguida, o endereço do Base_Block é adicionado ao valor SignatureOffset, que num ficheiro normal é 0x7980.

Text Description automatically generated

O endereço máximo do base_block é 0x7a00, agora a SymbolZone é permitida até 0x80 antes do limite.

Irá armazená-lo em result_2, ou seja, esse seria o limite máximo para a SymbolZone dentro do base block, então compara ambos os resultados se o primeiro for maior que o segundo, significa que saiu dos limites.

Text Description automatically generated

Text Description automatically generated

Obviamente o primeiro membro será maior que o segundo e não continuará, pois a primeira soma do cbSymbolZone + endereço final da _CLFS_BASE_RECORD_HEADER excede o limite (que é o result_2) e leva a um "out of bounds".

Graphical user interface, text, application Description automatically generated

Corrompendo o SignatureOffset

A última coisa que teríamos que descobrir é onde o valor SignatureOffset de 0x50 se torna 0xFFFF0050.Então, vamos recomeçar, reiniciar e parar em CLFS!CClfsBaseFilePersisted::LoadContainerQ onde o valor ainda não foi alterado na memória e ainda é 0x50.

Defina um breakpoint de acesso no offset 0x68 em SignatureOffset.

Calendar Description automatically generated

E após várias paradas, detectamos o momento certo em que ele modifica o valor, em ClfsEncodeBlockPrivate.

Graphical user interface, text Description automatically generated

Esta função não está corrigida (patched), então pode ser um comportamento causado pelo baixo valor de 0x50 e pelo restante dos valores sendo manipulados.

Entre os valores manipulados, podemos ver o valor ccoffsetArray cujo nome na estrutura _CLFS_BASE_RECORD_HEADER é rgClients e representa o array de offsets que apontam para o objeto Client Context.

O campo rgClients está localizado no offset 0x138 (0x9a8-0x800-0x70) da estrutura _CLFS_BASE_RECORD_HEADER.

Graphical user interface, table Description automatically generated

Text Description automatically generated with medium confidence

No PoC, este valor é malformado para apontar para um objeto de contexto de cliente falso, chamado FakeClientContext

Text, whiteboard Description automatically generated

A screenshot of a computer Description automatically generated with medium confidence

Esta é a estrutura Client Context _CLFS_CLIENT_CONTEXT

struct _CLFS_CLIENT_CONTEXT

{

CLFS_NODE_ID cidNode;

CLFS_CLIENT_ID cidClient;

USHORT fAttributes;

ULONG cbFlushThreshold;

ULONG cShadowSectors;

ULONGLONG cbUndoCommitment;

LARGE_INTEGER llCreateTime;

LARGE_INTEGER llAccessTime;

LARGE_INTEGER llWriteTime;

CLFS_LSN lsnOwnerPage;

CLFS_LSN lsnArchiveTail;

CLFS_LSN lsnBase;

CLFS_LSN lsnLast;

CLFS_LSN lsnRestart;

CLFS_LSN lsnPhysicalBase;

CLFS_LSN lsnUnused1;

CLFS_LSN lsnUnused2;

CLFS_LOG_STATE eState;

union

{

HANDLE hSecurityContext;

ULONGLONG ullAlignment;

};

};

O valor eState está no offset 0x78 a partir do início da estrutura, no arquivo manipulado 0x23a0+0x78.

Text Description automatically generated with medium confidence

Chart, scatter chart Description automatically generated

Este valor mostra o status do log.

typedef UCHAR CLFS_LOG_STATE, *PCLFS_LOG_STATE;
const CLFS_LOG_STATE CLFS_LOG_UNINITIALIZED = 0x01;
const CLFS_LOG_STATE CLFS_LOG_INITIALIZED = 0x02;
const CLFS_LOG_STATE CLFS_LOG_ACTIVE = 0x04;
const CLFS_LOG_STATE CLFS_LOG_PENDING_DELETE = 0x08;
const CLFS_LOG_STATE CLFS_LOG_PENDING_ARCHIVE = 0x10;
const CLFS_LOG_STATE CLFS_LOG_SHUTDOWN = 0x20;
const CLFS_LOG_STATE CLFS_LOG_MULTIPLEXED = 0x40;
const CLFS_LOG_STATE CLFS_LOG_SECURE = 0x80;

este valor é definido como CLFS_LOG_STATE CLFS_LOG_SHUTDOWN =0x20

O outro valor malformado é fAttributes que corresponde ao conjunto de flags FILE_ATTRIBUTE associadas ao arquivo de log base (como System e Hidden).

Graphical user interface Description automatically generated with low confidence

Text, letter Description automatically generated

Como o campo começa um byte antes em 0xa e abrange dois bytes, o valor de fAttributes é 0x100.

A picture containing table Description automatically generated

Graphical user interface, text, application Description automatically generated

Finalmente, há o valor blocknameoffset que aponta para o offset 0x1bb8, ou seja, somando 0x78 e 0x800 aponta para o offset 0x2428 do arquivo.

Text Description automatically generated

Text, letter Description automatically generated

Note que o offset para o Client Context é 0x1b30

Table Description automatically generated

Então, o Client Context está no offset 0x23a0.

Text Description automatically generated with medium confidence

Table Description automatically generated

E apenas 0x10 antes, está o valor correspondente a blocknameoffset.

Text, letter Description automatically generated

Table Description automatically generated with low confidence

Que apontaria para a string com o nome

o último é o blockattributeoffset que está 0xC antes do Client Context em 0x2394.

Table Description automatically generated

Estes dois últimos valores pertencem a uma estrutura anterior ao Client Context de 0x30 bytes de comprimento, chamada _CLFSHASHSYM

typedef struct _CLFSHASHSYM
{
CLFS_NODE_ID cidNode;
ULONG ulHash;
ULONG cbHash;
ULONGLONG ulBelow;
ULONGLONG ulAbove;
LONG cbSymName;
LONG cbOffset;
BOOLEAN fDeleted;
} CLFSHASHSYM, *PCLFSHASHSYM;

A picture containing diagram Description automatically generated

Text Description automatically generated

eles estão a 0x20 e 0x24 bytes do início da estrutura _CLFSHASHSYM, então na estrutura _CLFSHASHSYM o valor chamado blockNameOffset no POC é o campo cbSymName e o blockAttributteoffset é o campo cbOffset.

A picture containing text Description automatically generated

Text Description automatically generated with medium confidence

Esses são os valores malformados, agora precisamos ver como eles afetam a mudança do nosso SignaturesOffset do valor 0x50 para 0xFFFF0050.

Vamos dar uma olhada na função CClfsBaseFile::AcquireClientContext(), que deve retornar o contexto do cliente.

Graphical user interface, text, application, email Description automatically generated

ela chama CClfsBaseFile::GetSymbol com o quarto argumento que será _CLFS_CLIENT_CONTEXT ** onde armazenará o ponteiro para Client Context.

Graphical user interface, text, application Description automatically generated

Dentro da função CClfsBaseFile::GetSymbol passamos o offset malformado ccoffsetArray para CClfsBaseFile::OffsetToAddr e obtemos o endereço do contexto do cliente, vamos definir um breakpoint lá para que pare ao chamar o arquivo criado com CreatelogFile .

Graphical user interface, application Description automatically generated with medium confidence

Lá ele para com o argumento manipulado ccoffsetArray.

Graphical user interface, application Description automatically generated

Table Description automatically generated

A função CClfsBaseFile::OffsetToAddr retorna o falso Client Context

A picture containing graphical user interface Description automatically generated

E verifica que o valor de cbOffset não é zero, já que 0xC é encontrado antes da estrutura _CLFS_CLIENT_CONTEXT que está em RAX.

A screenshot of a computer Description automatically generated

A picture containing text Description automatically generated

Então ele compara cbOffset com ccoffsetArray (que está em RSI), eles devem ser iguais, caso contrário, obteremos um erro.

A screenshot of a computer Description automatically generated

Ele também verifica que cbSymName seja igual a cbOffset+0x88, caso contrário, também obteremos um erro.

Graphical user interface, text, application Description automatically generated with medium confidence

E finalmente, compara o byte cidClient com zero

A picture containing diagram Description automatically generated

Se todas essas verificações forem bem-sucedidas, o client context será salvo.

A screenshot of a computer Description automatically generated

A saída da função r14 aponta para Client Context

Graphical user interface, text, application Description automatically generated

Ao sair de CClfsLogFcbPhysical::Initialize teremos o endereço de CLFS_CLIENT_CONTEXT.

Text Description automatically generated

Agora ele lê o valor de fAttributes (0x100)

Graphical user interface, text, application Description automatically generated

esta função pertence à classe CClfsLogFcbPhysical

Text Description automatically generated

Graphical user interface, text, application Description automatically generated

Que foi alocada aqui, e seu tamanho é 0x15d0 e sua tag é “ClfC”

Text Description automatically generated with medium confidence

Vamos criar uma estrutura para armazenar o que estamos fazendo engenharia reversa, vamos chamá-la de: struct_CClfsLogFcbPhysical.

A picture containing table Description automatically generated

Note que em 0x2b0 ele salva o endereço da estrutura CClfsBaseFilePersisted.

A picture containing application Description automatically generated

Após salvar muitos valores na estrutura, ele vai para uma parte importante, testa o eState com 0x20.

Graphical user interface, text, application Description automatically generated

Graphical user interface, text, application, table Description automatically generated

Como o valor manipulado era 0x20, o teste retornará 1.

Table Description automatically generated

Graphical user interface, text, application Description automatically generated

Vemos que no construtor na vtable está

Text Description automatically generated

Ele verificará se o arquivo está multiplexado.

Graphical user interface, application Description automatically generated

Então, ele segue o caminho desejado, alcançando CClfsLogFcbPhysical::ResetLog.

Graphical user interface, application Description automatically generated

Text, application, table Description automatically generated with medium confidence

Vários campos são inicializados como zero, exceto um que é inicializado como 0xFFFFFFFF00000000.

Graphical user interface Description automatically generated with low confidence

Aqui recupera o Client Context

Graphical user interface, application Description automatically generated with medium confidence

ele armazena o valor 0xFFFFFFFF00000000.

Graphical user interface, application Description automatically generated

Graphical user interface, application Description automatically generated

A picture containing calendar Description automatically generated

Ele escreve 0xFFFFFFFF no offset 0x5c que é a parte alta de CLFS_LSN lsnRestart.ullOffset

Text Description automatically generated with medium confidence

Graphical user interface, text, letter Description automatically generated

Agora executamos a função ClfsEncodeBlockPrivate(), que é a responsável por sobrescrever o 0x50 com 0xFFFF0050 como vimos antes.

Lá ele lê o valor de SignatureOffset = 0x50 que ainda está como colocamos no arquivo malformado e o adiciona ao início de CLFS_LOG_BLOCK_HEADER.

Text Description automatically generated

este é um loop que está escrevendo 2 bytes, como o SignatureOffset em vez de apontar para um valor correto que em um arquivo normal é um valor alto, por exemplo 0x3f8 que o faz escrever mais adiante, aqui ele escreverá no mesmo CLFS_LOG_BLOCK_HEADER

A ideia é alterar o destino da escrita para tentar corromper o valor SignatureOffset.

Arquivo Normal

Table Description automatically generated

Neste ponto, ele começará a fazer um loop e escrever dois bytes.

Graphical user interface, application Description automatically generated

O contador deve atingir o valor 0x3d para sair do loop.

Graphical user interface, application Description automatically generated

RCX está aumentando a partir de 0x200, já estamos no terceiro ciclo, e seu valor é 0x600

Graphical user interface, text, application Description automatically generated

na iteração 0xe, RCX é 0x1a00

Graphical user interface, text Description automatically generated

A picture containing calendar Description automatically generated

Foi aí que ele escreveu o 0xFFFFFFFF000000.

Graphical user interface, text, application Description automatically generated

Table Description automatically generated with medium confidence

Ele está lendo os dois últimos bytes FFFF

Text Description automatically generated

E então copiará em R8

A picture containing calendar Description automatically generated

A picture containing calendar Description automatically generated

Como vimos, este valor é crítico, pois permite contornar a verificação e escrever fora dos limites para corromper o ponteiro pContainer do arquivo que segue o memset() e escrever zeros no topo e deixá-lo apontando para nossa memória controlada (HeapSpray).

Na CClfsBaseFilePersisted::AllocSymbol a mesma soma que vai obter o destino do memset que é cbSymbolZone + endereço final de CLFS_BASE_RECORD_HEADER compara antes com Base_block + 0xFFFF0050, então tem valores corrompidos em ambos os lados da equação.

CbSymbolZone= 0x1114B

É o valor malformado que somado ao endereço final de CLFS_BASE_RECORD_HEADER fará com que escreva fora dos limites e o outro membro da comparação que deveria ser o endereço do Base Block + SignatureOffset, permanece SignatureOffset =0xFFFF0050 o que permite que esta verificação passe e escreva fora dos limites no memset() e zere o topo do ponteiro que permanecerá apontando para nosso HeapSpray.

Graphical user interface, application, table Description automatically generated

Como RCX é menor que RDX.

Graphical user interface, application Description automatically generated

Como vimos antes. (Os valores podem diferir porque pertencem a uma execução anterior)

Ele corromperá o ponteiro, definindo os bytes mais altos como 0

Table Description automatically generated

Deixando-o apontando para uma área de memória que controlamos através de HeapSpray

A screenshot of a computer Description automatically generated with low confidence

Table Description automatically generated

Então, quando a vulnerabilidade é acionada, chegamos a CClfsBaseFilePersisted::RemoveContainer

A picture containing graphical user interface Description automatically generated

Lá estará o ponteiro já corrompido e pode ser explorado como vimos anteriormente.

Graphical user interface, application Description automatically generated

Neste ponto, temos o bug explorado, ele leva ao controle das funções que permitem ler o token SYSTEM e escrever em nosso próprio processo para alcançar a escalada de privilégio local.

Esperamos que ache útil, se tiver alguma dúvida, pode nos contactar em [email protected] e [email protected]

Aproveite!

Baixar ferramenta