Asuka Nakajima

Detectando keyloggers baseados em teclas de atalho usando uma estrutura de dados não documentada do kernel

Neste artigo, exploramos o que são keyloggers baseados em teclas de atalho e como detectá-los. Especificamente, explicamos como esses keyloggers interceptam as teclas digitadas e, em seguida, apresentamos uma técnica de detecção que utiliza uma tabela de teclas de atalho não documentada no espaço do kernel.

Detectando keyloggers baseados em teclas de atalho usando uma estrutura de dados não documentada do kernel

Detectando keyloggers baseados em teclas de atalho usando uma estrutura de dados não documentada do kernel

Neste artigo, exploramos o que são keyloggers baseados em teclas de atalho e como detectá-los. Especificamente, explicamos como esses keyloggers interceptam as teclas digitadas e, em seguida, apresentamos uma técnica de detecção que utiliza uma tabela de teclas de atalho não documentada no espaço do kernel.

Introdução

Em maio de 2024, o Elastic Security Labs publicou um artigo destacando novos recursos adicionados ao Elastic Defend (a partir da versão 8.12) para aprimorar a detecção de keyloggers em execução no Windows. Naquela postagem, cobrimos quatro tipos de keyloggers comumente usados em ataques cibernéticos — keyloggers baseados em polling, keyloggers baseados em hooking, keyloggers que usam o Raw Input Model e keyloggers que usam DirectInput — e explicamos nossa metodologia de detecção. Em particular, introduzimos um método de detecção baseado em comportamento usando o provedor Microsoft-Windows-Win32k dentro do Rastreamento de Eventos para Windows (ETW).

Pouco tempo após a publicação, tivemos a honra de ter nosso artigo notado por Jonathan Bar Or, Pesquisador Principal de Segurança da Microsoft. Ele nos deu um feedback inestimável ao destacar a existência de keyloggers baseados em teclas de atalho e até compartilhou o código de prova de conceito (PoC) conosco. Aproveitando o código PoC Hotkeyz como ponto de partida, este artigo apresenta um método potencial para detectar keyloggers baseados em teclas de atalho.

Visão geral dos keyloggers baseados em teclas de atalho

O que é uma tecla de atalho?

Antes de nos aprofundarmos nos keyloggers baseados em teclas de atalho, vamos esclarecer o que é uma tecla de atalho. Uma tecla de atalho é um tipo de atalho de teclado que aciona diretamente uma função específica em um computador ao pressionar uma única tecla ou uma combinação de teclas. Por exemplo, muitos usuários do Windows pressionam Alt + Tab para alternar entre tarefas (ou, em outras palavras, janelas). Neste caso, Alt + Tab atua como uma tecla de atalho que aciona diretamente a função de troca de tarefas.

