Abaixo do corte, está uma tradução da apresentação "O mecanismo de notificação do Windows: a superfície de ataque ao kernel mais indocumentada ainda", apresentada por Alex Ionescu e Gabrielle Viala na conferência BlackHat 2018 .


O que será discutido na publicação O que é o Windows Notification Facility (WNF)
O Windows Notification Facility é um mecanismo de notificação (disponível no kernel e no modo de usuário), baseado em um modelo de publicador-assinante ( pubsub , Publisher / Subscriber). O mecanismo foi adicionado no Windows 8: em parte para resolver algumas limitações de design de longa data no sistema operacional, mas também serviu de base para a implementação de notificações push semelhantes ao iOS / Android.
Sua principal característica é que é um modelo cego (principalmente sem registro) que permite assinatura e publicação desordenadas. Isso implica que um consumidor pode assinar uma notificação antes mesmo de ela ser publicada por sua fonte. E que quem gera os eventos não precisa "registrar" o aviso antecipadamente.
Além disso, o mecanismo suporta:
- notificações permanentes e temporárias
- monotonamente aumentando identificadores únicos
- buffer de carga útil (até 4 kilobytes) para cada evento
- modelo de notificação de conjunto de encadeamentos com serialização baseada em grupo
- um modelo de segurança baseado em escopo que implementa descritores de segurança por meio do mecanismo DACL / SACL padrão
Por que o WNF apareceu
Considere um exemplo canônico: existe um driver que deseja saber que um volume com acesso de leitura e gravação foi conectado. Para notificá-lo disso, o Autochk (um análogo do fsck no Windows) relata um evento chamado VolumesSafeForWriteAccess. Mas, para relatar um evento, você deve primeiro criar o próprio objeto de evento.
Também precisamos saber que o Autochk já está trabalhando no volume, mas ainda não sinalizou o evento que estamos aguardando. Solução ruim: sente-se em loop com sleep (), verificando a presença de um evento e quando o evento é criado - aguarde.
Mas depois de sair do aplicativo Windows, todos os seus descritores estão fechados. E quando o objeto não possui descritores, é destruído. Então, quem irá realizar este evento?
Sem o WNF, a solução é o kernel do SO gerar um evento antes do carregamento de qualquer driver e o Autochk abri-lo como um consumidor faria, mas, em vez de esperar, deve sinalizar esse evento.
Nomes de estados WNF
No mundo WNF, um nome de estado é um número de 64 bits. Mas há um truque - na verdade, é uma estrutura codificada. O nome do estado possui uma versão , uma vida útil , um escopo , um sinalizador de persistência de dados e um número de série exclusivo .
typedef struct _WNF_STATE_NAME_INTERNAL { ULONG64 Version:4; ULONG64 NameLifetime:2; ULONG64 DataScope:4; ULONG64 PermanentData:1; ULONG64 Unique:53; } WNF_STATE_NAME_INTERNAL, *PWNF_STATE_NAME_INTERNAL;
Mas esses dados estarão disponíveis apenas se formos pró-XOR um número de 64 bits com uma constante mágica:
#define WNF_STATE_KEY 0x41C64E6DA3BC0074
Tempo de vida do nome do estado
O nome do estado WNF pode ser (WNF_STATE_NAME_LIFETIME):
- bem conhecido
- permanente
- persistente
- temporário
Os três primeiros estão associados às chaves correspondentes no registro, onde as informações de status serão armazenadas:
- nomes conhecidos vivem em HKLM \ SYSTEM \ CurrentControlSet \ Control \ Notifications
- nomes persistentes estão em HKLM \ SOFTWARE \ Microsoft \ Windows NT \ CurrentVersion \ Notifications
- nomes persistentes estão em HKLM \ SOFTWARE \ Microsoft \ Windows \ CurrentVersion \ VolatileNotifications
Nomes conhecidos têm sua própria peculiaridade: eles não podem ser registrados. Esse nome já deve ser apresentado no registro no momento da inicialização do sistema. Nomes persistentes e persistentes requerem o privilégio SeCreatePermanentPrivilege incluído (como outros objetos globais) para criá-los. Os nomes persistentes ficam fora do processo do registrador, enquanto os nomes persistentes sobrevivem à reinicialização do sistema.
Escopo de dados
O escopo de dados define o primeiro limite de segurança em torno do nome do status do WNF; determina quem o vê e tem acesso a ele. O escopo do nome do estado pode ser:
- o sistema
- o carro
- sessão de usuário
- o usuário
- o processo
Além de fornecer limites de segurança, os escopos WNF podem ser usados para fornecer diferentes instâncias de dados para o mesmo nome. O kernel (como em outros mecanismos de segurança) ignora as verificações de acesso ao estado. O privilégio do TCB permite acesso de âmbito cruzado aos nomes de estado do WNF.
O escopo "sistema" e o escopo "máquina" são escopos globais. Eles não têm seus próprios identificadores (eles usam contêineres globais diferentes). O escopo da sessão do usuário usa o identificador da sessão (ID da sessão) como o ID. O escopo de um usuário específico usa o SID desse usuário como um identificador. O endereço do objeto EPROCESS é o identificador do escopo do processo.
Números de sequência
Para garantir a exclusividade, cada nome de estado possui um número de sequência exclusivo de 51 bits. Os nomes conhecidos incluem uma etiqueta de família de 4 caracteres em seu número de série e os 21 bits restantes são usados como um identificador exclusivo. Nomes permanentes armazenam seu número incremental com o valor do registro "SequenceNumber". Nomes persistentes e temporários usam um contador de incremento comum, localizado em uma variável global. Esses dados são armazenados e processados separadamente para cada contêiner (por silo) e estão disponíveis em PspHostSiloGlobals-> WnfSiloState.
Dentro da Microsoft, cada nome WNF possui um identificador "amigável" usado no código, às vezes é armazenado no espaço para nome global com o mesmo nome. Por exemplo, o símbolo nt! WNF_BOOT_DIRTY_SHUTDOWN, que possui o valor 0x1589012fa3bc0875. Após o XOR com a constante mágica WNF_STATE_KEY , obtemos o valor 0x544f4f4200000801, que pode ser interpretado em bits como:
BOOT1, Well-Known Lifetime, System Scope, Version 1
Chamadas do sistema para trabalhar com o WNF
As chamadas de sistema do kernel permitem registrar e excluir nomes de estado do WNF, publicar e receber dados do nome do estado do WNF e também receber várias notificações do WNF.
Nome do status do registro WNF
Com exceção dos nomes conhecidos (como mencionado anteriormente), o nome do status do WNF pode ser registrado enquanto o sistema operacional está em execução:
NTSTATUS ZwCreateWnfStateName ( _Out_ PWNF_STATE_NAME StateName, _In_ WNF_STATE_NAME_LIFETIME NameLifetime, _In_ WNF_DATA_SCOPE DataScope, _In_ BOOLEAN PersistData, _In_opt_ PCWNF_TYPE_ID TypeId,
Há uma chamada de sistema simétrica ZwDeleteWnfStateName com a qual você pode excluir o nome do estado registrado (novamente, exceto os conhecidos).
Publicar dados de status do WNF
Para definir ou alterar os dados do nome de status do WNF, você pode usar a chamada de sistema ZwUpdateWnfStateData:
NTSTATUS ZwUpdateWnfStateData ( _In_ PCWNF_STATE_NAME StateName, _In_reads_bytes_opt_(Length) const VOID* Buffer, _In_opt_ ULONG Length,
Há uma chamada de sistema simétrica ZwDeleteWnfStateData para excluir (limpar) os dados do nome do estado WNF.
Obtendo dados de status do WNF
Para solicitar os dados do nome de status do WNF, a seguinte chamada do sistema pode ser usada (a maioria dos parâmetros é semelhante à função Atualizar):
NTSTATUS ZwQueryWnfStateData ( _In_ PCWNF_STATE_NAME StateName, _In_opt_ PCWNF_TYPE_ID TypeId, _In_opt_ const VOID* ExplicitScope, _Out_ PWNF_CHANGE_STAMP ChangeStamp, _Out_writes_bytes_to_opt_(*BufferSize, *BufferSize) PVOID Buffer, _Inout_ PULONG BufferSize
A verdadeira força reside no fato de que as funções da API Update e Query na verdade não exigem um nome de estado WNF registrado . E se o nome não for temporário (e o código de chamada tiver privilégios suficientes), uma instância do nome poderá ser registrada em tempo real!
Notificações WNF
Até agora, assumimos que o consumidor sabe quando chamar a função de aquisição de dados. Mas também há bloqueio de leitura , que funciona usando um sistema de notificação (mais próximo do verdadeiro modelo de publicador-assinante).
Primeiro, o processo deve registrar o evento chamando a função ZwSetWnfProcessNotificationEvent. Em seguida, você precisa chamar a função ZwSubscribeWnfStateChange, especificando uma máscara de evento para obter o identificador de assinatura na saída. Eventos podem ser de dois tipos:
- Notificações de dados:
- 0x01 - aparência dos dados
- 0x10 - destruição de nome
- Metanotificações
- 0x02 - aparência de um assinante que recebe notificações de dados (Assinante de dados)
- 0x04 - aparência de um assinante que recebe meta notificações (Meta Subscriber)
- 0x08 - a aparência de um assinante que recebe notificações de dados e meta-notificações (Assinante Genérico)
Então você precisa aguardar o evento que foi gravado. E toda vez que o evento se tornar um sinal, você precisará chamar a função ZwGetCompleteWnfStateSubscription, que retorna WNF_DELIVERY_DESCRIPTOR.
Mas essas funções de API de baixo nível têm um problema (obrigado a Gabi por investigá-lo): cada processo pode ter apenas um evento registrado.
API de modo de usuário de alto nível (ntdll)
Quando se trata de notificações, as coisas ficam complicadas, então a camada rtl do ntdll.dll fornece uma interface mais simples:
NTSTATUS RtlSubscribeWnfStateChangeNotification ( _Outptr_ PWNF_USER_SUBSCRIPTION* Subscription, _In_ WNF_STATE_NAME StateName, _In_ WNF_CHANGE_STAMP ChangeStamp, _In_ PWNF_USER_CALLBACK Callback, _In_opt_ PVOID CallbackContext, _In_opt_ PCWNF_TYPE_ID TypeId, _In_opt_ ULONG SerializationGroup, _In_opt_ ULONG Unknown );
De fato, não há necessidade de chamar os serviços do sistema diretamente: basta usar uma única fila de eventos controlada pelo ntdll.dll.
Nos bastidores, o conteúdo de WNF_DELIVERY_DESCRIPTOR é convertido em parâmetros de retorno de chamada:
typedef NTSTATUS (*PWNF_USER_CALLBACK) ( _In_ WNF_STATE_NAME StateName, _In_ WNF_CHANGE_STAMP ChangeStamp, _In_opt_ PWNF_TYPE_ID TypeId, _In_opt_ PVOID CallbackContext, _In_ PVOID Buffer, _In_ ULONG BufferSize);
Para cada nova assinatura, é feita uma entrada, que é colocada na lista apontada pela variável global RtlpWnfProcessSubscriptions. A lista é criada em um dos campos WNF_NAME_SUBSCRIPTION, do tipo LIST_ENTRY. Cada um dos WNF_NAME_SUBSCRIPTION, por sua vez, possui outro campo LIST_ENTRY para organizar uma lista de WNF_USER_SUBSCRIPTION com retorno de chamada e contexto.
API de alto nível no nível do kernel (Ex)
O WNF também fornece funções quase idênticas para o código do modo kernel (que pode ser usado a partir do driver): através de chamadas de sistema exportadas e através de funções de API de alto nível no tempo de execução (camada Ex).
A função ExSubscribeWnfStateChange aceita o nome do estado, máscaras de tipo e o endereço da função de retorno de chamada + contexto como uma entrada e retorna um descritor de assinatura. As funções de retorno de chamada recebem o nome do destino, máscara de evento, rótulo de alteração, mas não o buffer ou seu tamanho.
A função ExQueryWnfStateData, com base no descritor de assinatura passado, lê os dados de status atuais. De fato, cada retorno de chamada acaba chamando a função ExQueryWnfStateData para obter os dados associados à notificação.
Para assinaturas no modo kernel e no modo usuário, o WNF (para rastrear assinaturas) cria uma instância da estrutura WNF_SUBSCRIPTION. Mas, no modo de usuário, alguns campos não serão preenchidos, por exemplo, retorno de chamada e contexto, porque, no modo de usuário, os endereços dos manipuladores são armazenados e processados pelo ntdll.dll.
Estruturas de dados WNF

De um tradutor : consulte a próxima seção.
Utilitários de análise WNF
De um tradutor : aqui vale lembrar novamente que a apresentação foi conduzida não apenas por Alex, mas também por Gabrielle Viala . Em particular, sua autoria pertence ao módulo WnfCom descrito abaixo. Além disso, Gabrielle descreveu as estruturas internas do WNF em detalhes suficientes (veja a ilustração na seção anterior). Infelizmente, a maioria de seus slides está ausente no pdf da apresentação (indicado como original) ou indicado exclusivamente por títulos. Mas:
E do tradutor : se alguém quiser complementar a tradução atual com o conteúdo dos slides de Gabrielle ou expandir a tradução de taquigrafia de qualquer parte do vídeo do discurso - seja bem-vindo. Para facilitar a adição / alteração de grandes blocos, posso publicar a fonte de tradução no github (ou outro servidor de controle de versão).
Wnfcom
O WnfCom é um módulo python ( código fonte do github ) que mostra interoperabilidade através do WNF. Principais recursos:
- permite ler / gravar dados de instâncias de instância existentes
- permite criar nomes de estado temporários (como servidor )
- permite obter uma instância de um objeto do lado do cliente que processará notificações sobre a alteração de uma instância específica de um nome
Exemplo de uso:
>>> from wnfcomimport Wnfcom >>> wnfserver = Wnfcom() >>> wnfserver.CreateServer() [SERVER] StateNamecreated: 41c64e6da5559945 >>> wnfserver.Write(b"potatosoup") Encoded Name: 41c64e6da5559945, Clear Name: 6e99931 Version: 1, Permanent: No, Scope: Machine, Lifetime: Temporary, Unique: 56627 State update: 11 bytes written
>>> from wnfcomimport Wnfcom >>> wnfclient = Wnfcom() >>> wnfclient.SetStateName("41c64e6da5559945") >>> wnfclient.Listen() [CLIENT] Event registered: 440 [CLIENT] Timestamp: 0x1 Size: 0xb Data:00000000: 70 6F 74 61 74 6F 20 73 6F 75 70 potato soup
Wnfdump
WnfDump é um utilitário de linha de comando escrito em C. O arquivo executável pode ser encontrado em https://github.com/ionescu007/wnfun selecionando o subdiretório da profundidade de bits necessária. O utilitário pode ser usado para procurar informações sobre nomes de estado do WNF:
- -d ( D ump) Despeja todos os nomes de estado WNF usando uma enumeração baseada em registro. Pode ser complementado com opções:
- -v ( V erbose) Uma saída detalhada que inclui um dump hexadecimal de dados do estado WNF;
- -s ( S ecurity) Descritores de segurança - sequências de permissões SDDL para o nome do status do WNF.
- -b (Força bruta) Enumeração direta de nomes de estado temporários do WNF (mais sobre isso abaixo)
- -i (Informação) Exibe informações sobre um único nome de estado WNF especificado
- -r (Leia) Leia dados do nome do status WNF especificado
- -w ( W rite) Grava dados no nome do status WNF especificado
- -n (Notificação) Registre um assinante de notificação para o nome de status WNF especificado (a seguir, será um caso de uso mais específico com o Edge)
Superfície de ataque WNF
Esta seção (mais precisamente, suas subseções) discutirá possíveis ataques e dados sensíveis interessantes do WNF.
Divulgação Privilegiada de Dados
Lendo os milhares de nomes de estados WNF existentes no sistema, vários podem ser observados, cujos dados parecem muito interessantes. Entre eles estavam alguns cujos dados são suspeitamente semelhantes a ponteiros ou outros dados privilegiados.
Depois de jogar em várias máquinas, em alguns casos foi possível encontrar várias informações privilegiadas que foram divulgadas além dos limites de privilégios. Os relatórios de erros / vulnerabilidades foram enviados ao MSRC em julho, mas foram corrigidos em novembro (após a apresentação). Por exemplo: 4 kilobytes de pilha vazaram através do evento WNF_AUDC *!
Os principais problemas são os mesmos que vimos em estudos anteriores de j00ro, taviso e outros. Certos nomes de estado WNF contêm estruturas de dados codificadas com vários problemas de preenchimento e / ou alinhamento. Em alguns casos, vazamentos de memória não inicializados.
Do tradutor : tradução da parte introdutória do documento Detecting Kernel Memory Disclosure with Emulation x86 and Taint Tracking de Mateusz Jurczyk aka j00ro .
Descoberta de nomes e permissões de estado
A primeira abordagem foi descobrir todos os nomes de estado possíveis que pudessem ser manipulados maliciosamente. Para nomes conhecidos, permanentes e persistentes, a enumeração é possível enumerando as chaves do Registro. Em seguida, os valores encontrados podem ser comparados com identificadores amigáveis (existem vários lugares onde você pode encontrá-los :))
Depois, também podemos examinar o descritor de segurança no registro (esta é a primeira coisa no buffer de dados). O descritor de segurança não é canônico: ele não possui um proprietário e um grupo, portanto, tecnicamente, não é válido. Mas não há problema em substituir um proprietário e um grupo falsos para corrigir o descritor de segurança.
Detecção de nomes de estado temporários e suas permissões
Mas com nomes temporários, os truques descritos acima não funcionarão: eles não estão no registro. E apenas o kernel armazena estruturas de dados para eles (! Wnf) na memória. Mas nomes temporários não são tão difíceis de forçar:
- Versão sempre importa 1
- A vida sempre importa WnfTemporaryStateName
- O sinalizador permanente é sempre limpo (o nome do estado temporário não pode ter dados permanentes)
- O escopo (escopo) pode assumir um dos 4 valores
Sim, mas o número de sequência restante é de 51 bits! De fato ... mas não esqueça que os números de série estão crescendo monotonamente. E para nomes temporários, a sequência é redefinida para 0 em cada inicialização. Convencionalmente, você pode obter uma janela de um milhão de números de série: em um loop, verifique a existência de cada nome (começando em 0) chamando ZwQueryWnfStateNameInformation com a classe de informações solicitadas WnfInfoStateNameExist (dado que o erro de acesso também indica a existência de um nome). Se não existir outro milhão de nomes, você poderá interromper a pesquisa.
Descritores de segurança de nomes temporários (como outros dados de nomes temporários) são armazenados no kernel. Portanto, a única maneira de solicitá-las é a extensão! Wnf ao depurar o modo kernel. Mas nós podemos:
- Faça uma conclusão sobre permissões de leitura ao tentar ler dados.
- Concluir que a gravação é permitida tentando gravar dados. Mas vale a pena considerar que uma gravação bem-sucedida de até 0 bytes destrói os dados que o consumidor real ainda não conseguiu obter. E, novamente, há um truque: podemos aplicar o carimbo de alteração apropriado. Estamos tentando escrever com o rótulo 0xFFFFFFFF: o rótulo é verificado após a verificação de acesso; portanto, o valor do erro resulta em um vazamento da permissão de gravação.
Isso não nos fornece um descritor de segurança completo, mas executando o código com privilégios diferentes, podemos ter uma idéia das restrições para diferentes contas do sistema (IL baixo / Usuário / Admin / SISTEMA).
Listando assinantes
Na estrutura WNF_PROCESS_CONTEXT, um dos campos é o cabeçalho da lista (LIST_ENTRY) de todas as assinaturas desse processo. Cada assinatura é uma instância separada do WNF_SUBSCRIPTION.
Os assinantes no modo kernel pertencem principalmente ao processo do sistema. Podemos usar o comando! List debugger para despejar manipuladores e seus parâmetros registrados no processo do sistema WNF_SUBSCRIPTION. Vale ressaltar que, em alguns casos, é usado um agregador de eventos (CEA.SYS), que oculta os endereços de retorno de chamada reais em sua estrutura de contexto.
Podemos repetir essa abordagem para processos no modo usuário, mas o endereço de retorno de chamada será NULL, pois são assinantes no modo usuário. Portanto, precisamos ingressar no espaço do usuário do processo, obter a tabela RtlpWnfProcessSubscriptions e despejar a lista de instâncias WNF_USER_SUBSCRIPTION, cada uma das quais já contém o endereço de retorno de chamada. Infelizmente, esse caractere é estático, o que significa que não está em caracteres abertos, mas pode ser encontrado desmontando. E novamente, vale a pena prestar atenção (por analogia com o modo de kernel CEA.SYS) que muitos dos manipuladores de modo de usuário usam o agregador de eventos (EventAggregation.dll), que armazena o retorno de chamada em seu contexto.
Nomes de estado WNF interessantes e sensíveis
Esta seção fornecerá alguns exemplos interessantes de como alguns nomes de estado do WNF revelam informações do sistema.
Determinando o status do sistema e o comportamento do usuário usando o WNF
Alguns identificadores WNF podem ser usados para obter informações sobre o estado da máquina que lhe interessa:
- WNF_WIFI_CONNECTION_STATUS - Status sem fio
- WNF_BLTH_BLUETOOTH_STATUS - da mesma forma, mas para Bluetooth (também WNF_TETH_TETHERING_STATE)
- WNF_UBPM_POWER_SOURCE - mostra a fonte de energia (bateria ou adaptador de energia)
- WNF_SEB_BATTERY_LEVEL - contém o nível da bateria
- WNF_CELL_ * - no Windows Phone contém informações sobre: rede, número, intensidade do sinal, EDGE ou 3G, ...
WNF :
- WNF_AUDC_CAPTURE/RENDER — ( PID), /
- WNF_TKBN_TOUCH_EVENT — ,
- WNF_SEB_USER_PRESENT/WNF_SEB_USER_PRESENCE_CHANGED — Windows
API
, API , API , , /. WNF . , , WNF .
: WNF_SHEL_(DESKTOP)_APPLICATION_(STARTED/TERMINATED) modern- ( , ) DCOM, Win32. — ShellExecute: Explorer, cmd.exe, ...
, WNF API , :
- WNF_SHEL_LOCKSCREEN_ACTIVE —
- WNF_EDGE_LAST_NAVIGATED_HOST — URL, ( ) Edge
WNF
WNF, . : WNF_FSRL_OPLOCK_BREAK — , (/), PID' !
WNF , . : WNF_SHEL_DDC_(WNS/SMS)_COMMAND – 4 , .
, WNF, . : WNF_CERT_FLUSH_CACHE_TRIGGER ( ), WNF_BOOT_MEMORY_PARTITIONS_RESTORE, WNF_RTDS_RPC_INTERFACE_TRIGGER_CHANGED, ...
WNF
:
- WriteProcessMemory —
- ( ) —
- (Atom) —
- — , WM_COPYDATA DDE,
- GUI — ( ) ,
WNF :
- WNF, (, )
- Rtl/ZwQueryWnfStateData WNF
, :
- APC s
- (Remote Threads)
- (Changing Thread Context)
- " window long " — , ,
WNF_USER_SUBSCRIPTION ( WNF_NAME_SUBSCRIPTION, RtlpWnfProcessSubscriptions). ( CFG ), ( 5 6 ).
, : , , , -.
WNF SEB_, ( S ystem E vents B roker). SystemEventsBrokerServer.dll SystemEventsBrokerClient.dll API . , SEB SEB, .
CEA.SYS EventAggregation.dll. " " (Event Aggregation Library), , : , , WNF , . WNF, . .
: .
, Windows Notification Facility Alex' Gabrielle. ( ) redp .

WNF ( ) wincheck . , Gabrielle Viala , redp, : http://redplait.blogspot.com/search/label/wnf .
PoC ( github ) explorer ( — notepad). modexp : Callback WNF_USER_SUBSCRIPTION. :
- explorer.exe
- WNF_USER_SUBSCRIPTION
- RWX- , WriteProcessMemory (, VirtualAllocEx + WriteProcessMemory)
- WNF_USER_SUBSCRIPTION ( WriteProcessMemory)
- ntdll!NtUpdateWnfStateData(...) ,
- WNF_USER_SUBSCRIPTION