Pesquisa de Ameaças – WOW64!Hooks: Subsistema WOW64 Interno e Técnicas de Hooking

Views: 539
0 0
Read Time:22 Minute, 45 Second

A Microsoft é conhecida por sua compatibilidade inversa. Quando eles lançaram a variante de 64 bits do Windows anos atrás, eles precisavam fornecer compatibilidade com aplicativos de 32 bits existentes. A fim de fornecer uma execução perfeita, independentemente da bitness do aplicativo, o sistema WoW (Windows on Windows) foi cunhado. Esta camada, que será referida como ‘WOW64’ a partir de agora, é responsável por traduzir todas as chamadas de API do Windows de 32 bits para o núcleo do sistema operacional de 64 bits. Este post do blog é dividido em duas seções. Primeiro começamos mergulhando fundo no sistema WOW64. Para fazer isso, rastreamos uma chamada do espaço de usuário de 32 bits e seguimos os passos necessários para finalmente fazer a transição para o kernel. A segunda parte do post avalia duas técnicas de Hooking e sua eficácia. Vou cobrir como este sistema funciona, a maneira como o malware abusa dele, e detalhar um mecanismo pelo qual todas as chamadas wow podem ser fisgados do espaço do usuário. Observe que todas as informações aqui são verdadeiras a partir do Windows 10, versão 2004 e em alguns casos mudou de como as versões anteriores do Windows foram implementadas.

Reconhecimento

Em primeiro lugar, este é um tema que tem pesquisas existentes por vários autores. Este trabalho foi fundamental na exploração eficiente dos internos e a pesquisa teria demorado muito mais tempo se esses autores não tivessem postado publicamente seu trabalho incrível. Gostaria de chamar as seguintes referências:

  • (wbenny): Uma visão extremamente detalhada dos internos WOW64 na ARM
  • (ReWolf): A PoC heaven’s gate implementation
  • (JustasMasiulis): Uma implementação muito limpa do portão do céu C++
  • (MalwareTech): Uma explicação de segmentação WOW64

WOW64 Internals

Para entender como o sistema WOW64 funciona internamente, exploraremos a sequência de chamadas começando em um modelo de usuário de 32 bits antes de fazer a transição para o kernel de dentro de um DLL do sistema. Dentro desses DLLs do sistema, o sistema operacional verificará os argumentos e eventualmente fará a transição para um stub conhecido como stub syscall. Este stub syscall é responsável por atender a chamada API no kernel. Em um sistema de 64 bits, o stub de chamada é simples, pois executa diretamente a instrução de chamada, conforme mostrado na Figura 1.


Figura 1: WOW64 Syscall Stub

Figura 2 mostra um stub de chamada para um processo de 32 bits em execução no WOW64


Figura 2: Nativo x64 Syscall Stub

Observe que, em vez de uma instrução de chamada na versão WOW64, wow64SystemServiceCall é chamado. No sistema WOW64, o que normalmente seria uma entrada no kernel é substituído por uma chamada para uma rotina de modelo de usuário. Seguindo este Wow64SystemServiceCall,podemos ver na Figura 3 que ele imediatamente executa um jmp indireto através de um ponteiro chamado Wow64Transition.


Figura 3: Wow64SystemService transições através de um ponteiro ‘Wow64Transition’

Observe que a função Wow64SystemServiceCall é encontrada dentro da ntdll rotulada como ntdll_77550000; um processo WOW64 tem dois módulos ntdll carregados, um de 32 bits e um de 64 bits. O WinDbg diferencia-se entre esses dois colocando o endereço do módulo após a variante de 32 bits. A ntdll de 64 bits pode ser encontrada em %WINDIR%\System32 e 32 bits em %WINDIR%\SysWOW64. Nos PDBs, os ntdlls de 64bits e 32bits são referidos como ntdll.pdb e wntdll.pdb, respectivamente, tente carregá-los em um desmontagem! Continuando com o rastreamento de chamada, se olharmos para o que o ponteiro Wow64Transition segura, podemos ver que seu destino é wow64cpu! KiFastSystemCall. Como um aparte, note que o endereço de wow64cpu! KiFastSystemCall é realizado no TEB de 32 bits (Thread Environment Block) via membro WOW32Reserved, isso não é relevante para este rastreamento, mas é útil saber. Na Figura 4 vemos o corpo de KiFastSystemCall.


