
Índice
- O que é um empacotador?: Uma introdução ao propósito dos empacotadores e um guia sobre quais são as necessidades de desenvolvimento de um empacotador.
- Pré-requisitos: Ferramentas necessárias para trabalhar com este tutorial.
- Experimentando: Uma demonstração do que você vai construir com este tutorial.
- Esboçando nosso projeto CMake: Um mini tutorial de CMake sobre como criar um sistema de build para as necessidades um tanto complexas de um empacotador.
- Empacotando binários no seu stub: Um tutorial sobre como escrever a parte do empacotador da combinação empacotador/stub e também uma introdução ao formato de arquivo executável do Windows.
- Gerenciando recursos
- Analisando um arquivo PE
- Manipulando um arquivo PE
- Simulando o loader: Um tutorial sobre como construir um stub executável mínimo para desempacotar e carregar um executável de destino, e também uma introdução à manipulação mais avançada de executáveis do Windows.
- Lendo nosso PE da memória
- Carregando nosso PE para execução
- Resolvendo importações de API
- Resolvendo endereços
- Transferindo a execução
- Exercícios complementares: Alguns exercícios para expandir ainda mais seu conhecimento em desenvolvimento de empacotadores.
Parece uma leitura assustadora? Experimente a versão em apresentação, que resume este readme. Vídeo no YouTube em breve!
O que é um empacotador?
Um empacotador é um programa que descomprime e lança outro programa dentro do seu espaço de endereçamento (ou, às vezes, no espaço de endereçamento de outro processo). Ele é por vezes conhecido por ser o vetor que ataca ambientes de análise, como depuradores e sandboxes virtuais. É usado principalmente para algumas coisas:
- Compressão: Os empacotadores são comumente empregados para comprimir o código de um determinado binário. Este é um dos seus poucos usos legítimos. Veja UPX para um exemplo de empacotador com compressão.
- Ofuscação: Os empacotadores também são empregados ao tentar ofuscar ou defender um programa de engenharia reversa. Veja o empacotador packman da Riot Games para um exemplo de empacotador anti-reversão.
- Evasão: Malware frequentemente usa uma variedade de empacotadores para evadir antivírus e até mesmo EDR. Veja esta análise de um empacotador SmokeLoader para um exemplo de empacotador evasivo.
Fundamentalmente, um empacotador tem apenas alguns passos básicos:
- Estágio de compressão: É aqui que o executável original é comprimido, ofuscado ou ambos, em um novo binário.
- Estágio de descompressão: É aqui que o executável empacotado descomprime ou desofusca seu executável original para carregamento.
- Estágio de carregamento: É aqui que o empacotador imita uma variedade de etapas semelhantes ao carregador de executáveis da plataforma host. Devido à complexidade dos binários executáveis, esta é a etapa mais complicada, dependendo do nível de profundidade que você deseja emular.
- Estágio de execução: É aqui que o código transfere do executável stub host para o código recém-carregado (ou seja, desempacotado).
Igualmente simples, um empacotador consiste apenas em algumas peças:
- O empacotador: Esta peça é responsável por preparar o que é chamado de stub executável e fornecer a ele os dados necessários para desempacotar um determinado binário. Este é, em última análise, o estágio de compressão.
- O stub: Este binário é a peça de código responsável por descomprimir, carregar e executar o binário original. Como você pode ver, o stub executável faz a maior parte do trabalho pesado de um empacotador.
Construir um empacotador pode ser complicado, já que o stub precisa ser construído e importado de alguma forma para o executável do empacotador. No Windows, aprender o sistema de build do Visual Studio além da simples compilação pode ser uma tarefa trabalhosa. Felizmente, o CMake fornece um sistema de build simples, multiplataforma, que suporta o Visual Studio e é altamente customizável!
Este tutorial tem como objetivo ensinar o seguinte:
- Como segmentar e integrar as duas principais peças de um empacotador em um ambiente de build coeso e testável.
- Como manipular um executável do Windows para adicionar dados adicionais e carregáveis.
- Como navegar em um executável do Windows para recuperar e carregar dados arbitrários.
- Como emular partes do carregador do Windows para carregar e executar um executável do Windows.
Se você já está familiarizado com C++ e CMake, sinta-se à vontade para pular direto para a seção de empacotamento. Caso contrário, continue lendo!
Pré-requisitos
- Conhecimento de C++: Este tutorial é meio inútil se você não sabe C++, pois vamos fazer muita aritmética de ponteiros.
- Visual Studio: O Visual Studio contém um compilador C++ para Windows completo. Este projeto foi testado com o Visual Studio 2019, mas versões mais novas devem funcionar.
- CMake: O CMake é o sistema de build que usamos para ajudar a instrumentar nossos builds para o compilador do Visual Studio.
Experimentando
Primeiro, vamos provar, de alguma forma, que este sistema de build funciona e cria um executável corretamente empacotado. Depois de instalar o CMake e o Visual Studio, navegue até o diretório raiz deste repositório com o terminal de sua preferência e execute o seguinte:```
$ mkdir build
$ cd build
$ cmake ../
Isso criará os arquivos de projeto necessários para compilar o código do tutorial do Packer. Em seguida, execute:```
$ cmake --build ./ --config Release
Isso compilará o projeto packer no modo Release. Em seguida, você pode executar o seguinte teste:```
$ ctest -C Release ./
Se tudo correr bem, test\_pack e test_unpack devem ser concluídos com êxito. Se você quiser ver os resultados do empacotamento por si mesmo, deverá ver isto:```
$ ./packed.exe
I'm just a little guy!
Vamos falar sobre todas as partes móveis que tornaram isso possível.
Esboçando nosso projeto CMake
Então sabemos quais são os principais componentes de um empacotador, mas como podemos testar nosso empacotador imediatamente como parte do ciclo de desenvolvimento? Precisaremos adicionar um terceiro binário ao nosso projeto geral para testar adequadamente o processo de empacotamento e desempacotamento do nosso binário. No geral, precisamos de três projetos:
- o empacotador
- o stub
- o executável dummy para empacotar
Também precisamos de uma biblioteca de compressão para garantir que o binário seja comprimido dentro do executável stub. zlib funcionará bem para isso.