**Le PAD pour améliorer cette page :** https://pad.zenk-security.com/p/merci ====== L'article n'est pas du tout fini. Ne pas le mettre dans le sommaire ! ====== TODO * Détailler plus le format ELF * Expliquer différentes injections Cet article va vous présenter les différentes techniques qui existent afin d'injecter du code de manière statique dans un fichier ELF, que ce soit sur architecture 32 bits ou 64 bits, sous une distribution GNU/Linux. ====== 1. Le format ELF ====== ===== Différents types de fichiers ELF ===== Les fichiers ELF (Executable and Linkable Format) peuvent permettre d'enregistrer différents types de fichier : * Exécutables normaux : ls, dd, ponysay,... * Bibliothèques partagées : libcrypto.so, libglib.so,... * Fichiers core : core dump * Fichiers objets : les fichiers .o générés pas les compilateurs qui n'ont pas encore été liés ===== Outils ===== Afin d'analyser un fichier ELF différents outils existent : * **readelf** : Permet d'afficher différentes informations concernant un fichier ELF * ** file ** : Affiche différentes informations sur le fichier ELF * ** dumpelf ** : Dump toutes les informations sur la structure d'un fichier ELF en équivalent d'une structure en C * ** lddtree ** : Montre l’arbre des dépendances d'un fichier ELF * ** objdump ** * ** scanelf ** * ** gdb ** * ** edb ** * ** IDA ** ===== En-tête ELF ===== Un fichier ELF va être constitué d'un en-tête et soit d'une table d'en-tête de programme ou d'une table des en-têtes de sections, soit les deux. Dans notre cas, il est nécessaire de posséder la table d'en-tête de programme, celle-ci est obligatoire pour les exécutables. En effet, cette partie va contenir les informations sur les sections du programme qui vont comprendre le code qui sera mappé dans l'espace d'adressage du programme. Voici la représentation de l'en-tête ELF (32 bits) sous la forme d'une structure C : /* The ELF file header. This appears at the start of every ELF file. */ #define EI_NIDENT (16) typedef struct { unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */ Elf32_Half e_type; /* Object file type */ Elf32_Half e_machine; /* Architecture */ Elf32_Word e_version; /* Object file version */ Elf32_Addr e_entry; /* Entry point virtual address */ Elf32_Off e_phoff; /* Program header table file offset */ Elf32_Off e_shoff; /* Section header table file offset */ Elf32_Word e_flags; /* Processor-specific flags */ Elf32_Half e_ehsize; /* ELF header size in bytes */ Elf32_Half e_phentsize; /* Program header table entry size */ Elf32_Half e_phnum; /* Program header table entry count */ Elf32_Half e_shentsize; /* Section header table entry size */ Elf32_Half e_shnum; /* Section header table entry count */ Elf32_Half e_shstrndx; /* Section header string table index */ } Elf32_Ehdr; Cette structure va nous donner différentes informations permettant de réaliser notre injection. Tout d'abord, elle va contenir les informations concernant l'architecture utilisée (32bits ou 64bits) (e_ident), le codage utilisé (Little endian ou Big endian) (e_ident), le système d'exploitation auquel le binaire est destiné (e_ident), le type du fichier (exécutable, objet,...) (e_type) et la machine (ARM, x86_64,...) (e_machine). De cette manière, nous pourrons savoir si le fichier cible est bien un ELF, qu'il n'est pas mal formé et si nous devons injecter du code pour une architecture 32 bits ou 64 bits. Elle va contenir le point d'entrée du programme (e_entry), c'est-à-dire, l'adresse virtuelle à laquelle le système va laisser la main au programme, cela va ainsi démarrer le processus (Ceci n'est pas le main() de votre programme !). Ce sera donc ce champ que nous devrons modifier pour rediriger le flux d'exécution à l'adresse du code que nous aurons injecté. Elle va aussi contenir le décalage en octet de la table d'en-tête de programme (e_phoff) par rapport au début du fichier et le nombre d'entrées qu'elle contient (e_phnum), c'est-à-dire le nombre d'éléments dans la table d'en-tête de programme qui vont contenir les descriptions des différents segments, cela nous permettra de savoir si la table existe ou non (Si e_phnum ou e_phoff valent 0 alors la table n'existe pas). Pour une description plus détaillé des différents champs, référez-vous au ''man elf'' ===== Table d'en-tête de programme ===== Voici la structure en C représentant un élément de la table d'en-tête de programme d'un ELF (32 bits) : /* Program segment header. */ typedef struct { Elf32_Word p_type; /* Segment type */ Elf32_Off p_offset; /* Segment file offset */ Elf32_Addr p_vaddr; /* Segment virtual address */ Elf32_Addr p_paddr; /* Segment physical address */ Elf32_Word p_filesz; /* Segment size in file */ Elf32_Word p_memsz; /* Segment size in memory */ Elf32_Word p_flags; /* Segment flags */ Elf32_Word p_align; /* Segment alignment */ } Elf32_Phdr; Le champ p_type va contenir le type du segment (LOAD, DYNAMIC, NOTE,...), nous ne nous intéresserons qu'au segment LOAD. Généralement, il y aura 2 segments LOAD, le premier ayant les flags (p_flags) Readable & Executable, qui va correspondre à la section .text (Le code du programme) et le second qui aura les flags Readable & Writable, qui va correspondre aux sections .data (Les variables globales) et .bss (Les variables non initialisés). Bien entendu, cela n'est pas obligé, ils peuvent contenir d'autres sections en plus, et avec différents flags, il s'agit là du cas général. Les segments LOAD vont donc être chargés depuis le fichier pour être mappés au début du segment de la mémoire. statler@cafetiere [~] $ readelf -W -l /bin/ls Elf file type is EXEC (Executable file) Entry point 0x404858 There are 9 program headers, starting at offset 64 Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align PHDR 0x000040 0x0000000000400040 0x0000000000400040 0x0001f8 0x0001f8 R E 0x8 INTERP 0x000238 0x0000000000400238 0x0000000000400238 0x00001a 0x00001a R 0x1 [Requesting program interpreter: /lib/ld-linux-x86-64.so.2] LOAD 0x000000 0x0000000000400000 0x0000000000400000 0x019acc 0x019acc R E 0x200000 LOAD 0x019dc0 0x0000000000619dc0 0x0000000000619dc0 0x000820 0x001588 RW 0x200000 DYNAMIC 0x019dd8 0x0000000000619dd8 0x0000000000619dd8 0x000200 0x000200 RW 0x8 NOTE 0x000254 0x0000000000400254 0x0000000000400254 0x000044 0x000044 R 0x4 GNU_EH_FRAME 0x01737c 0x000000000041737c 0x000000000041737c 0x0006f4 0x0006f4 R 0x4 GNU_STACK 0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RW 0x8 GNU_RELRO 0x019dc0 0x0000000000619dc0 0x0000000000619dc0 0x000240 0x000240 R 0x1 Section to Segment mapping: Segment Sections... 00 01 .interp 02 .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt .init .plt .text .fini .rodata .eh_frame_hdr .eh_frame 03 .init_array .fini_array .jcr .dynamic .got .got.plt .data .bss 04 .dynamic 05 .note.ABI-tag .note.gnu.build-id 06 .eh_frame_hdr 07 08 .init_array .fini_array .jcr .dynamic .got Vous pouvez voir ici un exécutable où on y voit sa table d'en-têtes de programme (Program Headers) et les différents segments associés (Section to Segment mapping). Chaque éléments (LOAD, DYNAMIC,...) vont respectivement correspondre aux numéros des segments. Le segment 0 correspond à un segment PHDR, le 3 à un segment LOAD,... ===== Table des en-têtes de sections ===== blabla ====== 2. Injection entre 2 segments LOAD ====== Nous allons maintenant voir comment il est possible d'insérer du code entre 2 segments LOAD sans changer la taille du fichier. En effet, dû à l'alignement ((http://stackoverflow.com/questions/2391865/what-is-alignment-field-in-binary-formats-why-is-it-needed)) il y aura de l'espace inutilisé entre 2 segments LOAD. Cependant, cette espace n'est pas toujours très grand il faudra donc vérifier qu'il y ait suffisamment de place pour y mettre notre code. statler@cafetiere [~] $ readelf -W -l /bin/ls Elf file type is EXEC (Executable file) Entry point 0x404858 There are 9 program headers, starting at offset 64 Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align PHDR 0x000040 0x0000000000400040 0x0000000000400040 0x0001f8 0x0001f8 R E 0x8 INTERP 0x000238 0x0000000000400238 0x0000000000400238 0x00001a 0x00001a R 0x1 [Requesting program interpreter: /lib/ld-linux-x86-64.so.2] LOAD 0x000000 0x0000000000400000 0x0000000000400000 0x019acc 0x019acc R E 0x200000 LOAD 0x019dc0 0x0000000000619dc0 0x0000000000619dc0 0x000820 0x001588 RW 0x200000 DYNAMIC 0x019dd8 0x0000000000619dd8 0x0000000000619dd8 0x000200 0x000200 RW 0x8 NOTE 0x000254 0x0000000000400254 0x0000000000400254 0x000044 0x000044 R 0x4 GNU_EH_FRAME 0x01737c 0x000000000041737c 0x000000000041737c 0x0006f4 0x0006f4 R 0x4 GNU_STACK 0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RW 0x8 GNU_RELRO 0x019dc0 0x0000000000619dc0 0x0000000000619dc0 0x000240 0x000240 R 0x1 Section to Segment mapping: Segment Sections... 00 01 .interp 02 .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt .init .plt .text .fini .rodata .eh_frame_hdr .eh_frame 03 .init_array .fini_array .jcr .dynamic .got .got.plt .data .bss 04 .dynamic 05 .note.ABI-tag .note.gnu.build-id 06 .eh_frame_hdr 07 08 .init_array .fini_array .jcr .dynamic .got On peut voir ici l'espace qu'il y a entre les deux segments LOAD (l'adresse où débute le second segment LOAD moins l'adresse où se termine le premier segment LOAD) : 0x019dc0 - 0x019acc = 756 octets ====== Liens externes ====== * [[http://www.linuxforums.org/articles/understanding-elf-using-readelf-and-objdump_125.html|Understanding ELF using readelf and objdump]] * [[wp>Executable_and_Linkable_Format]]] * [[http://linux.die.net/man/5/elf|man elf]] * [[http://www.eresi-project.org/|The ERESI Reverse Engineering Software Interface]] * [[http://www.phrack.org/issues.html?issue=61&id=8#article|Phrack n°61 : The Cerberus ELF Interface]] * [[http://www.phrack.org/issues.html?issue=63&id=9#article|Phrack n°63 : Embedded ELF Debugging : the middle head of Cerberus]] * [[https://github.com/statl3r/squirt]]