Figura 4: KiFastSystemCall transições para o modo x64 via seletor de segmento 0x33

O KiFastSystemCall executa um jmp usando o seletor do segmento 0x33 em um local de memória logo após a instrução. Este segmento 0x33 transita a CPU para o modo de 64 bits através de uma entrada GDT descrita por (MalwareTech).

Vamos recapitular o traço que fizemos até agora. Começamos a partir de uma chamada em NTDLL, NtResumeThread. Esta função chama a função Wow64SystemServiceCall que, em seguida, executa a Wow64Transition. O KiFastSystemCall realiza a transição de execução de 32 bits para 64 bits. O fluxo é mostrado na Figura 5.


Figura 5: Transição de 32 bits para 64 bits

O destino do salto de transição da CPU é o programa de código de 64 bits na Figura 6.


Figura 6: Destino do KiFastSystemCall

A Figura 6 mostra a primeira instrução de 64 bits que vimos executada neste rastro de chamada até agora. Para entendê-lo, precisamos ver como o sistema WOW64 se inicia. Para uma explicação detalhada disso consulte (wbenny). Por enquanto, podemos olhar para as partes importantes em wow64cpu! RunSimulatedCode.


Figura 7: Registros de 64bits são salvos no RunSimulatedCode

A Figura 7 mostra a recuperação do TEB de 64 bits que é usado para acessar o Thread Local Storage no slot index 1. Em seguida, a movimentação de uma tabela de ponteiro de função em registro r15. Os dados TLS recuperados é uma estrutura de dados não documentada WOW64_CPURESERVED que contém dados cadastrais e informações do estado da CPU usadas pela camada WOW64 para definir e restaurar registros nos limites de 32 bits e 64 bits. Dentro dessa estrutura está a estrutura WOW64_CONTEXT, parcialmente documentada no site da Microsoft. Listei ambas as estruturas no final deste post. Vamos ver como essa estrutura de contexto é usada mais tarde, mas para nossa compreensão do jmp anterior, tudo o que precisamos saber é que o R15 é uma tabela de ponteiro de função.

É interessante notar neste momento a arquitetura da camada WOW64. Do ponto de vista do kernel de 64 bits, a execução de aplicativos de uso de 32 bits (Wow64) é essencialmente um grande loop de tempo. O loop executa instruções x86 no modo de execução de 32 bits do processador e ocasionalmente sai do loop para atender uma chamada do sistema. Como o kernel tem 64 bits, o modo de processador é temporariamente alterado para 64 bits, a chamada do sistema atendida, então o modo mudou de volta e o loop continuou onde foi pausado. Pode-se dizer que a camada WOW64 age como um emulador onde as instruções são executadas na CPU física.

Voltando à instrução jmp que vimos na Figura 6, agora sabemos o que está ocorrendo. A instrução jmp [r15 + 0xF8] é equivalente ao código C jmp TurboThunkDispatch[0xF8 / sizeof(uint64_t)]. Olhando para o ponteiro de função neste índice podemos ver que estamos na função wow64cpu! CpupReturnFromSimulatedCode (Figura 8).


Figura 8: A última entrada de ponteiro de função do TurboThunk é uma rotina de saída

Essa rotina é responsável por salvar o estado dos registros de 32 bits na estrutura WOW64_CONTEXT que mencionamos antes, bem como recuperar os argumentos para a chamada. Há alguma trapaça acontecendo aqui, então vamos examinar isso em detalhes. Primeiro um ponteiro para a pilha é movido para r14 via xchg, o valor neste local será o endereço de retorno do stub syscall onde Wow64SystemServiceCall foi chamado. O ponteiro de pilha r14 é então incrementado por 4 para obter um ponteiro para onde a pilha deve ser redefinida na hora de restaurar todos esses valores de contexto. Esses dois valores são então armazenados nas variáveis EIP e ESP do contexto, respectivamente. O ponteiro de pilha r14 é então incrementado mais uma vez para obter o local onde os argumentos __stdcall estão (lembre-se stdcall passa todos os argumentos na pilha). Esta matriz de argumentos é importante para depois, lembre-se. O ponteiro de argumentos é movido para r11, então em C isso significa que r11 é equivalente a uma matriz de slots de pilha onde cada slot é um argumento uint32_t r11[argCount]. O resto dos registros e EFlags são salvos.

