
Tabla de contenido
- ¿Qué es un packer?: Una introducción al propósito de los packers y una guía sobre cuáles son las necesidades de desarrollo de un packer.
- Requisitos previos: Herramientas necesarias para trabajar con este tutorial.
- Un anticipo: Una demostración de lo que construirás con este tutorial.
- Diseñando nuestro proyecto CMake: Un mini tutorial de CMake sobre cómo crear un sistema de compilación para las necesidades algo complejas de un packer.
- Empaquetando binarios en tu stub: Un tutorial sobre cómo escribir la parte del packer en la combinación packer/stub, y también una introducción al formato de archivo ejecutable de Windows.
- Gestión de recursos
- Análisis de un archivo PE
- Manipulación de un archivo PE
- Simulando el cargador: Un tutorial sobre cómo construir un ejecutable stub mínimo para desempaquetar y cargar un ejecutable objetivo, y también una introducción a la manipulación más avanzada de ejecutables de Windows.
- Leyendo nuestro PE desde la memoria
- Cargando nuestro PE para su ejecución
- Resolviendo importaciones de API
- Resolviendo direcciones
- Transfiriendo la ejecución
- Ejercicios adicionales: Algunos ejercicios para ampliar aún más tus conocimientos sobre el desarrollo de packers.
¿Parece una lectura desalentadora? Prueba la versión en presentación, que resume este readme. ¡Pronto habrá un video de YouTube!
¿Qué es un packer?
Un packer es un programa que descomprime y lanza otro programa dentro de su espacio de direcciones (o, a veces, el espacio de direcciones de otro proceso). A veces se le conoce por ser el vector que ataca entornos de análisis, como depuradores y sandboxes virtuales. Se utiliza principalmente para algunas cosas:
- Compresión: Los packers se emplean comúnmente para comprimir el código de un binario determinado. Este es uno de sus pocos usos legítimos. Consulta UPX como ejemplo de un packer de compresión.
- Ofuscación: Los packers también se emplean cuando se intenta ofuscar o defender de otro modo un programa contra la ingeniería inversa. Consulta el packer packman de Riot Games como ejemplo de un packer anti-ingeniería inversa.
- Evasión: El malware utiliza frecuentemente una variedad de packers para evadir los antivirus e incluso el EDR. Consulta este análisis de un packer SmokeLoader como ejemplo de un packer evasivo.
Fundamentalmente, un packer solo tiene unos pocos pasos básicos:
- Etapa de compresión: Aquí es donde el ejecutable original se comprime, se ofusca o ambas cosas, en un nuevo binario.
- Etapa de descompresión: Aquí es donde el ejecutable empaquetado descomprime o desofusca su ejecutable original para cargarlo.
- Etapa de carga: Aquí es donde el packer imita una variedad de pasos similares al cargador de ejecutables de la plataforma anfitriona. Debido a la complejidad de los binarios ejecutables, este es el paso más complicado, dependiendo del nivel de profundidad que desees emular.
- Etapa de ejecución: Aquí es donde el código se transfiere desde el ejecutable stub anfitrión al código recién cargado (es decir, desempaquetado).
De manera igualmente simple, un packer solo consta de unas pocas piezas:
- El packer: Esta pieza es responsable de preparar lo que se llama el ejecutable stub y de darle los datos necesarios para desempaquetar un binario determinado. Esto es, en última instancia, la etapa de compresión.
- El stub: Este binario es la pieza de código responsable de descomprimir, cargar y ejecutar el binario original. Como puedes ver, el ejecutable stub hace la mayor parte del trabajo pesado de un packer.
Construir un packer puede ser complicado, ya que el stub debe compilarse e importarse de algún modo en el ejecutable del packer. En Windows, aprender el sistema de compilación de Visual Studio más allá de la simple compilación puede ser una tarea laboriosa. Afortunadamente, CMake proporciona un sistema de compilación simple y multiplataforma que soporta Visual Studio, ¡y es altamente personalizable!
Este tutorial tiene como objetivo enseñar lo siguiente:
- Cómo segmentar e integrar las dos piezas principales de un packer en un entorno de compilación cohesivo y comprobable.
- Cómo manipular un ejecutable de Windows para añadir datos adicionales y cargables.
- Cómo navegar por un ejecutable de Windows para recuperar y cargar datos arbitrarios.
- Cómo emular partes del cargador de Windows para cargar y ejecutar un ejecutable de Windows.
Si ya estás familiarizado con C++ y CMake, no dudes en saltar directamente a la sección de empaquetado. De lo contrario, ¡sigue leyendo!
Requisitos previos
- Conocimiento de C++: Este tutorial es un poco inútil si no sabes C++, ya que vamos a hacer mucha aritmética de punteros.
- Visual Studio: Visual Studio contiene un compilador de C++ para Windows con todas las funciones. Este proyecto está probado con Visual Studio 2019, pero las versiones más nuevas deberían funcionar.
- CMake: CMake es el sistema de compilación que usamos para ayudar a instrumentar nuestras compilaciones para el compilador de Visual Studio.
Un anticipo
Primero, demostremos, de algún modo, que este sistema de compilación funciona y crea un ejecutable correctamente empaquetado. Una vez que tengas CMake y Visual Studio instalados, navega al directorio raíz de este repositorio con tu terminal preferida y ejecuta lo siguiente:```
$ mkdir build
$ cd build
$ cmake ../
Esto creará los archivos de proyecto necesarios para compilar el código del tutorial de packer. Luego ejecute:```
$ cmake --build ./ --config Release
Esto compilará el proyecto packer en modo Release. Luego, puedes ejecutar la siguiente prueba:```
$ ctest -C Release ./
Si todo va bien, test\_pack y test_unpack deberían tener éxito. Si quieres ver los resultados del empaquetado tú mismo, deberías ver esto:```
$ ./packed.exe
I'm just a little guy!
Hablemos de todas las piezas móviles que hicieron posible esto.
Esbozando nuestro proyecto CMake
Así que ya sabemos cuáles son los componentes principales de un packer, pero ¿cómo podemos probar nuestro packer inmediatamente como parte del ciclo de desarrollo? Necesitaremos añadir un tercer binario a nuestro proyecto general para probar adecuadamente el proceso de empaquetado y desempacado de nuestro binario. Así que, en general, necesitamos tres proyectos:
- el packer
- el stub
- el ejecutable dummy para empaquetar
También necesitamos una librería de compresión para asegurarnos de que el binario se comprime dentro del ejecutable stub. zlib funcionará bien para esto.