==Phrack Inc.== Volume 0x0b, Issue 0x3f, Phile #0x09 of 0x14 |=------=[ Embedded ELF Debugging : the middle head of Cerberus ]=------=| |=----------------------------------------------------------------------=| |=------------=[ The ELF shell crew ]=--------------=| |=----------------------------------------------------------------------=| |=--------------=[ Traduit par TboWan pour arsouyes.org ]=--------------=| |=----------------------------------------------------------------------=| I. Introduction au déboguage endurci A. Travaux précédents & limites B. Au delà de PaX et ptrace C. Amélioration de l'interface II. Air de jeu du déboguage embarqué A. Injection dans un processus B. Alternance de scripting ELF sur disque et en mémoire C. Déboguage proprement dit : dumping, backtrace, breakpoints D. Génération d'analyseurs dynamiques III. De meilleures redirections ELF multi-architecture A. CFLOW : redirection de fonction statique résistante à PaX B. Revision de la technique ALTPLT C. Technique ALTGOT : Le complément RISC D. Technique EXTPLT : post-liaison de fonction inconnues E. Algorithmes compatibles IA32, SPARC32/64, ALPHA64, MIPS32 V. Déboguage contraint A. relocalisation ET_REL en mémoire B. Relocalisation ET_REL dans ET_DYN C. Extentions d'exécutables statiques D. Algorithmes indépendants de l'architecture VI. Passé et présent VII. Remerciements VIII. Références -------[ I. Introduction au déboguage sous contraintes Dans le passé, la manipulation de binaires s'est surtout portee sur l'écriture de virus, le cracking, le déploiement de backdoors, et la création de minuscules exécutable camouflés. A part les outils du projet GNU comme binutils qui inclu le GNU debugger [1] (qui s'intéresse plus à la portabilité qu'aux fonctionnalités), aucune suite majeure de manipulation de binaire n'existe. Depuis près de dix ans, le format ELF a été un succès et la plupart des systèmes et distributions UNIX se basent dessus. Cependant, les outils existants ne tirent pas avantages du format et la plupart des logiciels de reverse engineering ou de débugging sont soit trop spécifiques à une architecture, ou ne font tout simplement pas attention à la structure du binaire pour extraire ou rediriger des informations. Depuis notre première publication sur le shell ELF, nous avons tellement amélioré la nouvelle suite qu'il est maintenant temps de publier un second article de fond mettant l'accent sur les avancées dans les techniques ELF statiques et à la volée. Nous allons expliquer en grands détails les 8 nouvelles fonctionnalités de manipulations de binaires qui intersectent la méthodologie existante du reverse engineering. Ces techniques permettent un nouveau type d'approche du déboguage et l'extension de logiciels proprietaire en environnement contraint. Nous avons travaillé sur beaucoup d'architectures (x86, alpha, sparc, mips) et nous nous sommes concentrés sur les environements contraints où les binaires sont liés pour inclure des protections de sécurité (comme les binaires endurcis de gentoo) dans les machines protégées par PaX [2]. Ceci signifie que notre débugger reste utilisable quand il est injecté dans un processus (local ou) distant. ----[ A. Travaux précédents & limites Dans la première partie de la série d'articles Cerberus, nous avions introduit une nouvelle technique résidente appelée l'injection ET_REL. Elle consistait en la compilation d'un source en C vers un objet relogeable (.o) et de son injection dans un programme sans les sources sur architecture INTEL et SPARC pour le format ELF32. Nous avons amélioré cette technique pour qu'à la fois les binaires 32 et 64 bits soient supportés en ajoutant un support pour alpha64 et sparc64. Nous avons aussi travaillé sur l'architecture MIPS r5000 et fournissons maintenant un debut d'environnement pour cette architecture. Nous permettons maintenant aussi l'injection ET_REL dans les objets ET_DYN (librairies partagées), notre technique est donc complètement compatible avec des environnement complètement randomisés comme ceux fournis par les Gentoo endurcies en activant PaX sur le système d'exploitation Linux. Nous avons aussi travaillé sur d'autres OS comme les BSD's, Solaris et HP-UX et le code y a été compilé et testé régulièrement aussi. Une innovation majeure de notre suite de manipulation de binaires basée sur le débugging est l'absence d'utilisation de ptrace. Nous n'utilisons pas de résidence dans le noyau comme [8] pour que même les utilisateurs sans privilèges puisse l'utiliser. Enfin, l'environment n'est pas dépendant d'API optionelles du système d'exploitation. Les débogueurs existants se base sur l'appel système ptrace pour que le processus de déboguage s'attache au programme débogué et permette des manipulations internes variables du processus comme la copie de mémoire, le placement de points d'arrets, le backtracking, etc. Nous proposons les mêmes fonctionnalités sans utiliser l'appel système. Les raisons pour lesquelles nous n'utilisons pas ptrace sont simples et multiples. Tout d'abord, beaucoup de systèmes endurcis ou embarqués ne l'implémentent pas. C'est le cas des systèmes basés sur grsecurity, des systèmes de production, ou les systèmes sur téléphones dont le système d'exploitation est basé sur ELF mais sans interface pour ptrace. La deuxième raison de ne pas utiliser ptrace est la pénalité de performance due à ce système de déboguage. Nous n'avons aucune penalite de performances puisque notre débogueur se trouve dans le même processus. Nous fournissons une technique complète en mode utilisateur qui n'a pas besoin d'accéder à la mémoire noyau, elle est donc utile dans toutes les étapes de test de pénétration quand on veut déboguer un logiciel sensible dans un environnement endurci et qu'aucune mise à jour du système n'est possible. Nous permettons d'injecter du code C dans un nouveau fichier binaire (dans une optique statique) ou dans un processus (en mode à la volée) en utilisant une API unifiee. Quand cela est necessaire, nous n'utilisons que des techniques ELF qui réduisent les indices sur le disque et fonctionnent uniquement en mémoire. ----[ B. Au delà de PaX et ptrace Un autre point clef de notre suite est la technique de redirection grandement améliorée. Nous pouvons rediriger quasiment tous les flots de contrôle, que le code soit ou non dans le binaire lui même (technique CFLOW) ou dans une librairie dont le binaire dépend (notre précédent travail présentais une nouvelle technique d'hijacking, ALTPLT). Nous avons amélioré cette technique et sommes passé à travers plusieurs réécritures et fournissons à présent une implémentation complète et indépendante de l'architecture. Nous avons completé ALTPLT par une nouvelle technique appellée ALTGOT qui rend possible l'hijacking d'une fonction puis l'appel de son code original sur des machines Alpha, Mips et RISC. Nous avons aussi créé une nouvelle technique appellée EXTPLT qui autorise des fonctions inconnues (pour lesquelles il n'y a aucune information de liaison du tout dans le fichier ELF) utilisant un algorithme de post-liaison compatible avec les objets ET_EXEC et ET_DYN. ----[ C. Amélioration de l'interface Notre implémentation du débogueur ELF embarqué est un prototype. Il faut comprendre qu'il est réellement utilisable mais est encore au stade du développement. Tout le code présenté ici fonctionne. Cependant, nous ne sommes pas omniscients et vous pouvez rencontrer des problèmes. Dans ce cas, envoyez-nous un email pour que nous puissions voir comment faire un patch. La seule supposition que nous avons faite est la capacité de lire le programme débogué. Dans tous les cas, vous pouvez aussi déboguer en mémoire un binaire illisible sur disque en chargeant le débogueur via la variable LD_PRELOAD. Cependant, les fonctionalites du debugger sont plus interessantes quand le binaire est lisible. Puisque le débogueur fonctionne dans le même espace d'adressage, vous pouvez toujours lire la mémoire [3] [4] et restaurer le programme binaire bien que nous ne l'avons pas encore implemente nous meme. Le langage de communication central pour la suite Embedded ELF Debugger (e2dbg) est le langage de script ELFsh. Nous l'avons étendu avec des boucles et des contrôles conditionnels, un support transparent pour les variables typées dynamiquement (comme en python). La commande source (pour exécuter un script dans la session courante) et les macros défines par l'utilisateur (la commande scriptdir) sont aussi supportées. Nous avons aussi développé une pile pair à pair appellée Distributed Update Management Protocol - DUMP - qui permet de lier plusieurs instances du débogueur via le réseau, mais ses capacités ne font pas l'objet de cet article. Pour être complets, nous supportons maintenant des sessions multi-utilisateurs (en parallèle ou partagées) et les changements d'environnements en utilisant la commande workspace. Nous allons voir l'utilisation de cette interface dans la première partie de ce papier. Dans la seconde partie, nous donnons des détails techniques sur l'implémentation de ces fonctionnalités sur de multiples architectures. La dernière partie est dédiée aux avancées techniques les plus récentes que nous avons développé ces dernières semaines sur le déboguage contraint de binaires protégés. Le dernier algorithme du papier est indépendant de l'architecture et constitue le coeur du moteur de relocation dans ELFsh. -------[ II. Air de jeu du déboguage embarqué ---[ A. Injection dans un processus Nous avons différentes techniques d'injection du débogueur dans un processus débogué. Ils vont donc partager le même espace d'adressage et le débogueur sera capable de lire ses propres données et code pour récupérer et changer les informations du processus débogué. Puisque ELF shell est composé a l'heure actuelle de 40 000 lignes de code, nous n'avons pas voulu tout recoder pour permettre la modification de binaires. Nous avons utilisé des astuces qui nous ont permi de sélectionner si la modification sera faite en mémoire ou sur le disque. Le truc consiste en 10 lignes de code. En considérant que la macro PROFILE n'est pas obligatoire, voici le code exact : (libelfsh/section.c) ========= BEGIN DUMP 0 ========= void *elfsh_get_raw(elfshsect_t *sect) { ELFSH_PROFILE_IN(__FILE__, __FUNCTION__, __LINE__); /* sect->parent->base is always NULL for ET_EXEC */ if (elfsh_is_debug_mode()) { sect->pdata = (void *) sect->parent->base + sect->shdr->sh_addr; ELFSH_PROFILE_ROUT(__FILE__, __FUNCTION__, __LINE__, (sect->pdata)); } if (sect) ELFSH_PROFILE_ROUT(__FILE__, __FUNCTION__, __LINE__, (sect->data)); ELFSH_PROFILE_ERR(__FILE__, __FUNCTION__, __LINE__, "Invalid parameter", NULL); } ========= END DUMP 0 ========= Quelle est cette technique ? C'est assez simple : si le drapeau du débogueur interne indique le mode statique (modification sur le disque), alors, on retourne le pointeur vers le cache des données interne d'ELFsh pour la section de données (ou de code) que nous voulons accéder. Cependant, si nous sommes en mode dynamique (modification du processus), alors nous retournons juste l'adresse de la section. Le débogueur fonctionne dans le même processus et pensera donc que l'adresse retournée est un buffer en lecture (ou écriture). Nous pouvons réutiliser toute l'API de ELF shell en faisant juste attention à l'utilisation de la fonction elfsh_get_raw() quand nous accédont au pointeur ->data. La sélection processus/sur disque est transparente pour tout le code du debogueur/elfsh. L'idée d'injecter du code directement dans le processus n'est pas nouvelle et ça fait quelques années maintenant que nous l'étudions. L'injection de code embarqué est aussi utilisée par la communauté du cracking sous Windows [12] pour éviter la plupart des protection contre le traçage et le déboguage, mais nous n'avons vu nulle part d'implémentation d'un débogueur complet, capable de telles fonctionnalités avancées comme l'injection ET_REL ou la redirection de fonctions sur des architectures multiples, à la fois sur disque et en mémoire, dans un seul code. ---[ B. Alternance de scripts ELF sur disque et en mémoire (y compris avec la linkmap) Nous avons deux approches pour insérer le débogueur dans le programmé débogué. Quand on utilise une entrée DT_NEEDED et qu'on redirige le main débogué vers le point d'entrée main du débogueur ET_DYN, nous injectons aussi differentes sections pour permettre de faires des techniques essentielles comme EXTPLT. Ceci sera expliqué en détail dans la prochaine partie. La deuxième approche implique l'utilisation du LD_PRELOAD sur le programme débogué et de placer des points d'arrets (soit via l'opcode 0xCC sous x86, via l'opcode équivalent sur les autres architectures, ou en redirigeant des fonctions, ce qui est disponible sur beaucoup d'architectures et pour de nombreux genres de fonctions dans cette suite). Puisqu'une modification du binaire est nécessaire de toute façons, nous allons utiliser la technique DT_NEEDED pour ajouter les dépendances de librairies, et toutes les injections de sections et redirections décrites dans cet article avant le débogueur proprement dit. La technique du LD_PRELOAD est particulièrement plus utile quand vous ne pouvez pas lire le binaire que vous voulez déboguer. Le choix du type d'injection du débogueur est laissé à l'utilisateur, qui le fera en fonction des besoins du moment. Voyons comment utiliser le débogueur embarqué et sa commande "mode" qui fait la sélection en mémoire/sur disque. Ensuite, nous imprimons la Table Globale d'Offset [NDT : Global Offset Table - .got]. D'abord, la mémoire GOT est affichée, ensuite, nous revenons en mode static et la GOT sur disque est affichée : ========= BEGIN DUMP 1 ========= (e2dbg-0.65) list .::. Working files .::. [001] Sun Jul 31 19:23:33 2005 D ID: 9 /lib/libncurses.so.5 [002] Sun Jul 31 19:23:33 2005 D ID: 8 /lib/libdl.so.2 [003] Sun Jul 31 19:23:33 2005 D ID: 7 /lib/libtermcap.so.2 [004] Sun Jul 31 19:23:33 2005 D ID: 6 /lib/libreadline.so.5 [005] Sun Jul 31 19:23:33 2005 D ID: 5 /lib/libelfsh.so [006] Sun Jul 31 19:23:33 2005 D ID: 4 /lib/ld-linux.so.2 [007] Sun Jul 31 19:23:33 2005 D ID: 3 ./ibc.so.6 # e2dbg.so renamed [008] Sun Jul 31 19:23:33 2005 D ID: 2 /lib/tls/libc.so.6 [009] Sun Jul 31 19:23:33 2005 *D ID: 1 ./a.out_e2dbg # debuggee .::. ELFsh modules .::. [*] No loaded module (e2dbg-0.65) mode [*] e2dbg is in DYNAMIC MODE (e2dbg-0.65) got [Global Offset Table .::. GOT : .got ] [Object ./a.out_e2dbg] 0x080498E4: [0] 0x00000000 [Global Offset Table .::. GOT : .got.plt ] [Object ./a.out_e2dbg] 0x080498E8: [0] 0x0804981C <_DYNAMIC@a.out_e2dbg> 0x080498EC: [1] 0x00000000 0x080498F0: [2] 0x00000000 0x080498F4: [3] 0x0804839E 0x080498F8: [4] 0x080483AE 0x080498FC: [5] 0x080483BE 0x08049900: [6] 0x080483CE 0x08049904: [7] 0x080483DE <__libc_start_main@a.out_e2dbg> 0x08049908: [8] 0x080483EE 0x0804990C: [9] 0x080483FE 0x08049910: [10] 0x0804840E [Global Offset Table .::. GOT : .elfsh.altgot ] [Object ./a.out_e2dbg] 0x08049928: [0] 0x0804981C <_DYNAMIC@a.out_e2dbg> 0x0804992C: [1] 0xB7F4A4E8 <_r_debug@ld-linux.so.2 + 24> 0x08049930: [2] 0xB7F3EEC0 <_dl_rtld_di_serinfo@ld-linux.so.2 + 477> 0x08049934: [3] 0x0804839E 0x08049938: [4] 0x080483AE 0x0804993C: [5] 0xB7E515F0 <__libc_malloc@libc.so.6> 0x08049940: [6] 0x080483CE 0x08049944: [7] 0xB7E01E50 <__libc_start_main@libc.so.6> 0x08049948: [8] 0x080483EE 0x0804994C: [9] 0x080483FE 0x08049950: [10] 0x0804840E 0x08049954: [11] 0xB7DAFFF6 (e2dbg-0.65) mode static [*] e2dbg is now in STATIC mode (e2dbg-0.65) # Here we switched in ondisk perspective (e2dbg-0.65) got [Global Offset Table .::. GOT : .got ] [Object ./a.out_e2dbg] 0x080498E4: [0] 0x00000000 [Global Offset Table .::. GOT : .got.plt ] [Object ./a.out_e2dbg] 0x080498E8: [0] 0x0804981C <_DYNAMIC> 0x080498EC: [1] 0x00000000 0x080498F0: [2] 0x00000000 0x080498F4: [3] 0x0804839E 0x080498F8: [4] 0x080483AE 0x080498FC: [5] 0x080483BE 0x08049900: [6] 0x080483CE 0x08049904: [7] 0x080483DE <__libc_start_main> 0x08049908: [8] 0x080483EE 0x0804990C: [9] 0x080483FE 0x08049910: [10] 0x0804840E [Global Offset Table .::. GOT : .elfsh.altgot ] [Object ./a.out_e2dbg] 0x08049928: [0] 0x0804981C <_DYNAMIC> 0x0804992C: [1] 0x00000000 0x08049930: [2] 0x00000000 0x08049934: [3] 0x0804839E 0x08049938: [4] 0x080483AE 0x0804993C: [5] 0x080483BE 0x08049940: [6] 0x080483CE 0x08049944: [7] 0x080483DE <__libc_start_main> 0x08049948: [8] 0x080483EE 0x0804994C: [9] 0x080483FE 0x08049950: [10] 0x0804840E 0x08049954: [11] 0x0804614A ========= END DUMP 1 ========= Il y a beaucoup de choses à voir dans ce dump. Tout d'abord, vous pouvez vérifier qu'il fait effectivement ce qu'il est supposé faire en regardant les premières entrées de la GOT qui sont réservées pour la linkmap et la fonction rtld dl-resolve. Ces entrées sont remplies à l'exécution, donc, la version statique de la GOT contien des pointeurs NULL pour celles-là. Cependant, la GOT en mémoire a ces entrées qui sont remplies. En plus, la nouvelle version de l'éditeur de lien de GNU insère de multiples sections GOT dans les binaires ELF. La section .got gère les pointeurs vers les données externes, tandis que la .got.plt gère les pointeurs vers les fonctions externes. Dans les premières version de LD, ces deux sections étaient fusionnées. Nous supportons les deux conventions. Enfin, vous pouvez voir à la fin dans la section .elfsh.altgot. Ça fait partie de notre technique ALTGOT et sera expliquée comme un algorithme indépendant dans la prochaine partie du papier. La technique ALTGOT permet une augmentation de la taille de la GOT. Ça permet différentes choses suivant l'architecture sur laquelle on se trouve. Sous x86, ALGOT est seulement utilisée quand EXTPLT l'est aussi, pour pouvoir ajouter des fonctions supplémentaires au programme hôte. Sur MIPS et ALPHA, ALTGOT permet de rediriger une fonction externe (PLT) sans perdre l'adresse réelle de la fonction. Nous développerons ces deux techniques dans la partie suivante. ---[ C. Déboguage proprement dit : dumping, backtrace, breakpoints Quand nous déboguons en utilisant un débogueur embarqué dans le processus débogué, nous n'avons pas besoin de ptrace, et nous ne pouvons pas modifier si facilement l'espace d'adressage du processus. C'est pourquoi nous devons effectuer de petits changements statiques : nous pouvons par exemple ajouter le débogueur comme une dépendance DT_NEEDED (dans le cas ou nous n'utilisons pas LD_PRELOAD). Le débogueur va aussi surcharger certains gestionnaires de signaux (SIGTRAP, SIGINT, SIGSEGV ..) pour qu'il puisse garder le contrôle de ces évenements. Nous pouvons rediriger des fonctions en utilisant tout autant les techniques CFLOW ou ALTPLT utilisant des modifications sur disque, pour récupérer le contrôle au moment désiré. Nous pouvons évidement mettre des breakpoints lors de l'exécution, mais cela demande de mprotect la zone de code si elle n'était pas en écriture à ce moment. Nous avons des idées pour ne plus être embêtés par mprotect mais ça n'a pas été implémenté dans cette version (0.65). (En fait, beaucoup d'utilisations de l'appel systèle mprotect sont incompatible avec une des options de PaX). Heureusement, en assumant pour l'instant que nous avons un accès en lecture vers le programme débogué, on peut copier le fichier et désactivé cette option. Voici comment la dépendance DT_NEEDED est ajoutée : ========= BEGIN DUMP 2 ========= elfsh@WTH $ cat inject_e2dbg.esh #!../../vm/elfsh load a.out set 1.dynamic[08].val 0x2 set 1.dynamic[08].tag DT_NEEDED redir main e2dbg_run save a.out_e2dbg ========= END DUMP 2 ========= Voyons maintenant la section .dynamic modifiée du binaire, où les entrées DT_NEEDED supplémentaires ont été ajoutée grace à la technique DT_DEBUG publiée il y a deux ans [0] : ========= BEGIN DUMP 3 ========= elfsh@WTH $ ../../vm/elfsh -f ./a.out -d DT_NEEDED [*] Object ./a.out has been loaded (O_RDONLY) [SHT_DYNAMIC] [Object ./a.out] [00] Name of needed library => libc.so.6 {DT_NEEDED} [*] Object ./a.out unloaded elfsh@WTH $ ../../vm/elfsh -f ./a.out_e2dbg -d DT_NEEDED [*] Object ./a.out_e2dbg has been loaded (O_RDONLY) [SHT_DYNAMIC] [Object ./a.out_e2dbg] [00] Name of needed library => libc.so.6 {DT_NEEDED} [08] Name of needed library => ibc.so.6 {DT_NEEDED} [*] Object ./a.out_e2dbg unloaded ========= END DUMP 3 ========= Voyons maintenant comment la fonction main est redirigée vers la fonction hook_main. Vous pouvez noter les deux octets écrasés entre les deux jmp de hook_main. Cette technique est aussi disponible sur l'architecture MIPS, mais ce dump vient de l'implémentation IA32 : ========= BEGIN DUMP 4 ========= elfsh@WTH $ ../../vm/elfsh -f ./a.out_e2dbg -D main%40 [*] Object ./a.out_e2dbg has been loaded (O_RDONLY) 08045134 [foff: 308] hook_main + 0 jmp 08045139 [foff: 313] hook_main + 5 push %ebp 0804513A [foff: 314] hook_main + 6 mov %esp,%ebp 0804513C [foff: 316] hook_main + 8 push %esi 0804513D [foff: 317] hook_main + 9 push %ebx 0804513E [foff: 318] hook_main + 10 jmp
08045139 [foff: 313] old_main + 0 push %ebp 0804513A [foff: 314] old_main + 1 mov %esp,%ebp 0804513C [foff: 316] old_main + 3 push %esi 0804513D [foff: 317] old_main + 4 push %ebx 0804513E [foff: 318] old_main + 5 jmp
08048530 [foff: 13616] main + 0 jmp 08048535 [foff: 13621] main + 5 sub $2010,%esp 0804853B [foff: 13627] main + 11 mov 8(%ebp),%ebx 0804853E [foff: 13630] main + 14 mov C(%ebp),%esi 08048541 [foff: 13633] main + 17 and $FFFFFFF0,%esp 08048544 [foff: 13636] main + 20 sub $10,%esp 08048547 [foff: 13639] main + 23 mov %ebx,4(%esp,1) 0804854B [foff: 13643] main + 27 mov $<_IO_stdin_used + 43>,(%esp,1) 08048552 [foff: 13650] main + 34 call 08048557 [foff: 13655] main + 39 mov (%esi),%eax [*] No binary pattern was specified [*] Object ./a.out_e2dbg unloaded ========= END DUMP 4 ========= Exécutons maintenant le programme débogué dans lequel on a injecté le débogueur. ========= BEGIN DUMP 5 ========= elfsh@WTH $ ./a.out_e2dbg The Embedded ELF Debugger 0.65 (32 bits built) .::. .::. This software is under the General Public License V.2 .::. Please visit http://www.gnu.org [*] Sun Jul 31 17:56:52 2005 - New object ./a.out_e2dbg loaded [*] Sun Jul 31 17:56:52 2005 - New object /lib/tls/libc.so.6 loaded [*] Sun Jul 31 17:56:53 2005 - New object ./ibc.so.6 loaded [*] Sun Jul 31 17:56:53 2005 - New object /lib/ld-linux.so.2 loaded [*] Sun Jul 31 17:56:53 2005 - New object /lib/libelfsh.so loaded [*] Sun Jul 31 17:56:53 2005 - New object /lib/libreadline.so.5 loaded [*] Sun Jul 31 17:56:53 2005 - New object /lib/libtermcap.so.2 loaded [*] Sun Jul 31 17:56:53 2005 - New object /lib/libdl.so.2 loaded [*] Sun Jul 31 17:56:53 2005 - New object /lib/libncurses.so.5 loaded (e2dbg-0.65) b puts [*] Breakpoint added at (0x080483A8) (e2dbg-0.65) continue [..: Embedded ELF Debugger returns to the grave :...] [e2dbg_run] returning to 0x08045139 [host] main argc 1 [host] argv[0] is : ./a.out_e2dbg First_printf test The Embedded ELF Debugger 0.65 (32 bits built) .::. .::. This software is under the General Public License V.2 .::. Please visit http://www.gnu.org [*] Sun Jul 31 17:57:03 2005 - New object /lib/tls/libc.so.6 loaded (e2dbg-0.65) bt .:: Backtrace ::. [00] 0xB7DC1EC5 [01] 0xB7DC207F [02] 0xB7DBC88C [03] 0xB7DAB4DE [04] 0xB7DAB943 [05] 0xB7DA5FF0 [06] 0xB7DA68D6 [07] 0xFFFFE440 <_r_debug@ld-linux.so.2 + 1208737648> # sigtrap retaddr [08] 0xB7DF7F3B <__libc_start_main@libc.so.6 + 235> [09] 0x08048441 <_start@a.out_e2dbg + 33> (e2dbg-0.65) b .:: Breakpoints ::. [00] 0x080483A8 (e2dbg-0.65) delete 0x080483A8 [*] Breakpoint at 080483A8 removed (e2dbg-0.65) b .:: Breakpoints ::. [*] No breakpoints (e2dbg-0.65) b printf [*] Breakpoint added at (0x080483E8) (e2dbg-0.65) dumpregs .:: Registers ::. [EAX] 00000000 (0000000000) [EBX] 08203F48 (0136331080) <.elfsh.relplt@a.out_e2dbg + 1811272> [ECX] 00000000 (0000000000) [EDX] B7F0C7C0 (3086010304) <__guard@libc.so.6 + 1656> [ESI] BFE3B7C4 (3219371972) <_r_debug@ld-linux.so.2 + 133149428> [EDI] BFE3B750 (3219371856) <_r_debug@ld-linux.so.2 + 133149312> [ESP] BFE3970C (3219363596) <_r_debug@ld-linux.so.2 + 133141052> [EBP] BFE3B738 (3219371832) <_r_debug@ld-linux.so.2 + 133149288> [EIP] 080483A9 (0134513577) (e2dbg-0.65) stack 20 .:: Stack ::. 0xBFE37200 0x00000000 <(null)> 0xBFE37204 0xB7DC2091 0xBFE37208 0xB7DDF5F0 <_GLOBAL_OFFSET_TABLE_@ibc.so.6> 0xBFE3720C 0xBFE3723C <_r_debug@ld-linux.so.2 + 133131628> 0xBFE37210 0xB7DC22E7 0xBFE37214 0x00000014 <_r_debug@ld-linux.so.2 + 1208744772> 0xBFE37218 0xB7DDDD90 <__FUNCTION__.5@ibc.so.6 + 49> 0xBFE3721C 0xBFE37230 <_r_debug@ld-linux.so.2 + 133131616> 0xBFE37220 0xB7DB9DF9 0xBFE37224 0xB7DE1A7C 0xBFE37228 0xB7DA8176 0xBFE3722C 0x080530B8 <.elfsh.relplt@a.out_e2dbg + 38072> 0xBFE37230 0x00000014 <_r_debug@ld-linux.so.2 + 1208744772> 0xBFE37234 0x08264FF6 <.elfsh.relplt@a.out_e2dbg + 2208758> 0xBFE37238 0xB7DDF5F0 <_GLOBAL_OFFSET_TABLE_@ibc.so.6> 0xBFE3723C 0xBFE3726C <_r_debug@ld-linux.so.2 + 133131676> 0xBFE37240 0xB7DBC88C 0xBFE37244 0x0804F208 <.elfsh.relplt@a.out_e2dbg + 22024> 0xBFE37248 0x00000000 <(null)> 0xBFE3724C 0x00000000 <(null)> (e2dbg-0.65) continue [..: Embedded ELF Debugger returns to the grave :...] First_puts The Embedded ELF Debugger 0.65 (32 bits built) .::. .::. This software is under the General Public License V.2 .::. Please visit http://www.gnu.org [*] Sun Jul 31 18:00:47 2005 - /lib/tls/libc.so.6 loaded [*] Sun Jul 31 18:00:47 2005 - /usr/lib/gconv/ISO8859-1.so loaded (e2dbg-0.65) dumpregs .:: Registers ::. [EAX] 0000000B (0000000011) <_r_debug@ld-linux.so.2 + 1208744763> [EBX] 08203F48 (0136331080) <.elfsh.relplt@a.out_e2dbg + 1811272> [ECX] 0000000B (0000000011) <_r_debug@ld-linux.so.2 + 1208744763> [EDX] B7F0C7C0 (3086010304) <__guard@libc.so.6 + 1656> [ESI] BFE3B7C4 (3219371972) <_r_debug@ld-linux.so.2 + 133149428> [EDI] BFE3B750 (3219371856) <_r_debug@ld-linux.so.2 + 133149312> [ESP] BFE3970C (3219363596) <_r_debug@ld-linux.so.2 + 133141052> [EBP] BFE3B738 (3219371832) <_r_debug@ld-linux.so.2 + 133149288> [EIP] 080483E9 (0134513641) (e2dbg-0.65) linkmap .::. Linkmap entries .::. [01] addr : 0x00000000 dyn : 0x0804981C - [02] addr : 0x00000000 dyn : 0xFFFFE590 - [03] addr : 0xB7DE3000 dyn : 0xB7F0AD3C - /lib/tls/libc.so.6 [04] addr : 0xB7D95000 dyn : 0xB7DDF01C - ./ibc.so.6 [05] addr : 0xB7F29000 dyn : 0xB7F3FF14 - /lib/ld-linux.so.2 [06] addr : 0xB7D62000 dyn : 0xB7D93018 - /lib/libelfsh.so [07] addr : 0xB7D35000 dyn : 0xB7D5D46C - /lib/libreadline.so.5 [08] addr : 0xB7D31000 dyn : 0xB7D34BB4 - /lib/libtermcap.so.2 [09] addr : 0xB7D2D000 dyn : 0xB7D2FEEC - /lib/libdl.so.2 [10] addr : 0xB7CEB000 dyn : 0xB7D2A1C0 - /lib/libncurses.so.5 [11] addr : 0xB6D84000 dyn : 0xB6D85F28 - /usr/lib/gconv/ISO8859-1.so (e2dbg-0.65) exit [*] Unloading object 1 (/usr/lib/gconv/ISO8859-1.so) [*] Unloading object 2 (/lib/tls/libc.so.6) [*] Unloading object 3 (/lib/tls/libc.so.6) [*] Unloading object 4 (/lib/libncurses.so.5) [*] Unloading object 5 (/lib/libdl.so.2) [*] Unloading object 6 (/lib/libtermcap.so.2) [*] Unloading object 7 (/lib/libreadline.so.5) [*] Unloading object 8 (/home/elfsh/WTH/elfsh/libelfsh/libelfsh.so) [*] Unloading object 9 (/lib/ld-linux.so.2) [*] Unloading object 10 (./ibc.so.6) [*] Unloading object 11 (/lib/tls/libc.so.6) [*] Unloading object 12 (./a.out_e2dbg) * .:: Bye -:: The Embedded ELF Debugger 0.65 ========= END DUMP 5 ========= Comme vous venez de le voir, l'utilisation du débogueur est assez similaire aux autres débogueurs. La différence tient dans la technique d'implémentation qui permet un déboguage sur des systèmes embarqués et endurcis où ptrace n'est pas présent ou est désactivé. On nous a dit [9] que l'appel système sigaction active la possibilité d'exécution pas à pas sans utiliser ptrace. Nous n'avons pas eu le temps de l'implémenté mais nous fournirons un débogueur pas à pas dans un futur proche. Puisque cet appel n'est pas filtré par grsecurity et semble assez portable sous Linux, BSD, Solaris et HP-UX, ça vaut certainement la peine de le tester. ---[ D. Génération d'analyseurs dynamiques Évidement, les outils comme ltrace[7] peuvent maintenant être fait en utilisant des scripts elfsh pour des architectures multiples puisque tout le travail de redirections à été fait. Nous pensons aussi que la suite peut être utilisée dans des instrumentations de logiciels dynamiques. Puisque nous supportons des architectures multiples, nous laissons la porte ouverte à d'autres équipes de développement pour coder ce genre de modules ou d'extensions au sein de la suite ELF shell. Nous n'avons pas eu le temps d'inclure un script d'example qui le fait, mais nous le ferons bientôt. Le genre de choses intéressantes qui peuvent être faites et améliorée en utilisant la suite prendrait leur inspiration dans des projets comme fenris [6]. Ceci pourrait être fait pour de multiples architectures puisque le type de format d'instruction est intégré dans le moteur de script, en utilisant l'abstraction du code de libasm (qui est maintenant inclu comme source dans elfsh). Nous n'avons pas encore traité le chiffrement pour l'instant, mais des API prometteuses [5] pourrait être implémentées aussi pour de multiples architectures très facilement. -------[ III. Meilleures redirections ELF multi-architecture Dans la première édition du Cerberus ELF interface [0], nous avions présenté une technique de redirection appelée ALTPLT. Cette technique n'est pas suffisante puisqu'elle ne permet que des redirections du PLT sur des fonctions existantes du programme binaire, les fonctions utilisables pour étendre le logiciel sont donc limitées. De plus, nous avons remarqué un bogue dans l'implémentation de la technique ALTPLT publiée précédement : sur l'architecture SPARC, quand on appelait une fonction originale, la redirection était enlevée et le programme continuait à fonctionner comme si aucun détournement n'était installé. Ce bogue vient du fait que Solaris n'utilise pas le champt r_offset pour calculer ses relocations mais calcule l'offset dans le fichier en multipliant la taille de l'entrée du PLT par l'offset de relocation placé sur la pile au moment de la résolution dynamique. Nous avons trouvé une solution à ce problème. Cette solution consiste à mettre quelques corrections spécifiques à l'architecture au début de la section ALTPLT. Cependant, une telle correction est trop dépendante de l'architecture et nous avons commencé à pensé à une technique alternative pour implémenter ALTPLT. Tout en implémentant la technique DT_DEBUG en modifiant certaines entrées de la section .dynamic, nous avons découvert que beaucoup d'autres entrées sont écrasables et nous fournissent une technique très forte et indépendante de l'architecture pour redirriger l'accès à des sections variables. Plus précisément, quand on patche l'entrée DT_PLTREL, nous sommes capable de fournir notre propre pointeur. DT_PLTREL est une entrée DEPENDANTE de l'architecture et la documentation à son sujet est très petite, pour ne pas dire inexistante. Elle pointe en fait sur la section de l'exécutable qui est relogee à l'exécution (par exemple la GOT sous x86 et mips, PLT sous sparc et alpha). En changeant cette entrée, nous sommes capable de fournir notre propre PLT ou GOT, qui nous permet de l'étendre par la suite. Regardons d'abord la technique CFLOW, nous reviendrons ensuite sur les redirections relatives au PLT en utilisant les modifications de DT_PLTREL. ---[ A. CFLOW : redirection de fonction statique résistante à PaX CFLOW est une technique simple mais efficace pour rediriger des fonctions qui sont localisées dans le programme hôte et qui n'ont pas d'entrée dans la PLT. Voyons maintenant le fichier hôte que nous allons utiliser pour les tests : ========= BEGIN DUMP 6 ========= elfsh@WTH $ cat host.c #include #include #include int legit_func(char *str) { printf("legit func (%s) !\n", str); return (0); } int main() { char *str; char buff[BUFSIZ]; read(0, buff, BUFSIZ-1); str = malloc(10); if (str == NULL) goto err; strcpy(str, "test"); printf("First_printf %s\n", str); fflush(stdout); puts("First_puts"); printf("Second_printf %s\n", str); free(str); puts("Second_puts"); fflush(stdout); legit_func("test"); return (0); err: printf("Malloc problem\n"); return (-1); } ========= END DUMP 6 ========= Nous allons ici rediriger la fonction legit_func, localisée dans le fichier host.c par la fonction hook_funk localisée dans l'objet relogeable. Jetons un coup d'oeuil au fichier relogeable que nous allons injecter dans le binaire précédant. ========= BEGIN DUMP 7 ========= elfsh@WTH $ cat rel.c #include #include #include int glvar_testreloc = 42; int glvar_testreloc_bss; char glvar_testreloc_bss2; short glvar_testreloc_bss3; int hook_func(char *str) { printf("HOOK FUNC %s !\n", str); return (old_legit_func(str)); } int puts_troj(char *str) { int local = 1; char *str2; str2 = malloc(10); *str2 = 'Z'; *(str2 + 1) = 0x00; glvar_testreloc_bss = 43; glvar_testreloc_bss2 = 44; glvar_testreloc_bss3 = 45; printf("Trojan injected ET_REL takes control now " "[%s:%s:%u:%u:%hhu:%hu:%u] \n", str2, str, glvar_testreloc, glvar_testreloc_bss, glvar_testreloc_bss2, glvar_testreloc_bss3, local); free(str2); putchar('e'); putchar('x'); putchar('t'); putchar('c'); putchar('a'); putchar('l'); putchar('l'); putchar('!'); putchar('\n'); old_puts(str); write(1, "calling write\n", 14); fflush(stdout); return (0); } int func2() { return (42); } ========= END DUMP 7 ========= Comme vous pouvez le voir, l'objet relogeable utilise des fonctions inconnues comme write et putchar. Ces fonctions n'ont pas de symbole, d'entrée dans la PLT, d'entrée dans la GOT, ou même une entrée relogeable dans le programme hôte. Nous pouvons l'appeller quand même en utilisant la technique EXTPLT qui sera expliquée comme technique indépendante dans la prochaine partie du papier. Pour l'instant, nous nous concentrons sur la technique CFLOW qui permet de rediger legit_func vers hook_func. Cette fonction n'a pas d'entrée en PLT et nous ne pouvons pas utiliser une simple infection de PLT. Nous avons développé une technique qui ne craint pas PaX pour les redirections sur disque de ce genre de fonctions. Elle consiste à mettre un bon vieux jmp au début de legit_func et de rediriger le flot vers notre code. ELFsh fera attention à bien exécuter part les octets écrasés ailleur et rendra le contrôle à la fonction redirigée, juste après le détournement par jmp, pour qu'aucune restauration à l'exécution ne soit nécessaire et qu'elle reste immunisée sur le disque contre PaX. Quand ces techniques sont utilisées dans le débogueur directement en mémoire et pas sur le disque, elles cassent les protection mrprotect de PaX, ce qui veut dire que ce flag doit être désactivé si vous voulez rediriger le flot directement en mémoire. Nous avons utilisé l'appel système mrprotect sur de petites zones de code pour pouvoir changer quelques instructions spécifiques pour la redirection. Cependant, nous pensons que cette technique est plus intéressante pour le déboguage et pas pour autre chose, ce n'est donc pas, pour nous, une priorité de l'améliorer. Voyons le petit script ELFsh pour cet exemple : ========= BEGIN DUMP 8 ========= elfsh@WTH $ file a.out a.out: ELF 32-bit LSB executable, Intel 80386, dynamically linked, \ not stripped elfsh@WTH $ cat relinject.esh #!../../../vm/elfsh load a.out load rel.o reladd 1 2 redir puts puts_troj redir legit_func hook_func save fake_aout quit ========= END EXAMPLE 8 ========= La sortie du binaire ORIGINAL est la suivante : ========= BEGIN DUMP 9 ========= elfsh@WTH $ ./a.out First_printf test First_puts Second_printf test Second_puts LEGIT FUNC legit func (test) ! ========= END DUMP 9 =========== Injectons maintenant le bazar : ========= BEGIN DUMP 10 ======== elfsh@WTH $ ./relinject.esh The ELF shell 0.65 (32 bits built) .::. .::. This software is under the General Public License V.2 .::. Please visit http://www.gnu.org ~load a.out [*] Sun Jul 31 15:30:14 2005 - New object a.out loaded ~load rel.o [*] Sun Jul 31 15:30:14 2005 - New object rel.o loaded ~reladd 1 2 Section Mirrored Successfully ! [*] ET_REL rel.o injected succesfully in ET_EXEC a.out ~redir puts puts_troj [*] Function puts redirected to addr 0x08047164 ~redir legit_func hook_func [*] Function legit_func redirected to addr 0x08047134 ~save fake_aout [*] Object fake_aout saved successfully ~quit [*] Unloading object 1 (rel.o) [*] Unloading object 2 (a.out) * .:: Bye -:: The ELF shell 0.65 ========= END DUMP 10 ========= Exécutons maintenant le binaire modifié. ========= BEGIN DUMP 11 ========= elfsh@WTH $ ./fake_aout First_printf test Trojan injected ET_REL takes control now [Z:First_puts:42:43:44:45:1] extcall! First_puts calling write Second_printf test Trojan injected ET_REL takes control now [Z:Second_puts:42:43:44:45:1] extcall! Second_puts calling write HOOK FUNC test ! Trojan injected ET_REL takes control now [Z:LEGIT FUNC:42:43:44:45:1] extcall! calling write legit func (test) ! elfsh@WTH $ ========= END DUMP 11 ========= Bien. legit_func a clairement été redirigée vers hook_func, et elle a bien fait attention à rappeller legit_func en utilisant la technique du vieux symbole décrite dans la première édition des articles de Cerberus. Regardons le code original de legit_func et celui redirigé en utilisant la technique CFLOW sous x86 : ========= BEGIN DUMP 12 ========= 080484C0 legit_func + 0 push %ebp 080484C1 legit_func + 1 mov %esp,%ebp 080484C3 legit_func + 3 sub $8,%esp 080484C6 legit_func + 6 mov $<_IO_stdin_used + 4>,(%esp,1) 080484CD legit_func + 13 call <.plt + 32> 080484D2 legit_func + 18 mov $<_IO_stdin_used + 15>,(%esp,1) ========= END DUMP 12 ========= Maintenant, le code modifié : ========= BEGIN DUMP 13 ========= 080484C0 legit_func + 0 jmp 080484C5 legit_func + 5 nop 080484C6 legit_func + 6 mov $<_IO_stdin_used + 4>,(%esp,1) 080484CD legit_func + 13 call 080484D2 legit_func + 18 mov $<_IO_stdin_used + 15>,(%esp,1) 080484D9 legit_func + 25 mov 8(%ebp),%eax 080484DC legit_func + 28 mov %eax,4(%esp,1) 080484E0 legit_func + 32 call 080484E5 legit_func + 37 leave 080484E6 legit_func + 38 xor %eax,%eax ========= END DUMP 13 ========= Nous créons une nouvelle section .elfsh.hooks dont la donnée est un tableau de bouts de codes de détournement comme celui-ci : ========= BEGIN DUMP 14 ========= 08042134 hook_legit_func + 0 jmp 08042139 old_legit_func + 0 push %ebp 0804213A old_legit_func + 1 mov %esp,%ebp 0804213C old_legit_func + 3 sub $8,%esp 0804213F old_legit_func + 6 jmp ========= END DUMP 14 ========= Puisque nous voulons pouvoir rappeller la fonction originale (legit_func), nous avons rajouté les octets écrasés, juste après le premier jmp, et ensuite, nous rappellons legit_func avec le bon offset (pour ne pas avoir de récursion dans le détournement puisque la fonction a été hijackée), comme vous pouvez le voir commancant au symbole old_legit_func de l'exemple 14. Cette technique du vieux symbole est cohérente avec la technique ALTPLT qui a été publiée dans le premier article. Nous pouvons tout autant utiliser l'appel old_funcname() à l'intérieur du code injecté pour rappeler la bonne fonction hijackée, et nous le faisons sans un seul octet de restauration à l'exécution. C'est pour cela que la technique CFLOW ne craint pas PaX. Pour l'architecture MIPS, la technique CFLOW est assez similaire, nous pouvons voir sont résultat aussi (dump 15 est le binaire original et dump 16 celui modifié) : ======== BEGIN DUMP 15 ========= 400400 : lui gp,0xfc1 400404 : addiu gp,gp,-21696 400408 : addu gp,gp,t9 40040c : addiu sp,sp,-40 400410 : sw ra,36(sp) [...] ======== END DUMP 15 ========= Le code de fonction modifié est maintenant : ======== BEGIN DUMP 16 ========= 400400: addi t9,t9,104 # registre T9 comme fonction cible 400404: j 0x400468 # JMP Direct vers la fonction hook 400408: nop # delay slot sous MIPS 40040c: addiu sp,sp,-40 # le code de fonction original 400410: sw ra,36(sp) 400414: sw s8,32(sp) 400418: move s8,sp 40041c: sw gp,16(sp) 400420: sw a0,40(s8) ======== END DUMP 16 ========= La fonction func2 peut être n'importe laquelle, pourvu qu'elle ai le même nombre et type de paramètres. Quand la fonction func2 veut appeler la fonction originale (func), elle saute vers le symbole old_func qui pointe à l'intérieur de l'entrée de la section .elfsh.hooks pour ce détournement CFLOW. Voici à quoi ressemble un détournement d'entrée sous architecture MIPS : ======== BEGIN DUMP 17 ========= 3ff0f4 addi t9,t9,4876 3ff0f8 lui gp,0xfc1 3ff0fc addiu gp,gp,-21696 3ff100 addu gp,gp,t9 3ff104 j 0x400408 3ff108 nop 3ff10c nop ======== END DUMP 17 =========== Comme vous pouvez le voir, les trois instructions qui ont été écrasées pour installer le détournement CFLOW au début de func() sont maintenant localisées dans l'entrée de hook, pointée par le symbole old_func. Le registre T9 est aussi remi pour qu'on puisse revenir dans une situation sûre juste avant de sauter en arrière vers func + 8. ---[ B. Technique ALTPLT revue La technique ALTPLT v1 a été présentée dans le papier "the Cerberus ELF Interface" [0]. Comme déjà dit, elle n'était pas satisfaisante puisque le détournement était retiré dès le premier appel à la fonction originale. Puisque sous SPARC, les 4 premières entrées du PLT sont réservées, il y a de la place pour 12 instructions qui feront tout ce dont on a besoin (en fait, la première entrée PLT) quand ALTPLT+0 prendra le contrôle. ALTPLTv2 fonctionne en fait avec 12 instructions, mais il y avait besoin de réencoder la première entrée ALTPLT avec le code de PLT+0 (qui est relogé à l'exécution sous SPARC avant que le main n'aie le contrôle, ce qui explique pourquoi nous ne pouvons pas le patcher statiquement sur le disque). Mais ce comportement, il casse PaX, et son implantation sont très dépendante de l'architecture à cause de sa partie en assembleur SPARC. Pour ceux qui voudrait le voir, nous avons laissé le code de tout ceci dans l'arborescence du source d'ELFsh dans libelfsh/sparc32.c . Pour l'architecture ALPHA64, il fournis presque l'équivalents dans ses instructions respectives, et cette fois, l'implémentation est dans libelfsh/alpha64.c . Comme vous pouvez le voir dans le code (que nous ne reproduiront pas pour garder l'article clair), ALTPLTv2 est un vrai merdier et nous avions besoin d'éviter tout ce code assembleur qui demandait trop d'efforts pour un futur portage de cette technique sur d'autres architectures. Alors, nous avons découvert le truc du DT_PLTREL du .dynamic et nous avons essayé de voir ce qu'il se passe quand on change cette entrée du .dynamic dans le binaire hôte. Changer l'entrée DT_PLTREL est très intéressante puisqu'elle est entièrement indépendante de l'architecture et fonctionne donc partout. Voyons donc à quoi ressemble la table d'en-tête de sections et la section .dynamic utilisée dans la technique très simple ALTPLTv3. Nous utilisons la section .elfsh.altplt comme miroir de la .plt originale comme expliqué dans notre premier papier. Les autres sections .elfsh.* ont déjà été expliquées ou le seront juste après le log. La sortie (modifiée) du binaire ressemble à ceci : =============== BEGIN DUMP 18 ================ [SECTION HEADER TABLE .::. SHT is not stripped] [Object fake_aout] [000] 0x00000000 ------- foff:00000000 sz:0000000 link:00 [001] 0x08042134 a-x---- .elfsh.hooks foff:00000308 sz:0000016 link:00 [002] 0x08043134 a-x---- .elfsh.extplt foff:00004404 sz:0000048 link:00 [003] 0x08044134 a-x---- .elfsh.altplt foff:00008500 sz:0004096 link:00 [004] 0x08045134 a--ms-- rel.o.rodata.str1.32 foff:12596 sz:4096 link:00 [005] 0x08046134 a--ms-- rel.o.rodata.str1.1 foff:16692 sz:4096 link:00 [006] 0x08047134 a-x---- rel.o.text foff:00020788 sz:0004096 link:00 [007] 0x08048134 a------ .interp foff:00024884 sz:0000019 link:00 [008] 0x08048148 a------ .note.ABI-tag foff:00024904 sz:0000032 link:00 [009] 0x08048168 a------ .hash foff:00024936 sz:0000064 link:10 [010] 0x080481A8 a------ .dynsym foff:00025000 sz:0000176 link:11 [011] 0x08048258 a------ .dynstr foff:00025176 sz:0000112 link:00 [012] 0x080482C8 a------ .gnu.version foff:00025288 sz:0000022 link:10 [013] 0x080482E0 a------ .gnu.version_r foff:00025312 sz:0000032 link:11 [014] 0x08048300 a------ .rel.dyn foff:00025344 sz:0000016 link:10 [015] 0x08048310 a------ .rel.plt foff:00025360 sz:0000056 link:10 [016] 0x08048348 a-x---- .init foff:00025416 sz:0000023 link:00 [017] 0x08048360 a-x---- .plt foff:00025440 sz:0000128 link:00 [018] 0x08048400 a-x---- .text foff:00025600 sz:0000736 link:00 [019] 0x080486E0 a-x---- .fini foff:00026336 sz:0000027 link:00 [020] 0x080486FC a------ .rodata foff:00026364 sz:0000116 link:00 [021] 0x08048770 a------ .eh_frame foff:00026480 sz:0000004 link:00 [022] 0x08049774 aw----- .ctors foff:00026484 sz:0000008 link:00 [023] 0x0804977C aw----- .dtors foff:00026492 sz:0000008 link:00 [024] 0x08049784 aw----- .jcr foff:00026500 sz:0000004 link:00 [025] 0x08049788 aw----- .dynamic foff:00026504 sz:0000200 link:11 [026] 0x08049850 aw----- .got foff:00026704 sz:0000004 link:00 [027] 0x08049854 aw----- .got.plt foff:00026708 sz:0000040 link:00 [028] 0x0804987C aw----- .data foff:00026748 sz:0000012 link:00 [029] 0x08049888 aw----- .bss foff:00026760 sz:0000008 link:00 [030] 0x08049890 aw----- rel.o.bss foff:00026768 sz:0004096 link:00 [031] 0x0804A890 aw----- rel.o.data foff:00030864 sz:0000004 link:00 [032] 0x0804A894 aw----- .elfsh.altgot foff:00030868 sz:0000048 link:00 [033] 0x0804A8E4 aw----- .elfsh.dynsym foff:00030948 sz:0000208 link:34 [034] 0x0804AA44 aw----- .elfsh.dynstr foff:00031300 sz:0000127 link:33 [035] 0x0804AB24 aw----- .elfsh.reldyn foff:00031524 sz:0000016 link:00 [036] 0x0804AB34 aw----- .elfsh.relplt foff:00031540 sz:0000072 link:00 [037] 0x00000000 ------- .comment foff:00031652 sz:0000665 link:00 [038] 0x00000000 ------- .debug_aranges foff:00032324 sz:0000120 link:00 [039] 0x00000000 ------- .debug_pubnames foff:00032444 sz:0000042 link:00 [040] 0x00000000 ------- .debug_info foff:00032486 sz:0006871 link:00 [041] 0x00000000 ------- .debug_abbrev foff:00039357 sz:0000511 link:00 [042] 0x00000000 ------- .debug_line foff:00039868 sz:0000961 link:00 [043] 0x00000000 ------- .debug_frame foff:00040832 sz:0000072 link:00 [044] 0x00000000 ---ms-- .debug_str foff:00040904 sz:0008067 link:00 [045] 0x00000000 ------- .debug_macinfo foff:00048971 sz:0029295 link:00 [046] 0x00000000 ------- .shstrtab foff:00078266 sz:0000507 link:00 [047] 0x00000000 ------- .symtab foff:00080736 sz:0002368 link:48 [048] 0x00000000 ------- .strtab foff:00083104 sz:0001785 link:47 [SHT_DYNAMIC] [Object ./testsuite/etrel_inject/etrel_original/fake_aout] [00] Name of needed library => libc.so.6 {DT_NEEDED} [01] Address of init function => 0x08048348 {DT_INIT} [02] Address of fini function => 0x080486E0 {DT_FINI} [03] Address of symbol hash table => 0x08048168 {DT_HASH} [04] Address of dynamic string table => 0x0804AA44 {DT_STRTAB} [05] Address of dynamic symbol table => 0x0804A8E4 {DT_SYMTAB} [06] Size of string table => 00000127 bytes {DT_STRSZ} [07] Size of symbol table entry => 00000016 bytes {DT_SYMENT} [08] Debugging entry (unknown) => 0x00000000 {DT_DEBUG} [09] Processor defined value => 0x0804A894 {DT_PLTGOT} [10] Size in bytes for .rel.plt => 000072 bytes {DT_PLTRELSZ} [11] Type of reloc in PLT => 00000017 {DT_PLTREL} [12] Address of .rel.plt => 0x0804AB34 {DT_JMPREL} [13] Address of .rel.got section => 0x0804AB24 {DT_REL} [14] Total size of .rel section => 00000016 bytes {DT_RELSZ} [15] Size of a REL entry => 00000008 bytes {DT_RELENT} [16] SUN needed version table => 0x80482E0 {DT_VERNEED} [17] SUN needed version number => 001 {DT_VERNEEDNUM} [18] GNU version VERSYM => 0x080482C8 {DT_VERSYM} =============== END DUMP 18 ================ Comme vous pouvez le voir, plusieurs sections ont été copiées etétendues, et leurs entrées da ns .dynamic ont été changées. Ceci s'applique pour .got (DT_PLTGOT), .rel.plt (DT_JMPREL), .dunsym (DT_SYMTAB), et .dynstr (DT_STRTAB). Changer ces entrées nous fournis une nouvelle technique ALTPLT sans aucune ligne d'assembleur. Bien sûr, la technique ALTPLT version 3 n'a besoin d'aucune informations non-obligatoire comme la section debug. Ça peut sembler évident, mais certaines personnes nous ont vraiment posé la question. ---[ C. Technique ALTGOT : Le complément RISC Sous architecture MIPS, les appels vers les entrées de PLT sont fait différement. En fait, au lieu d'une instruction call directe sur l'entrée, un jmp indirect est utilisé pour utiliser l'entrée GOT liée à la fonction désirée. Si cette entrée est remplie, alors, la fonction est appelée directement. Par défaut, les entrées GOT contiennent un pointeur vers les entrées PLT. Pendant l'exécution, par la suite, le linker dynamique est appelé pour reloge la section GOT (MIPS, x86) ou la section PLT (sous SPARC ou ALPHA). Voici un log d'assembleur MIPS qui le prouve sur un bête helloworld qui utilise printf : 00400790
: 400790: 3c1c0fc0 lui gp,0xfc0 # Met GP à base de GOT 400794: 279c78c0 addiu gp,gp,30912 # address + 0x7ff0 400798: 0399e021 addu gp,gp,t9 # utilise t9 (= main) 40079c: 27bdffe0 addiu sp,sp,-32 4007a0: afbf001c sw ra,28(sp) 4007a4: afbe0018 sw s8,24(sp) 4007a8: 03a0f021 move s8,sp 4007ac: afbc0010 sw gp,16(sp) 4007b0: 8f828018 lw v0,-32744(gp) 4007b4: 00000000 nop 4007b8: 24440a50 addiu a0,v0,2640 4007bc: 2405002a li a1,42 4007c0: 8f828018 lw v0,-32744(gp) 4007c4: 00000000 nop 4007c8: 24460a74 addiu a2,v0,2676 4007cc: 8f99803c lw t9,-32708(gp) # charge l'entrée # GOT de printf 4007d0: 00000000 nop 4007d4: 0320f809 jalr t9 # y saute 4007d8: 00000000 nop 4007dc: 8fdc0010 lw gp,16(s8) 4007e0: 00001021 move v0,zero 4007e4: 03c0e821 move sp,s8 4007e8: 8fbf001c lw ra,28(sp) 4007ec: 8fbe0018 lw s8,24(sp) 4007f0: 27bd0020 addiu sp,sp,32 4007f4: 03e00008 jr ra # retour de la fonction 4007f8: 00000000 nop 4007fc: 00000000 nop On note que le registre de pointeur global %gp contient toujours l'adresse de base de la section GOT sous MIPS, plus ou moins un certain offset fixé, dans notre cas 0x7ff0 (0x8000 sous ALPHA). Pour pouvoir appeler une fonction dont l'adresse n'est pas connue, les entrées GOT sont remplies et ensuite, le jmp indirect sous MIPS n'utilise plus l'entrée PLT. Qu'apprenons nous de ceci ? Simplement que nous ne pouvons plus nous baser sur un détournement classique du PLT puisque le code de l'entrée PLT ne sera pas appelé si l'entrée GOT est déjà remplie, ce qui veut dire que nous ne détourneront la fonction que la première fois. À cause de ceci, nous détournerons les fonction en patchant le GOT sous MIPS. Cependant, il ne résoud pas le problème de rappeller la fonction originale. Pour permetre ces rappels, nous alons juste ajouter le symbols old_ sur l'entrée PLT réelle, pour que nous puissions toujours accéder aux morceaux de code du méchanismes de liaison dynamique même si la GOT a été modifiée. Voyons les résultats détaillés de la technique ALTGOT sous architecture ALPHA et MIPS. Ceci a été fait sans aucune ligne d'assembleur, ce qui la rend très portable : ========= BEGIN DUMP 19 ========= elfsh@alpha$ cat host.c #include #include #include int main() { char *str; str = malloc(10); if (str == NULL) goto err; strcpy(str, "test"); printf("First_printf %s\n", str); fflush(stdout); puts("First_puts"); printf("Second_printf %u\n", 42); puts("Second_puts"); fflush(stdout); return (0); err: printf("Malloc problem %u\n", 42); return (-1); } elfsh@alpha$ gcc host.c -o a.out elfsh@alpha$ file ./a.out a.out: ELF 64-bit LSB executable, Alpha (unofficial), for NetBSD 2.0G, dynamically linked, not stripped ========= END DUMP 19 ========= Exécution du binaire original : ========= BEGIN DUMP 20 ========= elfsh@alpha$ ./a.out First_printf test First_puts Second_printf 42 Second_puts ========= END DUMP 20 ========== Regardons encore une fois l'objet relogeable que nous allons injecter : ========= BEGIN DUMP 21 ========= elfsh@alpha$ cat rel.c #include #include #include int glvar_testreloc = 42; int glvar_testreloc_bss; char glvar_testreloc_bss2; short glvar_testreloc_bss3; int puts_troj(char *str) { int local = 1; char *str2; str2 = malloc(10); *str2 = 'Z'; *(str2 + 1) = 0x00; glvar_testreloc_bss = 43; glvar_testreloc_bss2 = 44; glvar_testreloc_bss3 = 45; printf("Trojan injected ET_REL takes control now " "[%s:%s:%u:%u:%hhu:%hu:%u] \n", str2, str, glvar_testreloc, glvar_testreloc_bss, glvar_testreloc_bss2, glvar_testreloc_bss3, local); old_puts(str); fflush(stdout); return (0); } int func2() { return (42); } ========= END DUMP 21 ========= Comme vous pouvez le voir, l'objet relogeable rel.c utilise le symbole old_ qui signifie qu'il se base sur la technqiue ALTPLT. Cependant, nous ne faisont pas encore appel à la technique EXTPLT sous ALPHA et MIPS, nous ne pouvons donc pas encore appeler des fonctions inconnues à partir du binaire sur ces architectures. Notre rel.c est une copie de celui de l'example 7 sans l'appel aux fonction inconnues write et putchar de l'example 7. Maintenant, nous injectons le tout : ========= BEGIN DUMP 22 ========= elfsh@alpha$ ./relinject.esh > relinject.out elfsh@alpha$ ./fake_aout First_printf test Trojan injected ET_REL takes control now [Z:First_puts:42:43:44:45:1] First_puts Second_printf 42 Trojan injected ET_REL takes control now [Z:Second_puts:42:43:44:45:1] Second_puts ========= END DUMP 22 ========== La list des section sous ALPHA est la suivante. Un coup d'oeil à la section injectée est recommandé : ========= BEGIN DUMP 23 ========= elfsh@alpha$ elfsh -f fake_aout -s -p [*] Object fake_aout has been loaded (O_RDONLY) [SECTION HEADER TABLE .::. SHT is not stripped] [Object fake_aout] [000] 0x000000000 ------- foff:00000 sz:00000 [001] 0x120000190 a------ .interp foff:00400 sz:00023 [002] 0x1200001A8 a------ .note.netbsd.ident foff:00424 sz:00024 [003] 0x1200001C0 a------ .hash foff:00448 sz:00544 [004] 0x1200003E0 a------ .dynsym foff:00992 sz:00552 [005] 0x120000608 a------ .dynstr foff:01544 sz:00251 [006] 0x120000708 a------ .rela.dyn foff:01800 sz:00096 [007] 0x120000768 a------ .rela.plt foff:01896 sz:00168 [008] 0x120000820 a-x---- .init foff:02080 sz:00128 [009] 0x1200008A0 a-x---- .text foff:02208 sz:01312 [010] 0x120000DC0 a-x---- .fini foff:03520 sz:00104 [011] 0x120000E28 a------ .rodata foff:03624 sz:00162 [012] 0x120010ED0 aw----- .data foff:03792 sz:00000 [013] 0x120010ED0 a------ .eh_frame foff:03792 sz:00004 [014] 0x120010ED8 aw----- .dynamic foff:03800 sz:00352 [015] 0x120011038 aw----- .ctors foff:04152 sz:00016 [016] 0x120011048 aw----- .dtors foff:04168 sz:00016 [017] 0x120011058 aw----- .jcr foff:04184 sz:00008 [018] 0x120011060 awx---- .plt foff:04192 sz:00116 [019] 0x1200110D8 aw----- .got foff:04312 sz:00240 [020] 0x1200111C8 aw----- .sdata foff:04552 sz:00024 [021] 0x1200111E0 aw----- .sbss foff:04576 sz:00024 [022] 0x1200111F8 aw----- .bss foff:04600 sz:00056 [023] 0x120011230 a-x---- rel.o.text foff:04656 sz:00320 [024] 0x120011370 aw----- rel.o.sdata foff:04976 sz:00008 [025] 0x120011378 a--ms-- rel.o.rodata.str1.1 foff:04984 sz:00072 [026] 0x1200113C0 a-x---- .alt.plt.prolog foff:05056 sz:00048 [027] 0x1200113F0 a-x---- .alt.plt foff:05104 sz:00120 [028] 0x120011468 a------ .alt.got foff:05224 sz:00072 [029] 0x1200114B0 aw----- rel.o.got foff:05296 sz:00080 [030] 0x000000000 ------- .comment foff:05376 sz:00240 [031] 0x000000000 ------- .debug_aranges foff:05616 sz:00048 [032] 0x000000000 ------- .debug_pubnames foff:05664 sz:00027 [033] 0x000000000 ------- .debug_info foff:05691 sz:02994 [034] 0x000000000 ------- .debug_abbrev foff:08685 sz:00337 [035] 0x000000000 ------- .debug_line foff:09022 sz:00373 [036] 0x000000000 ------- .debug_frame foff:09400 sz:00048 [037] 0x000000000 ---ms-- .debug_str foff:09448 sz:01940 [038] 0x000000000 ------- .debug_macinfo foff:11388 sz:12937 [039] 0x000000000 ------- .ident foff:24325 sz:00054 [040] 0x000000000 ------- .shstrtab foff:24379 sz:00393 [041] 0x000000000 ------- .symtab foff:27527 sz:02400 [042] 0x000000000 ------- .strtab foff:29927 sz:00948 [Program header table .::. PHT] [Object fake_aout] [00] 0x120000040 -> 0x120000190 r-x => Program header table [01] 0x120000190 -> 0x1200001A7 r-- => Program interpreter [02] 0x120000000 -> 0x120000ECA r-x => Loadable segment [03] 0x120010ED0 -> 0x120011510 rwx => Loadable segment [04] 0x120010ED8 -> 0x120011038 rw- => Dynamic linking info [05] 0x1200001A8 -> 0x1200001C0 r-- => Auxiliary information [Program header table .::. SHT correlation] [Object fake_aout] [*] SHT is not stripped [00] PT_PHDR [01] PT_INTERP .interp [02] PT_LOAD .interp .note.netbsd.ident .hash .dynsym .dynstr .rela.dyn .rela.plt .init .text .fini .rodata [03] PT_LOAD .data .eh_frame .dynamic .ctors .dtors .jcr .plt .got .sdata .sbss .bss rel.o.text rel.o.sdata rel.o.rodata.str1.1 .alt.plt.prolog .alt.plt .alt.got rel.o.got [04] PT_DYNAMIC .dynamic [05] PT_NOTE .note.netbsd.ident [*] Object fake_aout unloaded ========= END DUMP 23 ========= Les segments sont étendus comme il faut. On le voit à la corrélation entre SHT et PHT : toutes les bornes sont correctes. La section .alt.plt.prolog est là pour implémenter ALTPLTv2 sous ALPHA. Ceci pourra patcher à la volée les octets de la première entrée ALTPLT avec ceux de la première entrée PLT la première fois que l'entrée ALTPLT sera appelée (quand on appelera une fonction originale à partir d'une fonction de détournement la première fois). Quand nous avons découvert comment faire ALTPLTv3 (sans une ligne d'assembleur), la .alt.plt.prolog est devenue une section de bourrage pour que GOT et ALTGOT soit bien aligné sur une certaine taille qui est nécessaire pour installer ALTPLT à cause de l'encodage des instruction ALPHA pour un saut indirect du flux de contrôle. ---[ D. Technique EXTPLT : post-liaison de fonctions inconnues Cette technique est l'une des ameliorations majeures dans la nouvelle version d'ELFsh. Elle fonctionne sur les fichiers ET_EXEC et ET_DYN, inclus quand l'injection est faite directement en mémoire. EXTPLT consiste à ajouter une nouvelle section (.elfsh.extplt) pour que nous puissions ajouter des entrées pour les nouvelles fonctions. Quand on couple l'extension par copie de .rel.plt, .got, .dynsym, et .dynstr, cela permet de placer des entrées de relocation qui correspondent aux besoin des nouveaux couples ALTPLT/ALTGOT. Regardons les informations de relocation additionnelles en utilisant la commande elfsh -r. Tout d'abord, regardons la table de relocation originale du binaire : ========= BEGIN DUMP 24 ========= [*] Object ./a.out has been loaded (O_RDONLY) [RELOCATION TABLES] [Object ./a.out] {Section .rel.dyn} [000] R_386_GLOB_DAT 0x08049850 sym[010] : __gmon_start__ [001] R_386_COPY 0x08049888 sym[004] : stdout {Section .rel.plt} [000] R_386_JMP_SLOT 0x08049860 sym[001] : fflush [001] R_386_JMP_SLOT 0x08049864 sym[002] : puts [002] R_386_JMP_SLOT 0x08049868 sym[003] : malloc [003] R_386_JMP_SLOT 0x0804986C sym[005] : __libc_start_main [004] R_386_JMP_SLOT 0x08049870 sym[006] : printf [005] R_386_JMP_SLOT 0x08049874 sym[007] : free [006] R_386_JMP_SLOT 0x08049878 sym[009] : read [*] Object ./testsuite/etrel_inject/etrel_original/a.out unloaded ========= END DUMP 24 ========= Regardons mainteant la table modifiée : ========= BEGIN DUMP 25 ========= [*] Object fake_aout has been loaded (O_RDONLY) [RELOCATION TABLES] [Object ./fake_aout] {Section .rel.dyn} [000] R_386_GLOB_DAT 0x08049850 sym[010] : __gmon_start__ [001] R_386_COPY 0x08049888 sym[004] : stdout {Section .rel.plt} [000] R_386_JMP_SLOT 0x0804A8A0 sym[001] : fflush [001] R_386_JMP_SLOT 0x0804A8A4 sym[002] : puts [002] R_386_JMP_SLOT 0x0804A8A8 sym[003] : malloc [003] R_386_JMP_SLOT 0x0804A8AC sym[005] : __libc_start_main [004] R_386_JMP_SLOT 0x0804A8B0 sym[006] : printf [005] R_386_JMP_SLOT 0x0804A8B4 sym[007] : free [006] R_386_JMP_SLOT 0x0804A8B8 sym[009] : read {Section .elfsh.reldyn} [000] R_386_GLOB_DAT 0x08049850 sym[010] : __gmon_start__ [001] R_386_COPY 0x08049888 sym[004] : stdout {Section .elfsh.relplt} [000] R_386_JMP_SLOT 0x0804A8A0 sym[001] : fflush [001] R_386_JMP_SLOT 0x0804A8A4 sym[002] : puts [002] R_386_JMP_SLOT 0x0804A8A8 sym[003] : malloc [003] R_386_JMP_SLOT 0x0804A8AC sym[005] : __libc_start_main [004] R_386_JMP_SLOT 0x0804A8B0 sym[006] : printf [005] R_386_JMP_SLOT 0x0804A8B4 sym[007] : free [006] R_386_JMP_SLOT 0x0804A8B8 sym[009] : read [007] R_386_JMP_SLOT 0x0804A8BC sym[011] : _IO_putc [008] R_386_JMP_SLOT 0x0804A8C0 sym[012] : write [*] Object fake_aout unloaded ========= END DUMP 25 ========= Comme vous le voyez, les fonctions _IO_putc (nom interne de putchar) et write ont été utilisée dans l'objet injecté. Nous avons du les insérer dans le binaire hôte pour que le binaire final puisse fonctionner. La section .elfsh.relplt est copiée à partir de la section .rel.plt mais avec une taille double pour avoir de la place pour des entrées additionnelles. Même si nous n'étendons qu'une des table de relocation, les deux doivent être copiées, parce que sur les fichier ET_DYN, le rtld reclame que les deux tables soient adjacentes en mémoire, nous ne pouvons donc pas juste copier .rel.plt mais avons aussi besoin de .rel.dyn (ou .rel.got) juste avant la copie de .rel.plt. C'est pourquoi vous pouvez voir .elfsh.reldyn et .elfsh.relplt . Quand on a besoin de plus de symboles, d'autres sections sont déplacées après le BSS, incluant .dynsym et .dynstr. ---[ E. Algorithmes compatibles IA32, SPARC32/64, ALPHA64, MIPS32 Introduisons maintenant les détails des algorithmes des techniques montrées dans les exemples des paragraphes précédents. Nous couvrons ici tous les pseudos algorithmes pour la redirection ELF. Les algorithmes détaillés sur le déboguage plus contraint sont donné à la fin de la partie suivante. Puisque les techniques ALTPLT et ALTGOT sont si complémentaires, nous les avons implémentées dans un seul algorithme que nous donnons ici. Il n'y a aucune condition sur l'architecture SPARC puisque c'est l'architecture par défaut dans la liste. L'algorithme principal de ALTPLTv3 / ALTGOT (libelfsh/altplt.c) se trouve dans elfsh_build_plt() et elfsh_relink_plt() et est comme suit. Il pourrait probablement être nettoyé si le code était remplacé par des morceaux dédiés aux architectures mais ça dupliquerait du code, nous l'avons donc laissé comme suit : Algorithme Multiarchitecture ALTPLT / ALTGOT +--------------------------------------------+ 0/ IF [ ARCH = MIPS ET PLT n'est pas trouvé AND Le fichier est dynamique ] [ - récupère l'adresse de base de la section .text - Trouve la signature d'opcode MIPS pour le PLT embarqué à l'intérieur du PLT - Modifier le SHT pour inclure l'en-tête de la section PLT ] 1/ SWITCH sur l'architecture ELF [ MIPS: * Insère la section .elfsh.gotprolog * Insère la section .elfsh.padgot ALPHA: * Insère la section.elfsh.pltprolog DEFAULT: * Insère la section.elfsh.altplt (copie du .plt) ] 2/ IF [ ARCH est (MIPS ou ALPHA ou IA32) ] [ * Insère la section .elfsh.altgot (copie du .got) ] 3/ POUR CHAQUE ENTRÉE (ALT)PLT : [ IF [ PREMIÈRE entrée PLT ] [ IF [ARCH est MIPS ] [ * Insère des paires d'instructions ld/st dans .elfsh.gotprolog pour copier les adresses de variables externes placé dans GOT par le RTLD dans la section ALTGOT. Voir le gestionnaire d'altplt MIPS dans libelfsh/mips32.c ] ELSE IF [ ARCH est IA32 ] [ * Réencode la première entrée PLT en utilisant la différence d'adresse GOT - ALTGOT (pour nous replacer dans ALTGOT au lieu de GOT) ] ] IF [ ARCH est MIPS ] * Injecte le symbole OLD sur l'entrée PLT courante ELSE * Injecte le symbole OLD sur l'entrée ALTPLT courante IF [ ARCH est ALPHA ] * Change l'entrée de relocalisation pointant vers l'entrée courante IF [ ARCH est IA32 ] * Réencode l'entrée courante du PLT et ALTPLT ] 4/ SWITCH sur l'architecture ELF [ MIPS: IA32: * Change l'entrée DT_PLTGOT de l'adresse GOT vers ALTGOT * Change les relocations GOT correspondantes SPARC: * Change l'entrée DT_PLTGOT de l'adresse PLT vers ALTPLT * Change les relocations PLT correspondantes ] Sous MIPS, il n'y a pas de table de relocation dans les binaires ET_EXEC. Si nous voulons changer la relocation qui fait référence à GOT dans le code MIPS, nous devons chercher après un pattern dans le code tel qu'on puisse le modifier avec la différence ALTGOT-GOT. Ils sont facilement trouvés puisque le patch est toujours sur le même pattern d'instruction suivant : 3c1c0000 lui gp,0x0 279c0000 addiu gp,gp,0 Le champ 0 dans ces instructions devrait être changé au moment de la liaison quand elles correspondront aux relocations MIPS HI16 et LO16. Cependant, cette information n'est pas disponible dans une table pour les binaires ET_EXEC, nous avons donc besoin de les retrouver dans le code binaire. C'est plus facilement faisable sous RISC puisque toutes les instructions font la mêem taille, les faux positifs sont donc vraiment improbables. Une fois qu'on trouve ces patterns, on les corrige avec la différence ALTGOT-GOT dans les champs de la table de relocation. Bien sur, nous ne changeront pas TOUTES les références à GOT dans le code, puisque ça ne ferait que déplacer la GOT, sans détournement. On corrige donc ces références dans les premiers 0x100 octets du .text et dans .init, .fini, ce qui concerne uniqument les références aux entrées GOT réservées (remplies par les adresses virtuelles dl-resolve et les adresses de linkmap). De cette façon, nous obligeons le code original à utiliser la section ALTGOT quand il accès aux entrées réservées (puisqu'elles ont été relocalisées à l'exécution dans ALTGOT et pas dans GOT) et les entrées GOT originales quand il accès aux entrées des fonctions (ont peux alors détourner les fonctions en modifiant la GOT). Algorithme EXTPLT +-----------------+ L'algorithme EXTPLT rentre bien dans l'algorithme précédent. Nous avons juste besoin d'ajouter deux étapes dans l'algo précédent : Étape 2bis : Insèrer la section EXTPLT (copie de PLT) sur les architectures supportées. Étape 5 : Faire une copie miroir (et étendue) de la section de liaison dynamique sur les architectures supportées. Regardons un peut plus en détail l'algorithme implémenté dans libelfsh/extplt.c. * Copie les sections .rel.got (.rel.dyn) et .rel.plt après le BSS, dans une section de taille double. Ces deux sections doivent rester adjacentes en mémoire pour que EXTPLT fonctionne aussi sur les objet ET_DYN. * Mettre à jours les entrées DT_REL et DT_JUMPREL dans .dynamic * Copier les sections .dunsym et .dynstr avec une taille double. * Mettre à jours les entrées DT_SUMTAB e tDT_STRTAB dans .dynamic Une fois que ces opérations sont faites, nous avons assez de place dans toutes ces sections orientées liaison dynamique et nous pouvons y ajouter à la demande des symboles dynamiques, des noms de symboles, et des entrées de relocalisation nécessaires pour ajouter des entrées PLT supplémentaires dans la section EXTPLT. Maintenant, chaque fois que nous rencontrons un symbole inconnu dans le processus de relocation d'un objet ET_REL dans un objet ET_EXEC ou ET_DYN, nous pouvons utiliser l'algorithme REQUESTPLT, comme implémenté dans la fonction elfsh_request_pltent() du fichier libelfsh/extplt.c file : * Vérifier la place dans les copies des sections EXTPLT, RELPLT, DYNSYM, DYNSTR, et ALTGOT. * Initialiser l'entrée ALTGOT par la nouvelle entrée EXTPLT allouée. * Encoder l'entrée EXTPLT pour utiliser l'entrée ALTGOT. * Insèrer une entrée de relocation dans .elfsh.relplt pour la nouvelle entrée ALTGOT. * Ajouter la taille de l'entrée de relocation à la valeur de l'entrée DT_PLTRELSZ dans la section .dynamic. * Insèrer le symbole manquant dans .elfsh.dynsym avec le nom inséré dans la section .elfsh.dynstr. * Ajouter la longueur du nom de symbole à la valeur de l'entrée DT_STRSZ dans la section .dynamic. Cet algorithme est appelé à partir de l'algorithme principal d'injection et de relocation ET_REL chaque fois que l'objet ET_REL utilise une fonction inconnue dont le symbole n'est pas présent dans le programme hôte. Le nouvel algorithme d'injection ET_REL est donné à la fin de la partie sur le déboguage contraint de l'article. ALgorithme CFLOW +-----------------+ Cette technique est implémentée en utilisant des morceaux dédiés aux architectures, mais l'algorithme global est le même pour toutes les architectures : - Créer la section .elfsh.hooks (une seule fois) - Trouver le nombre d'octets aligné sur la taille de l'instruction : * avec libasm sous IA32 * manuellement sur machine RISC - Insèrer les entrées HOOK à la demande (voir le dump CFLOW pour le format) - Insèrer JMP vers l'entrée hook dans le prologue de la fonction détournée. - Aligner le JUMP sur la taille d'instruction avec des nop dans le prologue détourné. - Insèrer les symboles hook_funcname et old_funcname dans l'entrée hook pour être capable de rappeler la fonction originale. Cette technique passe Pax puisqu'elle n'a besoin d'aucune étape de restauration d'octets pendant l'exécution. Nous pouvons détourner l'adresse de notre choix en utilisant la technqiue CFLOW, cependant, exécuter les octets originaux dans l'entrée hook au lieu de leur place originale ne fonctionnera pas si on place le détournement sur une instruction de branchement relatif. En fait, les branchements relatifs seront résolu vers une mauvaise adresse virtuelle nous nous exécutons leurs opcode au mauvais endroit (dasn le .elfsh.hooks au lieu de leur place originale) dans le processus. Souvenez-vous en quand vous placez des détournements CFLOW : ils ne sont pas fait pours détourner les instructions de branchements conditionnels. -------[ V. Déboguage contraint Dans les environnements actuels, les binaires endurcis sont souvent du type ET_DYN. Nous devons supporter ce type d'injection puisqu'il permet des modifications de librairies aussi puissantes que les modifications d'exécutables. De plus, certaines distributions sont fournies avec un ensemble de binaires par défaut compilé en ET_DYN, comme les gentoo endurcies. Une autre amélioration que nous voulions faire est la relocation ET_REL en mémoire. L'algorithme pour celle-ci est le même que pour l'injection sur disque, mais cette fois le disque n'est pas changé, ça réduit donc les traces comme dans [12]. On pense que ce type d'injection peut être utilisé dans les exploits et le backdooring de processus sans toucher au disque dur. Diabolique non ?! Nous sommes au courant d'une autre implémentation de l'injection ET_REL en mémoire [10]. La notre supporte plus d'architectures et se couple avec la technique EXTPLT directement en mémoire, ce qui n'a encore jamais été implémenté à notre connaissance. Une dernière technique que nous voulions implémenter concerne l'extension et le déboguage d'exécutables statiques. Nous avons développé cette nouvelle technique que nous avons appelé l'algorithme EXTSTATIC. Elle permet l'injection statique en utilisant libc.a quand une fonction ou du code est manquant. Le même algorithme que celui de l'injection ET_REL est utilisé sauf que plus d'un fichier relogeable pris de libc.a est injecté à la fois en utilisant un algorithme de dépendances récursives. ---[ A. Relocation ET_REL en mémoire Vu qu'on veut que l'utilisateur puisse fournir son propre gestionnaire de breakpoints, nous permettons un mapping direct d'un objet ET_REL en mémoire. Pour cela, nous utilisons des zones mmap supplémentaire, en faisant toujours attention à ne pas casser PaX : nous ne mappons aucune zone à la fois en exécution ET en écriture. Dans e2dbg, les breakpoints peuvent être implémentés de deux façons. Soit un opcode dépendant de l'architecture (comme 0xCC sous IA32) est utilisé sur l'adresse désirée, ou alors les primitives CFLOW/ALTPLT peuvent être utilisées à l'exécution. Dans le second cas, l'appel système mprotect doit être utilisé pour pouvoir modifier le code à l'exécution. Cependant, nous pourrons nous passer de mrprotect bientôt pour l'injection à la volée quand la technique CFLOW sera assez forte pour respecter Pax en static et à l'exécution. Jetons un coup d'oeil à un binaire qui n'utilise que printf et puts pour mieux comprendre ces concepts : ========= BEGIN DUMP 26 ========= elfsh@WTH $ ./a.out [host] main argc 1 [host] argv[0] is : ./a.out First_printf test First_puts Second_printf test Second_puts LEGIT FUNC legit func (test) ! ========= END DUMP 26 ========= Nous utilisons un petit script elfsh comme e2dbg pour créer un autre fichier avec le débogueur injecté dedans, en utilisant les techniques elfsh habituelles. Jetons-y un coup d'oeil : ========= BEGIN DUMP 27 ========= elfsh@WTH $ cat inject_e2dbg.esh #!../../vm/elfsh load a.out set 1.dynamic[08].val 0x2 # entrée pour DT_DEBUG set 1.dynamic[08].tag DT_NEEDED redir main e2dbg_run save a.out_e2dbg ========= END DUMP 27 ========= Nous exécutons ensuite le binaire modifié. ========= BEGIN DUMP 28 ========= elfsh@WTH $ ./aout_e2dbg The Embedded ELF Debugger 0.65 (32 bits built) .::. .::. This software is under the General Public License V.2 .::. Please visit http://www.gnu.org [*] Sun Jul 31 16:24:00 2005 - New object ./a.out_e2dbg loaded [*] Sun Jul 31 16:24:00 2005 - New object /lib/tls/libc.so.6 loaded [*] Sun Jul 31 16:24:00 2005 - New object ./ibc.so.6 loaded [*] Sun Jul 31 16:24:00 2005 - New object /lib/ld-linux.so.2 loaded [*] Sun Jul 31 16:24:00 2005 - New object /lib/libelfsh.so loaded [*] Sun Jul 31 16:24:00 2005 - New object /lib/libreadline.so.5 loaded [*] Sun Jul 31 16:24:00 2005 - New object /lib/libtermcap.so.2 loaded [*] Sun Jul 31 16:24:00 2005 - New object /lib/libdl.so.2 loaded [*] Sun Jul 31 16:24:00 2005 - New object /lib/libncurses.so.5 loaded (e2dbg-0.65) quit [..: Embedded ELF Debugger returns to the grave :...] [e2dbg_run] returning to 0x08045139 [host] main argc 1 [host] argv[0] is : ./a.out_e2dbg First_printf test First_puts Second_printf test Second_puts LEGIT FUNC legit func (test) ! elfsh@WTH $ ========= END DUMP 28 ========= Ok, c'était facile. Et si on voulais faire quelque chose de plus intéressant comme de l'injection ET_REL en mémoire ? Nous allons utiliser la commande profile pour découvrir la fonctionnalité d'auto-profiling [NDT : ~ auto-description] d'e2dbg. Cette commande est toujours utile pour en apprendre plus sur le débogueur lui-même, et pour des problèmes de déboguage interne qui peuvent apparaitre pendant le développement. Notre léger pattern matching d'appel de fonctions rend la sortie plus compréhensible qu'un affichage brute des informations de profiling et a prix peu d'heures pour l'implémenter en utilisant les macros ELFSH_PROFILE_{OUT,ERR,ROUT} dans libelfsh-internals.h et libelfsh/error.c. Nous allons aussi afficher la liste de linkmap. Les premiers champs de la linkmap sont indépendant de l'OS. Il y a beaucoup d'autres champs internes que nous n'avons pas affiché mais beaucoup d'informations peuvent être récoltée à partir de là. Voici le truc en action : ========= BEGIN DUMP 29 ========= elfsh@WTH $ ./a.out_e2dbg The Embedded ELF Debugger 0.65 (32 bits built) .::. .::. This software is under the General Public License V.2 .::. Please visit http://www.gnu.org [*] Sun Jul 31 16:12:48 2005 - New object ./a.out_e2dbg loaded [*] Sun Jul 31 16:12:48 2005 - New object /lib/tls/libc.so.6 loaded [*] Sun Jul 31 16:12:48 2005 - New object ./ibc.so.6 loaded [*] Sun Jul 31 16:12:48 2005 - New object /lib/ld-linux.so.2 loaded [*] Sun Jul 31 16:12:48 2005 - New object /lib/libelfsh.so loaded [*] Sun Jul 31 16:12:48 2005 - New object /lib/libreadline.so.5 loaded [*] Sun Jul 31 16:12:48 2005 - New object /lib/libtermcap.so.2 loaded [*] Sun Jul 31 16:12:48 2005 - New object /lib/libdl.so.2 loaded [*] Sun Jul 31 16:12:48 2005 - New object /lib/libncurses.so.5 loaded (e2dbg-0.65) linkmap .::. Linkmap entries .::. [01] addr : 0x00000000 dyn : 0x080497D4 - [02] addr : 0x00000000 dyn : 0xFFFFE590 - [03] addr : 0xB7E73000 dyn : 0xB7F9AD3C - /lib/tls/libc.so.6 [04] addr : 0xB7E26000 dyn : 0xB7E6F01C - ./ibc.so.6 [05] addr : 0xB7FB9000 dyn : 0xB7FCFF14 - /lib/ld-linux.so.2 [06] addr : 0xB7DF3000 dyn : 0xB7E24018 - /lib/libelfsh.so [07] addr : 0xB7DC6000 dyn : 0xB7DEE46C - /lib/libreadline.so.5 [08] addr : 0xB7DC2000 dyn : 0xB7DC5BB4 - /lib/libtermcap.so.2 [09] addr : 0xB7DBE000 dyn : 0xB7DC0EEC - /lib/libdl.so.2 [10] addr : 0xB7D7C000 dyn : 0xB7DBB1C0 - /lib/libncurses.so.5 (e2dbg-0.65) list .::. Working files .::. [001] Sun Jul 31 16:24:00 2005 D ID: 9 /lib/libncurses.so.5 [002] Sun Jul 31 16:24:00 2005 D ID: 8 /lib/libdl.so.2 [003] Sun Jul 31 16:24:00 2005 D ID: 7 /lib/libtermcap.so.2 [004] Sun Jul 31 16:24:00 2005 D ID: 6 /lib/libreadline.so.5 [005] Sun Jul 31 16:24:00 2005 D ID: 5 /lib/libelfsh.so [006] Sun Jul 31 16:24:00 2005 D ID: 4 /lib/ld-linux.so.2 [007] Sun Jul 31 16:24:00 2005 D ID: 3 ./ibc.so.6 [008] Sun Jul 31 16:24:00 2005 D ID: 2 /lib/tls/libc.so.6 [009] Sun Jul 31 16:24:00 2005 *D ID: 1 ./a.out_e2dbg .::. ELFsh modules .::. [*] No loaded module (e2dbg-0.65) source ./etrelmem.esh ~load myputs.o [*] Sun Jul 31 16:13:32 2005 - New object myputs.o loaded [!!] Loaded file is not the linkmap, switching to STATIC mode ~switch 1 [*] Switched on object 1 (./a.out_e2dbg) ~mode dynamic [*] e2dbg is now in DYNAMIC mode ~reladd 1 10 [*] ET_REL myputs.o injected succesfully in ET_EXEC ./a.out_e2dbg ~profile .:: Profiling enable + ~redir puts myputs + + + + + + [P] --[ [P] --- Last 1 function(s) recalled 1 time(s) --- + [W] Symbol not found [P] --[ [P] --[ [P] --- Last 2 function(s) recalled 12 time(s) --- + + + [P] --[ [P] --- Last 1 function(s) recalled 114 time(s) --- + + + + + + + + + + [P] --[ [P] --- Last 1 function(s) recalled 4 time(s) --- + [P] --[ [P] --- Last 1 function(s) recalled 1 time(s) --- + + [P] --[ [P] --[ [P] --[ [P] --- Last 3 function(s) recalled 1 time(s) --- + + + + + + [P] --[ [P] --- Last 1 function(s) recalled 1 time(s) --- + + [P] --[ [P] --- Last 1 function(s) recalled 1 time(s) --- + + + [W] Symbol not found [P] --[ [P] --- Last 1 function(s) recalled 114 time(s) --- + [W] Invalid NULL parameter + + + [P] --[ [P] --- Last 1 function(s) recalled 1 time(s) --- + [P] --[ [P] --[ [P] --[ [P] --[ [P] --[ [P] --- Last 5 function(s) recalled 1 time(s) --- + + + + [P] --[ [P] --[ [P] --[ [P] --- Last 3 function(s) recalled 3 time(s) --- + + [P] --[ [P] --[ [P] --[ [P] --[ [P] --[ [P] --- Last 5 function(s) recalled 44 time(s) --- + [P] --[ [P] --- Last 1 function(s) recalled 1 time(s) --- + + + [P] --[ [P] --[ [P] --[ [P] --[ [P] --- Last 4 function(s) recalled 1 time(s) --- + + + + + + + + + + + + + + + + + + + + + [*] Function puts redirected to addr 0xB7FB6000 + ~profile + .:: Profiling disable [*] ./etrelmem.esh sourcing -OK- (e2dbg-0.65) continue [..: Embedded ELF Debugger returns to the grave :...] [e2dbg_run] returning to 0x08045139 [host] main argc 1 [host] argv[0] is : ./a.out_e2dbg First_printf test Hijacked puts !!! arg = First_puts First_puts Second_printf test Hijacked puts !!! arg = Second_puts Second_puts Hijacked puts !!! arg = LEGIT FUNC LEGIT FUNC legit func (test) ! elfsh@WTH $ ========= END DUMP 29 ========= Vraiment cool. Nous avons détourner 2 fonctions (puts et legit_func) en utilisant 2 techniques différentes (ALTPLT et CFLOW). Pour ceci, nous n'avons pas eu besoin d'injecter un fichier ET_REL additionnel dans l'hôte ET_REL, mais nous avons directement injecté le module de détournement dans la mémoire en utilisant mmap. Nous aurions pu afficher la SHT et la PHT juste après l'injection ET_REL en mémoire. Nous gardons trace de tous les mappages quand nous injectons ce genre d'objet relogeable, pour pouvoir les démapper et les remapper plus tard : ========= BEGIN DUMP 30 ========= (e2dbg-0.65) s [SECTION HEADER TABLE .::. SHT is not stripped] [Object ./a.out_e2dbg] [000] 0x00000000 ------- foff:00000 size:00308 [001] 0x08045134 a-x---- .elfsh.hooks foff:00308 size:00015 [002] 0x08046134 a-x---- .elfsh.extplt foff:04404 size:00032 [003] 0x08047134 a-x---- .elfsh.altplt foff:08500 size:04096 [004] 0x08048134 a------ .interp foff:12596 size:00019 [005] 0x08048148 a------ .note.ABI-tag foff:12616 size:00032 [006] 0x08048168 a------ .hash foff:12648 size:00064 [007] 0x080481A8 a------ .dynsym foff:12712 size:00176 [008] 0x08048258 a------ .dynstr foff:12888 size:00112 [009] 0x080482C8 a------ .gnu.version foff:13000 size:00022 [010] 0x080482E0 a------ .gnu.version_r foff:13024 size:00032 [011] 0x08048300 a------ .rel.dyn foff:13056 size:00016 [012] 0x08048310 a------ .rel.plt foff:13072 size:00056 [013] 0x08048348 a-x---- .init foff:13128 size:00023 [014] 0x08048360 a-x---- .plt foff:13152 size:00128 [015] 0x08048400 a-x---- .text foff:13312 size:00800 [016] 0x08048720 a-x---- .fini foff:14112 size:00027 [017] 0x0804873C a------ .rodata foff:14140 size:00185 [018] 0x080487F8 a------ .eh_frame foff:14328 size:00004 [019] 0x080497FC aw----- .ctors foff:14332 size:00008 [020] 0x08049804 aw----- .dtors foff:14340 size:00008 [021] 0x0804980C aw----- .jcr foff:14348 size:00004 [022] 0x08049810 aw----- .dynamic foff:14352 size:00200 [023] 0x080498D8 aw----- .got foff:14552 size:00004 [024] 0x080498DC aw----- .got.plt foff:14556 size:00040 [025] 0x08049904 aw----- .data foff:14596 size:00012 [026] 0x08049910 aw----- .bss foff:14608 size:00008 [027] 0x08049918 aw----- .elfsh.altgot foff:14616 size:00044 [028] 0x08049968 aw----- .elfsh.dynsym foff:14696 size:00192 [029] 0x08049AC8 aw----- .elfsh.dynstr foff:15048 size:00122 [030] 0x08049BA8 aw----- .elfsh.reldyn foff:15272 size:00016 [031] 0x08049BB8 aw----- .elfsh.relplt foff:15288 size:00064 [032] 0x00000000 ------- .comment foff:15400 size:00665 [033] 0x00000000 ------- .debug_aranges foff:16072 size:00120 [034] 0x00000000 ------- .debug_pubnames foff:16192 size:00042 [035] 0x00000000 ------- .debug_info foff:16234 size:06904 [036] 0x00000000 ------- .debug_abbrev foff:23138 size:00503 [037] 0x00000000 ------- .debug_line foff:23641 size:00967 [038] 0x00000000 ------- .debug_frame foff:24608 size:00076 [039] 0x00000000 ---ms-- .debug_str foff:24684 size:08075 [040] 0x00000000 ------- .debug_macinfo foff:32759 size:29295 [041] 0x00000000 ------- .shstrtab foff:62054 size:00496 [042] 0x00000000 ------- .symtab foff:64473 size:02256 [043] 0x00000000 ------- .strtab foff:66729 size:01665 [044] 0x40019000 aw----- myputs.o.bss foff:68394 size:04096 [045] 0x00000000 ------- .elfsh.rpht foff:72493 size:04096 [046] 0x4001A000 a-x---- myputs.o.text foff:76589 size:04096 [047] 0x4001B000 a--ms-- myputs.o.rodata.str1.1 foff:80685 size:04096 (e2dbg-0.65) p [Program Header Table .::. PHT] [Object ./a.out_e2dbg] [00] 0x08045034 -> 0x08045134 r-x memsz(00256) filesz(00256) [01] 0x08048134 -> 0x08048147 r-- memsz(00019) filesz(00019) [02] 0x08045000 -> 0x080487FC r-x memsz(14332) filesz(14332) [03] 0x080497FC -> 0x08049C30 rw- memsz(01076) filesz(01068) [04] 0x08049810 -> 0x080498D8 rw- memsz(00200) filesz(00200) [05] 0x08048148 -> 0x08048168 r-- memsz(00032) filesz(00032) [06] 0x00000000 -> 0x00000000 rw- memsz(00000) filesz(00000) [07] 0x00000000 -> 0x00000000 --- memsz(00000) filesz(00000) [SHT correlation] [Object ./a.out_e2dbg] [*] SHT is not stripped [00] PT_PHDR [01] PT_INTERP .interp [02] PT_LOAD .elfsh.hooks .elfsh.extplt .elfsh.altplt .interp .note.ABI-tag .hash .dynsym .dynstr .gnu.version .gnu.version_r .rel.dyn .rel.plt .init .plt .text .fini .rodata .eh_frame [03] PT_LOAD .ctors .dtors .jcr .dynamic .got .got.plt .data .bss .elfsh.altgot .elfsh.dynsym .elfsh.dynstr .elfsh.reldyn .elfsh.relplt [04] PT_DYNAMIC .dynamic [05] PT_NOTE .note.ABI-tag [06] PT_GNU_STACK [07] PT_PAX_FLAGS [Runtime Program Header Table .::. RPHT] [Object ./a.out_e2dbg] [00] 0x40019000 -> 0x4001A000 rw- memsz(4096) filesz(4096) [01] 0x4001A000 -> 0x4001B000 r-x memsz(4096) filesz(4096) [02] 0x4001B000 -> 0x4001C000 r-x memsz(4096) filesz(4096) [SHT correlation] [Object ./a.out_e2dbg] [*] SHT is not stripped [00] PT_LOAD myputs.o.bss [01] PT_LOAD myputs.o.text [02] PT_LOAD myputs.o.rodata.str1.1 (e2dbg-0.65) ========= BEGIN DUMP 30 ========= Notre algorithme n'est pas vraiment optimisé puisqu'il alloue un nouveau PT_LOAD par section. Ici, nous avons créer une table RPHT (Runtime PHT) qui gère la liste de toutes les pages injectées à l'exécution. Cette table n'a pas d'existance légale dans le fichier ELF, mais elle nous évite d'étendre la PHT réelle avec des zones mémoires additionnelles. La technique ne casse pas PaX puisque toutes les zones sont allouées en utilisant les droits strictement nécessaires. Cependant, si vous voulez rediriger une fonction existante vers une fonction nouvellement ajoutée de myputs.o, vous aurez besoin de changer un peu de code à l'exécution, et donc il devient nécessaire de désactiver l'options mrprotect pour éviter de casser PaX. ---[ B. Relocation ET_REL dans ET_DYN Nous avons porté l'injection ET_REL et la technique EXTPLT pour les fichiers ET_DYN. La plus grosse différence est que les fichiers ET_DYN ont un espace d'adressage relatif sur le disque. Bien sur, les binaires strippes n'ont pas d'effet nefaste sur nos algorithmes et nous n'avons pas besoin d'informations optionnelles comme la section debug ou autre chose (ça peut sembler évident, mais des gens nous posent vraiment la question). Voyons ce qu'il se passe sur ce fichier ET_DYN hôte : ========= BEGIN DUMP 31 ========= elfsh@WTH $ file main main: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), stripped elfsh@WTH $ ./main 0x800008c8 main(argc=0xbfa238d0, argv=0xbfa2387c, envp=0xbfa23878, auxv=0xbfa23874) __guard=0xb7ef4148 ssp-all (Stack) Triggering an overflow by copying [20] of data into [10] of space main: stack smashing attack in function main() Aborted elfsh@WTH $ ./main AAAAA 0x800008c8 main(argc=0xbf898e40, argv=0xbf898dec, envp=0xbf898de8, auxv=0xbf898de4) __guard=0xb7f6a148 ssp-all (Stack) Copying [5] of data into [10] of space elfsh@WTH $ ./main AAAAAAAAAAAAAAAAAAAAAAAAAAA 0x800008c8 main(argc=0xbfd3c8e0, argv=0xbfd3c88c, envp=0xbfd3c888, auxv=0xbfd3c884) __guard=0xb7f0b148 ssp-all (Stack) Copying [27] of data into [10] of space main: stack smashing attack in function main() Aborted ========= END DUMP 31 ========= Juste pour le fun, nous avons décidé d'étudier en priorité les binaires endurcis de gentoo [11]. Ceux-ci sont fournis avec PIE (Position independant Executable) et SSP (Stack Smashing Protection) incorporés. Ça ne change pas une ligne de notre algorithme. Voici quelques tests fait sur un binaire protégé contre les débordement dans la pile avec un overflow dans le premier paramètre, déclanchant le gestionnaire de débordement de pile. Nous allons rediriger ce gestionnaire pour montrer qu'il s'agit d'une fonction habituelle qui utilise les méchanismes PLT classiques. Voici le code que nous allons injecter : ========= BEGIN DUMP 32 ========= elfsh@WTH $ cat simple.c #include #include #include int fake_main(int argc, char **argv) { old_printf("I am the main function, I have %d argc and my " "argv is %08X yupeelala \n", argc, argv); write(1, "fake_main is calling write ! \n", 30); old_main(argc, argv); return (0); } char* fake_strcpy(char *dst, char *src) { printf("The fucker wants to copy %s at address %08X \n", src, dst); return ((char *) old_strcpy(dst, src)); } void fake_stack_smash_handler(char func[], int damaged) { static int i = 0; printf("calling printf from stack smashing handler %u\n", i++); if (i>3) old___stack_smash_handler(func, damaged); else printf("Same player play again [damaged = %08X] \n", damaged); printf("A second (%d) printf from the handler \n", 2); } int fake_libc_start_main(void *one, void *two, void *three, void *four, void *five, void *six, void *seven) { static int i = 0; old_printf("fake_libc_start_main \n"); printf("start_main has been run %u \n", i++); return (old___libc_start_main(one, two, three, four, five, six, seven)); } ========= END DUMP 32 ========= Le script elfsh qui permet les modifications est le suivant : ========= BEGIN DUMP 33 ========= elfsh@WTH $ cat relinject.esh #!../../../vm/elfsh load main load simple.o reladd 1 2 redir main fake_main redir __stack_smash_handler fake_stack_smash_handler redir __libc_start_main fake_libc_start_main redir strcpy fake_strcpy save fake_main quit ========= END DUMP 33 ========= Regardons le en actions ! ========= BEGIN DUMP 34 ========= elfsh@WTH $ ./relinject.esh The ELF shell 0.65 (32 bits built) .::. .::. This software is under the General Public License V.2 .::. Please visit http://www.gnu.org ~load main [*] Sun Jul 31 17:24:20 2005 - New object main loaded ~load simple.o [*] Sun Jul 31 17:24:20 2005 - New object simple.o loaded ~reladd 1 2 [*] ET_REL simple.o injected succesfully in ET_DYN main ~redir main fake_main [*] Function main redirected to addr 0x00005154 ~redir __stack_smash_handler fake_stack_smash_handler [*] Function __stack_smash_handler redirected to addr 0x00005203 ~redir __libc_start_main fake_libc_start_main [*] Function __libc_start_main redirected to addr 0x00005281 ~redir strcpy fake_strcpy [*] Function strcpy redirected to addr 0x000051BD ~save fake_main [*] Object fake_main saved successfully ~quit [*] Unloading object 1 (simple.o) [*] Unloading object 2 (main) * .:: Bye -:: The ELF shell 0.65 ========= END DUMP 34 ========= Et quel est le résultat ? ========= BEGIN DUMP 35 ========= elfsh@WTH $ ./fake_main fake_libc_start_main start_main has been run 0 I am the main function, I have 1 argc and my argv is BF9A6F54 yupeelala fake_main is calling write ! 0x800068c8 main(argc=0xbf9a6e80, argv=0xbf9a6e2c, envp=0xbf9a6e28, auxv=0xbf9a6e24) __guard=0xb7f78148 ssp-all (Stack) Triggering an overflow by copying [20] of data into [10] of space The fucker wants to copy 01234567890123456789 at address BF9A6E50 calling printf from stack smashing handler 0 Same player play again [damaged = 39383736] A second (2) printf from the handler elfsh@WTH $ ./fake_main AAAA fake_libc_start_main start_main has been run 0 I am the main function, I have 2 argc and my argv is BF83A164 yupeelala fake_main is calling write ! 0x800068c8 main(argc=0xbf83a090, argv=0xbf83a03c, envp=0xbf83a038, auxv=0xbf83a034) __guard=0xb7f09148 ssp-all (Stack) Copying [4] of data into [10] of space The fucker wants to copy AAAA at address BF83A060 elfsh@WTH $ ./fake_main AAAAAAAAAAAAAAA fake_libc_start_main start_main has been run 0 I am the main function, I have 2 argc and my argv is BF8C7F24 yupeelala fake_main is calling write ! 0x800068c8 main(argc=0xbf8c7e50, argv=0xbf8c7dfc, envp=0xbf8c7df8, auxv=0xbf8c7df4) __guard=0xb7f97148 ssp-all (Stack) Copying [15] of data into [10] of space The fucker wants to copy AAAAAAAAAAAAAAA at address BF8C7E20 ========= END DUMP 35 ========= Aucun problème ici : nous detournons strcpy, main, libc_start_main et __stack_smash_handler vers nos propres procédures comme le montre la sortie. Nous appelons aussi write qui n'était pas disponible dans le binaire original, ce qui montre que EXTPLT fonctionne sur les objets ET_DYN, la bonne chose étant que ça marche sans modifications. Dans la version courante (0.65rc1), il y a cependant une limitation sur ET_DYN. Nous devons éviter les variables non-initialisées parce que ça pourrait ajouter des entrées dans la table de relocation. Ce n'est pas un problème d'en ajouter puisque nous copions aussi .rel.got (rel.dyn) dans EXTPLT sous ET_DYN, mais ce n'est pas encore implémenté. ---[ C. Extentions d'exécutables statics Maintenant, nous aimerions pouvoir déboguer des binaires statics de la même manière que pour les dynamiques. Puisque nous ne pouvons pas injecter e2dbg en utilisant les dépendances DT_NEEDED sur les binaires statiques, l'idée est d'injecter e2dbg comme ET_REL dans ET_EXEC puisque c'est possible sur les binaires statiques. E2dbg a beaucoup plus de dépendances qu'un simple programme rel.c. L'idée étendue est d'injecter la partie manquante des librairies statiques quand c'est nécessaire. Nous devons résoudre les dépendances à la volée pendant que l'injection ET_REL est faite. Pour cela, nous utiliserons un simple algorithme récursif sur le code relogeable existant : quand un symbole n'est pas trouvé au moment du relogeage, soit c'est un symbole old_* et c'est mis en attente de la deuxième étape de relogeage (en fait, les symboles old_* apparaissent au moment de la redirection, qui est fait après l'injection du fichier ET_REL, nous ignorons donc ces symboles dans un premier temps), ou le symbole de fonction est vraiment inconnu et nous devons ajouter des informations pour que le rtld puisse le résoudre quand même. Pour permettre de trouver le ET_REL adéquat à injecter, ELFsh extrait tous les objets ET_REL à partir d'une librairie statique (.a) et ensuite, la résolution est faite en utilisant cet ensemble de binaires. La fonctionnalité de workspace d'elfsh est assez utile pour ça, quand des sessions sont faites sur plus d'un milier de fichier ELF ET_EXEC en même temps (après extraction des modules de libc.a et d'autres binaires statique, par exemple). Les dépendances circulaires sont résolues en utilisant une deuxième étape de relocalisation quand le symbole requis se trouve dans un fichier qui sera injecté après le fichier courant. La même seconde phase du mechanisme de relocation est utilisée quand on doit reloger les objets ET_REL qui utilisent les symboles old_*. Puisque les symboles OLD sont injectés au moment de la redirection et que les fichiers ET_REL doivent être injectés avant (pour que nous puissions utiliser les fonctions de l'objet ET_REL comme fonctions de détournement), nous n'avons pas de symboles OLD au moment de la relocation. La seconde phase de relocation est alors déclanchée au moment de la sauvegarde (pour des modifications sur le disque) ou récursivement résolu quand on injecte plusieurs ET_REL avec des dépendances circulaires. Un problème reste, jusqu'a présent, nous avions un PT_LOAD par section injectée, nous atteignons vite plus de 500 PT_LOAD. Ceci semble un peu trop pour un ficheir ELF static habituel. Nous devons améliorer le méchanisme d'allocation PT_LOAD pour pouvoir ajouter de plus grosses extensions à ce genre de binaire hôte. Cette technique fournis les mêmes fonctionnalités que EXTPLT mais pour les binaires statiques : nous pouvons injecter ce que nous voulons (sans se soucier de ce que le fichier hôte contient). Donc, voici un plus petit exemple fonctionnel : ========= BEGIN DUMP 36 ========= elfsh@WTH $ cat host.c #include #include #include int legit_func(char *str) { puts("legit func !"); return (0); } int main() { char *str; char buff[BUFSIZ]; read(0, buff, BUFSIZ-1); puts("First_puts"); puts("Second_puts"); fflush(stdout); legit_func("test"); return (0); } elfsh@WTH $ file a.out a.out: ELF 32-bit LSB executable, Intel 80386, statically linked, not stripped elfsh@WTH $ ./a.out First_puts Second_puts legit func ! ========= END DUMP 36 ========= Le code du fichier source injecté est le suivant : ========= BEGIN DUMP 37 ========= elfsh@WTH $ cat rel2.c #include #include #include #include #include int glvar_testreloc = 42; int glvar_testreloc_bss; char glvar_testreloc_bss2; short glvar_testreloc_bss3; int hook_func(char *str) { int sd; printf("hook func %s !\n", str); return (old_legit_func(str)); } int puts_troj(char *str) { int local = 1; char *str2; int fd; char name[16]; void *a; str2 = malloc(10); *str2 = 'Z'; *(str2 + 1) = 0x00; glvar_testreloc_bss = 43; glvar_testreloc_bss2 = 44; glvar_testreloc_bss3 = 45; memset(name, 0, 16); printf("Trojan injected ET_REL takes control now " "[%s:%s:%u:%u:%hhu:%hu:%u] \n", str2, str, glvar_testreloc, glvar_testreloc_bss, glvar_testreloc_bss2, glvar_testreloc_bss3, local); free(str2); gethostname(name, 15); printf("hostname : %s\n", name); printf("printf called from puts_troj [%s] \n", str); fd = open("/etc/services", 0, O_RDONLY); if (fd) { if ((a = mmap(0, 100, PROT_READ, MAP_PRIVATE, fd, 0)) == (void *) -1) { perror("mmap"); close(fd); printf("mmap failed : fd: %d\n", fd); return (-1); } printf("-=-=-=-=-=- BEGIN /etc/services %d -=-=-=-=-=\n", fd); printf("host : %.60s\n", (char *) a); printf("-=-=-=-=-=- END /etc/services %d -=-=-=-=-=\n", fd); printf("mmap succeed fd : %d\n", fd); close(fd); } old_puts(str); fflush(stdout); return (0); } ========= END DUMP 37 ========= Le script load_lib.esh, généré en utilisant un petit script bash, ressemble à ça : ========= BEGIN DUMP 38 ========= elfsh@WTH $ head -n 10 load_lib.esh #!../../../vm/elfsh load libc/init-first.o load libc/libc-start.o load libc/sysdep.o load libc/version.o load libc/check_fds.o load libc/libc-tls.o load libc/elf-init.o load libc/dso_handle.o load libc/errno.o ========= END DUMP 38 ========= Voici le script ELFsh de l'injection : ========= BEGIN DUMP 39 ========= elfsh@WTH $ cat relinject.esh #!../../../vm/elfsh exec gcc -g3 -static host.c exec gcc -g3 -static rel2.c -c load a.out load rel2.o source ./load_lib.esh reladd 1 2 redir puts puts_troj redir legit_func hook_func save fake_aout quit ========= END DUMP 39 ========= Sortie épurée de l'injection : ========= BEGIN DUMP 40 ========= elfsh@WTH $ ./relinject.esh The ELF shell 0.65 (32 bits built) .::. .::. This software is under the General Public License V.2 .::. Please visit http://www.gnu.org ~exec gcc -g3 -static host.c [*] Command executed successfully ~exec gcc -g3 -static rel2.c -c [*] Command executed successfully ~load a.out [*] Sun Jul 31 16:37:32 2005 - New object a.out loaded ~load rel2.o [*] Sun Jul 31 16:37:32 2005 - New object rel2.o loaded ~source ./load_lib.esh ~load libc/init-first.o [*] Sun Jul 31 16:37:33 2005 - New object libc/init-first.o loaded ~load libc/libc-start.o [*] Sun Jul 31 16:37:33 2005 - New object libc/libc-start.o loaded ~load libc/sysdep.o [*] Sun Jul 31 16:37:33 2005 - New object libc/sysdep.o loaded ~load libc/version.o [*] Sun Jul 31 16:37:33 2005 - New object libc/version.o loaded [[... 1414 files later ...]] [*] ./load_lib.esh sourcing -OK- ~reladd 1 2 [*] ET_REL rel2.o injected succesfully in ET_EXEC a.out ~redir puts puts_troj [*] Function puts redirected to addr 0x080B7026 ~redir legit_func hook_func [*] Function legit_func redirected to addr 0x080B7000 ~save fake_aout [*] Object fake_aout saved successfully ~quit [*] Unloading object 1 (libpthreadnonshared/pthread_atfork.oS) [*] Unloading object 2 (libpthread/ptcleanup.o) [*] Unloading object 3 (libpthread/pthread_atfork.o) [*] Unloading object 4 (libpthread/old_pthread_atfork.o) [[... 1416 files later ...]] .:: Bye -:: The ELF shell 0.65 ========= END DUMP 40 ========= Cela fonctionne-t-il ? ========= BEGIN DUMP 41 ========= elfsh@WTH $ ./fake_aout Trojan injected ET_REL takes control now [Z:First_puts:42:43:44:45:1] hostname : WTH printf called from puts_troj [First_puts] -=-=-=-=-=- BEGIN /etc/services 3 -=-=-=-=-= host : # /etc/services # # Network services, Internet style # # Not -=-=-=-=-=- END /etc/services 3 -=-=-=-=-= mmap succeed fd : 3 First_puts Trojan injected ET_REL takes control now [Z:Second_puts:42:43:44:45:1] hostname : WTH printf called from puts_troj [Second_puts] -=-=-=-=-=- BEGIN /etc/services 3 -=-=-=-=-= host : # /etc/services # # Network services, Internet style # # Not -=-=-=-=-=- END /etc/services 3 -=-=-=-=-= mmap succeed fd : 3 Second_puts hook func test ! Trojan injected ET_REL takes control now [Z:legit func !:42:43:44:45:1] hostname : WTH printf called from puts_troj [legit func !] -=-=-=-=-=- BEGIN /etc/services 3 -=-=-=-=-= host : # /etc/services # # Network services, Internet style # # Not -=-=-=-=-=- END /etc/services 3 -=-=-=-=-= mmap succeed fd : 3 legit func ! ========= END DUMP 41 ========= Oui, ça fonctionne. Maintenant, jetons un coup d'oeil au fichier static fake_aout : ========= BEGIN DUMP 42 ========= elfsh@WTH $ ../../../vm/elfsh -f ./fake_aout -s [*] Object ./fake_aout has been loaded (O_RDONLY) [SECTION HEADER TABLE .::. SHT is not stripped] [Object ./fake_aout] [000] 0x00000000 ------- foff:000000 sz:00000 [001] 0x080480D4 a------ .note.ABI-tag foff:069844 sz:00032 [002] 0x08048100 a-x---- .init foff:069888 sz:00023 [003] 0x08048120 a-x---- .text foff:69920 sz:347364 [004] 0x0809CE10 a-x---- __libc_freeres_fn foff:417296 sz:02222 [005] 0x0809D6C0 a-x---- .fini foff:419520 sz:00029 [006] 0x0809D6E0 a------ .rodata foff:419552 sz:88238 [007] 0x080B2F90 a------ __libc_atexit foff:507792 sz:00004 [008] 0x080B2F94 a------ __libc_subfreeres foff:507796 sz:00036 [009] 0x080B2FB8 a------ .eh_frame foff:507832 sz:03556 [010] 0x080B4000 aw----- .ctors foff:512000 sz:00012 [011] 0x080B400C aw----- .dtors foff:512012 sz:00012 [012] 0x080B4018 aw----- .jcr foff:512024 sz:00004 [013] 0x080B401C aw----- .data.rel.ro foff:512028 sz:00044 [014] 0x080B4048 aw----- .got foff:512072 sz:00004 [015] 0x080B404C aw----- .got.plt foff:512076 sz:00012 [016] 0x080B4060 aw----- .data foff:512096 sz:03284 [017] 0x080B4D40 aw----- .bss foff:515380 sz:04736 [018] 0x080B5FC0 aw----- __libc_freeres_ptrs foff:520116 sz:00024 [019] 0x080B6000 aw----- rel2.o.bss foff:520192 sz:04096 [020] 0x080B7000 a-x---- rel2.o.text foff:524288 sz:04096 [021] 0x080B8000 aw----- rel2.o.data foff:528384 sz:00004 [022] 0x080B9000 a------ rel2.o.rodata foff:532480 sz:04096 [023] 0x080BA000 a-x---- .elfsh.hooks foff:536576 sz:00032 [024] 0x080BB000 aw----- libc/printf.o.bss foff:540672 sz:04096 [025] 0x080BC000 a-x---- libc/printf.o.text foff:544768 sz:04096 [026] 0x080BD000 aw----- libc/gethostname.o.bss foff:548864 sz:04096 [027] 0x080BE000 a-x---- libc/gethostname.o.text foff:552960 sz:04096 [028] 0x080BF000 aw----- libc/perror.o.bss foff:557056 sz:04096 [029] 0x080C0000 a-x---- libc/perror.o.text foff:561152 sz:04096 [030] 0x080C1000 a--ms-- libc/perror.o.rodata.str1.1 foff:565248 sz:04096 [031] 0x080C2000 a--ms-- libc/perror.o.rodata.str4.4 foff:569344 sz:04096 [032] 0x080C3000 aw----- libc/dup.o.bss foff:573440 sz:04096 [033] 0x080C4000 a-x---- libc/dup.o.text foff:577536 sz:04096 [034] 0x080C5000 aw----- libc/iofdopen.o.bss foff:581632 sz:04096 [035] 0x00000000 ------- .comment foff:585680 sz:20400 [036] 0x080C6000 a-x---- libc/iofdopen.o.text foff:585728 sz:04096 [037] 0x00000000 ------- .debug_aranges foff:606084 sz:00136 [038] 0x00000000 ------- .debug_pubnames foff:606220 sz:00042 [039] 0x00000000 ------- .debug_info foff:606262 sz:01600 [040] 0x00000000 ------- .debug_abbrev foff:607862 sz:00298 [041] 0x00000000 ------- .debug_line foff:608160 sz:00965 [042] 0x00000000 ------- .debug_frame foff:609128 sz:00068 [043] 0x00000000 ------- .debug_str foff:609196 sz:00022 [044] 0x00000000 ------- .debug_macinfo foff:609218 sz:28414 [045] 0x00000000 ------- .shstrtab foff:637632 sz:00632 [046] 0x00000000 ------- .symtab foff:640187 sz:30192 [047] 0x00000000 ------- .strtab foff:670379 sz:25442 [*] Object ./fake_aout unloaded elfsh@WTH $ ../../../vm/elfsh -f ./fake_aout -p [*] Object ./fake_aout has been loaded (O_RDONLY) [Program Header Table .::. PHT] [Object ./fake_aout] [00] 0x8037000 -> 0x80B3D9C r-x memsz(511388) foff(000000) =>Loadable seg [01] 0x80B4000 -> 0x80B7258 rw- memsz(012888) foff(512000) =>Loadable seg [02] 0x80480D4 -> 0x80480F4 r-- memsz(000032) foff(069844) =>Aux. info. [03] 0x0000000 -> 0x0000000 rw- memsz(000000) foff(000000) =>Stackflags [04] 0x0000000 -> 0x0000000 --- memsz(000000) foff(000000) =>New PaXflags [05] 0x80B6000 -> 0x80B7000 rwx memsz(004096) foff(520192) =>Loadable seg [06] 0x80B7000 -> 0x80B8000 rwx memsz(004096) foff(524288) =>Loadable seg [07] 0x80B8000 -> 0x80B8004 rwx memsz(000004) foff(528384) =>Loadable seg [08] 0x80B9000 -> 0x80BA000 rwx memsz(004096) foff(532480) =>Loadable seg [09] 0x80BA000 -> 0x80BB000 rwx memsz(004096) foff(536576) =>Loadable seg [10] 0x80BB000 -> 0x80BC000 rwx memsz(004096) foff(540672) =>Loadable seg [11] 0x80BC000 -> 0x80BD000 rwx memsz(004096) foff(544768) =>Loadable seg [12] 0x80BD000 -> 0x80BE000 rwx memsz(004096) foff(548864) =>Loadable seg [13] 0x80BE000 -> 0x80BF000 rwx memsz(004096) foff(552960) =>Loadable seg [14] 0x80BF000 -> 0x80C0000 rwx memsz(004096) foff(557056) =>Loadable seg [15] 0x80C0000 -> 0x80C1000 rwx memsz(004096) foff(561152) =>Loadable seg [16] 0x80C1000 -> 0x80C2000 rwx memsz(004096) foff(565248) =>Loadable seg [17] 0x80C2000 -> 0x80C3000 rwx memsz(004096) foff(569344) =>Loadable seg [18] 0x80C3000 -> 0x80C4000 rwx memsz(004096) foff(573440) =>Loadable seg [19] 0x80C4000 -> 0x80C5000 rwx memsz(004096) foff(577536) =>Loadable seg [20] 0x80C5000 -> 0x80C6000 rwx memsz(004096) foff(581632) =>Loadable seg [21] 0x80C6000 -> 0x80C7000 rwx memsz(004096) foff(585728) =>Loadable seg [SHT correlation] [Object ./fake_aout] [*] SHT is not stripped [00] PT_LOAD .note.ABI-tag .init .text __libc_freeres_fn .fini .rodata __libc_atexit __libc_subfreeres .eh_frame [01] PT_LOAD .ctors .dtors .jcr .data.rel.ro .got .got.plt .data .bss __libc_freeres_ptrs [02] PT_NOTE .note.ABI-tag [03] PT_GNU_STACK [04] PT_PAX_FLAGS [05] PT_LOAD rel2.o.bss [06] PT_LOAD rel2.o.text [07] PT_LOAD rel2.o.data [08] PT_LOAD rel2.o.rodata [09] PT_LOAD .elfsh.hooks [10] PT_LOAD libc/printf.o.bss [11] PT_LOAD libc/printf.o.text [12] PT_LOAD libc/gethostname.o.bss [13] PT_LOAD libc/gethostname.o.text [14] PT_LOAD libc/perror.o.bss [15] PT_LOAD libc/perror.o.text [16] PT_LOAD libc/perror.o.rodata.str1.1 [17] PT_LOAD libc/perror.o.rodata.str4.4 [18] PT_LOAD libc/dup.o.bss [19] PT_LOAD libc/dup.o.text [20] PT_LOAD libc/iofdopen.o.bss |.comment [21] PT_LOAD libc/iofdopen.o.text [*] Object ./fake_aout unloaded ========= END DUMP 42 ========= On peut noter le ET_REL réellement injecté : printf.o@libc, dup.o@libc, gethostname.o@libc, perror.o@libc et iofdopen.o@libc. Chaque fichier injecté crée quelques segments PT_LOAD. Pour cet exemple, c'est bon, mais pour injecter E2dbg, c'est vraiment trop. Cette techniuque sera améliorée aussi vite que possible en réutilisant les entrées PT_LOAD quand c'est possible. ----[ D. Algorithmes indépendants de l'architecture Dans cette partie, nous donnons tous les algorithmes indépendants des architectures que nous avons développés pour la nouvelle technique de résidence en mémoire, librairies ET_DYN, ou exécutables statiques. Le nouvel algorithme générique d'injection ET_REL n'est pas si différent de celui présenté dans l'article précédant dans l'article Cerberus ELF Interface [0], c'est pourquoi nous ne montrons encore que sa version courte. Cependant, le nouvel algorithme a amélioré sa portabilité et modularité. Nous détaillerons certaines parties qui n'ont pas été expliquée dans les articles précédents. L'implémentation est principalement dans elfsh_inject_etrel() du fichier relinject.c : Nouvel Algorithme de relocalisation générique +---------------------------------------------+ 1/ Injecter le BSS du ET_REL après le BSS du programme hote dans une nouvelle section dédiée 2/ FOREACH section dans l'objet ET_REL [ IF [ Section est mappee et Section n'est pas BSS ] [ - Injecter la section dans le fichier hôte ou la mémoire ] ] 3/ Fusionner les tables de symboles du ET_REL et du fichier hôte 4/ Reloger l'objet ET_REL (Phase 1) 5/ Quand on sauvegarde, reloger l'objet ET_REL (Phase 2 pour les symboles old) Nous n'avions qu'une seule phase de relocalisation dans le passé. Nous devons maintenant en avoir une deuxième puisque tous les symboles requis ne sont pas disponibles (comme les symboles old issus de la redirections CFLOW qui peut apparaitre après l'injection ET_REL). Pour les modifications sur le disque, la deuxième phase est faite au moment de la sauvegarde. Certaines étapes dans cet algorithme sont assez évidentes, comme les étapes 1 et 3. Elles ont été expliquées dans le premier article [0], cependant l'algorithme BSS a changé par compatibilité avec les fichiers ET_DYN et les injections ET_REL multiples. Maintenant, le BSS est injecté comme une autre section, au lieu d'ajouter un algorithme complexe de zone BSS pour garder toujours un BSS dans le programme. Algorithme d'injection de sections ET_DYN / ET_EXEC +---------------------------------------------------+ L'algorithme d'injection pour les sections de DONNÉES ne change pas entre les fichiers ET_EXEC et ET_DYN. Cependant, l'injection de section de code change un peu pour supporter à la fois les hôte sous forme de binaire et de librairie. Voici le nouvel algorithme pour cette opération : * Trouver le PT_LOAD exécutable * Corriger la taille des sections injectées par congruence de taille des pages IF [ Hostfile est ET_EXEC ] [ * Mettre la vaddr de la section injectée à la vaddr la plus basse de la section mappée * Soustraire la nouvelle taille de section à la nouvelle adresse virtuelle de la section ] ELSE IF [ Hostfile est ET_DYN ] [ * Mettre la vaddr de la section injectée à la vadr la plus basse de la section mappée ] * Étendre la taille du segment de code par la taille de la section injectée. IF [ Hostfile est ET_EXEC ] [ * Soustraire la vadr de la section injectée à la vaddr du PT_LOAD de l'exécutable ] FOREACH [ Entry dans PHT ] [ IF [ Segment est PT_PHDR et Hostfile est ET_EXEC ] [ * Soustraire la taille de la section injectée au segment p_vaddr / p_paddr ] ELSE IF [ Segment est après le PT_LOAD étendu ] [ * Ajouter la taille de la section injectée au segment p_offset IF [ Hostfile est ET_DYN ] [ * Ajouter la taille de la section injectée au segment p_vaddr et p_paddr ] ] ] IF [ Hostfile est ET_DYN ] [ FOREACH [ entrée de relocation dans chaque table de relocation ] [ IF [ offset de relocation pointe après la section injectée ] [ * Changer l'offset de relocation à partir de la taille de la section injectée ] ] En utilisant la taille de la section injectée : * Changer les symboles quand ils pointent après elle * Changer les symboles dynamiques (même condition) * Changer les entrées dynamiques du D_PTR * Changer les entrées GOT * Si elles existent, changer les entrées ALTGOT * Changer DTORS et CTORS * Changer le point d'entrée de l'en-tête ELF ] * Injecter le nouveau symbole SECTION dans le code injecté Algorithme d'injection statique de section ET_EXEC +--------------------------------------------------+ Cet algorithme est utilisé pour insérer des sections dans les binaires statiques. On le trouve dans libelfsh/inject.c, dans la fonction elfsh_insert_static_section() : * Changer la taille de la section injectée pour rester congru à la taille de page. * Créer un nouvel en-tête de programme PT_LOAD dont les bornes correspondent à celles de la nouvelle section * Insèrer la nouvelle sections en utilisant les algorithmes classiques * Insèrer le nouvel en-tête de programme dans la PHT Algorithme d'injection de section à l'exécution en mémoire +----------------------------------------------------------+ Cet algorithme se trouve dans libelfsh/inject.c, dans la fonction elfsh_insert_runtime_section() : * Créer un nouvel en-tête de programme PT_LOAD * Insèrer une entrée SHT pour la nouvelle section à l'exécution (pour garder une table statique à jours) * Insèrer la nouvelle section en utilisant les algorithmes classiques * Insèrer le nouveau PT_LOAD dans la table runtime PHT (RPHT) avec les mêmes bornes. La Runtime PHT est une nouvelle table que nous avons introduite pour nous permettre de séparer les segments habituellements mappés par l'éditeur de lien dynamique (le segment PHT original) des segments injectés à l'exécution. Cette manière nous conduira dans le furut à un algorithme plus facile pour la reconstruction du binaire à partir de son image mémoire. Nous allons maintenant détailler l'algorithme principal (à haut niveau) de relocation comme il est implémenté dans les fonctions elfsh_relocate_object() et elfsh_relocate_etrel_section() du fichier libelfsh/relinject.c . Ce code est commun pour tous les types de fichier hôtes et pour toutes les étapes de relocalisation. Il est utilisé à l'étape 4 de l'algorithme général : Algorithme portable principal de relocation +-----------------------------------------------+ Cet algorithme n'a pas encore été expliqué dans aucun article, le voici : FOREACH section ET_REL injectée dans le fichier hôte [ FOREACH entrée de relocation dans le fichier ET_REL [ * trouver les symboles nécessaires dans ET_REL pour cette relocalisation IF [ Symbol est COMMON ou NOTYPE ] [ * Trouver le symbole correspondant le fichier hôte IF [ Symbol est NOT FOUND ] [ IF [ symbol est OLD et RELOCSTAGE == 1 ] [ * Retarder sa relocalisation ] ELSE [ IF [ le type du symbole ET_REL est NOTYPE ] [ * Demander une nouvelle entrée PLT et utilise son adresse pour faire la relocation (Algorithme EXTPLT) ] ELSE IF [ Fichier hôte est STATIC ] [ * Utiliser la technique EXTSTATIC (Algorithme suivant) ] ELSE [ * L'algorithme a échoué, retourne ERROR ] ] ] ELSE [ * Utilise la valeur du symbole du fichier hôte ] ] ELSE [ * Utilise l'adresse de base de la section injectée comme valeur du symbole ] - Entrée de relocalisation (switch/case entre les gestionnaires dédiés aux architectures) ] ] Extension EXTSTATIC de l'algorithme de relocalisation +-----------------------------------------------------+ Dans ce cas, le fichier hôte est statique, nous pouvons essayer de récupérer les symboles inconnus à partir des fichiers relogeables des librairies statiques qui sont disponibles sur le disque. Un exemple d'utilisation de la technique EXTSTATIC se trouve dans le répertoire testsuite/etrel_inject/. Voici l'algorithme EXTSTATIC qui se trouve à l'endroit spécifié dans l'algorithme précédent pour fournir les mêmes fonctionnalités que EXTPLT mais pour les binaires statics : FOREACH objet ET_REL chargé dans ELFSH [ IF [ Symbol est trouvé n'importe où dans l'ET_REL analysé pour l'instant ] [ IF [ Le symbole trouvé est plus fort que le résultat courant ] [ * Met à jours le meilleurs symbole et le fichier ET_REL associé ] ELSE [ * Ignore le résultat de l'itération courante ] ] ] * Injècter les dépendances ET_REL dans le fichier hôte * Utiliser le nouveau symbole injecté dans le fichier hôte comme symbole de relocation dans l'algorithme principal de relocation. Algorithme du symbole le plus fort +----------------------------------+ Quand nous devons choisir entre plusieurs symboles qui ont le même nom dans différents objets (autant pendant les injections statiques qu'à l'exécution), nous utilisons ce simple algorithme pour déterminer lequel utiliser : IF [ Le symbole choisi pour l'instant a STT_NOTYPE ] [ * Le symbole est un choix temporaire ] ELSE IF [ Le symbole candidat a STT_NOTYPE ] [ * Le symbole est un choix temporaire ] ELSE IF [ le champ bind du symbole candidat > symbole courant ] [ * Le symbole candidat devient le choix definitif ] -------[ VI. Passé et présent Dans le passé, nous avons montré que l'injection ET_REL dans les objets ET_EXEC non-relogeable était possible. Ce papier vous a montré des extentions multiples et des portages de cette technique de résidence (pour des cibles ET_DYN et des exécutables statiques). Couplée à la technique EXTPLT qui permet une post liaison complète du fichier hôte, nous pouvons ajouter des définitions de fonctions et utiliser des fonctions inconnues dans le logiciel étendu. Toutes ces techniques d'injections statiques marchent quand toutes les options de PaX sont activées sur le binaire modifié. Bien sûr, les fonctionnalités de position indépendante, et de protection de débordement de pile de Gentoo ne protègent rien quand c'est appliqué à de la manipulation de binaire, qu'elle ai lieu sur le disque où à l'exécution. Nous avons aussi montré qu'il est possible de débogué sans utiliser l'appel système ptrace, ce qui ouvre une porte pour le reverse engineering et les méthodes de déboguage embarquées qui contournent des techniques anti-débogue connues. Le débogueur embarqué n'est pas complètement Pax-Proof et il est encore nécessaire de désactiver le flag mrprotect. Même si ça ne semble pas un réel problème, nous continuons de chercher une méthode pour placer des breakpoints (e.g. des redirections) sans le désactiver. Nos techniques principales sont portables à beaucoup d'architectures (x86, alpha, mips, sparc) à la fois sur des fichiers 32bits et 64bits. Cependant, notre débogueur "preuve de concept" n'a été implémenté que pour x86. Nous pensons que nos techniques sont suffisament portable pour pouvoir fournir un débogueur pour d'autres architectures sans trop de problèmes. Distribuez et appréciez cette suite, les contributions sont les bienvenues. -------[ VII. Remerciements Nous remercions tous les gens à WhatTheHack party 2005 aux Pays-Bas. Nous nous sommes tant amusés avec vous, et nous reviendrons la prochaine fois. Des remerciements spéciaux à andrewg pour nous avoir expliquer la technique sigaction, dvorak pour son intéret dans l'optimisation de la technique ALTPLTv2 pour SPARC, sk pour libasm, et solar pour nous avoir fournis les tests pour ET_DYN pie/ssp. Nos respects à Devhell Labs, l'équipe PaX, le Phrackstaff, GOBBLES, MMHS, ADM et Synnergy Networks. Un clin d'oeil final à s/ash de RTC pour nous avoir conduit à WTH et le Coconut Crew pour tout le reste, vous vous reconnaitrez. -------[ VIII. Références [0] The Cerberus ELF Interface mayhem http://www.phrack.org/show.php?p=61&a=8 [1] The GNU debugger GNU project http://www.gnu.org/software/gdb/ [2] PaX / grsecurity The PaX team http://pax.grsecurity.net/ [3] binary reconstruction from a core image Silvio Cesare http://vx.netlux.org/lib/vsc03.html [4] Antiforensic evolution: Self Ripe & Pluf http://www.phrack.org/show.php?p=63&a=11 [5] Next-Gen. Runtime binary encryption Zeljko Vbra http://www.phrack.org/show.php?p=63&a=13 [6] Fenris Michal Zalewski http://lcamtuf.coredump.cx/fenris/ [7] Ltrace Ltrace team http://freshmeat.net/projects/ltrace/ [8] The dude (replacement to ptrace) Mammon http://www.eccentrix.com/members/mammon/Text/d\ ude_paper.txt [9] Binary protection schemes Andrewg http://www.codebreakers-journal.com/viewar\ ticle.php?id=51&layout=abstract [10] ET_REL injection in memory JP http://www.whatever.org.ar/~cuco/MERCANO.TXT [11] Hardened Gentoo project Hardened team http://www.gentoo.org/proj/en/hardened/ [12] Unpacking by Code Injection Eduardo Labir http://www.codebreakers-journal.com/viewart\ icle.php?id=36&layout=abstract