Uma vez que o contexto de 32 bits é salvo, a camada WOW64 então calcula o TurboThunk apropriado para invocar, pegando os 16 bits superiores do número de syscall e despacha para aquele thunk. Observe que no início desta matriz está a função TurboDispatchJumpAddressEnd, mostrada na Figura 9, que é invocada para funções que não suportam TurboThunks.


Figura 9: A primeira entrada de ponteiro de função da tabela TurboThunk é uma rotina de entrada

TurboThunks são descritos por (wbenny)— leia seu post no blog neste momento, se você não tiver. Para resumir o post, para funções que têm argumentos simples com larguras <= tamanhos (uint32_t) a camada WOW64 ampliará diretamente esses argumentos para 64 bits via zero ou extensão de sinal e, em seguida, executará uma chamada direta no kernel. Tudo isso ocorre dentro de wow64cpu, em vez de executar um caminho mais complexo detalhado da seguinte forma. Isso funciona como uma otimização. Para funções mais complexas que não suportam TurboThunks o stub TurboDispatchJumpAddressEnd é usado que despacha para wow64! SystemServiceEx para executar a chamada do sistema como mostrado na Figura 10.


Figura 10: Chamadas complexas do sistema passam pelo Wow64SystemServiceEx

Vamos olhar para esta rotina em um momento como é a carne deste post de blog, mas por enquanto vamos terminar este rastreamento de chamada. Uma vez que o Wow64SystemServiceEx retorna de fazer o sistema chamar o valor de retorno no eax é movido para a estrutura WOW64_CONTEXT e, em seguida, os estados de registro de 32 bits são restaurados. Há dois caminhos para isso, um caso comum e um caso que parece existir apenas para ser usado pela NtContinue e outros internos wow64. Uma bandeira no início da estrutura WOW64_CPURESERVED recuperada do slot TLS é verificada e controles que restauram o caminho a seguir conforme mostrado na Figura 11.


Figura 11: O estado da CPU é restaurado assim que a chamada do sistema é feita; há um caminho simples e um complexo manuseando registros XMM

O estojo mais simples construirá um jmp que usa o seletor de segmento 0x23 para fazer a transição para o modo de 32 bits depois de restaurar todos os registros salvos no WOW64_CONTEXT. O caso mais complexo irá, além disso, restaurar alguns segmentos, valores xmm e os registros salvos na estrutura WOW64_CONTEXT e, em seguida, fará um iret para transição de volta. O caso comum jmp uma vez construído é mostrado na Figura 12.


Figura 12: JMP construído dinamicamente para transição de volta para o modo 32bit

Neste momento, nosso rastro de chamada está completo. A camada WOW64 voltou para o modo de 32 bits e continuará a execução no ret após o Wow64SystemServiceCall no stub syscall com o qual começamos. Agora que se entende uma compreensão do fluxo da camada WOW64 em si, vamos examinar a chamada Wow64SystemServiceEx que já passamos antes.

Um pouco na rotina wow64SystemServiceEx, a Figura 13 mostra alguma lógica interessante que usaremos mais tarde.


Figura 13: Rotinas de registro invocadas antes e depois de despachar as chamadas

A rotina começa indexando-se em tabelas de serviço que seguram ponteiros para rotinas que convertem o conjunto de argumentos passados nos tipos mais amplos de 64 bits esperados pelos módulos regulares do sistema de 64 bits. Este conjunto de argumentos é exatamente o slot de pilha que foi armazenado anteriormente no r14.

Duas chamadas para a função LogService existem, no entanto, elas só são chamadas se o DLL %WINDIR%\system32\wow64log.dll estiver carregado e tiver as exportações Wow64LogInitialize, Wow64LogSystemService, Wow64LogMessageArgList e Wow64LogTerminate. Este DLL não está presente no Windows por padrão, mas pode ser colocado lá com privilégios de administrador.

A próxima seção detalhará como este DLL de registro pode ser usado para enganchar chamadas que transitam através desta wow64layer. Como a rotina de registro logService é invocada antes e depois que a chamada é atendida, podemos alcançar uma função de retorno de callback padrão com estilo de hook inline capaz de inspecionar argumentos e valores de devolução.

