==Phrack Inc.== Volume 0x0b, Issue 0x3c, Phile #0x09 of 0x10 |=-------------------=[ Big Loop Integer Protection]=------------------=| |=----------------------------------------------------------------------------=| |=-----------=[ Oded Horovitz ohorovitz@entercept.com ]=----------=| --[ Table des matieres 1 - Introduction 2 - Partie I - Problemes de nombres entiers 2.1 - Introduction 2.2 Exemples de codes basiques 3 - Partie II - Modele d'exploitation 3.1 - Une entree, deux interpretations 3.2 - Quelle est la nature de l'entree ? 3.3 - Detection suggeree 4 - Partie III - Implementation 4.1 - Introduction 4.2 - Pourquoi gcc ? 4.3 - Quelques mots a propos de gcc 4.3.1 - Flux de compilation 4.3.2 - Le AST 4.3.3 - Demarrons 4.4 - Buts du patch 4.5 - Passage en revue du patch 4.5.1 - Tactique 4.5.2 - Modifions le AST 4.6 - Limites 5 - References 6 - Remerciements 7 - Annexe A - Des exemples dans la vraie vie 7.1 - Encodage de Apache Chunked 7.2 - Authentification OpenSSH 8 - Annexe B - Utilisons blip --[ 1 - Introduction Les failes de debordement de nombres entiers et de signe de nombres entiers sont aujourd'hui connues de tous/toutes. Ceci a mene a une exploitation avancee des failles du meme genre. L'article tentera de suggerer un moyen de detecter ces failles en ajoutant un support au compilateur qui detecte et signale les exploitations de failles integer. Un patch pour gcc est specialement presente pour demontrer la faisabilite de cette technique. L'article est divise en trois parties. La premiere contient une breve introduction a plusieurs des failles communes basees sur des integers. Nous listons plusieurs des failles publiques recentes. La deuxieme partie de l'article tente d'expliquer la cause premiere du probleme des failles integer. En utilisant de vrais exemples, l'article explique en premier lieu pourquoi l'exploitation est possible, et comment il serait possible de detecter l'exploitation de failles intger, meme quand la faille n'est pas connue d'avance. La troisieme partie passe en revue l'implementation du plan de detection suggere. Comme la forme de cette implementation de ce plan de detection suggere est un patch de gcc, des informations d'introduction a propos du fonctionnement interne de gcc sont donnees. Nous resummons l'article en demontrant la protection en action contre OpenSSH et les paquetages htppd de Apache --[ 2 - Partie I - Problemes de nombres entiers ----[ 2.1 - Introduction Au cours de l'annee derniere l'attention semble s'etre tournee vers une nouvelle mauvaise pratique de programmation. Cette pratique a a voir avec la possibilite de deborder des nombres entiers ce qui cause des debordements de tampons. Il se trouve que les tres populaires et (soit-disant) tres securises paquetages de logiciels (OpenSSH, Apache, *BSD kernel) partagent cette faille. La cause premiere de cette mauvaise pratique est l'insuffisante verification d'entree pour les entrees de type integer. L'entree de nombres entiers semble tellement naive, seulement longue de quelques bits. Qu'est ce qui peut aller mal ici ? Et bien, il semble que beaucoup de choses peuvent aller mal. Ce qui suit est un tableau des failles liees a des nombres entiers prises des listes de securite de OpenBSD et FreeBSD. Toutes les failles ont ete rapportees pendant l'annee 2002. -------------------------------------------------------------------------- | Paquetage vulnerable | Courte description de la faille | -------------------------------------------------------------------------- | OpenBSD select syscall | Le test de la limite positive contre les | | (See reference [4]) | valeurs entieres permet un debordement de pile| -------------------------------------------------------------------------- | RPC xdr_array | Un integer overflow cause une allocation de | | | petit tampon qui est overflow-e par l'entree | -------------------------------------------------------------------------- | OpenSSH Authentication | Un integer overflow cause une allocation de | | | petit tampon qui est overflow-e par l'entree | -------------------------------------------------------------------------- | Apache chunked-encoding| La condition positive faite sur les entiers | | | signes permet un heap overflow | -------------------------------------------------------------------------- | | La condition positive faite sur les entiers | | FreeBSD get_pallette | signes permet une fuite d'informations | | | de l'espace noyau vers l'espace utilisateur | -------------------------------------------------------------------------- | FreeBSD accept1,getsoc-| La condition positive faite sur les entiers | | kname1,getpeername1 | signes permet une fuite d'informations | | | de l'espace noyau vers l'espace utilisateur | -------------------------------------------------------------------------- Table 1 - Exemple de failles integer dans l'annee 2002. Le probleme commun qui existe dans toutes les failles ci-dessus est qu'une entree de type integer (signe ou non-signe) etait utilisee pour declencher l'overflow (en ecriture) ou une fuite d'information (en lecture) vers/en provenance des tampons du programme. Toutes les failles ci-dessus auraient ete evitees si des limites propres avaient ete mises en vigueur. Les failles integer peuvent etre illustree plus en avant en regardant les deux simples exemples de code suivants : Example 1 (int overflow): ------------------------- 01 int main(int argc,char* argv[]){ 02 unsigned int len,i; 03 char *buf; 04 if(argc != 3) return -1; 05 len=atoi(argv[1]); 06 buf = (char*)malloc(len+1); 07 if(!buf){ 08 printf("Allocation echouee\n"); 09 return -1; 10 } 11 for(i=0; i < len; i++){ 12 buf[i] = _toupper(argv[2][i]); 13 } 14 buf[i]=0; 15 printf("%s\n",buf); 16 } Le code ci-dessus semble tout a fait regulier. Le programe convertit une chaine de caracteres en sa representation en majuscules. D'abord il alloue assez d'espace pour la chaine et le caractere de terminaison NULL. Ensuite il convertit chaque caractere en sa valeur en majuscule. Mais quand on y regarde d'un peu plus pres, nous pouvons identifier deux problemes majeurs dans le code. D'une part le programe fait confiance a l'utilisateur pour avoir autant de caracteres qu'il le specifie (ce qui n'est evidemment pas le ca) (ligne 5). D'autre part le programe ne tient pas compte du fait qu'en calculant l'espace a allouer, un integer overflow pourrait se produire (ligne 6). En essayant de generaliser le probleme, le premier bug peut permettre a l'attaquant de lire l'information qu'il ne fournit pas (en faisant confiance a l'entree de l'utilisateur et en lisant les caracteres *len* en provenance de argv[2]). Le second bug permet a l'attaquant de faire un heap overflow avec ses propres donnees, et par consequent de compromettre totalement le programe. Example 2 (outrepassage des tests de signe ): ------------------------------ 01 #define BUF_SIZE 10 02 int max = BUF_SIZE; 03 int main(int argc,char* argv[]){ 04 int len; 05 char buf[BUF_SIZE]; 06 if(argc != 3) return -1; 07 len=atoi(argv[1]); 08 if(len < max){ 09 memcpy(buf,argv[2],len); 10 printf("Donnees copiees\n"); 11 } 12 else 13 printf("Trop de donnees\n"); 14 } Le second exemple montre un programe qui a l'intention de resoudre le probleme presente dans le premier exemple, en tentant de mettre en vigueur une longueur d'entree utilisateur a une valeur maximale connue et predefinie. Le probleme dans ce code est que len est definit comme un entier signe. Dans ce cas uns tres grosse valeur (non-signee bien entendu) est interpretee comme une valeur negative (ligne 8), qui outrepassera le est de limite. Encore, a la ligne 9 la meme valeur est interpretee comme un nombre positif non-signe causant uun debordement de tampon et permettant une possible compromission totale. --[ 3 - Partie II - Modele d'exploitation ----[ 3.1 - Une entree, deux interpretations Alors quel est le vrai probleme ? Comment de tels packetages orientes securite ont ces failles ? La reponse est que les entrees d'integer ont parfois une interpretation ambigue a differantes parties du code (les entiers peut changer de signe a differantes valeurs, type cast implicite, integer overflows). Cette interpretation ambigue est difficile a remarquer en implementant du code de validation d'entree. Pour expliquer cette ambiguite laissez-nous regarder le premier exemple. Au moment de l'allocation (ligne 6), le code croit que comme l'entree est un nombre, alors ajouter un autre nombre produira un gros nombre (len+1). Mais comme le langage C habituel ignore les les integer overflows que la nombre particulier 0xffffffff ne s'applique pas a cet essai et produira un reultat innattendu. Malheureusement, la meme erreur nest *PAS* repetee plus tars dans le code. Par consequent la meme entree 0xffffffff interpretee cette fois comme une valeur non-signee (un enorme nombre positif). Dans le second exemple l'ambiguite de l'entree est encore plus evidente. Le code inclut un casting de type silencieux genere par le compilateur quand il appelle memcpy. Par consequent le code teste la valeur de l'entree comme si c'etait un nombre signe (ligne 8) pendant qu'il l'utilise pour copier des donnees comme si c'etait non-signe (ligne 9). Cette ambiguite est invisible pour l'oeil du coder, et peut ne pas etre detecte, lassant le code vulnerable a cette attaque "furtive". ----[ 3.2 - Quelle est la nature de l'entree? En regardant en arriere les exemples ci-dessus revelent unn sens commun pour l'entree de l'attaquant. (Desole si les quelques lignes a venir expliquent l'evidence :>) L'entree du dessus est un nombre pour une raison. C'est un compteur ! Cela compte des items ! Pas d'importance ce que sont ces "items" (octets, caracteres, objets, fichiers, etc.). Ce sont de tranquilles quantites comptables d'items. Et que pouvez-vous faire avec un compteur ? Eh bien, vous etes probablement en train de faire quelques comptes. En remarque je dirais que *tout* nombre n'est pas aussi un compteur. Il y a beaucoup d'autres raisons d'avoir des nombres autour. Mais le seul qui ait quelque chose a voir avec les failles integer sont la plupart du temps des "compteurs". Par exemple, si le compte est pour deviner les reponses vous voudriez peut-etre lire "compte" la somme des reponses (OpenSSH). Ou si le compte est une longueur de tampon vous voudriez peut-etre copiez "compte" la somme d'octets de d'un endroit de la memoire vers l'autre (Apache httpd). La ligne du bas est que quelque part derriere ce nombre il y a la "boucle" propre dans le code qui fera des processus, et elle les fera "compte" fois. Cette "boucle" pourrait avoir de multiples formes comme la boucle for dans le premier exemple, ou comme une boucle implicite dans memcpy. Toutes ces sortes de boucles se terminerons apres le "compte". ----[ 3.3 - Detection suggeree OK, qu'avons-nous a faire a propos de ces failles ? - L'entree etait utilisee de maniere ambigue dans le code. - Quelque par dans le code il y a une boucle qui utilise l'entree de nombre entier comme un compteur d'iteration. Pour l'interpretation du nombre ambigu, l'attaquant doit envoyer un enorme nombre. En regardant le premier exemple nous voyons que pour faire que le nombre soit ambigu l'attaquant avait besoin d'envoyer un tel nombre que si on fait (len+1) le nombre va deborder. Pour que cela arrive l'attaquant devra envoyer la valeur 0xffffffff. En regardant le second exemple, pour l'interpretation du nombre ambigu, l'attaquant a besoin d'envoyer un tel nombre qu'il va tomber dans le rang negative d'un entier 0x80000000-0xffffffff. Le meme nombre enorme envoye par l'attaquant pour declencher la faille est plus tard utilise dans une boucle comme le compteur d'iterations (comme decrit dans la section "Quelle est la nature de l'entree ?"). A present analysons le processus de l'exploit : 1. L'attaquant veut deborder un tampon. 2. L'attaquant pourrait utiliser une faille integer. 3. L'attaquant envoie un enorme entier pour declencher la faille. 4. Le compte de la boucle s'execute (probablement) en utilisant l'entree de l'attaquant comme borne de la boucle. 5. Un tampon est deborde (aux premieres iterations de la boucle !) Par consequent detecter (et prevenir) l'exploitation d'une faille integer est possible en validant les bornes de la boucle avant son execution. La validation de la boucle testera si la limite de boucle n'est pas au dessus d'un seuil predefini, et si la limite est pus haute que le seuil un handler special sera declenche pour s'occuper de la possible exploitation. Comme la valeur requise pour declencher la plupart les failles integer est enorme, nous pouvons supposer (esperer) que la plupart des boucles legitimes ne declencherons pas cette protection. Pour avoir une idee de quelles valeurs nous nous attendons a voir dans les failles integer, examinons les exemples suivants : - Allocation d'un tampon pour les donnees de l'utilisateur et les donnees du programme Ca ressemble a : buf = malloc(len + sizeof(header)); Dans ce cas la valeur requise pour declencher un integer verflow est tres pres de 0xffffffff car les tailles des structures de la plupart des programmes sont de l'ordre de plusieurs octets a des centaines d'octets au plus. - Allocation de tableau Ca ressemble a : buf = malloc(len * sizeof(object)); Dans ce cas la valeur requise pour declencher l'overflow pourrait etre bien plus petite que dans le premier exemple mais c'est toujours une valeur relativement grande. Par exemple si sizeof(object) == 4 alors la valeur devrait etre plus grande que 0x40000000 (un giga). De meme si sizeof(object)== 64 alors la valeur devrait etre plus grande que 0x4000000 (64 megas) pour causer un overflow. - Tomber dans l'ordre negatif Dans ce cas la valeur requise pour faire un nombre negatif est n'importe quel nombre plus grand que 0x7fffffff. En regardant les valeurs requises pour declencher la faille integer, nous pouvons choisir un seuil comme 0x40000000 (un giga) qui va prendre en main la plupart des cas. Ou nous pouvons choisir un seuil plus petit pour une meilleure protection, qui pourrait declencher des faux-positifs. --[ 4 - Partie III - Implementation ----[ 4.1 - Introduction Une fois que nous avons un moyen de detecter les attaques integer, ce sera joli d'implementer un systeme base sur cette idee. Un candidat possible l'implementation de se systeme est d'etendre un compilateur existant. Comme le compilateur est au courant de toutes les boucles dans l'application, il sera possible pour le compilateur d'ajouter les tests de securite appropries avant tout "comptage de boucle". Faire comme ceci securisera l'application sans aucune connaissance de la faille specifique. Par consequent j'ai choisi d'implementer ce systeme comme un patch pour gcc et de le nommer "Big Loop Integer Protection" alias blip. Utiliser le flag --fblip pourrait etre capable de proteger cette application du prochain integer exploit a etre public. ----[ 4.2 - Pourquoi gcc ? Coisir gcc n'etait pas une decision difficile. D'abord ce compilateur est l'un des compilateurs les plus communs dans le monde GNU/Linux et *nix. Par consequent, patcher gcc permettra de proteger toutes les applications compilees avec gcc. Deuxiemement, gcc est open-source et donc il serait faisable d'y implementer ce patch en premier lieu. Troisiemement, de precedants patchs de securite furent implementes comme des patches pour gcc (Stackguard, ProPolice). Donc pourquoi pas suivre leur sagesse ? ---- [ 4.2 - Quelques mots a propos de gcc Eh bien..., tout joyeux je pose que je suis sur le point de faire un patch pour gcc pour eviter les attaques integer. Mais, sauf cela, que sais-je reellement a propos de gcc ? Je dois admettre que la reponse a cette question etait - "pas grand chose". Pour depasser ce petit probleme, j'ai cherche de la documentation a propos du fonctionnement interne de gcc. J'ai egalement espere trouver quelque chose de similaire a ce que je voulais faire, qui existait deja. Assez rapidement, il devint clair qu'avant de sauter sur d'autres exemples, je devait comprendre la bete gcc. .. Deux semaines plus tard, j'avais lu suffisamment de la documentation interne de gcc et j'avais passe suffisamment de temps en sessiosn de debugging du compilteur pour etre capable de commencer a modifier le code. Malgre tout avant de commencer a aller dans les details je voudrais fournir quelques generalites sur comment fonctionne gcc, que j'espere le lecteur / la lectrice trouvera utile. ------[ 4.3.1 - Flux de compilation. Le compilateur gcc est reellement une machine amusante. Les intentions de gcc incluent la capacite a supporter de multiples langages de programmation, qui peuvent plus tard etre compiles sur de multiples plates-formes et sets d'instructions. Pour atteindre un tel but, le compilateur utilise plusieurs niveaux d'abstraction./ En premier lieu, un fichier de langage est traite (parse) par un "Front End" de langage. A chaque fois que vous invoquez le compilateur gcc, il decidera lequel des "Front End" disponibles est bon pour parser les fichier entree, et executera ce "Front End". Le "Front End" va parser le fichier entree en entier et le convertira (en utilisant des fonctions globales assistantes) en un "Arbre d'abstraction de syntaxe" (AST [ : "Abstract Syntax Tree"]). En faisant comme cela le compilateur fait que le langage de programmation d'origine devient transparant pour le le "Back End" de gcc. Le AST, comme son nom l'indique, est une structure de donnees, qui reside en memoire et qui peut representer toutes les fonctionalites de tous les langages de programmation que gcc supporte. A chaque fois que le "Front End" finit de parser une fonction complete, et la convertit en sa representation AST, une fonction de gcc nommee rest_of_compilation est appellee. Cette fonction prend le AST sorti du parser et "l'etend" en un "Langage de Transfer de Registre" (["Register Transfer Language"] : RTL). Le RTL, qui est la version "etendue" du AST, est ensuite traite encore et encore au travers de differantes phases de compilation. [NDT : et oui, RTL, comme quoi gcc, c'est pour les grosses tetes :] Pour avoir une idee de ce qui est fait sur l'arbre RTL, voici une liste des differantes phases : - Obtimisation des jump - CSE (Common sub-expression elimination : Elimination des sous-expression communes) - Analyse du flux de donnees - Combinaison des instructions - Plan d'execution des intructions - Re-ordonnancement des blocs de base - Raccourcissement des branches - Finale (generation du code) J'ai choisi seulement quelques phases parmi la longue liste des phases pour demontrer le travail effectue sur le RTL. La liste complete est nettement plus longue et peut etre trouvee dans les docs sur le fonctionnement interne de gcc (voir la sections "Demarrons" pour les liens vers les docs). Le truc sympa a propos du RTL est que toutes ces phases sont effectuees independamment de la machine cible. La derniere phase qui est effectuee sur l'arbre RTL sera la phase "finale". A ce point la representation est prete a etre substituee a d'actuelles instructions assembleur qui ont a voir avec l'architecture specifique. Cette phase est possible grace au fait que gcc maintient une definition d'abstraction des "modes machine". Un set de fichier qui peut decrire chaque hardware des machines supportees et un set d'intruction dans un sens qui rend possible de traduire le RTL en code approprie a la machine. ------[ 4.3.3 - Le AST Je me focaliserai sur le AST, auquel je vais me referer comme "l'ARBRE". Cet ARBRE est la sortie du parsing du front end d'un fichier langage. L'ARBRE contient toute l'information existant dans le fichier source qui est requise pour la generation du code (i.e. declarations, fonctions, types,...). En plus le TREE inclut egalement plusieurs des attributs et des transformations implicites que le compilateur pourrait choisir d'effectuer (i.e. conversion de type, variables auto,...). Comprendre l'ARBRE est critique pour creer ce patch. Par chance, l'ARBRE est bien structure est meme si sa programmation-comme-orientee-objet-en-utilisant-le-C [NDT : y'en a j'vous jure ...] est accablante au debut, apres un peu de sessions de debugging, tout commence a se mettre en place. Le noyau de structure de donnee de l'ARBRE est le tree_node (defini dans tree.h). Cette structure est actuellement une grosse union qui peut representer n'importe quel morceau d'information. La maniere dont cela fonctionne est que tout noeud de l'arbre a son code, qui est accessible en faisant "TREE_CODE (tree node)". En utilisant ce code le compilateur peut savoir lesquels des champs de l'union sont appropries pour ce noeud (i.e. Un nombre constant aura le TREE_CODE() == INTEGER_CST, par consequent le node->int_cst est sur le point d'etre le membre de l'union qui aura l'information valide.). En remarque, je dirai qu'il n'y a aucun besoin d'acceder a n'importe quel champ de structure de noeud de l'arbre directement. Pour chaque champ dans cette structure il y a une macro dediee qui uniformise l'acces a ce champ. Dans la plupart des cas cette macro contiendra des verifications additionelles du noeud, et peut-etre meme de la logique a executer a chaque fois qu'un acces a ce champ est effectue (i.e. DECL_RTL qui est responsable d'extraire la representation RTL d'un noeud d'un arbre, appellera make_decl() si aucune expression RTL n'existe poiur ce noeud). Donc nous savons a propos de l'ARBRE et des noeuds, et nous savons que chaque noeud peut representer des choses differantes, qu'est-t-il important de connaitre a propos des noeuds d'un arbre ? Eh bien, une chose est la maniere dont les noeuds d'un arbre sont lies a tous les autres. Je vais tenter de donner quelques exemples de scenarios qui representent la plupart des cas ou un noeud est relie a un autre. Reference I : Chaines : Une chaine est une relation qui peut etre mieux decrite comme une liste. Lorsque le compilateur a besoin de maintenir une liste de noeuds *qui n'ont aucune information a propos des liens*, il utilisera simplement le champ chaine du noeud (accessible par la macro TREE_CHAIN() ). Un exemple pour un tel cas est la liste des declarations de noeuds dans le corps d'une fonction. Pouir chaque declaration dans une liste CMOPOUND_STMT il y a une declaration de chaine qui represente la declaration suivante dans le code. Reference II : Listes : A chaque fois qu'une chaine simple n'est pas suffisante, le compilateur utilisera un noeud special de TREE_LIST. TREE_LIST permet au compilateur de sauvegarder des informations attachees a chaque objet dans la liste. Pour ce faire chaque objet dans la liste est represente par trois noeuds. Le premier noeud aura le code TREE_LIST. Ce noeud aura le pointeur TREE_CHAIN pointant sur le prochain noeud de la liste. Il aura le pointeur TREE_VALUE pointant sur l'objet du noeud actuel, et il aura egalement le pointeur TREE_PURPOSE qui pourrait pointer sur un autre noeud qui tient des informations supplementaires a propos de la signification de cet objet dans la liste. Par exemple le noeud du code CALL_EXPR aura un TREE_LIST comme seconde operande. Cette liste representera les parametres envoyes a la fonction appelee. Reference III : Reference directe : Plusieurs des champs du noeud d'un arbre sont eux meme des noeuds d'un arbre. Cela pourrait etre sujet a confusion au premier coup d'oeuil, mais ce sera clair assez prochainement. Quelques exemples communs sont : - TREE_TYPE ce champ represent le type d'un noeud. Par exemple chaque noeud avec une expression de code doit avoir un type. - DECL_NAME a chaque fois que des noeud de declaration ont un nom, il n'existera pas en tant que chaine de caractere pointee directement par le noeud de declaration. Au lieu d'utiliser DECL_NAME on peut obtenir l'acces a un autre noeud de code IDENTIFIER_NODE. Le second aura l'information demandee. - TREE_OPERAND() L'une des commandes les plus utilisees. A chaque fois qu'il y a un noeud qui a un nombre defini de noeud "fils", le tableau TREE_OPERAND sera utilise (i.e. le noeud de code IF_STMT aura TREE_OPERAND(t,0) comme un noeud COND_EXPR, TREE_OPERAND(t,1) comme un noeud de declaration THEN_CLAUSE, et TREE_OPERAND(t,2) comme noeud de declaration ELSE_CLAUSE.) Reference IV - Vecteurs : La derniere et la moins commune est le noeud vecteur. Ce conteneur, qui est accessible en utilisant les macros TREE_VEC_XXX, est utilise pour maintenir des vecteurs de tailles qui peuvent varier. Il y a beaucoup plus a savoir a propos des noeuds de l'arbre AST pour lesquels les documents internes de gcc auront peut-etre de meilleures et plus completes explications. Je vais donc stopper ici ma revue de AST en suggerant de lire les docs. En addition au conte des codes d'abstraction dans le AST. Il y a plusieurs structures globales, qui sont tres utilisees par le compilateur. Je tenterai d'en nommer quelques-unes de ces structures globales que je trouve tres utiles a verifier au cours des sessions de debugging. - current_stmt_tree : fournit le dernier stmt ajoute a l'arbre, le type de la derniere expression, et le nom de fichier de l'expression. - current/global_binding_level : fournit des informations de lien, comme les noms definis dans un niveau de lien particulier, et les pointeurs de blocs. - lineno : variable contenant le numero de la ligne qui est parsee a ce moment - input_filename : nom du fichier qui est parse a ce moment ------[ 4.3.3 - Demarrons Si vous voulez experimenter le AST par vous-meme, ou creuser dans les details du patch, il est recommende de lire cette sections. Vous n'aurez aucun probleme a passer a la section suivante si vous ne voulez pas faire ceci. La premiere de toutes les choses, prenez le code source du compilateur. La version que j'ai utilise comme base pour ce patch est gcc 3.2. Pour de l'information a propos du telechargement et de la construction du compilateur regardez s'il vous plait http://gcc.gnu.org/install/ . (Rappellez-vous s'il vous plait de specifier la version du compialteur que vous voulez telecharger. La version par defaut pourrait etre la derniere sortie, qui n'a pas ete teste avec ce patch). La chose suivante que vous voudriez peut-etre faire est de vous asseoir et de lire attentivement les documentations internes de gcc. (Pour la cause de ce patch, vous devriez vous devriez etre familier avec la section 9 de ce document.) Le document est situe a http://gcc.gnu.org/onlinedocs/gccint/ Supposant que vous lisez le document et que vous voulez allez au niveau suivant, je recommande d'avoir un set de programmes simple pour etre utilise comme fichier de langage du compilateur, le debugger de votre choix, et de commencer a debugger le compilateur. Quelques bons points d'arret que vous pourriez trouver utiles : - add_stmt : appele a chaque fois que le parse decide d'ajouter une nouvelle declaration dans le AST. Ce point pourrait etre tres pratique quand le comment un noeud specifique est cree n'est pas aussi clair. En breakant sur add_stmt et en verifiant la pile d'appel, il est facile de trouver de plus interessants endroits ou creuser. - rest_of_compiliation : appele a chaque fois qu'une fonction est completement convertie en une representation AST. Si vous etes interesse a regarder comment le AST devient RTL c'est un bon endroit pour debuter. - expand_stmt : appele a chaque fois qu'une declaration est sur le point d'etre etendue en code RTL. Mette un point d'arret ici vous permettra d'investiguer facilement dans la structure d'un noeud AST sans le besoin d'aller au travers de niveaux niches interminables. Comme le compilateur gcc arretera d'appeler le compilateur cc1 pour les fichiers *.c, vous voudriez peut-etre debugger cc1 dans un premier temps, et sauver vous-meme le trouble de faire suivre a votre debugger le processus fils de gcc. Assez rapidement vous aurez besoin de quelques references pour toutes les petites macros utilisee par la construction de l'arbre AST. Pour cela je recommende de se familiariser avec les fichiers suivants : gcc3.2/gcc/gcc/tree.h gcc3.2/gcc/gcc/tree.def ----[ 4.4 - Buts du patch Comme pour chaque projet dans votre vie, vous devez definir les buts du projets. D'abord vous saurez mieux si vous avez atteint vos buts. Deuxiemement, qui n'est pas moins important, comme les ressources sont limitees, il est plus simple de vous proteger d'un projet sans fin. Les buts de ce patch etaient avant tout d'etre une preuve de concept pour l'expose suggere de prevention des exploits integer. Ce n'est par consequent *pas un but de resoudre tous les problemes actuels et futurs dans le monde de la securite, ou meme de resoudre tous les exploits qui sont lies a une entree d'integer. Le second but de cette implementation est de garder le patch simple. Comme le patch n'est seulement qu'une preuve de concept, nous avons prefere de conserver les choses simples et d'eviter les solutions fantaisistes si elles requierent du code plus complexe. Last but not least, le troisieme but est de rendre ce patch utilisable. Cela signifie simple a utiliser, intuitif et capable de proteger les paquetages du monde reel plus gros que 30 lignes de code :) . ----[ 4.5 - Passage en revue du patch Le patch introduira un nouveau flag au compilateur gcc nomme "blip". En compilant un fichier en utilisant le flag -fblip, le compilateur genere du code qui verifira les conditions "blip" pour toutes les bocles for/while et pour tous les appels a des fonctions pareilles a des boucles. Une fonction pareille a une boucle est n'importe quelle fonction qui est un synonyme pour une boucle (i.e. mecpy, bcopy, memset, etc.). La verification generee evaluera si une boucle est sur le point de s'executer un "enorme" nombre de fois (determine par LIBP_MAX). A chaque fois qu'une boucle est sur le point de s'executer, le code genere verifie que la limite de la boucle est plus petite que le seuil. Si une tentative d'execution d'une boucle superieure a la valeur du seuil est identifiee, le handler __blip_violation() sera appele au lieu de la boucle, menant a une fin controlee du processus. La version actuelle du patch ne supportera que le langage C. Cette decision a ete prise pour garder cette premiere version du patch petite et simple. Egalement, tous les paquetages vulnerables que ce patch projetait de proteger sont ecrits en C. J'ai donc pense que n'avoir que le C est un bon debut. ------[ 4.5.1 - Tactique Ayant en tete les buts ci-dessus, j'ai du prendre des decisions durant le developpement de ce patch. L'un des problemes que j'avais etait de choisir le bon endroit ou tailler le code. Il y a beaucoup d'options disponibles, et je tenterai de donner des pour et des contres pour chaque option, en esperant que cela aidera d'autres a prendre des decisions reflechies lorsqu'ils / elles rencontrerons les memes dilemmes. La premiere chose que je devais decider etait la representation du programme que je voulais modifier. Le processus de compilation ressemble plus ou moins a ceci : Processus Representation du programme ------------- ----------------------------- Programmation => 1. Code source Parsing => 2. AST Etendement => 3. RTL "final" => 4. Fichier objet Alors quelle est la bonne place ou implementer les verifications ? Le tableau suivant liste quelques pour et contre pour la modification du code aux differantes etapes durat le processus de compilation. +----------+----------------------------+---------------------------+ |Etape |Pour | Contre | +----------+----------------------------+---------------------------+ | AST |- Independant de la cile |- Pas d'acces aux registres| | |- Independent du langage | ni aux instructions | | |- Independant des | materiels | | | optimisations | | | |- Acces haut niveau au | | | | langage "source" | | | |- Ajout du code intuituif | | +----------+----------------------------+---------------------------+ | RTL |- Independant de la cible |- Acces de bas niveau a | | |- Independant du langage | la source | | |- Plein acces au materiel |- Pourrait interferer avec | | | de la cible | les optimisations | +----------+----------------------------+---------------------------+ | Fichier |- Independant du langage |- Dependent du materiel | | objet | |- Manque d'information | | | | de syntaxe | | | |- La modification du flux | | | | pourrait casser la logique| | | | du compilateur | +----------+----------------------------+---------------------------+ Apres quelques reflexions j'ai decide de modifier la representation AST. Cela semble etre l'endroit le plus naturel pour faire un tel changement. Premierement le patch n'a pas vraiment besoin d'acceder a des informations de bas niveau comme les registres materiels, ou meme les allocations de registres virtuels. Deuxiemement le patch peut facilement modifier le ASt pour injecter dedans de la logique sur mesure, alors que faire la meme chose au niveau du RTL requiererait des changements majeurs, qui blesserons les niveaux d'abstraction definis dans gcc. Resoudre mon second dilemme ne fut pas aussi simple que le premier. A present que le patching du AST etait le plan que j'avais en tete, j'avais besoin de trouver le meilleur point dans le temps ou j'examinerai l'arbre AST existant, et emettre mes tests dessus. J'avais trois options possibles. 1) Ajouter un appel a ma fonction depuis le code du parser de quelques langages (qui passent pour etre le C). En faisant cela, j'ai la chance d'evaluer et de modifier l'arbre "a la volee" et par consequent sauver un passage supplementaire sur l'arbre plus tard. Un desavantage clair est que le patch devient dependant du langage. 2) Attendre jusqu'a ce que la fonction entiere soit parsee par le front-end. Apres aller a travers l'arbre cree, avant de le convertir en RTL et trouiver les endroit qui requierent un test et les patcher. Un avantage de cette methode est que le patch n'est plus dependant du langage. D'un autre cote, implementer un "parcours d'arbre" qui scannera un arbre donne est une tache trescomplexe et source d'erreur, qui ira contre les buts que nous avons definits au-dessus comme un patch simple et utile. 3) Patcher l'arbre AST *pendant* qu'il est convertit en RTL. Malgre que cette option semble etre la plus avantageuse (independante du langage, pas besoin d'un parcours d'arbre) elle a toujours un desavantage majeur qui est l'incertitude d'etre capable de modifier de maniere *sure* l'arbre AST a ce moment. Comme la "convertion machine" du RTL est deja faite pour certaines parties de l'arbre AST, il peut etre dangereux de patcher l'arbre AST a ce moment. Finalement, j'ai decide que le but de faire ce patch simple impliquait de choisir la premiere option d'appeler mes fonctions d'evolution depuis le parser C. J'ai place le grapin dans mon patch en trois endroits. Deux appels dans le code de c-parse.y (fichier principal du parser) permettant d'examiner les boucles FOR et WHILE et de les modifier a la volee. Le troisieme appel est localise en dehors du parser car attraper tous les endroits etait tres delicat a faire depuis l'interieur du parser. Basiquement, comme dans plein d'autres situations differantes, un CALL_EXPR est cree les reliant tous semble ne pas etre naturel. L'alternative que j'ai trouvee qui semble bien fonctionner pour moi, etait d'ajouter un appel a ma fonction dans build_function_call() a l'interieur du fichier typeck.c (constructeur d'expressions de verification de type du compilateur C). L'entree principale dans le patch est la fonction blip_check_loop_limit() qui fera tout le travail de verifier si une boucle semble etre approprie, et d'appeler la bonne fonction qui fera le patch actuel de l'arbre AST. Pour qu'une boucle soit consideree elle doit ressembler a une boucle de comptage. Le patch blip essaiera par consequent d'examiner chaque boucle et de decider si la boucle semble etre une boucle de comptage (des criteres supplementaires pour l'examen des boucles suivront). Pour chaque boucle de comptage un essai est fait pour detecter la variable "compteur" et la variable "limite". Exemple de boucles simples et leurs variables : - for(i=0; i < j; i+=3}{;} ==> boucle incrementale, i = compteur j = limite - while(len--){;} ==> boucle decrementale, len = compteur, 0 = limite L'implementation actuelle considere une boucle comme etant une boucle de comptage si et seulement si : - 2 variables sont detectees dans la condition de la boucle (parfois l'une d'entre elles peut etre une constante) - l'une de ces variables est modifiee dans la condition de la boucle ou dans l'expression de la boucle - *seulement une* variable est modifiee - La modification est du type incrementation/decrementation (++,--,+=,-=) Le code qui examine la boucle est execute dans blip_find_loop_vars() et il pourrait etre ameliore a l'avenir pour identifier plus de boucles comme boucle de comptage. Apres avoir detecte la direction de la boucle, son compteur et sa limite, l'arbre AST est modifie pour inclure un test qui verifie qu'une grosse boucle est rapportee comme une violation de blip. Dans le but de maintenir le patch simple et sans risque, a chaque fois qu'une boucle semble trop complexe pour etre prise pour une boucle de comptage, la boucle sera ignoree (en utilisant les flags de warning de blip il est possible de lister les boucles ignorees, et la raison pour laquelle elles furent ignorees). ------[ 4.5.2 - Modifions le AST Lorsque vous commencez a patcher des applications complexes comme gcc, vous voulez vous assurer que vous ne causez aucun "effet papillon" pendant que vous modifiez a la volee des structures residant en memoire. Pour vous garder de beaucoup d'ennuis je suggererai d'eviter les modification d'aucune structure directement. Mais utilisez a la place les fonctions existantes que le paser de langage aurait utilise si le code que vous voulez "injecter" avait ete trouve dans le code source oiginal. Suivre ce niveau d'encapsulation sous empechera de faire des erreurs comme oublier d'initialiser un membre de structure, ou ne pas mettre a jour une autre variable globale ou flag. J'ai trouve tres utile de simuler l'injection de code en modifiant reellement le code source, et de tracer le compilateur quand il contruit l'arbre AST, et de mimer plus tard la creation de code en utilisant les memes fonctions utilisees par le parser pour construire mon nouveau code de test. De cette maniere j'etais capable d'eliminer le besoin d'acces "impropre" a l'arbre AST, lequel m'effrayais pendant que je commencais la modification. Connaissant le bon set de fonctions a utiliser pour injecter n'importe quel code voulu, la question devint qu'aimerais vraiment injecter ? La reponse differe un peu entre les differents types de boucles. Dans le cas d'une boucle for le patch blip ajoutera l'expression de test comme derniere expresion dans la declaration de FOR_INIT. Dans le cas d'une boucle while le patch blip ajoutera l'expression de test en tant que nouvelle declaration avant la boucle while. Dans le cas d'un appel a une fonction ressemblant a une boucle, comme memcpy, le patch blip remplacera l'expression d'appel entiere avec une nouvelle expression de condition, ayant le __blip_violation sur le cote "vrai" et l'espression d'appel originale sur le cote "faux". Illustrons le dernier paragraphe avec quelques exemples... Avant blip ---------- 1) for(i=0;i< len;i++){} 2) While(len--){} 3) p = memcpy(d,s,l) Apres blip ---------- 1) for(i=0,?__blip_violation:0;i?__blip_violation:0; while(len--){} 3) p = ?__blip_violation : memcpy(d,s,l) Le en lui-meme est tres simple. Si la boucle est incrementale (allant en augmentant) alors le test ressemblera a (limite > compteur && limite - compteur > max). Si la boucle va en baissant le test sera (compteur > limit && compteur - limite > max). Il y a besoin de tester la differance entre le compteur et la limite et pas seulement la limite car nous ne voulons pas declencher un faux positif dans une boucle comme : len = 0xffff0000; for(i=len-20;i < len; i++){}; L'exemple ci-dessus pourrait ressembler au debut a un exploit integer. Mais il pourrait egalement etre une boucle legitime qui s'occupe simplement d'iterer sur de tres grandes valeurs. La fonction responsable du est blip_build_check_exp(), et son code est explicite, donc je ne vais pas dupliquer les commentaires ici. L'une des difficultes que j'ai eues en injectant le code de blip fut l'injection de la fonction __blip_violation dans le fichier cible. Pendant la creation de j'ai simlpement cree des expressions qui referencie les memes noeuds d'arbres que j'ai trouves dans la condition de boucle ou comme parametre d'appel d'une fonction qui ressemble a une boucle. MAis la fonction __blip_violation n'existe pas dans l'espace nom du fichier compile, et par consequent tenter de la referencer etait un peu plus astucieux, ou du moins c'est ce que je pensais. D'habitude lorsqu'un CALL_EXPR est cree, un FUNCTION_DECL est identifie (comme l'une des fonctions disponibles visibles depuis l'appelant) et un ADDR_EXPR est plus tard cree pour exprimer l'adresse de la fonction declaree. Comme __blip_violation n'etait pas declaree, les tentatives d'executer lookup_name() pour cela produiront une declaration vide. Par chance gcc etait suffisamment aimable / negligent, et je fus capable de construire une FUNCTION_DECL et de la referencer en laissant tout le reste du travail pour que le RTL le calcule. Toutes les modifications ci-dessus ont ete faites dans l'esprit d'une preuve de concept pour la detection d'exploits integer blip. Il n'y a aucune garantie que le patch augmentera reellement la protection de quelque systeme que ce soit, ni qu'il gardera le compilateur stable et utilisable (en utilisant -fblip), ni que quelque recommandation de coding / patching que ce soit aura un quelconque sens pour ne noyau dur de mainteneur du projet gcc :>. ----[ 4.6 - Limites Cette section resumme les limites a ma connaissance au moment d'ecrire cet article. Je commencerai par les limites de haut niveau en allant vers les limites techniques de bas niveau. - La premiere limite est la couverture de ce patch. Le patch est concu pour stopper les failles integer qui produisent de grosses boucles. D'autres failles qui sont dues a une mauvaise conception ou un manque de validation de nombres entiers ne seront pas protegees. Par exemple le code suivant est vulnerable mais ne peut pas etre protege par le patch : void foo(unsigned int len,char* buf){ char dst[10]; if(len < 10){ strcpy(dst,buf); } } - Parfois un integer overflow generique fait "comme dans les livres" ne sera pas detecte. Un exemple pour un tel cas sera la faille xdr_array. Le probleme est du au fait que la fonction malloc etait appelee avec l'expression debordee de *deux* entree integer differantes, alors que la protection blip ne peut intercepter qu'une simple boucle de comptage. En regardant la boucle xdr_array, nous pouvons voir qu'il serai facile pour l'attaquant / l'attaquante de fournir de telles entrees de nombres entiers qui deborderons l'expression malloc, mais qui gardera encore le compte de boucle petit. - Quelques boucles de comptage ne seront pas considerees. Un exemple est une condition de boucle complexe et ce n'est pas trivial d'identifier la boucle de comptage. De telles boucles doivent etre ignorees, ou autrement des faux positifs pourraient apparaitre qui pourraient mener a une execution non-definie. - [Limite technique] La version actuelle est concue pour fonctionner uniquement avec le langage C. - [Limite technique] La version actuelle n'examinera pas le code assembleur embarque qui pourrait inclure des instructions de boucles. Par consequent elle permettra a l'exploitation d'integer owerflow de n'etre pas detectee. --[ 5 - References [1] StackGuard Detection et prevention automatique des attaques de smash de pile http://www.immunix.org/StackGuard/ [2] ProPolic Extension GCC pour proteger les applications des attaques de smash de pile http://www.trl.ibm.com/projects/security/ssp/ [3] GCC GNU Compiler Collection http://gcc.gnu.org [4] noir Smashing The Kernel Stack For Fun And Profit Phrack Issue #60, Phile 0x06 by noir [5] Halvar Flake Third Generation Exploits on NT/Win2k Platforms http://www.blackhat.com/presentations/bh-europe-01/halvar-flake/bh- europe-01-halvarflake.ppt [6] MaXX Vudo malloc tricks Phrack Issue 0x39, Phile #0x08 [7] Once upon a free().. Phrack Issue 0x39, Phile #0x09 [8] Aleph One Smashing The Stack For Fun And Profit Phrack Issue 0x31, Phile #0x0E --[ 6 - Thanks Je veux remercier mon equipe pour m'avoir aide dans le processus de creation de l'article. Merci Monty, sinan, yona, shok pour vos commentaires utiles et vos idees pour ameliorer l'article. Si vous pensez que l'Anglais est massacre dans cet article alors imaginez ce que mon team a du supporter :>. Sans vous les mecs je ne l'aurai jamais fait. Merci a anonymous : > pour avoir relu l'article, et m'avoir fournit du feedback technique interessant et de l'assurance. --[ 7 - Annexe A - Des exemples dans la vraie vie Ayant le patch pret, j'ai voulu le tester sur une faille connue. Les criteres utililses pour tester le patch etaient : - le paquetage doit etre compile avec succes avec le patch - le patch doit etre capable de proteger le paqutetage contre l'exploitation des bugs connus. J'ai choisi de tester le patch sur les paquetages Apache httpd et OpenSSH. Car les deux paquetages sont : de haut profil ont des failles dont le patch est cense proteger, et sont suffisamment gros pour certifier le patch. Le test de protectiona demontre son succes :), et la version vulnerable compilee avec -fblip a ete demontree comme etant non exploitable. La section suivante explique comment compiler les paquetages avec le patch blip. Nous montrerons le code assembleur genere avant et apres le patch pour le code qui permettait l'exploit de deborder les tampos du programe. ----[ 7.1 - Apache Chunked encoding --[ Informations sur la faille Juste pour nous assurer que tout est OK avec la le probleme de la faille du chunked-encoding de Apache, je vais lister une partie du code vulnerable suivi de quelques explications. Code: Apache src/main/http_protocol.c : ap_get_client_block() 01 len_to_read = get_chunk_size(buffer); 02 r->remaining = len_to_read; 03 len_to_read = (r->remaining > bufsiz) ? bufsiz : r->remaining; 04 len_read = ap_bread(r->connection->client, buffer , len_to_read); La faille dans ce cas autorise a un(e) attaquant(e) d'envoyer une longueur de chunk negative. Faire ceci passera au travers du test de la ligne 03, et terminera en appelant ap_bread() avec un enorme nombre positif. --[ Testons le patch Pour compiler le Apache httpd avec le -fblip accorde, on pourrait edit le fichier src/apaci et ajouter la ligne suivante a la fin du fichier "echo '-fblip'". Toute tentative d'envoyer une longueur de chunk negative apres avoir compile Apache httpd avec le patch blip terminera avec le httpd executant la __blip_ violation. Selon la theorie de blip, l'attaque devrait declencher une sorte de boucle. Nous voyons a la ligne 04 du code liste qu'un appel est fait a la fonction ap_bread(). Docn si la theorie est correcte nous sommes supposes trouver une boucle a l'interieur de cette fonction. /* * Lis jusqu'a nbyte octets dans buf * Si moins d'octets que byte sont actuellement disponible, alors les retourner * Retourne 0 pour EOF, -1 pour erreur. * NOTE EBCDIC: Le tampon readaway contient _toujours_ des donnees *non converties* * Une conversion n'est faite que lorsque l'appelant reprend des donnees du tampon (appelle bread), si le flag de conversion est mis a ce moment. */ API_EXPORT(int) ap_bread(BUFF *fb, void *buf, int nbyte) { int i, nrd; if (fb->flags & B_RDERR) return -1; if (nbyte == 0) return 0; if (!(fb->flags & B_RD)) { /*Lecture non dans le tampon. Verifier d'abord si il y avait * quelque chose dans le tampon avant que nous soyons * non-tampon-ises. */ if (fb->incnt) { i = (fb->incnt > nbyte) ? nbyte : fb->incnt; #ifdef CHARSET_EBCDIC if (fb->flags & B_ASCII2EBCDIC) ascii2ebcdic(buf, fb->inptr, i); else #endif /*CHARSET_EBCDIC*/ memcpy(buf, fb->inptr, i); fb->incnt -= i; fb->inptr += i; return i; } i = read_with_errors(fb, buf, nbyte); #ifdef CHARSET_EBCDIC if (i > 0 && ap_bgetflag(fb, B_ASCII2EBCDIC)) ascii2ebcdic(buf, buf, i); #endif /*CHARSET_EBCDIC*/ return i; } nrd = fb->incnt; /* pouvons-nous remplir le tampon */ if (nrd >= nbyte) { #ifdef CHARSET_EBCDIC if (fb->flags & B_ASCII2EBCDIC) ascii2ebcdic(buf, fb->inptr, nbyte); else #endif /*CHARSET_EBCDIC*/ memcpy(buf, fb->inptr, nbyte); fb->incnt = nrd - nbyte; fb->inptr += nbyte; return nbyte; } if (nrd > 0) { #ifdef CHARSET_EBCDIC if (fb->flags & B_ASCII2EBCDIC) ascii2ebcdic(buf, fb->inptr, nrd); else #endif /*CHARSET_EBCDIC*/ memcpy(buf, fb->inptr, nrd); nbyte -= nrd; buf = nrd + (char *) buf; fb->incnt = 0; } if (fb->flags & B_EOF) return nrd; /* fait une simple lecture */ if (nbyte >= fb->bufsiz) { /* lit directement dans le tampon de l'appelant */ i = read_with_errors(fb, buf, nbyte); #ifdef CHARSET_EBCDIC if (i > 0 && ap_bgetflag(fb, B_ASCII2EBCDIC)) ascii2ebcdic(buf, buf, i); #endif /*CHARSET_EBCDIC*/ if (i == -1) { return nrd ? nrd : -1; } } else { /* lit dans le tampon tenu, puis memcpy */ fb->inptr = fb->inbase; i = read_with_errors(fb, fb->inptr, fb->bufsiz); if (i == -1) { return nrd ? nrd : -1; } fb->incnt = i; if (i > nbyte) i = nbyte; #ifdef CHARSET_EBCDIC if (fb->flags & B_ASCII2EBCDIC) ascii2ebcdic(buf, fb->inptr, i); else #endif /*CHARSET_EBCDIC*/ memcpy(buf, fb->inptr, i); fb->incnt -= i; fb->inptr += i; } return nrd + i; } Nous voyons dans le code plusieurs flux d'execution possibles. Chacun d'eux inclut une boucle qui bouge toute les donnees dans le paraletre buf. Si le code supporte CHARSET_EBCDIC alors la fonction ascii2ebdcdic execute la boucle mortelle. Dans les cas normaux, la fonction memcpy implemente la boucle meurtriere. Ce qui suit est le code assembleur genere pour la fonction ci-dessus. .type ap_bread,@function ap_bread: pushl %ebp movl %esp, %ebp subl $40, %esp movl %ebx, -12(%ebp) movl %esi, -8(%ebp) movl %edi, -4(%ebp) movl 8(%ebp), %edi movl 16(%ebp), %ebx testb $16, (%edi) je .L68 movl $-1, %eax jmp .L67 .L68: movl $0, %eax testl %ebx, %ebx je .L67 testb $1, (%edi) jne .L70 cmpl $0, 8(%edi) je .L71 movl 8(%edi), %esi cmpl %ebx, %esi jle .L72 movl %ebx, %esi .L72: cmpl $268435456, %esi ---------------------------- jbe .L73 movl %esi, (%esp) Test de blip (utilisant esi) call __blip_violation jmp .L74 ---------------------------- .L73: movl 4(%edi), %eax movl 12(%ebp), %edx movl %edx, (%esp) movl %eax, 4(%esp) movl %esi, 8(%esp) call memcpy .L74: subl %esi, 8(%edi) addl %esi, 4(%edi) movl %esi, %eax jmp .L67 .L71: movl %edi, (%esp) movl 12(%ebp), %eax movl %eax, 4(%esp) movl %ebx, 8(%esp) call read_with_errors jmp .L67 .L70: movl 8(%edi), %edx movl %edx, -16(%ebp) cmpl %ebx, %edx jl .L75 cmpl $268435456, %ebx ---------------------------- jbe .L76 movl %ebx, (%esp) Test de blip (utilisant ebx) call __blip_violation jmp .L77 ---------------------------- .L76: movl 4(%edi), %eax movl 12(%ebp), %edx movl %edx, (%esp) movl %eax, 4(%esp) movl %ebx, 8(%esp) call memcpy .L77: movl -16(%ebp), %eax subl %ebx, %eax movl %eax, 8(%edi) addl %ebx, 4(%edi) movl %ebx, %eax jmp .L67 .L75: cmpl $0, -16(%ebp) jle .L78 cmpl $268435456, -16(%ebp) ------------------------ jbe .L79 movl -16(%ebp), %eax Test de blip movl %eax, (%esp) (utilisant [ebp-16]) call __blip_violation jmp .L80 ------------------------ .L79: movl 4(%edi), %eax movl 12(%ebp), %edx movl %edx, (%esp) movl %eax, 4(%esp) movl -16(%ebp), %eax movl %eax, 8(%esp) call memcpy .L80: subl -16(%ebp), %ebx movl -16(%ebp), %edx addl %edx, 12(%ebp) movl $0, 8(%edi) .L78: testb $4, (%edi) je .L81 movl -16(%ebp), %eax jmp .L67 .L81: cmpl 28(%edi), %ebx jl .L82 movl %edi, (%esp) movl 12(%ebp), %eax movl %eax, 4(%esp) movl %ebx, 8(%esp) call read_with_errors movl %eax, %esi cmpl $-1, %eax jne .L85 jmp .L91 .L82: movl 20(%edi), %eax movl %eax, 4(%edi) movl %edi, (%esp) movl %eax, 4(%esp) movl 28(%edi), %eax movl %eax, 8(%esp) call read_with_errors movl %eax, %esi cmpl $-1, %eax jne .L86 .L91: cmpl $0, -16(%ebp) setne %al movzbl %al, %eax decl %eax orl -16(%ebp), %eax jmp .L67 .L86: movl %eax, 8(%edi) cmpl %ebx, %eax jle .L88 movl %ebx, %esi .L88: cmpl $268435456, %esi ---------------------------- jbe .L89 movl %esi, (%esp) Test de blip (utilisant esi) call __blip_violation jmp .L90 ---------------------------- .L89: movl 4(%edi), %eax movl 12(%ebp), %edx movl %edx, (%esp) movl %eax, 4(%esp) movl %esi, 8(%esp) call memcpy .L90: subl %esi, 8(%edi) addl %esi, 4(%edi) .L85: movl -16(%ebp), %eax addl %esi, %eax .L67: movl -12(%ebp), %ebx movl -8(%ebp), %esi movl -4(%ebp), %edi movl %ebp, %esp popl %ebp ret On peut remarquer qu'avant tout appel a la fonction memcpy (qui est l'une des fonctions ressemblant a une boucle) [NDT : dans tout l'article c'est l'expression "loop like" function que j'ai traduite comme cela, desole], un peu de code fut ajoute qui appelle __blip_violation au cas ou le troisieme parametre de memcpy est plus grand que blip_max. Une autre chose qui vaut d'etre mentionnee est le moyen par lequel le test injecte accede au troisieme parametre. Dans le premier bloc de code injecte le parametre est stocke dans le registre esi, dans le second bloc le parametre est dans le registre ebx et dans le troisieme bloc le parametre est stocke dans la pile a ebp-16. La raison a cela est tres simple. Comme la modification du code a ete faite dans l'arbre AST, et comme de patch etaient en train d'utiliser exactement le meme noeud de l'arbre qui etait utilise dans l'expression d'appel a memcpy, le RTL a genere le meme code pour l'expression d'appel et l'expression de test. A present retournons a la fonction ap_bread. Et supposons que CHARSET_EBCDIC etait en effet definie. Dans ce cas la fonction ascii2ebcdic aurait ete celle qui aurait eu la boucle "vulnerable". Par consequent nous esperons que le patch blip aurait aussi bien teste la boucle dans cette fonction. Ce qui suit est le code de ascii2ebcdic extrait de src/ap/ap_ebcdic.c API_EXPORT(void *) ascii2ebcdic(void *dest, const void *srce, size_t count) { unsigned char *udest = dest; const unsigned char *usrce = srce; while (count-- != 0) { *udest++ = os_toebcdic[*usrce++]; } return dest; } Resultat de la compilation de la focntion ci-dessus avec -fblip .type ascii2ebcdic,@function ascii2ebcdic: pushl %ebp movl %esp, %ebp pushl %edi pushl %esi pushl %ebx subl $12, %esp movl 16(%ebp), %ebx movl 8(%ebp), %edi movl 12(%ebp), %esi cmpl $0, %ebx ------------------- jbe .L12 cmpl $268435456, %ebx jbe .L12 Test de Blip movl %ebx, (%esp) call __blip_violation .L12: ------------------- decl %ebx cmpl $-1, %ebx je .L18 .L16: movzbl (%esi), %eax movzbl os_toebcdic(%eax), %eax movb %al, (%edi) incl %esi incl %edi decl %ebx cmpl $-1, %ebx jne .L16 .L18: movl 8(%ebp), %eax addl $12, %esp popl %ebx popl %esi popl %edi popl %ebp ret .Lfe2: Pendant qu'il s'occupait de la fonction ascii2ebcdic, le patch blip a identifie la boucle while comme etant une boucle de comptage. La condition de la boucle fournti toute les informations requises pour creer un . D'abord nous identifions les variables de la boucle. Dans ce cas "count" est une variable est la constante "0" est la seconde. En cherchant des modifications de la variable, nous voyons que "count" est decrementee dans l'expression "count--". Comme "count" est la seule variable modifiee nous pouvosn dire que "count" est la variable-compteur et que la constante 0 est la varable limite. Nous pouvons egalement dire que la boucle est une boucle decrementale car l'operation de modification est "--". Le test sera par consequent (count > limit && count - limit > MAX_BLIP). En regardant le code assembleur ci-dessus, nous voyons que le compteur de boucle est stocke dans le registre ebx (Il est facile de reperer ceci en regardant le code sous l'etiquette 12 (L12). Ce code represente la condition while. Ca decremente d'abord ebx et le compare plus tard avec la constant de la boucle.). Par consequent le utilsera le registre ebx pour le test. ----[ 7.2 - Authentification OpenSSH --[ Informations sur la faille La faille OpenSSH est un exemple de bug integer overflow, qui mene a un mauvais calcul de la taille de l'allocation. Ce qui suit est un bout du code vulnerable : OpenSSH auth2-chall.c : input_userauth_info_response() 01 nresp = packet_get_int(); 02 response = xmalloc(nresp * sizeof(char*)); 03 for(i = 0; i < nresp; i++) 04 response[i] = packet_get_string(NULL); A la ligne 01 le code lit un entier dans une variable non-signee. Plus tard la code alloue un tableau a nresp entrees. Le probleme est que nresp * sizeof(char*) est une expression qui pourreait deborder. Par consequent envoyer nresp plus grand que 0x40000000 permet l'allocation d'un petit tampon qui peut etre plus tard deborde par l'assigment de la ligne 04. --[ Testons le patch Pour compiler le paquetage OpenSSH avec -fblip, on peut ajouter -fbliup a la definition CFLAGS de Makefile.in (i.e. CFLAGS=@CFLAGS@ -fblip) Toute tentative d'envoyer un grand nombre de reponses apres avoir compile OpenSSH avec le patch blip terminera avec OpenSSH executant la __blîp_violation. Ce qui suit est un morceau de la fonction vulnerable. static void input_userauth_info_response(int type, u_int32_t seq, void *ctxt) { Authctxt *authctxt = ctxt; KbdintAuthctxt *kbdintctxt; int i, authenticated = 0, res, len; u_int nresp; char **response = NULL, *method; nresp = packet_get_int(); if (nresp != kbdintctxt->nreq) fatal("input_userauth_info_response: wrong number of replies"); if (nresp > 0) { ----------------------------------------- ** Code vulnerable** ----------------------------------------- response = xmalloc(nresp * sizeof(char*)); for (i = 0; i < nresp; i++) response[i] = packet_get_string(NULL); } } La fonction ci-dessus se traduit par le code assembleur qui suit si compilee avec la protection -fblip. (Pour rendre lisible la modification blip, lle code a ete compile en utilisant -O au lieu d'utiliser -O2, qui va reordonner les blocs de base). .type input_userauth_info_response,@function input_userauth_info_response: movl -16(%ebp), %eax movl $0, 4(%eax) call packet_get_int movl %eax, %esi movl -20(%ebp), %edx cmpl 12(%edx), %eax je .L111 movl $.LC15, (%esp) call fatal .L112: testl %esi, %esi je .L113 leal 0(,%esi,4), %eax movl %eax, (%esp) call xmalloc movl %eax, -32(%ebp) movl $0, %ebx cmpl $0, %esi jbe .L115 cmpl $268435456, %esi ------------------------ jbe .L115 movl %esi, (%esp) Test de Blip call __blip_violation .L115: ------------------------ cmpl %esi, %ebx jae .L113 .L120: movl $0, (%esp) call packet_get_string movl -32(%ebp), %ecx movl %eax, (%ecx,%ebx,4) incl %ebx cmpl %esi, %ebx jb .L120 Le patch blip a identifie la boucle for comme une boucle de comptage est a injecte un code pour rediriger le flux vers l'intercepteur _blip_violation dans le cas ou la limite (i.e. nresp) est plus grand que BLIP_MAX. Par consequent si la valeur de nresp est assez haute pour declencher un overflow dans l'appel de xmalloc, elle sera egalement assez haute pour se faire prendre par le . --[ 8 - Annexe B - Utilisons blip Pour mettre en service le patch blip on devrait d'abord ajouter le flag -fblip quand on execute le compilateur gcc. Le patch blip tentera d'emettre le a chaque fois que cela semble possible de le faire. Le patch ignorera sans rien dire toutes les boucles qui ne peuvent pas etre protegees. Pour voir les boucles ignorees on peut utiliser l'un des flags de warning suivants, qui vont egalement fournir un message decrivant la raison pour laquelle la boucle a ete ignoree. Flags de warning : - blip_for_not_emit - rapporte les boucles for ignorees. - blip_while_not_emit - rapporte les boucles while ignorees. - blip_call_not_emit - rapporte les appels ignores a des fonctions ressemblant a des boucles. Une raison pour ignorer une boucle sera l'une des suivantes : - Les variables de boucles sont longues de moins de 4 octets - L'intitalisation du for n'est pas une expression - L'appel a la fonction est fait en utilisant un pointeur vers une fonction - Les parametres d'appel ont des effets de bords. Reutiliser l'expression peut causer des resultats inattendus - La condition de boucle est trop complexe pour trouver les variables de la boucle - Aucune des variables de la boucle n'est modifiee (pas suffisamment d'informations pour effectuer le test) - Les deux variables de la boucle sont modifiees - La condition est trop complexe Le patch blip est egalement capable de rapporter les statistiques de de tests. En utilisant -fblip_stat on peut faire imprimer des informations statistiques a propos du nombre de boucles effectuees et le nombre de boucles qui ont ete testees avec succes. La ligne de commande qui suit compilera le code du premier exemple. La sortie de la compilation suivra. $ gcc -o sample -fblip -fblip_stat -O sample.c -=] Blip statistics (checks emits) Total: 1/100% 1/100% for: 1/100% 1/100% while: 0/0% 0/0% calls: 0/0% 0/0% -=] End Blip Statistics begin 640 blip.patch M9&EF9B`M3G5R(&=C8RTS+C(O9V-C+TUA:V5F:6QE+FEN(&=C8RTS+C(M8FQI M<"]G8V,O36%K969I;&4N:6X-"BTM+2!G8V,M,RXR+V=C8R]-86ME9FEL92YI M;@E4:'4@36%Y(#(S(#$P.C4W.C(Q(#(P,#(-"BLK*R!G8V,M,RXR+6)L:7`O M9V-C+TUA:V5F:6QE+FEN"4UO;B!$96,@(#(@,3DZ-#(Z,SD@,C`P,@T*0$`@ M+36]U="YO('-T2!O9@T-"BL@ M*B`@("!-15)#2$%.5$%"24Q)5%D@;W(@1DE43D534R!&3U(@02!005)424-5 M3$%2(%!54E!/4T4N("!3964@=&AE#0T**R`J("`@($=.52!'96YE7-T96TN:"(-#0HK(VEN8VQU9&4@(FUA8VAM;V1E M+F@B#0T**R-I;F-L=61E(")R=&PN:"(-#0HK(VEN8VQU9&4@(G1R964N:"(- M#0HK(VEN8VQU9&4@(G1O<&QE=BYH(@T-"BLC:6YC;'5D92`B8FQI<"YH(@T- M"BLC:6YC;'5D92`B9FQA9W,N:"(-#0HK(VEN8VQU9&4@(F,M8V]M;6]N+F@B M#0T**PT-"BLO*B!T:&ES('-T7,@#0T**R`J('-T871L97-S M+"!T:&%N(&ETR)B8V]P>2(L,GTL#0T**PE[(F)Z M97)O(BPQ?2P-#0HK"7LB2D@"7D@/R`H>"`J(#$P,"DO>2`Z(#`- M#0HK#0T**R\J('!R:6YT(&)L:7`@2!D;R!S M;R!I9B!T:&4@'`@/2!B=6EL9%]F=6YC=&EO;E]C M86QL*&)L:7!?9G5N8U]D96-L+'!A'`[#0T**WT-#0HK#0T**R\J(`E#'`@9F]R('1H M92!B;&EP(&-O;F1I=&EO;B!T:&4@97AP('=I;&P@8F4@;V8@0T].1%]%6%!2 M(`T-"BL@*B`)='EP92P@86YD('=I;&P@:&%V92!T:&4@9F]L;&]W:6YG(&9O M2!T:&4@9&ER96-T:6]N(&]F('1H92!L;V]P(`T-"BL@*@T-"BL@ M*B`)($%S(&$@;F]T92P@:2!C;W5L9"!H879E(&%D9"!S;VUE(&5X=')A(&QO M9VEC('1O(&5L:6UI;F%T92!T:&4@8V]M<&QE>"`-#0HK("H@"2!C:&5C:R!I M9B!T:&4@;&EM:70O8V]U;G0@87)E(&-O;G-T86YT7!E7VYO9&4[ M#0T**PET"D@/2!O<%]T=#L-#0HK#0T**PT-"BL)+RH@:68@;&]O<"!C;W5N=&5R(&]R M(&QO;W`@;&EM:70@87)E('-M86QL97(@=&AE;B`T8GET92!I;G1S(`T-"BL) M("H@9&]N="!E=F5N(&)O=&AE$9&1D9&1D9&*7L-#0HK M"0D);&]O<%]L:6UI="YL:6UI="`](&EN=&5G97)?>F5R;U]N;V1E.PD)#0T* M*PD)?0T-"BL)#0T**PD-#0HK"0DO*B!C;VYV97)T('1H92!L:6UI="!A;F0@ M8V]U;G0@:6YT;R!U;G-I9VYE9"!I;G0@:68@=&AE>2!APT-"BL)"0EL;V]P7VQI;6ET+FQI;6ET M(#T@8G5I;&0Q("A#3TY615)47T584%(L;&]N9U]U;G-I9VYE9%]T>7!E7VYO M9&4L#0T**PD)"0D)"0D)"0EL;V]P7VQI;6ET+FQI;6ET*3L-#0HK"0E]#0T* M*PD)#0T**PD)#0T**PD)+RH@8V]N'!R97-S M:6]NPT-"BL)"0EM:6YU"D[#0T* M*PD):68H(6UI;G5S7V=T7VUA>"D@"D[#0T* M*PD-#0HK"0EI9B@A=%]A;F1I9BD@PT-"BL)"0EL M;V]P7VQI;6ET+FQI;6ET(#T@8G5I;&0Q("A#3TY615)47T584%(L;&]N9U]U M;G-I9VYE9%]T>7!E7VYO9&4L#0T**PD)"0D)"0D)"0EL;V]P7VQI;6ET+FQI M;6ET*3L-#0HK"0E]#0T**PT-"BL)"6]P7V=T7VUA>"`](&)U:6QD("A'5%]% M6%!2+&)O;VQE86Y?='EP95]N;V1E+&QO;W!?;&EM:70N;&EM:70L8FQI<%]M M87@I.PT-"BL)"6EF*"%O<%]G=%]M87@I(')E='5R;B!.54Q,.PT-"BL-#0HK M"0EC;VYD7W1E'`B(&%S(&9A;'-E(&5X<"!O9B!T:&4@ M0T].1%]%6%!2("HO#0T**PEC;VYD7V5X<"`](&)U:6QD("A#3TY$7T584%(L M='0L8V]N9%]T97-T+&)L:7!?=FEO;&%T:6]N7V-A;&PL#0T**PD)"0D@97AP M(#\@97AP(#H@:6YT96=EPT-"BL- M#0HK"71R964)8VAE8VM?'`],#L- M#0HK#0T**PEC:&5C:U]E>'`@/2!B;&EP7V)U:6QD7V-H96-K7V5X<"AE>'`I M.PT-"BL):68H(6-H96-K7V5X<"D@'`I>PT-"BL)"6)L:7!? M=V%R;FEN9RA.15]&3U(L#0T**PD)"2)B;&EP.B!I;G1E'`[#0T**PE]#0T**PEE;'-E>PT-"BL)"2\J M(&-O;G-T'!R97-S:6]N("HO#0T**PD) M8V]M<&]U;F1?97AP7!E M7VYO9&4L#0T**PD)"0D)"0D)5%)%15]/4$5204Y$("AF;W)?:6YI="PP*2P- M#0HK"0D)"0D)"0EB;&EP7V5X<"D[#0T**PD-#0HK"0EI9B@A8V]M<&]U;F1? M97AP65T(&%D9&5D('1O('1H92!T M2!A9&0@;W5R(&-O;F0N(`T-"BL@*B\-#0HK M(`T-"BMB;V]L("`-#0HK8FQI<%]E;6ET7W=H:6QE7VQO;W!?8VAE8VMS*"D- M#0HK>PT-"BL)=')E90EB;&EP7W-T;70[#0T**PD)#0T**PEB;&EP7W-T;70@ M/2!B;&EP7V)U:6QD7V-H96-K7W-T;70H3E5,3%]44D5%*3L-#0HK"6EF*"%B M;&EP7W-T;70I(')E='5R;B!F86QS93L-#0HK"0T-"BL)861D7W-T;70H8FQI M<%]S=&UT*3L-#0HK"0T-"BL)'!R97-S:6]N+B`J+R`-#0HK#0T**W1R964@(`T-"BMB;&EP7V5M:71?8V%L M;%]C:&5C:W,H8V%L;"D-#0HK"71R964)8V%L;#L-#0HK"0D-#0HK>PT-"BL) M=')E90EC:&5C:U]E>'`[#0T**PT-"BL)8VAE8VM?97AP(#T@8FQI<%]B=6EL M9%]C:&5C:U]E>'`H8V%L;"D[(`T-"BL-#0HK"2\J(&EF('=E(&9A:6QE9"!T M;R!C;VYV97)T('1H92!E>'`@:6YT;R!O=7(@8VAE8VLL(`T-"BL)("H@=&AE M;B!R971U'`I(')E='5R;B!C86QL.PT-"BL-#0HK"7)E='5R;B!C:&5C:U]E>'`[#0T* M*WT-#0HK#0T**R\J(&-H96-K(&EF(&$@9&5C;"!I'!R('=A'!R('1O(&-O;7!L97@@=&\@=F5R9FEY#0T**R`J(`D@*'=E(&-A;B!C M:&5A="!I;B!F:7)S="!V97)S:6]N(&%N9"!C;&%I;2!T:&%T(&%L;6]S="!E M=F5R>71H:6YG#0T**R`J(`D@:7,@=&]O(&AA'`@/2!44D5%7T]015)!3D0@*'0L,2D[#0T**PD) M"6EF*`E44D5%7T]015)!3D0@*'0L,"D@/3T@9&5C;"`F)@T-"BL)"0D)97AP M("8F#0T**PD)"0E44D5%7T]015)!3D0@*&5X<"PP*2`]/2!D96-L("8F#0T* M*PD)"0E44D5%7T]015)!3D0@*&5X<"PQ*2`F)@T-"BL)"0D)5%)%15]#3TY3 M5$%.5"`H5%)%15]/4$5204Y$("AE>'`L,2DI*7L-#0HK#0T**PT-"BL)"0D) M:68H5%)%15]#3T1%("AE>'`I(#T](%!,55-?15A04BE[#0T**PD)"0D);&]O M<%]L:6UI="YD:7(@/2!)3D-214U%3E0[#0T**PD)"0D)'!R+"!I;B!T:&ES(&-A'!R97-S:6]N'!R(&1O;G0@:&%V92!A9&1R97-S(&5X<'(B*3L)#0T**PD)#L-#0HK"0D) M8G)E86L[#0T**PD)?0T-"BL)?0T-"BL)#0T**PDO*B!I9B!F=6YC=&EO;B!N M;W0@9F]U;F0@:6X@;&]O<%]L:6ME"`F)B!P87)A;3L@:2LK*7L-#0HK"0EP87)A M;2`](%12145?0TA!24X@*'!APT-"BL)"0D)"6)L:7!?=V%R;FEN9RA314Q&7T-( M14-++`T-"BL)"0D)"0D)(F)L:7`Z(&-A;G0@9FEN9"!L;V]P(&1E8VP@:6X@ M8V]N9&ET:6]N(BD[#0T**PD)"0D)+RH@8V]N9&ET:6]N('1O;R!C;VUP;&5X M+"!R971U&ES="D@*B\-#0HK"0D) M:68H;G5M7VUO9&EF:65D(#T](#`I>PT-"BL)"0D)8FQI<%]W87)N:6YG*%-% M3$9?0TA%0TLL#0T**PD)"0D)(F)L:7`Z('=H:6-H(&QO;W`@=F%R(&ES(&UO M9&EF:65D/R`H8F]D>2!N;W0@PT-"BL)+RH@3&]O<"!P87)TF4@;&]O<%]L:6UI="!G M;&]B86P@*B\-#0HK"6QO;W!?;&EM:70N'!R M(&9O2!C;VYD:71I;VX@97AP&5U8W1I;VX@("HO#0T**PT-"BL)8V%S92!&3U)?4U1-5#H-#0HK M"0EB;&EP7W-T870N9F]R7V-H96-K2D@=6YK;F]W;B!I;G1E9V5R(&]V97)F;&]W M(&%N9"!S:6=N('9U;&YE0T**W1H92!&7!E M9&5F(&5N=6T@;&]O<%]D:7(-"BM[#0HK"55.2TY/5TY?1$E2+"\J('=E(&-A M;FYO="!T96QL(&QO;W`@9&ER96-T:6]N("HO#0HK"4E.0U)%345.5"P)+RH@ M;&]O<"!IPT**PE314Q&7T-(14-++`DO*B!U7!E9&5F('-T M2P@;65M;6]V92XN(&9O"!T;R!T:&4@<&%R86T@ M#0HK("H@=VAI8V@@:7,@7!E9&5F('-T7-?:6YL:6YE M(BP@1$5#3%]!5%1224)55$53("AF;BDI("$]($Y53$PI#0H@("`@(')E='5R M;B`Q.PT*(`T**R`@:68H1$5#3%],04Y'7U-014-)1DE#("AF;BD@/3T@3E5, M3"D@#0HK"2`@7!E/C(I.R!]#0H@"2`@8SDY7V)L;V-K7VQI M;F5N;U]L86)E;&5D7W-T;70-"BT)"7L@4D5#2$%)3E]35$U44R`H)#QT='EP M93XV+"!72$E,15]"3T19("@D/'1T>7!E/C8I*3L@?0T**PD)>R!214-(04E. M7U-43513("@D/'1T>7!E/C8L(%=(24Q%7T)/1%D@*"0\='1Y<&4^-BDI.WT- M"B`)?"!D;U]S=&UT7W-T87)T#0H@"2`@)R@G(&5X<'(@)RDG("<[)PT*("`@ M("`@("`@("`@("`@("![($1/7T-/3D0@*"0Q*2`]('1R=71H=F%L=65?8V]N M=F5RR!214-(04E.7U-4 M3513("@D/'1T>7!E/C(L($9/4E]"3T19("@D/'1T>7!E/C(I*3L-"BL)"0D) M("!B;&EP7V-H96-K7VQO;W!?;&EM:70@*"0\='1Y<&4^,BD[('T-"B`)?"!3 M5TE40T@@)R@G(&5X<'(@)RDG#0H@"0E[('-T;71?8V]U;G0K*SL-"B`)"2`@ M)#QT='EP93XD(#T@8U]S=&%R=%]C87-E("@D,RD[('T-"F1I9F8@+4YU7!E M8VLN8PT*+2TM(&=C8RTS+C(O9V-C+V,M='EP96-K+F,)5&AU($UA7!E("AR97-U M;'0I.PT*9&EF9B`M3G5R(&=C8RTS+C(O9V-C+V9L86=S+F@@9V-C+3,N,BUB M;&EP+V=C8R]F;&%G'1E'1E'1E&-E<'1I;VX-"B`@(%]5;G=I;F1?4VI,:E]&;W)C9615;G=I;F0-"B`@(%]5 M;G=I;F1?4VI,:E]297-U;64-"BL-"BL@(",@0FEG($QO;W`@26YT96=E&ET("HO#0HK#0HK(VEF9&5F($Q?8FQI<%]V M:6]L871I;VX-"BMV;VED(%]?8FQI<%]V:6]L871I;VX@*'5N'1E2!I;F9O'!L;VET M871I;VYS+B`J+PT**PT**VEN="!F;&%G7V)L:7`@/2`P.PT**VEN="!F;&%G M7V)L:7!?PT*0$`@+3$Q-3`L-B`K,3$V-2PQ,"!`0`T* M("`@($Y?*")297!O2!A;&QO8V%T:6]N M(&%T(&5N9"!O9B!R=6XB*2!]+`T*("`@>R`B=')A<'8B+"`F9FQA9U]TR)B M;&EP7W=H:6QE7VYO=%]E;6ET(BP@)G=A2!P;VEN="!O9B!C8S$L(&-C,7!L=7,L(&IC,2P@9C