Técnica usa nomes de threads no Windows para injeção de código em processos

Thread Name-Calling abusa de APIs de descrição de threads e APCs para gravar e executar shellcode em processos remotos sem depender do direito PROCESS_VM_WRITE.

ComponenteAPIs de descrição de threads do Windows, incluindo SetThreadDescription, GetThreadDescription, NtSetInformationThread e NtQueryInformationThread, combinadas com APCs de usuário.
VetorUm processo com acesso a handles de processo e thread usa permissões como THREAD_SET_LIMITED_INFORMATION e THREAD_SET_CONTEXT para associar um buffer ao nome de uma thread remota e recuperá-lo no contexto do processo alvo.
ImpactoA técnica permite alocar e preencher memória no processo remoto com shellcode e depois acionar execução por APC, sem exigir PROCESS_VM_WRITE no handle do processo.
PrioridadeMonitorar eventos ETW de alteração de ThreadName, uso incomum de APCs remotos e combinações de acesso a threads que fogem de fluxos legítimos de depuração ou observabilidade.
VersõesAs funções de descrição de thread existem desde Windows 10 versão 1607; QueueUserAPC2 é exposta oficialmente no Windows 11 build 22000, e NtQueueApcThreadEx2 foi observada no Windows 10 build 19045.
ArtefatosUso de UNICODE_STRING, armazenamento em ETHREAD -> ThreadName, alocação em NonPagedPoolNx, eventos ETW com ProcessID, ThreadID e nome definido para a thread.
Resumo técnico

Thread Name-Calling é uma técnica de injeção em processo que explora uma finalidade legítima do Windows: permitir que aplicações atribuam nomes descritivos a threads. O mecanismo esperado ajuda depuração e diagnóstico, mas o mesmo caminho pode transportar um buffer controlado para uma thread de outro processo. A particularidade técnica é que a gravação do conteúdo não depende do direito clássico PROCESS_VM_WRITE, frequentemente tratado por produtos de segurança como sinal de injeção. Em vez de chamar diretamente APIs tradicionalmente associadas a escrita em memória remota, a técnica usa a infraestrutura de nome de thread para fazer o núcleo armazenar um UNICODE_STRING associado ao campo ThreadName da estrutura ETHREAD.

O fluxo descrito combina duas ideias. A primeira é usar SetThreadDescription ou uma variante de baixo nível baseada em NtSetInformationThread para colocar dados no nome de uma thread remota. A segunda é fazer com que GetThreadDescription seja executada no contexto do processo alvo, provocando uma alocação local naquele processo e copiando para ela o conteúdo previamente associado à thread. Com isso, um buffer que representa shellcode pode acabar materializado na memória do processo remoto por um caminho incomum. A execução pode ser acionada por APCs de usuário, inclusive com variantes mais recentes que reduzem a dependência de uma thread em estado alertável.

O valor defensivo da técnica está em mostrar uma região de baixa visibilidade para detecção comportamental. Muitas regras de EDR e antivírus procuram sequências conhecidas de injeção, como abertura de processo com direito de escrita, alocação explícita em processo remoto, gravação de bytes e criação de thread remota. Thread Name-Calling quebra parte desse encadeamento ao deslocar a escrita para APIs que, isoladamente, parecem ligadas a diagnóstico. A defesa não deve tratar todo nome de thread como malicioso, mas precisa correlacionar tamanho do nome, origem do processo, permissões de handle, chamada remota por APC e ausência de contexto legítimo de depuração.

Fluxo técnico

A base da técnica está no comportamento das APIs introduzidas a partir do Windows 10 versão 1607 para nomeação de threads. Para definir uma descrição, um processo precisa abrir um handle da thread com THREAD_SET_LIMITED_INFORMATION. Esse requisito é menor do que os direitos normalmente associados a escrita remota e permite associar um buffer arbitrário a uma thread de outro processo. Pela interface documentada, o buffer é tratado como string Unicode terminada por caractere nulo duplo. O limite observado é generoso para esse tipo de campo: até 0x10000 bytes, com cerca de 0x10000 - 2 bytes úteis quando o terminador é considerado. Isso torna o campo grande o bastante para transportar um bloco de código codificado ou outro conteúdo binário adaptado à restrição de string.