Contornando hooks inline

Como descrito neste post no blog, o Windows fornece uma maneira de aplicativos de 32 bits executarem chamadas de 64 bits em um sistema de 64 bits usando a camada WOW64. No entanto, o switch de segmentação que notamos anteriormente pode ser executado manualmente, e o shellcode de 64 bits pode ser escrito para configurar uma chamada. Esta técnica é popularmente chamada de “Portão do Céu”. O trabalho de JustasMasiulis call_function64 pode ser usado como referência para ver como isso pode ser feito na prática (JustasMasiulis). Quando as chamadas do sistema são realizadas desta forma, o stub de syscall de 32 bits que a camada WOW64 usa é completamente ignorado na cadeia de execução. Isso é lamentável para produtos de segurança ou ferramentas de rastreamento porque quaisquer hooks inline no lugar desses stubs também são ignorados. Os autores de malware sabem disso e utilizam o “Heaven’s Gate” como uma técnica de bypass em alguns casos. A Figura 14 e a Figura 15 mostram o fluxo de execução de um stub de syscall regular através da camada WOW64, e o stub de syscall ligado onde o malware utiliza “Heaven’s Gate”.


Figura 14: NtResumeThread transição através da camada WOW64


Figura 15: hook inline NtResumeThread antes de passar pela camada WOW64

Como visto na Figura 15, ao usar a técnica Heaven’s Gate, a execução começa após o hook inline e a camada WOW64 ser feita. Esta é uma técnica de bypass eficaz, mas que é fácil de detectar a partir de um nível inferior, como um driver ou hipervisor. O desvio mais fácil para hooks inline é simplesmente restaurar os bytes de função original, geralmente de bytes no disco. Malwares como AgentTesla e Conti são conhecidos por utilizar esta última técnica de evasão.

Hook WOW64 via hooks Inline

Como um analista de malware ser capaz de detectar quando as amostras tentam contornar a camada WOW64 pode ser muito útil. A técnica óbvia para detectar isso é colocar hooks inline nos stubs de syscall de 64 bits, bem como os stubs de syscall de 32 bits. Se o hook de 64 bits detecta uma invocação que também não passou pelo hook de 32 bits, então sabe-se que uma amostra está utilizando o Portão do Céu. Esta técnica pode detectar ambas as técnicas de evasão previamente detalhadas. No entanto, na prática, isso é muito difícil de implementar. Olhando para os requisitos que devem ser satisfeitos para enganchar o stub de 64 bits syscall nós elaboramos com esta lista:

  1. Instale hook de 64 bits de um módulo de 32 bits
    • Como ler/escrever espaço de endereço de 64 bits a partir de um módulo de 32 bits?
  2. Implementar um retorno de chamada de 64 bits a partir de um módulo de 32 bits
    • Normalmente, o hook inline usa funções C como stubs de retorno de chamada, mas estamos compilando um módulo de 32 bits, então teremos um retorno de chamada de 32 bits em vez do necessário de 64 bits.

Para resolver o primeiro desafio, a NTDll fornece as exportações NtWow64ReadVirtualMemory64, NtWow64WriteVirtualMemory64e NtWow64QueryInformationProcess64. Usando-os, é possível ler memória, escrever memória e recuperar o PEB de um módulo de 64 bits a partir de um processo de 32 bits. No entanto, o segundo desafio é muito mais difícil, pois um shellcode ou um JIT serão obrigados a criar um stub de retorno de chamada da bitness certa. Na prática, o ASMJIT pode ser utilizado para isso. Este é, no entanto, uma técnica muito tediosa para rastrear um grande número de APIs. Há outros desafios para essa técnica também. Por exemplo, no Windows 10 moderno, o endereço base do NTDLL64 é definido como um endereço alto de 64 bits em vez de um endereço de 32 bits mais baixo como no Windows 7. Devido a isso, o suporte de retornos dos retornos de chamadas de volta ao stub original e a alocação de um trampolim dentro do intervalo de memória necessário é difícil, uma vez que a instrução ret padrão não tem bits suficientes na pilha para representar o endereço de retorno de 64 bits.

