Un empacador/protector para binarios de elfos x86-64 en Linux. Kiteshield envuelve los binarios de elfos con múltiples capas de cifrado y les inyecta el código del cargador que descifra, mapea y ejecuta el binario empaquetado por completo en el espacio de usuarios. Un motor de tiempo de ejecución basado en PTRACE asegura que solo las funciones en la pila de llamadas actual se descifran en cualquier momento dado y, además, implementan una variedad de técnicas anti-debugging para hacer que los binarios empaquetados loan lo más difíciles posible. Se admiten binarios individuales y multiproceso.
Consulte las secciones de diseño de arquitectura y base de código a continuación para obtener una vista de ojo de pájaros de cómo funciona Kiteshield.
Kiteshield está destinado a ser un ejercicio académico divertido en la ofuscación binaria en lugar de algo que puede usarse en el mundo real dado el código fuente y, por lo tanto, cómo funciona, es público.
Llamado por los escudos preferidos por los normandos en el siglo XI (alternativamente: los kiteshields que prevalecen en la vieja escuela Runescape).
Kiteshield requiere que la biblioteca de desastres de bitdefender decodifique las instrucciones en el empacador. Se incluye como un submódulo en packer/bddisasm . Para construirlo a partir de un clon nuevo, ejecute lo siguiente (tenga en cuenta que deberá tener CMake instalado):
git submodule update --init
cd packer/bddisasm
mkdir build
cd build
cmake ..
make
Ahora puede construir Kiteshield en modo de lanzamiento ejecutando make desde el directorio de nivel superior. Alternativamente, puede crear una construcción de depuración con make debug . Las construcciones de depuración de Kiteshield deshabilitan todas las características anti-fugas y encienden el registro de depuración muy detallado del cargador.
Para depurar la funcionalidad anti-defensa real, puede construir kiteshield en modo de depuración con funcionalidad anti-fibra activada utilizando make debug-antidebug .
Para empacar un binario llamado program y emitir el binario empacado a packed.ks , ejecutar:
./packer/kiteshield program packed.ks
packed.ks ahora se puede ejecutar y debe ser funcionalmente equivalente al program , pero encriptado y difícil de revertir la ingeniería. Tenga en cuenta que para el cifrado de la capa 2 que se aplicará, el binario de entrada no debe ser despojado ya que Kiteshield se basa en la tabla de símbolos que está presente. En la mayoría de las distribuciones de Linux, las utilidades del sistema estándar (por ejemplo /bin/ls ) generalmente se despojan.
Sin embargo, aún puede empacar binarios sin quiebra sin cifrado de capa 2 usando el indicador -n :
./packer/kiteshield -n program packed.ks
Esto producirá un binario de salida empaquetado con solo cifrado de capa 1 y el motor de tiempo de ejecución omitido.
Kiteshield está compuesto por dos partes separadas. El Packer es una aplicación C regular que lee, instrumenta y cifra binarios de entrada. El cargador es una aplicación C independiente responsable del descifrado de la función dinámica y la funcionalidad anti-fondos que el empacador inyecta en los binarios de entrada. Recibe el control inicial del núcleo, mapea todos los segmentos apropiados del binario en la memoria (incluido el enlazador dinámico si corresponde) y el control de la aplicación a la aplicación. El cargador también contiene el motor de tiempo de ejecución, que descifra y cifra dinámicamente las funciones a medida que se ingresan y salen en tiempo de ejecución.
Dado que el cargador recibe el control inicial del núcleo (es decir, antes de que las bibliotecas compartidas normalmente sean asignadas por el enlazador dinámico), no tiene acceso a LIBC y, por lo tanto, toda la funcionalidad necesaria proporcionada por LIBC se vuelve a implementar en el código del cargador.
El código de Packer y el cargador se puede encontrar en el packer/ y loader/ directorios respectivamente. El código que es común a ambos se puede encontrar en el directorio common/ . Una descripción general de alto nivel de las partes importantes de la base de código es la siguiente:
kiteshield
├── common # Code common to packer/loader
│ ├── include
│ │ ├── defs.h
│ │ ├── obfuscation.h
│ │ └── rc4.h
│ ├── obfuscation.c # Obfuscation utilities
│ └── rc4.c # RC4 stream cipher implementation
├── LICENSE
├── loader # Loader code
│ ├── anti_debug.c # Anti-debugging functionality
│ ├── bin_to_header.py # Script to "headerize" a compiled loader
│ ├── entry.S # Loader entry point code
│ ├── include
│ │ ├── anti_debug.h
│ │ ├── debug.h
│ │ ├── elf_auxv.h
│ │ ├── errno.h
│ │ ├── malloc.h
│ │ ├── obfuscated_strings.h # Generated file produced by string_obfuscation.py
│ │ ├── signal.h
│ │ ├── string.h
│ │ ├── syscalls.h
│ │ └── types.h
│ ├── link.lds # Custom linker script for building loader
│ ├── loader.c # Binary loading/mapping code (userspace exec)
│ ├── Makefile
│ ├── malloc.c # Freestanding malloc/free implementation
│ ├── runtime.c # Main body of runtime engine code
│ ├── string.c # String processing utilities (eg. strncat)
│ ├── string_obfuscation.py # String obfuscation helper script
│ ├── syscalls.c # System call implementations in inline assembly
│ └── test # Loader unit tests
│ ├── attounit.h
│ ├── Makefile
│ ├── test_main.c
│ ├── test_malloc.c
│ └── test_rc4.c
├── Makefile
├── packer # Packer code
│ ├── bddisasm # Bitdefender x86-64 disassembler library (submodule)
│ ├── elfutils.c # ELF binary reading/writing utilities
│ ├── include
│ │ └── elfutils.h
│ ├── kiteshield.c # Main body of packer code
│ └── Makefile
├── README.md
└── testing # Integration tests (see testing/README.md)
Kiteshield envuelve binarios de elfos de entrada en dos capas (o una, si usa el indicador -n ) de cifrado RC4 de modo que el binario en el disco esté bastante bien ofuscado. Estas capas son eliminadas en tiempo de ejecución por el cargador.
La primera capa de cifrado (mencionada en la base de código como la "capa externa") consiste en un solo RC4 sobre todo el binario de entrada. Esto está diseñado principalmente para combatir el análisis estático. Debido a la forma en que se desobfusta la clave (que depende del código del cargador), la primera capa de cifrado también verifica efectivamente el código del cargador antes de ejecutar el binario empaquetado, haciendo que el kiteshield sea resistente al parche de código.
La segunda capa de cifrado (mencionada en la base de código como la "capa interna") consiste en cifrado individual de casi todas las funciones en el binario de entrada (identificado a través de la tabla de símbolos en el tiempo de paquete). Se activa un motor de tiempo de ejecución basado en PTRACE en cada entrada de función y salida mediante el reemplazo de las instrucciones de entrada de cada función y todas sus instrucciones de retorno con instrucciones int3 (que entregan un SIGTRAP cuando se ejecuta). Al recibir una trampa, el motor de tiempo de ejecución busca la función actual y la cifra o la descifra según sea necesario, de modo que solo las funciones dentro de la pila de llamadas actual se descifran en cualquier momento.
After stripping off layer 1, Kiteshield effectively re-implements the exec syscall in userspace (See loader/loader.c . This technique is commonly referred to in literature as a "userspace exec" or "userland exec".) to map the packed binary into memory via a series of mmap / mprotect calls before forking and handing control off to the packed binary in the child and the runtime engine in the parent (which attaches to the Niño que usa Ptrace).
Además del cifrado, el código del cargador de Kiteshield también contiene una serie de características anti-debugging diseñadas para que sea lo más difícil posible analizar un binario en funcionamiento (ver loader/anti_debug.c y loader/include/anti_debug.h ).
Para dar un ejemplo concreto de kiteshield en acción, considere el siguiente programa Hello World, que empacaremos con Kiteshield en modo de depuración.
#include <stdio.h>
int main ()
{
puts ( "Hello World!" );
return 0 ;
}Cuando se empaqueta en modo de depuración, el código del cargador en binarios empaquetados registrará información de depuración muy detallada. El siguiente es el registro de un binario empacado correspondiente al programa anterior. Se ha anotado (y nuevas líneas adicionales agregadas para mayor claridad) para proporcionar un ejemplo concreto de kiteshield en acción:
$ ./packed.ks
# Runtime startup
[kiteshield] starting ptrace runtime
[kiteshield] number of trap points: 5
[kiteshield] number of encrypted functions: 3
# List of points in memory that have been instrumented with an int3 instruction
[kiteshield] list of trap points:
[kiteshield] 8000011ac value: c3, type: ret, function: __libc_csu_init (#0)
[kiteshield] 800001150 value: 41, type: ent, function: __libc_csu_init (#0)
[kiteshield] 800001050 value: 31, type: ent, function: _start (#1)
[kiteshield] 80000114b value: c3, type: ret, function: main (#2)
[kiteshield] 800001135 value: 55, type: ent, function: main (#2)
# Runtime has started and is waiting on the packed app, begin mapping binary
# Stripping layer one encryption
[kiteshield] RC4 decrypting binary with key 85e19ad41fb8cc13e87e3cd1589a45fb
[kiteshield] decrypted 12336 bytes
# Mapping segments from packed binary program header table
[kiteshield] mapping LOAD section from packed binary at 800000000
[kiteshield] mapping LOAD section from packed binary at 800001000
[kiteshield] mapping LOAD section from packed binary at 800002000
[kiteshield] mapping LOAD section from packed binary at 800003000
# Mapping dynamic linker specified in INTERP header of packed binary
[kiteshield] mapping INTERP ELF at path /lib64/ld-linux-x86-64.so.2
[kiteshield] mapped LOAD section from fd at b00000000
[kiteshield] interpreter base address is b00000000
[kiteshield] mapped LOAD section from fd at b00001000
[kiteshield] mapped LOAD section from fd at b0001f000
[kiteshield] mapped extra space for static data (.bss) at b00029000 len 400
[kiteshield] mapped LOAD section from fd at b00027000
[kiteshield] binary base address is 800000000
# Modifying ELF auxiliary vector as needed for program execution
[kiteshield] taking 7ffff2997c60 as auxv start
[kiteshield] replaced auxv entry 9 with value 34359742544 (0x800001050)
[kiteshield] replaced auxv entry 3 with value 34359738432 (0x800000040)
[kiteshield] replaced auxv entry 7 with value 47244640256 (0xb00000000)
[kiteshield] replaced auxv entry 5 with value 11 (0xb)
[kiteshield] finished mapping binary into memory
[kiteshield] control will be passed to packed app at b00001090
# Mapping done
# Runtime attaches to the packed application with ptrace
[kiteshield] child is traced, handing control to packed binary
# Program is executing, functions are logged on entry/exit
[kiteshield] tid 13508: entering encrypted function _start decrypting with key 74c974f9d097e5a8c60049c3842c6110
[kiteshield] tid 13508: entering encrypted function __libc_csu_init decrypting with key 9124511baa87daa76c09b2426355dfca
[kiteshield] tid 13508: leaving function __libc_csu_init for address 7fa9a821d02a (no function record) via ret at 8000011ac
[kiteshield] tid 13508: entering encrypted function main decrypting with key 2f1c20e77ff3617b0b89a579669aba48
# Actual program output
Hello World!
# Packed application returns from main() and exits
[kiteshield] tid 13508: leaving function main for address 7fa9a821d09b (no function record) via ret at 80000114b
[kiteshield] tid 13508: exited with status 0
[kiteshield] all threads exited, exiting
Kiteshield tiene un amplio conjunto de pruebas de integración para verificar la corrección en varias plataformas diferentes. Consulte testing/README.md para más detalles.
Kiteshield fue escrito con ofuscación en mente, no de velocidad. Atraparse en el tiempo de ejecución del kiteshield en cada entrada y salida de funciones es muy costoso. Los programas llenos de cifrado de la capa 2 deben esperar un rendimiento grave.
MIT © Rhys Rustad-Elliott