Outils d'utilisateurs

Outils du Site


injection_elf

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 :

/usr/include/elf.h 65-85
/* 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) :

/usr/include/elf.h 537-561
/* 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 1) 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

injection_elf.txt · Dernière modification: 2019/10/14 20:42 par M0N5T3R