À parte, deve-se notar que a camada WOW64 contém o que é provavelmente um bug ao lidar com as funções NtWow64*. Todas essas APIs tomam um HANDLE como primeiro argumento, que deve ser estendido para 64 bits. No entanto, isso não ocorre para essas APIs, portanto, ao usar a pseudo handle -1 a chamada falha com STATUS_INVALID_HANDLE. Este bug foi introduzido em uma versão desconhecida do Windows 10. Para usar com sucesso essas APIs OpenProcess devem ser usadas para recuperar uma alça real e positiva.

Eu não vou estar cobrindo os internos de como inline hook o stub syscall de 64 bits já que este post já é muito longo. Em vez disso, mostrarei como minha biblioteca de hooks PolyHook2 pode ser estendida para suportar hooks de arquitetura cruzada usando essas APIs do Windows, e deixar o resto como um exercício para o leitor. Isso funciona porque os trampolins da PolyHook não se limitam a +-2GB e não estragam os registros. A internal de como isso é alcançado é um tópico para outro post. A Figura 16 mostra como sobrecarregar a API C++ de polihook para ler/escrever memória usando os WinAPIs acima mencionados.


Figura 16: Sobrecarregando as operações de memória para ler/escrever/proteger a memória de 64 bits

Uma vez que estes hooks inline estão no lugar nos stubs de 64 bits, qualquer aplicação utilizando Heaven’s Gate será devidamente interceptada. Esta técnica de hook é muito invasiva e complicada e ainda pode ser ignorada se uma amostra for executar diretamente uma instrução de syscall em vez de usar o stub de syscalls do módulo de 64 bits. Portanto, um motorista ou hipervisor é mais adequado para detectar essa técnica de evasão. Em vez disso, podemos nos concentrar nas técnicas mais comuns de evasão de restauração byte e procurar uma maneira de enganchar a própria camada WOW64. Isso não envolve modificações de montagem.

Hook WOW64 via LogService

Pensando no fluxo de execução da camada WOW64, sabemos que todas as chamadas enviadas através da rotina Wow64SystemServiceEx podem invocar a rotina wow64LogSystemService se o DLL de registro estiver carregado. Podemos utilizar esta DLL de registro e rotina para implementar hooks que podem ser escritos exatamente da mesma forma que hooks inline, sem modificar qualquer montagem.

O primeiro passo para implementar isso é forçar todos os caminhos de chamadas da API através da rotina Wow64SystemServiceEx para que a rotina de log possa ser chamada. Lembre-se anteriormente que aqueles que suportam TurboThunks não tomarão esse caminho. Sorte nossa sabemos que qualquer entrada TurboThunk que aponta para TurboDispatchJumpAddressEnd tomará este caminho. Portanto, apontando cada entrada na tabela TurboThunk para apontar para esse endereço, o comportamento desejado é alcançado. O Windows gentilmente implementa este patching via wow64cpu! BTCpuTurboThunkControl como mostrado na Figura 17.


Figura 17: Remendar a tabela TurboThunk é implementado para nós

Note que nas versões anteriores do Windows o módulo que exportou isso e como ele fez é diferente do Windows 10, versão 2004. Depois de invocar essa rotina de patches, todos os caminhos de syscall através do WOW64SystemServiceEx e podemos nos concentrar em criar um DLL de registro que os MITMs (Man-in-the-Middles) todas as chamadas. Há alguns desafios a serem considerados aqui:

  1. Como determinamos qual chamada do sistema está ocorrendo atualmente a partir do DLL de registro?
  2. Como os retornos de chamada são escritos? Wow64log é DLL de 64 bits, gostaríamos de um retorno de chamada de 32 bits.
    • O shellcode é necessário, ou podemos fazer bons retornos de função estilo C?
  3. Que APIs podemos chamar? Tudo o que está carregado é 64 bits ntdll.

A primeira preocupação é bastante fácil, de dentro do WOW64log DLL podemos ler o número de syscall a partir dos stubs syscall para criar um mapa de número para nome. Isso é possível porque os stubs de chamadas de syscall sempre começam com o mesmo conjunto e o número de chamada está em um deslocamento estático de 0x4. A Figura 18 mostra como podemos comparar os valores neste mapa com o número de chamadas passadas para a estrutura de parâmetros do Wow64LogSystemService WOW64_LOG_SERVICE.

uint32_t uint32_t* WOW64_ARGUMENTS;
estrutura WOW64_LOG_SERVICE

