Un packer / protecteur pour les binaires ELF x86-64 sur Linux. Kiteshield enveloppe les binaires ELF avec plusieurs couches de chiffrement et les injecte avec du code de chargeur qui décrypte, cartographie et exécute le binaire emballé entièrement dans l'espace utilisateur. Un moteur d'exécution basé sur PTRACE garantit que seules les fonctions dans la pile d'appels actuelles sont décryptées à tout moment et implémentent en outre une variété de techniques anti-débugage afin de rendre les binaires emballés aussi difficiles à rétro-ingénieurs que possible. Les binaires simples et multithread sont pris en charge.
Voir les sections de disposition de l'architecture et de la base de code ci-dessous pour une vue d'oeil sur le fonctionnement de Keeteshield.
Kiteshield est destiné à être un exercice académique amusant de l'obscurcissement binaire plutôt que quelque chose qui peut être utilisé dans le monde réel compte tenu du code source, et donc comment cela fonctionne, est public.
Nommé pour les boucliers préférés par les Normands au 11ème siècle (alternativement: les kiteshields qui sont si répandus dans Runescape old school).
Kiteshield nécessite que la bibliothèque BitDefender Disassebler pour décoder les instructions dans le packer. Il est inclus comme sous-module chez packer/bddisasm . Pour le construire à partir d'un nouveau clone, exécutez ce qui suit (note, vous devrez installer CMake):
git submodule update --init
cd packer/bddisasm
mkdir build
cd build
cmake ..
make
Vous pouvez désormais construire Kiteshield en mode de libération en make à partir du répertoire supérieur. Vous pouvez également créer une version de débogage avec make debug . Les constructions de débogage de Kiteshield désactivent toutes les fonctionnalités anti-débugage et activent la journalisation de débogage très verbeux du chargeur.
Afin de déboguer les fonctionnalités anti-débugage réelles, vous pouvez créer Kiteshield en mode débogage avec des fonctionnalités anti-débugage activées en utilisant make debug-antidebug .
Pour emballer un binaire appelé program et sortir le binaire emballé à packed.ks , exécutez:
./packer/kiteshield program packed.ks
packed.ks peuvent désormais être exécutés et doivent être fonctionnellement équivalents au program , mais cryptés et durs à rétro-ingénieurs. Notez que pour que le chiffrement de la couche 2 soit appliqué, le binaire d'entrée ne doit pas être dépouillé car Kiteshield s'appuie sur le tableau des symboles présente. Sur la plupart des distros Linux, les services publics de systèmes standard (par exemple /bin/ls ) sont généralement dépouillés.
Vous pouvez toujours emballer des binaires dépouillés sans cryptage de couche 2 en utilisant le drapeau -n :
./packer/kiteshield -n program packed.ks
Cela produira un binaire de sortie emballé avec seulement un cryptage de couche 1 et le moteur d'exécution omis.
Kiteshield est composé de deux pièces distinctes. Le Packer est une application C ordinaire qui lit, instruments et chiffre les binaires d'entrée. Le chargeur est une application C autoportante responsable du déchiffrement de la fonction dynamique et des fonctionnalités anti-débugage qui sont injectées en binaires d'entrée par le packer. Il reçoit le contrôle initial du noyau, cartographie tous les segments appropriés du binaire en mémoire (y compris le lieur dynamique le cas échéant) et maintient le contrôle à l'application. Le chargeur contient également le moteur d'exécution, qui décrypte et chiffre dynamiquement les fonctions au fur et à mesure qu'elles sont entrées et sorties au moment de l'exécution.
Étant donné que le chargeur reçoit le contrôle initial du noyau (c'est-à-dire avant que toute bibliothèque partagée ne soit normalement mappée par le lien dynamique), il n'a pas accès à LIBC et donc toutes les fonctionnalités nécessaires fournies par LIBC sont réimplémentées dans le code de chargeur.
Le code packer et le chargeur se trouve respectivement dans le packer/ et loader/ les répertoires respectivement. Le code commun aux deux peut être trouvé dans le répertoire common/ . Un aperçu de haut niveau des parties importantes de la base de code est la suivante:
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 enroule les binaires Elfe d'entrée en deux (ou un, si vous utilisez le drapeau -n ) des couches de cryptage RC4 de telle sorte que le binaire sur disque soit assez bien obstiné. Ces couches sont éliminées au moment de l'exécution par le chargeur.
La première couche de cryptage (appelée dans la base de code comme la "couche externe") se compose d'un seul passage RC4 sur l'ensemble du binaire d'entrée. Ceci est conçu principalement pour lutter contre l'analyse statique. En raison de la façon dont la clé est désobfusée (qui dépend du code de chargeur), la première couche de cryptage vérifie également efficacement le code de chargeur avant d'exécuter le binaire emballé, ce qui rend Keeteshield résistant au correctif de code.
La deuxième couche de cryptage (appelée dans la base de code comme la "couche intérieure") est constituée d'un chiffrement individuel de presque toutes les fonctions du binaire d'entrée (identifiée via le tableau des symboles au moment du pack). Un moteur d'exécution basé sur PTRACE est déclenché sur chaque entrée de fonction et sortie via le remplacement des instructions d'entrée de chaque fonction et toutes ses instructions de retour avec des instructions int3 (qui fournissent un SIGTRAP lors de l'exécution). Lors de la réception d'un piège, le moteur d'exécution recherche la fonction actuelle et le crypte ou le décrypte selon les besoins, de sorte que seules les fonctions dans la pile d'appels actuelle sont décryptées à tout moment.
Après avoir déshabillé la couche 1, Kiteshield réimplame efficacement l' exec Syscall dans l'espace utilisateur (voir loader/loader.c . Cette technique est communément appelée dans la littérature en tant que "Userpace Exec" ou "Userland Exec".) Pour mapper le binaire emballé dans la mémoire via une série d'appels mmap / mprotect avant de fixer le contrôle de la binary en utilisant ptrace).
En plus du chiffrement, le code de chargeur de Kiteshield contient également un certain nombre de fonctionnalités anti-débugage conçues pour rendre le plus difficile que possible d'analyser un binaire emballé en cours d'exécution (voir loader/anti_debug.c et loader/include/anti_debug.h ).
Pour donner un exemple concret de Kiteshield en action, considérez le programme Hello World suivant, que nous allons emballer avec Kiteshield en mode débogage.
#include <stdio.h>
int main ()
{
puts ( "Hello World!" );
return 0 ;
}Lorsqu'elle est emballée en mode débogage, le code de chargeur dans les binaires emballés enregistrera des informations de débogage très verbeux. Ce qui suit est le journal d'un binaire emballé correspondant au programme ci-dessus. Il a été annoté (et des Newlines supplémentaires ajoutées pour plus de clarté) pour fournir un exemple concret de Kiteshield en action:
$ ./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
Kotshield a un ensemble étendu de tests d'intégration pour vérifier l'exactitude sur plusieurs plates-formes différentes. Voir testing/README.md pour plus de détails.
Kiteshield a été écrit avec l'obscurcissement à l'esprit, pas sur la vitesse. Le piégeage dans le runtime Kiteshield sur chaque entrée et sortie de fonction est très cher. Les programmes remplis de chiffrement de la couche 2 devraient s'attendre à un grave succès.
MIT © Rhys Rustad-Elliott