Internamente, SetThreadDescription prepara uma estrutura UNICODE_STRING e passa os dados para NtSetInformationThread. No núcleo, a informação fica associada à thread em ETHREAD -> ThreadName, com alocação em NonPagedPoolNx. Esse detalhe é importante porque o nome não fica apenas como uma cadeia textual simples em memória de usuário. Quando GetThreadDescription consulta a descrição, a função cria uma cópia local no heap do processo chamador, reorganiza os campos iniciais da estrutura e entrega ao chamador uma string larga terminada por nulo. Se essa recuperação ocorrer remotamente no processo alvo, o efeito prático é uma alocação dentro daquele processo contendo os bytes controlados que foram definidos como nome de thread.

A restrição de Unicode é um limite técnico real. Pela chamada de alto nível, a presença de bytes nulos duplos no conteúdo encerra a string antes do fim do buffer, o que atrapalha a transferência direta de shellcode. O caminho descrito contorna essa limitação ao preparar manualmente a estrutura UNICODE_STRING e chamar NtSetInformationThread, em vez de depender da inicialização feita por RtlInitUnicodeStringEx. O ponto relevante para defesa é que a cópia posterior usa o comprimento declarado na estrutura e operações de movimentação de memória que não interpretam bytes nulos internos como terminadores. Assim, a técnica deixa de depender apenas de conteúdo textual válido e passa a permitir transporte de bytes que uma API de string normalmente truncaria.

Depois que o buffer chega ao processo remoto, a execução é tratada por APCs. O Windows permite enfileirar rotinas em threads existentes por meio de NtQueueApcThreadEx, NtQueueApcThread e wrappers como QueueUserAPC, desde que o handle da thread tenha THREAD_SET_CONTEXT. O uso de APC evita a criação direta de uma nova thread, evento que costuma acionar callbacks de núcleo monitorados por componentes de segurança. A variante mais nova, associada a QUEUE_USER_APC_FLAGS_SPECIAL_USER_APC, reduz a dependência de encontrar uma thread em estado alertável, pois a rotina é executada quando o fluxo da thread permite a entrega da APC. A técnica também descreve o uso de uma função legítima como intermediária para despachar a rotina, evitando passar diretamente o endereço de memória privada como alvo imediato da APC.

Superfície afetada

A superfície exposta inclui ambientes Windows nos quais processos possam abrir handles para threads de outros processos com permissões suficientes para definir descrição e enfileirar APCs. O material não descreve uma vulnerabilidade corrigível por patch específico nem um produto de terceiros vulnerável. Trata-se de abuso de funcionalidade legítima do sistema operacional, especialmente relevante para controles que dependem de padrões conhecidos de injeção. Sistemas com Windows 10 a partir da versão 1607 já têm as APIs de descrição de threads, enquanto a disponibilidade das variantes modernas de APC amplia as opções de execução remota em versões mais recentes e também em builds onde a função de baixo nível foi observada.

O risco não é uniforme para todos os processos. A técnica depende de handles apropriados, do contexto de privilégio do processo que tenta interagir com o alvo e da capacidade de chamar funções no processo remoto. Aplicações que expõem muitas threads de longa duração, serviços com permissões excessivas para processos do mesmo usuário e ambientes onde ferramentas não confiáveis executam no mesmo contexto de sessão aumentam a área prática para abuso. A defesa deve tratar a combinação de nomeação remota de thread e APC remota como mais forte do que cada evento isolado.

  • Threads remotas abertas com THREAD_SET_LIMITED_INFORMATION para receber descrições controladas por outro processo.
  • Threads remotas abertas com THREAD_SET_CONTEXT para enfileiramento de APCs de usuário.
  • Processos em que a escrita remota ocorre sem PROCESS_VM_WRITE, reduzindo a eficácia de detecções baseadas apenas nesse direito.
  • Ambientes Windows com APIs de descrição de thread e suporte a variantes modernas de APC, incluindo casos em Windows 10 e Windows 11 citados no contexto técnico.
Hunting e telemetria

O principal ponto de observação é o evento ETW gerado quando o nome de uma thread é definido. Esse evento inclui dados como ProcessID, ThreadID e o valor configurado como ThreadName, permitindo relacionar quem alterou a descrição e qual thread recebeu o conteúdo. Em fluxos legítimos, nomes de threads tendem a ser curtos, legíveis e coerentes com o papel da aplicação. Um nome muito grande, com baixa entropia textual, caracteres incomuns, padrões de codificação ou alterações frequentes feitas por um processo sem relação operacional com o alvo deve elevar a prioridade da investigação.

