Home Malware Reverse Engineering | Analyzing and Rewriting the WINTAPIX Driver and Learning From It Português
Post
Cancel

Malware Reverse Engineering | Analyzing and Rewriting the WINTAPIX Driver and Learning From It Português

Author: João Vitor (@Keowu) - Malware Security Researcher

Sample identification hash

Wintapix é um malware identificado e reportado originalmente pela empresa “Fortinet” em um artigo anunciando sua descoberta. particularmente muito bem feito. meu artigo foi escrito na tentativa de explicar como um payload shellcode é injetado em uma thread a partir de um kernel driver, de forma a bypassar mecanismos EDR, e além disso investigar alguns pontos de seu funcionamento. eu irei me abster da analise do payload .net(Donut e seu shellcode) e apenas explicar aqui a injeção de modo kernel e como funciona aplicado ao sistema windows, além é claro de reverter e fornecer o código fonte completo para estudiosos e entusiastas que se animam com malwares, assim como eu.

Table of Contents

  1. Overview
  2. A brief look at the concept of the Windows kernel
  3. Wintapix
  4. Criteria for choosing targets
  5. How writes to virtual memory occur and how a thread is created from the kernel using the SSDT
  6. How they work and what are the techniques for persistence in kernel mode
  7. Taking advantage of the knowledge acquired, the Driver is being completely rewritten
  8. Proof of concept of the attack
  9. References

Overview

Wintapix é um malware que tem como alvo países do oriente médio. e explora da SSDT(KeServiceDescriptorTable) para efetuar chamadas para as system calls do sistema operacional.

A brief look at the concept of the Windows kernel

No Windows existem duas SSDT’s sendo a primeira KeServiceDescriptorTable e a segunda KeServiceDescriptorTableShadow ambas são estruturas, SSDT é só um apelido para a System Service Dispatch, sua composição são arrays de ponteiros de maneira direta. a SSDT prove funcionalidades tanto ao usermode(de maneira indireta via a ntdll.dll por exemplo usando o número da syscall), como para o kernelmode(drivers como o Wintapix) porem obviamente nem todas as system calls podem ser utilizadas diretamente e isso não é diferente com a SSDT(pelo menos nas versões x64 dos sistemas operacionais), porem em sistemas x32 ela pode ser acessada chamando MmGetSystemRoutineAddress, aqui tem um pequeno exemplo de como fazer isso:

1
2
3
UNICODE_STRING uniStr;
RtlInitUnicodeString(&uniStr, L"KeServiceDescriptorTable");
PVOID pSsdt = MmGetSystemRoutineAddress(&uniStr);

Porem em sistemas operacionais x64 essa tarefa não é tão simples, faz-se então necessário o uso de técnicas de busca pelo ponteiro da SSDT. a criatividade é sem limites aqui porem o objetivo é o mesmo encontrar a KeServiceDescriptorTable\KeServiceDescriptorTableShadow. a técnica utilizada pelo desenvolvedor do WintaPix é um tanto quanto criativa, e sera detalhada ao decorrer do artigo.

Antes de avançar com a leitura, vamos entender um pouco de como funcionam as system calls no windows através da ntdll.dll faz-se um pouco necessário compreender por conta do WintaPix utiliza-la em buscas dos chamados “índices” de system calls, convenhamos que o nome em inglês é bem melhor ou seja “index”. todas as system calls dos sistemas operacionais seguem o mesmo padrão de código assembly, variando apenas o índice, veja um exemplo desta rotina:

1
2
3
4
5
6
7
8
9
10
11
12
Zw/Nt/SomeCall proc near
    mov r10, rcx
    mov eax, SYCALL_INDEX
    test byte ptr ds:7FFE0308h, 1
    jnz flag
    syscall ; Kernel syscall
    ret

flag:
    int 2Eh
    retn
Zw_Nt_SomeCall endp

vamos supor que eu deseja obter o índice para “NtCreateUserProcess”:

1
2
3
4
5
6
7
8
9
10
11
12
13
ZwCreateUserProcess proc near
    mov r10, rcx
    mov eax, 0BDh
    test byte ptr ds:7FFE0308h, 1
    jnz flag
    syscall
    ret

flag:
    int 2Eh
    retn

ZwCreateUserProcess endp

Nesse caso nosso índice é determinado pelo valor 0xBD. é importante entendermos este conceito pois ele vai ser essencial para compreender como o WintaPix parseia a NTDLL em busca do índice para utilizar na estrutura da SSDT recuperada através de um signature scanner e recuperar o endereço da rotina para efetuar a devida chamada.

WintaPix

