PEB Walk

Evadindo IAT com PEB Walk

Hoje em dia, se você usar direto as APIs do Windows (tipo VirtualAllocEx, CreateRemoteThread, etc.), seu binário vai ser cravado fácil por qualquer EDR ou AV mais decente. Isso acontece porque essas APIs aparecem na IAT (Import Address Table), que é basicamente um "print" do que seu código chama. E aí, qualquer analista ou ferramenta que lê PE consegue sacar o comportamento do binário só olhando ali.

Mas tem um jeito de burlar isso: PEB Walk.

Por padrão, quando queremos por exemplo começar uma analise estática de um possível malware, é extremamente interessante, ver as importações que esse programa realiza. como por exemplo, se a gente compilar um código simples, usando a API, MessageBoxA, para imprimir uma mensagem:

#include <windows.h>

int main() {
	MessageBoxA(NULL, "Hello, World!", "My Message Box", MB_OK);
	return 0;
}

Se a gente olhar as importações desse código em C, já compilado vamos ver, que a gente consegue ver com facilidade que o programa está importando a MessageBoxA, ficando visível na IAT do programa.


Em contrapartida se a gente, olhar as importações de outro programa que executa essa mesma função para chamar MessageBoxA para a impressão de uma mensagem, mas dessa fez com o PEB walk implementado, vamos ver que ao tentar acessar a IAT do programa, não vamos estar conseguindo assim ver a importação da MessageBoxA Dessa forma, por exemplo um analista não consegue ter uma ideia do comportamento do programa se depender da lista IAT.


Por que fazer PEB Walk?

A lógica é simples:

  • Você não quer deixar rastro na IAT.

  • E consegue diminuir a taxa de detecção baseada em assinatura estática.

  • E isso por si só, por pouco que pareça, já é importante para um desenvolvedor de malware.


Entendendo o PEB

Quando um processo é criado, o Windows aloca uma estrutura interna chamada PEB. Dentro dela, tem um campo Ldr que aponta pra outra estrutura chamada _PEB_LDR_DATA, que tem uma lista com todas as DLLs carregadas:

Essa lista se chama InLoadOrderModuleList. E cada item nela é um LDR_DATA_TABLE_ENTRY, que tem o caminho da DLL e a DllBase, ou seja, o endereço onde a DLL tá na memória.

Um exemplo prático que poderíamos realizar para ver isso, é usar o WinDBG anexar ele ao um processo em execução, e digitar !peb então assim vamos estar conseguindo ver o campo Ldr.InMemoryOrderModuleList que vai estar mostrando assim a lista de DLLs carregadas para o processo atual:


Tá, mas como acessar o PEB?

Então, como o malware (ou qualquer outro código de baixo nível) pode acessar essa estrutura?

Uma das formas é fazendo acesso direto via assembly inline (montagem embutida no código), utilizando registradores de segmento que apontam para estruturas internas do processo.

Se o programa for em 32 bits, a linha em assembly seria basicamente:

E em 64 bits:

Mas o que é fs e gs?

Esses são registradores de segmento usados pelo Windows para apontar para o início de estruturas internas por thread:

  • Em 32 bits, o registrador fs aponta para o início do TEB (Thread Environment Block).

  • Em 64 bits, o registrador usado é o gs.

O que é o TEB?

O TEB (Thread Environment Block) é uma estrutura que contém informações específicas da thread atual — como:

  • ID da thread,

  • ponteiro para o PEB do processo ao qual pertence,

  • dados de exceção,

  • pilha de chamada,

  • e por ai vai. Ou seja: quando fazemos fs:[0x30] (em 32 bits) ou gs:[0x60] (em 64 bits), estamos acessando um campo dentro do TEB no caso o ProcessEnvironmentBlock que aponta diretamente para a estrutura PEB — a estrutura que vai conter informações globais do processo, como pode ser visto nessa imagem de exemplo:


Resumindo

Definindo a estrutura do PEB:

Definindo a estrutura PEB_LDR_DATA:

Para obter o endereço da Tabela de Dados do Carregador, tudo o que precisamos fazer é ler o deslocamento Ldr da estrutura PEB:

O que esse código faz?

  • Primeiro, ele acessa o PEB do processo atual usando o registrador de segmento (__readgsqword(0x60) em 64 bits).

  • Depois, pega o campo Ldr do PEB, que aponta para a estrutura PEB_LDR_DATA.

  • Com isso, já temos acesso à lista de módulos carregados pelo processo, que é o ponto de partida para o PEB Walk.

  • Esse acesso é feito totalmente em tempo de execução, sem depender da IAT, dificultando a análise estática.

Isso em assembly seria assim:

Definindo a estrutura LDR_DATA_TABLE_ENTRY:

Como mencionado anteriormente, é possível assumir a ordem dos 3 primeiros módulos para um aplicativo nativo. Portanto, o código mais simples para obter a entrada do módulo para o executável atual, ntdll e kernel32 é o seguinte.

O que esse código faz?

LDR_DATA_TABLE_ENTRY *main_module = (LDR_DATA_TABLE_ENTRY *)ldr->InLoadOrderModuleList.Flink; Aqui, acessamos o primeiro elemento da lista InLoadOrderModuleList, que está dentro da estrutura PEB_LDR_DATA.

Flink é um ponteiro para o próximo item da lista (o primeiro módulo carregado). Esse primeiro item normalmente representa o executável principal do processo (ou seja, o próprio .exe que está rodando).

LDR_DATA_TABLE_ENTRY *ntdll = (LDR_DATA_TABLE_ENTRY *)main_module->InLoadOrderLinks.Flink; Agora, pegamos o campo InLoadOrderLinks.Flink do primeiro módulo (o executável). Isso nos leva ao próximo módulo carregado, que geralmente é o ntdll.dll. Cada módulo na lista tem um campo InLoadOrderLinks que aponta para o próximo módulo, formando uma lista encadeada.

LDR_DATA_TABLE_ENTRY *kernel32 = (LDR_DATA_TABLE_ENTRY *)ntdll->InLoadOrderLinks.Flink; Repetimos o processo: pegamos o próximo da lista, que normalmente é o kernel32.dll. Assim, caminhando de um item para o próximo usando o campo Flink, conseguimos acessar os módulos carregados na ordem em que o Windows os adicionou.

Em assembly seria assim:


O código C a seguir demonstra como procurar a entrada do módulo para kernelbase.dll:


Conclusão

O PEB Walk é uma técnica poderosa para acessar informações sobre módulos carregados em um processo Windows sem depender da Import Address Table (IAT). Ao navegar manualmente pelas estruturas internas do sistema operacional, como o PEB, PEB_LDR_DATA e LDR_DATA_TABLE_ENTRY, é possível localizar e interagir com DLLs e funções de forma discreta, dificultando a análise estática e a detecção por antivírus.

Essa abordagem é amplamente utilizada em desenvolvimento de malware e também pode ser útil para pesquisadores de segurança e engenheiros reversos que desejam entender melhor o funcionamento interno dos processos no Windows.

Dominar o PEB Walk abre portas para uma compreensão mais profunda do Windows Internals e permite criar soluções mais sofisticadas, seja para fins de pesquisa, análise ou desenvolvimento de ferramentas avançadas.

Last updated