Outra linha de caça é correlacionar a sequência temporal. Um processo abre handle para uma thread remota com permissões limitadas de informação, define uma descrição extensa, em seguida provoca execução remota de função ou enfileira APC, e depois ocorre execução em memória privada no processo alvo. Mesmo que a técnica evite PROCESS_VM_WRITE, ela não elimina todos os rastros. A abertura de handles, chamadas a APIs nativas, entrega de APCs, mudanças anômalas de nome de thread e execução em região privada continuam compondo uma narrativa detectável quando analisadas em conjunto.

A telemetria de endpoint deve observar também o uso incomum de NtQueueApcThreadEx2, QueueUserAPC2 ou da flag QUEUE_USER_APC_FLAGS_SPECIAL_USER_APC. Essas APIs têm usos legítimos, mas sua presença em ferramentas que não fazem orquestração avançada de threads, depuração ou instrumentação pode ser suspeita. O uso de uma função intermediária como despachante de callback também merece análise quando aparece junto com ponteiros para regiões privadas ou sem associação clara a módulos mapeados. O objetivo defensivo é detectar o desvio de padrão, não bloquear indiscriminadamente APIs usadas pelo sistema e por software legítimo.

  • Eventos ETW de alteração de ThreadName com valores extensos, binários, codificados ou incompatíveis com nomes humanos de threads.
  • Processos que definem nomes de threads em processos remotos sem relação esperada de depuração, monitoramento ou gerenciamento.
  • Sequência de SetThreadDescription ou NtSetInformationThread seguida por GetThreadDescription executada no contexto do processo alvo.
  • Uso de APC remota com THREAD_SET_CONTEXT, especialmente com variantes especiais de APC de usuário.
  • Execução posterior em memória privada que não corresponde a imagem mapeada de módulo conhecido.
Mitigação

A mitigação mais efetiva começa por ampliar a cobertura comportamental para além das cadeias clássicas de injeção. Regras que exigem PROCESS_VM_WRITE como condição central podem perder essa técnica, porque a escrita é desviada para o mecanismo de descrição de thread. Controles de endpoint devem correlacionar chamadas de nomeação remota de threads, tamanho do buffer, identidade do processo chamador, direitos de handle e APCs subsequentes. Em ambientes com telemetria ETW centralizada, eventos de ThreadName devem ser preservados com contexto suficiente para permitir investigação retroativa.

A redução de superfície passa por limitar a execução de código não confiável no mesmo contexto de usuário de processos sensíveis, revisar permissões entre processos e fortalecer políticas de controle de aplicação. Quando um alerta indicar possível abuso, a resposta deve coletar árvore de processos, handles abertos, threads afetadas, regiões de memória privada executável, histórico de chamadas nativas quando disponível e eventos ETW correlacionados. A contenção deve priorizar o processo originador da alteração e o processo alvo onde o buffer foi materializado, pois a técnica separa o transporte do conteúdo e a execução final.

Também é importante validar falsos positivos com cuidado. Ferramentas legítimas de diagnóstico, depuradores, runtimes e agentes de observabilidade podem nomear threads e usar APCs. A diferença está no padrão: nomes curtos e semânticos, relação esperada entre processos, ausência de execução em memória privada e comportamento repetível por versão do software tendem a indicar benignidade. Já nomes com tamanho próximo ao limite, dados não textuais, execução logo após a recuperação da descrição e uso de APCs especiais formam uma combinação de alto valor para investigação.

  • Criar detecções que não dependam exclusivamente de PROCESS_VM_WRITE para identificar injeção em processo.
  • Registrar e correlacionar eventos ETW de ThreadName com abertura de handles e chamadas de APC remota.
  • Alertar para descrições de thread anormalmente grandes ou com aparência de conteúdo codificado em processos que não deveriam manipular threads remotas.
  • Investigar execução em memória privada após mudanças de nome de thread e enfileiramento de APC.
  • Revisar controles de aplicação e privilégios para reduzir a capacidade de processos não confiáveis interagirem com threads de processos sensíveis.