O Driver do WintaPix utiliza a versão comercial do protector VM Protect para proteger seu código e dificultar analises, esse protector é conhecido por executar o escopo do programa em uma arquitetura não convencional e criada pelo próprio protector. nesse caso em específico a rotina que vamos analisar não continha a tecnologia de virtualização. o único ponto virtualizado eram as rotinas de DriverEntry e DriverUnload. após certa correção do binário e IAT iniciei então a minha analise do artefato.

O funcionamento principal do malware ocorre através de uma thread de sistema:

#1

Esta rotina é chamada pela rotina de “DriverEntry”(que foi virtualizada pelo vmprotect). após a criação da thread de sistema a rotina principal responsável por todas as funcionalidades do malware entra em ação. apelidada por mim de “StartRoutine” para manter tudo um pouco mais organizado.

#2

O escopo do funcionamento esta dividido em etapas:

  • Encontrar um processo com as características e nível de privilégio desejado
  • Obter um handle para o processo em execução
  • Alocar memória virtual no processo em execução
  • Encontrar o ponteiro para a SSDT, encontrar o índice da system call e obter o endereço da função e além disso declarar sua assinatura e calling convention correta para utilização
  • Utilizar NtWriteVirtualMemory para copiar o shellcode para a memória virtual
  • Criar uma thread no processo em memória virtual usando NtCreateThreadEx e garantir que sua execução acontecera
  • Encerrar o processo utilizado para injeção para evitar detecções
Criteria for choosing targets

Vamos entender o escopo do funcionamento do WintaPix durante a escolha de um processo, através do procedimento “open_target_process”:

#3

A rotina tem como objetivo encontrar um processo com permissões de sistemas para injeção do shellcode. então para realizar esta tarefa o WintaPix utiliza-se de chamadas para a winapi “ZwQuerySystemInformation” com o intuíto de obter uma listagem da estrutura “SYSTEM_PROCESS”.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct _SYSTEM_PROCESSES {
  ULONG NextEntryDelta;
  ULONG ThreadCount;
  ULONG Reserved1[6];
  LARGE_INTEGER CreateTime;
  LARGE_INTEGER UserTime;
  LARGE_INTEGER KernelTime;
  UNICODE_STRING ProcessName;
  KPRIORITY BasePriority;
  SIZE_T ProcessId;
  SIZE_T InheritedFromProcessId;
  ULONG HandleCount;
  ULONG Reserved2[2];
  VM_COUNTERS VmCounters;
  IO_COUNTERS IoCounters;
  SYSTEM_THREADS Threads[1];
};

Com base na estruct populada e após obviamente determinar o tamanho correto da cadeia de estruturas e alocar uma pool com a tag “xp” para armazenamento. o malware ira iterar entre as estructures usando offset presente no atributo “NextEntryDelta” de maneira a verificar um possível processo para a injeção. além disso o malware ira verificar se o PID do processo verificado é 4 ou se de o PID de herança também é 4. este PID é sempre reservado para o processo “System.exe”, além é claro de verificar se o processo faz parte de uma blacklist de processos, os seguintes processos compõem a lista de blacklist do WintaPix:

1
2
3
4
5
6
wininit.exe
csrss.exe
smss.exe
services.exe
winlogon.exe
lsass.exe

Caso o processo passe nas condições estabelecidas pelo WintaPix, ele efetuara mais uma verificação e desta vez verificando se o processo pertence ao grupo de SID “S-1-5-18”, esse SID indica a identidade usada pelo sistema operacional e seus serviços configurados para agirem como LocalSystem.

#4

Para encontrar o SID do processo atual de busca, o WintaPix utiliza da “ZwQuerySystemInformation” em busca de obter uma referência ao SID de busca, e então o converte para uma UNICODE_STRING utilizando “RtlConvertSidToUnicodeString” e armazena no ponteiro de referência de seu segundo argumento.

#5

A verificação de um possível processo de escolha é usado quando o procedimento “executeCheckingSecurityIdentifiers” retorna um resultado TRUE após a comparação da macro NT_SUCESS. e então a utilização do standard “wcscmp” compara acessando a referência da UNICODE_STRING em “buffer” como uma const wchar, e compara com o SID “S-1-5-18” com o intuíto de validar se o processo possível a permissão LocalSystem. caso você fique curioso de quais outras SID um PEPROCESS pode assumir recomendo a leitura do artigo da Microsoft Learn Identificadores de segurança.

Caso o processo passe na validação de SID performada pelo WintaPix, ocorrera a última verificação. dessa vez ele precisara garantir que o processo em questão seja de WOW64(x64) e não um processo de x32. para que possa então injetar o seu shellcode. para esta tarefa o WintaPix utiliza o procedimento apelidado de “check_process_64”.

#6

Durante a verificação de arquiteura de processo. o WintaPix utiliza-se de uma chamada para ZwQueryInformationProcess passando a PROCESSINFOCLASS “ProcessBasicInformation” com o intuíto de recuperar um ponteiro para a estrutura da PEB e acessar “Wow64Process”.