(Nota: embora existam outros tipos de atalhos de teclado, este artigo se concentra exclusivamente nas teclas de atalho. Além disso, todas as informações aqui são baseadas no Windows 10 versão 22H2 OS Build 19045.5371 sem segurança baseada em virtualização. Observe que as estruturas de dados internas e o comportamento podem diferir em outras versões do Windows.

Abusando da funcionalidade de registro de teclas de atalho personalizadas

Além de usar as teclas de atalho pré-configuradas no Windows, como mostrado no exemplo anterior, você também pode registrar suas próprias teclas de atalho personalizadas. Existem vários métodos para fazer isso, mas uma abordagem direta é usar a função RegisterHotKey da API do Windows, que permite que um usuário registre uma tecla específica como uma tecla de atalho. Por exemplo, o seguinte trecho de código demonstra como usar a API RegisterHotKey para registrar a tecla A (com um código de tecla virtual de 0x41) como uma tecla de atalho global:

/*
BOOL RegisterHotKey(
  [in, optional] HWND hWnd, 
  [in]           int  id,
  [in]           UINT fsModifiers,
  [in]           UINT vk
);
*/
RegisterHotKey(NULL, 1, 0, 0x41);

Após registrar uma tecla de atalho, quando a tecla registrada é pressionada, uma mensagem WM_HOTKEY é enviada para a fila de mensagens da janela especificada como o primeiro argumento para a API RegisterHotKey (ou para a thread que registrou a tecla de atalho se NULL for usado). O código abaixo demonstra um loop de mensagens que utiliza a API GetMessage para verificar uma mensagem WM_HOTKEY na fila de mensagens e, se recebida, extrai o código da tecla virtual (neste caso, 0x41) da mensagem.

MSG msg = { 0 };
while (GetMessage(&msg, NULL, 0, 0)) {
    if (msg.message == WM_HOTKEY) {
        int vkCode = HIWORD(msg.lParam);
        std::cout << "WM_HOTKEY received! Virtual-Key Code: 0x"
            << std::hex << vkCode << std::dec << std::endl;
    }
}

Em outras palavras, imagine que você está escrevendo algo em um aplicativo de bloco de notas. Se a tecla A for pressionada, o caractere não será tratado como entrada de texto normal — ele será reconhecido como uma tecla de atalho global.

Neste exemplo, apenas a tecla A é registrada como uma tecla de atalho. No entanto, você pode registrar várias teclas (como B, C ou D) como teclas de atalho separadas ao mesmo tempo. Isso significa que qualquer tecla (ou seja, qualquer código de tecla virtual) que possa ser registrada com a API RegisterHotKey pode ser potencialmente sequestrada como uma tecla de atalho global. Um keylogger baseado em teclas de atalho abusa dessa capacidade para capturar as teclas digitadas pelo usuário.

Com base em nossos testes, descobrimos que não apenas as teclas alfanuméricas e de símbolos básicos, mas também essas teclas quando combinadas com o modificador SHIFT, podem ser registradas como teclas de atalho usando a API RegisterHotKey. Isso significa que um keylogger pode monitorar efetivamente cada pressionamento de tecla necessário para roubar informações sensíveis.

Capturando teclas furtivamente

Vamos passar pelo processo real de como um keylogger baseado em teclas de atalho captura as teclas digitadas, usando o keylogger Hotkeyz como exemplo.

No Hotkeyz, ele primeiro registra cada código de tecla virtual alfanumérico — e algumas teclas adicionais, como VK_SPACE e VK_RETURN — como teclas de atalho individuais usando a API RegisterHotKey.

Em seguida, dentro do loop de mensagens do keylogger, a API PeekMessageW é utilizada para verificar se alguma mensagem WM_HOTKEY dessas teclas de atalho registradas apareceu na fila de mensagens. Quando uma mensagem WM_HOTKEY é detectada, o código da tecla virtual que ela contém é extraído e eventualmente salvo em um arquivo de texto. Abaixo está um trecho do código do loop de mensagens, destacando as partes mais importantes.

while (...)
{
    // Get the message in a non-blocking manner and poll if necessary
    if (!PeekMessageW(&tMsg, NULL, WM_HOTKEY, WM_HOTKEY, PM_REMOVE))
    {
        Sleep(POLL_TIME_MILLIS);
        continue;
    }
....
   // Get the key from the message
   cCurrVk = (BYTE)((((DWORD)tMsg.lParam) & 0xFFFF0000) >> 16);

   // Send the key to the OS and re-register
   (VOID)UnregisterHotKey(NULL, adwVkToIdMapping[cCurrVk]);
   keybd_event(cCurrVk, 0, 0, (ULONG_PTR)NULL);
   if (!RegisterHotKey(NULL, adwVkToIdMapping[cCurrVk], 0, cCurrVk))
   {
       adwVkToIdMapping[cCurrVk] = 0;
       DEBUG_MSG(L"RegisterHotKey() failed for re-registration (cCurrVk=%lu,    LastError=%lu).", cCurrVk, GetLastError());
       goto lblCleanup;
   }
   // Write to the file
  if (!WriteFile(hFile, &cCurrVk, sizeof(cCurrVk), &cbBytesWritten, NULL))
  {
....

Um detalhe importante é: para evitar alertar o usuário sobre a presença do keylogger, uma vez que o código da tecla virtual é extraído da mensagem, o registro da tecla de atalho é temporariamente removido usando a API UnregisterHotKey. Depois disso, a tecla pressionada é simulada com keybd_event para que pareça ao usuário que a tecla foi pressionada normalmente. Uma vez que o pressionamento da tecla é simulado, a tecla é registrada novamente usando a API RegisterHotKey para esperar por mais entradas. Este é o núcleo mecanismo por trás de como um keylogger baseado em teclas de atalho opera.

Detectando keyloggers baseados em atalhos de teclado

Agora que entendemos o que são keyloggers baseados em teclas de atalho e como eles operam, vamos explicar como detectá-los.

O ETW não monitora a API RegisterHotKey.

Seguindo a abordagem descrita em artigo anterior, primeiro investigamos se o Rastreamento de Eventos para Windows (ETW) poderia ser usado para detectar keyloggers baseados em teclas de atalho. Nossa pesquisa revelou rapidamente que o ETW atualmente não monitora as APIs RegisterHotKey ou UnregisterHotKey. Além de revisar o arquivo de manifesto do provedor Microsoft-Windows-Win32k, realizamos engenharia reversa dos componentes internos da API RegisterHotKey — especificamente, a função NtUserRegisterHotKey no win32kfull.sys. Infelizmente, não encontramos evidência de que essas APIs disparem eventos ETW quando executadas.

A imagem abaixo mostra uma comparação entre o código descompilado para **NtUserGetAsyncKeyState** (que é monitorado por ETW) e **NtUserRegisterHotKey**. Note que no início de **NtUserGetAsyncKeyState**, há uma chamada para **EtwTraceGetAsyncKeyState** — uma função associada ao logging de eventos ETW — enquanto **NtUserRegisterHotKey** não contém tal chamada.

 
Embora também tenhamos considerado o uso de provedores ETW diferentes do Microsoft-Windows-Win32k para monitorar indiretamente chamadas para a API RegisterHotKey, descobrimos que o método de detecção usando a "tabela de teclas de atalho" — que será introduzido em seguida e não depende do ETW — alcança resultados comparáveis ou até melhores do que monitorar a API RegisterHotKey. No final, escolhemos implementar este método.

Detecção usando a Tabela de Teclas de Atalho (gphkHashTable)

Após descobrir que o ETW não monitora diretamente as chamadas para a API RegisterHotKey, começamos a explorar métodos de detecção que não dependem do ETW. Durante nossa investigação, nos perguntamos: "As informações das teclas de atalho registradas não estão armazenadas em algum lugar? E, se sim, esses dados poderiam ser usados para detecção? Com base nessa hipótese, rapidamente encontramos uma tabela de hash chamada gphkHashTable dentro de NtUserRegisterHotKey. A busca na documentação online da Microsoft não revelou detalhes sobre gphkHashTable, sugerindo que é uma estrutura de dados do kernel não documentada.

Por meio de engenharia reversa, descobrimos que essa tabela de hash armazena objetos que contêm informações sobre teclas de atalho registradas. Cada objeto armazena detalhes, como o código da tecla virtual e os modificadores especificados nos argumentos para a API RegisterHotKey. O lado direito da Figura 3 mostra parte da definição da estrutura de um objeto de tecla de atalho (chamado HOT_KEY), enquanto o lado esquerdo exibe como os objetos de tecla de atalho registrados aparecem quando acessados via WinDbg.

Também determinamos que ghpkHashTable é estruturado conforme mostrado na Figura 4. Especificamente, ele usa o resultado da operação de módulo (com 0x80) no código da tecla virtual (especificado pela API RegisterHotKey) como o índice na tabela de hash. Objetos de teclas de atalho que compartilham o mesmo índice são ligados em uma lista, permitindo que a tabela armazene e gerencie informações de teclas de atalho mesmo quando os códigos de teclas virtuais são idênticos, mas os modificadores diferem.

Em outras palavras, ao escanear todos os objetos HOT_KEY armazenados no ghpkHashTable, podemos recuperar detalhes sobre cada tecla de atalho registrada. Se descobrirmos que cada tecla principal — por exemplo, cada tecla alfanumérica individual — está registrada como uma tecla de atalho separada, isso indica fortemente a presença de um keylogger ativo baseado em teclas de atalho.

Implementação da ferramenta de detecção

Agora, vamos passar para a implementação da ferramenta de detecção. Como gphkHashTable reside no espaço do kernel, ele não pode ser acessado por um aplicativo em modo de usuário. Por essa razão, foi necessário desenvolver um driver de dispositivo para detecção. Mais especificamente, decidimos desenvolver um driver de dispositivo que obtém o endereço do gphkHashTable e examina todos os objetos de tecla de atalho store na tabela de hash. Se o número de teclas alfanuméricas registradas como teclas de atalho exceder um limite predefinido, isso nos alertará sobre a possível presença de um keylogger baseado em teclas de atalho.

Como obter o endereço de gphkHashTable

Ao desenvolver a ferramenta de detecção, um dos primeiros desafios que enfrentamos foi obter o endereço do gphkHashTable. Após algumas considerações, decidimos extrair o endereço diretamente de uma instrução no driver win32kfull.sys que acessa gphkHashTable.

Por meio de engenharia reversa, descobrimos que dentro da função IsHotKey — logo no início — há uma instrução lea (lea rbx, gphkHashTable) que acessa gphkHashTable. Usamos a sequência de bytes do opcode (0x48, 0x8d, 0x1d) dessa instrução como assinatura para localizar a linha correspondente e, em seguida, calculamos o endereço de gphkHashTable usando o deslocamento de 32 bits (4 bytes) obtido.

Além disso, como IsHotKey não é uma função exportada, também precisamos saber o seu endereço antes de procurar por gphkHashTable. Por meio de engenharia reversa adicional, descobrimos que a função exportada EditionIsHotKey chama a função IsHotKey. Portanto, decidimos calcular o endereço de IsHotKey dentro da função EditionIsHotKey usando o mesmo método descrito anteriormente. (Para referência, o endereço base do win32kfull.sys pode ser encontrado usando a API PsLoadedModuleList.)

Acessando o espaço de memória do win32kfull.sys

Assim que finalizamos nossa abordagem para obter o endereço de gphkHashTable, começamos a escrever código para acessar o espaço de memória do win32kfull.sys e recuperar esse endereço. Um desafio nessa fase foi que win32kfull.sys é um driver de sessão. Antes de seguir adiante, aqui está uma explicação breve e simplificada do que é uma sessão.

No Windows, quando um usuário faz log in, uma sessão separada (com números de sessão começando em 1) é atribuída a cada usuário. Simplificando, o primeiro usuário a fazer log in é atribuído à Sessão 1. Se outro usuário fizer login enquanto a sessão estiver ativa, esse usuário receberá a Sessão 2, e assim por diante. Cada usuário tem seu próprio ambiente de trabalho dentro de sua sessão atribuída.

Os dados do kernel que devem ser gerenciados separadamente para cada sessão (ou seja, por usuário conectado) são armazenados em uma área isolada da memória do kernel chamada espaço da sessão. Isso inclui objetos de GUI gerenciados por drivers win32k, como janelas e dados de entrada de mouse/teclado, garantindo que a tela e a entrada permaneçam devidamente separadas entre os usuários.

(Esta é uma explicação simplificada. Para uma discussão mais detalhada sobre as sessões, consulte o post do blog de James Forshaw.)

Com base no exposto acima, win32kfull.sys é conhecido como um driver de sessão. Isso significa que, por exemplo, as informações de teclas de atalho registradas na sessão do primeiro usuário logado (Sessão 1) só podem ser acessadas de dentro dessa mesma sessão. Então, como podemos contornar essa limitação? Nesses casos, sabe-se que KeStackAttachProcess pode ser utilizado.

KeStackAttachProcess permite que a thread atual se conecte temporariamente ao espaço de endereço de um processo especificado. Se conseguirmos anexar a um processo GUI na sessão de destino — mais precisamente, um processo que carregou win32kfull.sys — então poderemos acessar win32kfull.sys e seus dados associados dentro dessa sessão. Para nossa implementação, presumindo que apenas um usuário está logado, decidimos localizar e anexar ao winlogon.exe, o processo responsável por lidar com operações de logon do usuário.

Enumerando teclas de atalho registradas

Depois de nos conectarmos com sucesso ao processo winlogon.exe e determinarmos o endereço de gphkHashTable, o próximo passo é simplesmente escanear gphkHashTable para verificar as teclas de atalho registradas. A seguir está um trecho desse código:

BOOL CheckRegisteredHotKeys(_In_ const PVOID& gphkHashTableAddr)
{
-[skip]-
    // Cast the gphkHashTable address to an array of pointers.
    PVOID* tableArray = static_cast<PVOID*>(gphkHashTableAddr);
    // Iterate through the hash table entries.
    for (USHORT j = 0; j < 0x80; j++)
    {
        PVOID item = tableArray[j];
        PHOT_KEY hk = reinterpret_cast<PHOT_KEY>(item);
        if (hk)
        {
            CheckHotkeyNode(hk);
        }
    }
-[skip]-
}

VOID CheckHotkeyNode(_In_ const PHOT_KEY& hk)
{
    if (MmIsAddressValid(hk->pNext)) {
        CheckHotkeyNode(hk->pNext);
    }

    // Check whether this is a single numeric hotkey.
    if ((hk->vk >= 0x30) && (hk->vk <= 0x39) && (hk->modifiers1 == 0))
    {
        KdPrint(("[+] hk->id: %u hk->vk: %x\n", hk->id, hk->vk));
        hotkeyCounter++;
    }
    // Check whether this is a single alphabet hotkey.
    else if ((hk->vk >= 0x41) && (hk->vk <= 0x5A) && (hk->modifiers1 == 0))
    {
        KdPrint(("[+] hk->id: %u hk->vk: %x\n", hk->id, hk->vk));
        hotkeyCounter++;
    }
-[skip]-
}
....
if (CheckRegisteredHotKeys(gphkHashTableAddr) && hotkeyCounter >= 36)
{
   detected = TRUE;
   goto Cleanup;
}

O código em si é direto: ele percorre cada índice da tabela de hash, seguindo a lista encadeada para acessar cada objeto HOT_KEY e verifica se as teclas de atalho registradas correspondem a teclas alfanuméricas sem modificadores. Em nossa ferramenta de detecção, se cada tecla alfanumérica for registrada como uma tecla de atalho, um alerta é emitido, indicando a possível presença de um keylogger baseado em teclas de atalho. Para simplificar, esta implementação foca apenas em teclas de atalho alfanuméricas, embora seja fácil expandir a ferramenta para verificar teclas de atalho com modificadores como SHIFT.

Detectando Hotkeyz

A ferramenta de detecção (Hotkey-based Keylogger Detector) foi disponibilizada abaixo. Instruções detalhadas de uso também são fornecidas. Além disso, esta pesquisa foi apresentada na NULLCON Goa 2025, e os slides da apresentação estão disponíveis.

https://github.com/AsuNa-jp/HotkeybasedKeyloggerDetector

A seguir, temos um vídeo de demonstração que mostra como o Detector de Keylogger baseado em teclasd e atalho detecta Hotkeyz.

DEMO_VIDEO.mp4

Agradecimentos

Gostaríamos de expressar nossa sincera gratidão a Jonathan Bar Or por ler nosso artigo anterior, compartilhar seus insights sobre keyloggers baseados em teclas de atalho e publicar generosamente a ferramenta PoC Hotkeyz.

Compartilhe este artigo