{

uint64_t BtLdrEntry;

WOW64_ARGUMENTS Argumentos;
ULONG ServiceTable;
ULONG ServiceNumber;
Status NTSTATUS;
Boolean PostCall;
};
EXTERN_C __declspec(dllexport)

NTSTATUS

NTAPI

Wow64LogSystemService(serviço WOW64_LOG_SERVICE*)

{

para (uint32_t i = 0; i < LAST_SYSCALL_ID; i++)

{ const char* sysname = SysCallMap[i].name;

uint32_t syscallNum = SysCallMap[i]. SystemCallNumber;
se (ServiceParameters->ServiceNumber != syscallNum)

continuar;
//LOG sysname
     }
}

Figura 18: Exemplo mínimo de determinar qual syscall está ocorrendo — na prática, a tabela de serviços também deve ser verificada

Escrever retornos de chamadas é um pouco mais desafiador. O wow64log DLL está sendo executado no modo de 64 bits e gostaríamos de ser capazes de escrever retornos de chamadas no modo de 32 bits, já que é muito fácil carregar módulos adicionais de 32 bits em um processo WOW64. A melhor maneira de lidar com isso é escrever shellcode que é capaz de fazer a transição de volta para o modo de 32 bits, executar o callback e, em seguida, voltar ao modo de 64 bits para continuar a execução no DLL wow64log. As transições do segmento em si são bastante fáceis neste momento, sabemos que só precisamos usar seletores do segmento 0x23 ou 0x33 ao saltar. Mas também precisamos lidar com as diferenças de convenção entre 64 bits e 32 bits. Nosso shellcode será, portanto, responsável por mover slots de registro/pilha de argumentos de 64 bits para os slots de registro/pilha de argumentos de 32 bits. Impor que os retornos de chamada de 32 bits só podem ser __cdecl torna isso mais fácil, pois todos os argumentos estão na pilha e o shellcode tem controle total do layout da pilha e limpeza. A Figura 19 mostra a localização dos argumentos para cada convenção de chamadas. Uma vez que os primeiros 4 argumentos são realocados todos os argumentos adicionais podem ser movidos em um loop, uma vez que ele está simplesmente movendo valores de pilha para slots mais baixos. Isso é relativamente fácil de implementar usando arquivos de massa externos em MSVC. Bytes crus precisarão ser emitidos em pontos em vez de usar a montadora devido à mistura de arquiteturas. Alternativamente, poderia ser usado o conjunto inline GCC ou Clang. O trabalho da ReWolf alcança a direção oposta de 32 bits -> 64 bits e implementa o shellcode via msvc inline asm. X64 MSVC não suporta isso e há complicações com prefixos REX ao usar esse método. É melhor usar arquivos de massa externas e contar com o linker para implementar este shellcode.

Número de ArgLocalização do CdeclLocalização de chamada rápidaCaso Especial?
0[ebp + 8]RcxSim
1[ebp + 12]RdxSim
2[ebp + 16]r8dSim
3[ebp + 20]r9dSim
4[ebp + 24][rbp + 32 + 8]Não
5[ebp + 28][rbp + 32 + 16]Não
6[ebp + 32][rbp + 32 + 24]Não

Figura 19: Posições de argumento Cdecl vs Fastcall

Uma vez que este shellcode é escrito e embrulhado em uma função C++agradável, é possível para o wow64log DLL invocar o retorno de chamada através de uma chamada de ponteiro de função estilo C simples mostrado na Figura 20.


Figura 20: call_function32 invoca o shellcode para chamar um retorno de chamada de 32 bits do DLL de registro de 64 bits

Dentro do retorno de chamada de 32 bits, qualquer operação MITM desejada pode ser realizada, mas existem restrições sobre as quais as APIs são callable. Devido à economia de contexto que a camada WOW64 realiza, as APIs de 32 bits que reentrariam na camada WOW64 podem não ser chamadas, pois os valores de contexto seriam corrompidos. Estamos, portanto, limitados apenas a apenas APIs que não reentrarão no WOW64, que são aquelas que são exportadas a partir do ntdll de 64 bits. A exportação NtWriteFile pode ser usada para escrever facilmente para stdout ou um arquivo, mas devemos reinserar o modo de execução de 64 bits e fazer o mapeamento de argumentos inversos como antes. Esta rotina de registro pode ser chamada de dentro dos retornos de chamada de 32 bits e é mostrada na Figura 21 e Figura 22.