Caso toda a lista dos processos chegue ao fim, e o WintaPix não consiga encontrar um bom processo para sua injeção, ele utiliza-ra o procedimento apelidado de “set_thread_delay”:

#7

Por padrão o delay antes de reiniciar uma nova busca por processos para injeção é de 5000000.

Quando um processo passa em todos os critérios impostos pelo WintaPix, inicia-se o segundo estágio. dividido em duas partes. sendo o primeiro responsável por escrever na memória virtual do processo através do procedimento “resolve_and_call_NtWriteVirtualMemory”. além de criar uma thread no processo utilizando o procedimento “resolve_and_call_NtCreateThreadEx”.

Vamos iniciar nossa analise pelo procedimento “resolve_and_call_NtWriteVirtualMemory”:

#8

Nesta rotina o WintaPix vai alocar memória virtual no processo anteriormente escolhido por meio da system call “ZwAllocateVirtualMemory”. e em seguida resolver o endereço da system call “NtWriteVirtualMemory” utilizando a SSDT para então escrever na memória virtual alocada. após armazenara o endereço base da região alocada para ser utilizado no momento de criação de sua thread no processo alvo.

Em seguida o procedimento resolve_and_call_NtWriteVirtualMemory é utilizado para criar uma thread na memória virtual do processo:

#9

Nesta etapa o malware vai criar uma nova thread na memória virtual do processo, recuperando o endereço da system call “NtCreateThreadEx” da SSDT e a partir disso efetuando sua chamada. por padrão o malware sempre espera que a thread seja encerrada utilizando ZwWaitForSingleObject ao seu favor para esta tarefa, dado que o parâmetro “bWaitForEnd” sempre sera true o malware sempre ira esperar a completa execução de sua thread injetada.

Por fim após todas as etapas de injeção de thread em um processo do modo de usuário com base em critérios específicos, o WintaPix vai encerrar o processo ao qual foi utilizado como “hospedeiro” de sua thread, o procedimento responsável por esta tarefa foi apelidado de “terminate_usermode_process”:

#10

Com base no processo alvo, o malware efetua uma chamada para ZwTerminateProcess. com o intúito de encerra-lo. pois a injeção por ele efetuada ira mapear um novo processo independente do processo pai(alvo da injeção), tornando esta uma técnica rasoavelmente efiente de evasão a soluções de segurança EDR’s.

How writes to virtual memory occur and how a thread is created from the kernel using the SSDT

Todas as interações com a SSDT, como por exemplo signature scanner na ntoskrnl.exe até obtenção de system call index para recuperar os endereços acessando o membro ServiceTable ocorrem por subprocedimentos de “get_function_from_ssdt”:

#11

Vamos iniciar nossa explicação e descobertas de onde algumas técnicas foram “baseadas” pelo desenvolvedor do WintaPix, e como elas funcionam em detalhes. iniciaremos a partir do procedimento “parse_ntdll_get_index”, este procedimento recebe como parâmetro uma referência “char*” contendo a cadeia de caracteres que compõem o nome da syscall a ser buscada na ntdll:

#12

Esta é a declaração da estrutura “struct_malware” revertida similar a utilizada pelo desenvolvedor do WintaPix para armazenar os dados necessários para seu escaneamento do index:

1
2
3
4
5
6
struct struct_malware {
  PVOID pMemBuffer; // Full ntdll.dll mapped buffer
  _QWORD image_base_ntdll; // Ntdll.dll image base
  _QWORD section_range; // Section range for search
  unsigned int fileLowPart; // File size
};

Um exemplo mais compreensivel explana como a verificação ocorre, comparando a verificação realizada pelo WintaPix e o padrão de chamadas presente na ntdll.dll:

#13

As funções responsáveis por mapear a ndll, parsear, encontrar o nome da system call na export directory e o endereço para escaneamento, são feitos pelos procedimentos “open_ntdll_and_parse_pe” e “parse_pe_file_exported”:

#14

Após encontrar o index da system call, avançamos para a busca da SSDT, nesta etapa vamos entender como e onde o autor do WintaPix se baseou para criar suas rotinas de busca, o procedimento principal utilizado nas buscas é apelidado de “acess_ssdt_by_index”, sendo ele o responsável por chamar o sub procedimento apelidado de “determine_os_best_signature_and_find_ssdt_by_pattern” e claro calcular o deslocamento correto para recuperar o ponteiro para a system call(considerando apenas WoW64):

#15

Quando analisamos a lógica utilizada pelo WintaPix para fazer sua signature scan conseguimos identificar algumas inspirações do desenvolvedor do malware em um código fonte de um tutorial em um fórum online:

#16

Apesar de algumas modificações a ideia de utilizar o endereço de uma outra system call para buscar pela assinatura da SSDT foi utilizada pelo autor do WintaPix, obviamente utilizando uma assinatura própria e mais efetiva para seu ataque, demonstrando que mesmo baseando em uma ideia de um fórum ele possui um bom conhecimento em engenharia reversa e system internals.

Além da signature scan o autor do malware também pode ter se baseado no mesmo post, porem na ideia da verificação e offset para resolver a questão das diferenças entre as instruções nas versões:

#17

Com base nessas informações conseguimos determinar o escopo de ataque do malware, e em quais builds a assinatura usada funciona, sendo eles:

Targets OS build numbers
Windows 10 1903
Windows 10 1809
Windows 10 1803
Windows 10 1709
Windows 10 1703
Windows 10 1607
Windows 10 1511
Windows 10 1507
Windows 8.1 All builds
Windows 8 build 8102
Windows Server 2012 All builds
Windows 7 All builds
Windows Vista, Service Pack 2
Windows Server 2003 build 3790
Windows XP build 2600
How they work and what are the techniques for persistence in kernel mode

Além das injeções em processos em execução no sistema operacional, o WintaPix age como uma backdoor, definindo mecanismos de persistência eficazes na tarefa de garantir que sua execução sempre ocorra sem problemas e sem levantar suspeitas, além de garantir que o serviço do driver execute sem problemas, bem como seja carregado em safe boot e faça a prevenção de alteração do arquivo em disco.

#18

O procedimento “persistence_stuff_main” é chamado pela rotina virtualizada de DriverEntry, antes da thread de injeção ser criada, efetua as modificações e define notificações de mudança para as chaves de registro, bem como garante que seu serviço sempre execute sem problemas, além é claro de criar a thread de monitoração de sistema de arquivos para detectar qualquer alteração no seu driver em disco:

#19

Para verificar por eventos de mudança ou alterações em seu arquivo em disco, o WintaPix utiliza-se da system call “NtNotifyChangeDirectoryFile” resolvendo o endereço a partir da SSDT. a system call armazena os dados na struct “FILE_NOTIFY_INFORMATION”:

1
2
3
4
5
6
typedef struct _FILE_NOTIFY_INFORMATION {
  DWORD NextEntryOffset;
  DWORD Action;
  DWORD FileNameLength;
  WCHAR FileName[1];
} FILE_NOTIFY_INFORMATION, *PFILE_NOTIFY_INFORMATION;

O WintaPix utiliza o atributo “NextEntryOffset” para acessar os próximos dados da lista. enquanto “NextEntryOffset” for diferente de zero. e compara utilizando “FileName” se o nome do arquivo e path é exatamente o mesmo do arquivo do driver do malware no diretório de drivers na system32. caso seja o malware certifica-ra de apagar o arquivo e sobreescrever seu conteúdo utilizando o buffer do arquivo anteriormente armazenado, dessa forma o malware sempre conseguira garantir que execute sem ser comprometido por ferramentas de terceiros por exemplo. e em conjunto com as chamadas da system call “ZwNotifyChangeKey” conseguira identificar e previnir alterações em suas chaves de persistência.

Taking advantage of the knowledge acquired, the Driver is being completely rewritten

Após uma analise do malware. e de toda a sua estrutura, conseguimos replicar e entender completamente seu funcionamento dando insumos necessários para reescrevermos todo o seu comportamento:

#20

Com base no código fonte revertido, tipos de dados corrigidos, e algumas corrupções do meu banco dados do IDA(Obrigado Hex-Rays por conseguir salvar um arquivo de 8 MB com bytes completamente zerados), eu criei um projeto usando o kit de desenvolvimento de drivers fornecido pela Microsoft, e recriei o malware(as rotinas podem estar diferentes, porem cumpre ao executar exatamente o mesmo comportamento).

#21

#22

#23

O Código fonte pode ser encontrado no repositório:

wintapix-source-code-reversed.

Proof of concept of the attack

Uma prova de conceito utiliza um shellcode simples que cria um novo processo a partir de uma thread injetada a partir de kernel mode utilizando todos os recursos revertidos do malware WintaPix. bem como demonstra todas os mecanismos de persistências utilizados e revertidos.

Veja o vídeo da prova de conceito:

References

REVAY, G.; JAZI, H. WINTAPIX: A new kernel driver targeting countries in the Middle East. Disponível em: https://www.fortinet.com/blog/threat-research/wintapix-kernal-driver-middle-east-countries.

OGILVIE, D. TitanHide: Hiding kernel-driver for x86/x64. Disponível em: https://github.com/mrexodia/TitanHide.

HFIREF0X. WinObjEx64: Windows Object Explorer 64-bit. Disponível em: https://github.com/hfiref0x/WinObjEx64.

This post is licensed under CC BY 4.0 by the author.