Figura 21: call_function64 invoca código de concha para chamar o WriteFile de 64bits com o retorno de chamada de 32bits


Figura 22: Os retornos de chamada de 32bit devem ser logs através de rotinas que só chamam de APIs WOW64 não reentrantes

O resultado são stubs de retorno de chamada de aparência limpa que funcionam exatamente como hooks inline podem, mas com zero modificações de montagem necessárias. Os argumentos também podem ser manipulados também, mas o status de retorno pode não ser modificado a menos que um pequeno hacker de stack walk seja implementado. A única outra consideração é que o próprio Wow64log DLL precisa ser cuidadosamente elaborado para não construir com nenhum mecanismo de CRT. As bandeiras necessárias são:

  • Desativar CRT com /NODEFAULT LIB (todas as APIs C agora indisponíveis), defina um novo nome de ponto de entrada para não init CRT NtDllMain
  • Desativar todas as rotinas de segurança do CRT /GS-
  • Desativar exceções C++
  • Remova bibliotecas de linker padrão, apenas link ntdll.lib
  • Use extern “C” __declspec(dllimport) <typedef> para vincular-se ao NtApis correto

Um exemplo de um programa que conecta suas próprias chamadas de sistema através de hooks de linha wow64log é mostrado na Figura 23.


Figura 23: Demonstração de hooks inline em ação

Conclusão

Usando hooks WOW64 inline, hooks wow64log e hooks kernel/hipervisor, todas as técnicas de evasão de hooks de uso podem ser identificadas de forma fácil e automática. Detectar quais camadas de hooks são ignoradas ou ignoradas dará uma visão de qual técnica de evasão é empregada. A tabela de identificação é:

Modo de evasão32bit inlinewow64Log64bit inlineKernel/Hypervisor
Prologue Restaurar
Heavens Gate sys-stub
Heavens Gate chamada direta

Apêndice da estrutura

estrutura _WOW64_CPURESERVED

{

Bandeiras USHORT;

USHORT MachineType;
contexto WOW64_CONTEXT;
char ContextEx[1024];
};

typedef ULONG *WOW64_LOG_ARGUMENTS;
struct

_WOW64_SYSTEM_SERVICE {

__int32 não assinado SystemCallNumber : 12;

__int32 não assinado ServiceTableIndex

: 4; __int32 turbothunknumber não

assinado : 5; __int32 não assinado AlwaysZero : 11;

};
#pragma

pack (push, 1) struct _WOW64_FLOATING_SAVE_AREA



{ DWORD

ControlWord; DWORD StatusWord;
DWORD TagWord;
DWORD ErrorOffset;
Selecionador de erros DWORD;
DWORD DataOffset;
DWORD DataSelector;
Byte RegisterArea[80];
DWORD Cr0NpxState;
};
#pragma pacote (pop)

#pragma pack (push, 1)

struct _WOW64_CONTEXT

{

DWORD ContextFlags;

DWORD Dr0;
DWORD Dr1;
DWORD Dr2;
DWORD Dr3;
DWORD Dr6;
DWORD Dr7;
WOW64_FLOATING_SAVE_AREA FloatSave;
DWORD SegGs;
DWORD SegFs;
DWORD SegEs;
DWORD SegDs;
DWORD Edi;
DWORD Esi;
DWORD Ebx;
DWORD Edx;
DWORD Ecx;
DWORD Eax;
DWORD Ebp;
DWORD Eip;
DWORD SegCs;
DWORD EFlags;
DWORD Esp;
DWORD SegSs;
BYTE ExtendedRegistersUnk[160];
M128A Xmm0;
M128A Xmm1;
M128A Xmm2;
M128A Xmm3;
M128A Xmm4;
M128A Xmm5;
M128A Xmm6;
M128A Xmm7;
M128A Xmm8;
M128A Xmm9;
M128A Xmm10;
M128A Xmm11;
M128A Xmm12;
M128A Xmm13;
M128A Xmm14;
M128A Xmm15;
};
#pragma pacote (pop)

FONTE: FIREEYE

POSTS RELACIONADOS