Forums d'entraide informatique - Astuces - Conseils

Des experts à votre écoute pour tous vos dysfonctionnements

Vous n'êtes pas identifié.


#1 31-08-2008 22:51:08

Admin
Administrateur
Date d'inscription: 30-07-2008
Messages: 683

Recherche de vulnérabilités à l'aide du fuzzing

Dans cet article, nous vous proposons une courte introduction au fuzzing, un moyen de découvrir relativement rapidement des erreurs dans des programmes. La méthodologie est simple : des données quasi » conformes au protocole employé sont générées aléatoirement et envoyées au système à tester, permettant ainsi de jauger la réaction du programme à ces entrées inhabituelles. Nous vous présentons le fuzzing grâce à quelques exemples de base, tout en donnant un aperçu de la manière de l'employer pour détecter des faiblesses dans des programmes.
Introduction
Le fuzzing est une méthode par laquelle on peut assez rapidement traquer des vulnérabilités dans des programmes. La pratique en est simple : en premier lieu, on génère des données aléatoires, qui sont à peu près conformes au protocole. Ces données peuvent être par exemple de très longues entrées (telles que 2342 x «A»), des indications de format (comme «%n%n%n%n»), des limites de valeurs d'entier (-1 ou Oxffffffff) ou des caractères Unicode. Celles-ci ne sont pas totalement aléatoires, mais correspondent grosso modo aux spécifications du protocole. Elles doivent obéir en gros à la logique de programmation du système à tester, en particulier ses limites dans les opérations de parsing. Si l'on envoyait au système des données totalement fantaisistes, il rejetterait certainement ces entrées dès qu'il remarquerait qu'elles ne correspondent pas à ce qui est attendu. Nous choisissons plutôt des données semi-valides, qu'un parser ne refusera pas tout de suite, mais qui pourront cependant provoquer assez de problèmes. Comme l'ont démontré de nombreux exemples par le passé, le parser d'un système est souvent le maillon faible
Ethereal (http://www.ethereal.comn a déjà détecté des erreurs dans les nombreux parsers pour paquets réseaux. Par exemple, avec la version 0.10.13, vingt vulnérabilités furent en tout résolues, qui concernaient le parser du protocole réseau
▪    Dans le parser de Snort, une vulnérabilité critique
BackOrifice a été découverte à la mi-octobre 2005, qui autorisait une prise de contrôle à distance (Remote Command Execution) [Snort05]
▪    Beaucoup de navigateurs plantent à cause de pages web
complexes, car le parser provoque des erreurs lors du rendu. Mais nous y reviendrons...
Lors de la deuxième étape, on envoie ces données au système à tester. On entend ici système au sens large du terme: On peut tester, à l'aide du fuzzing, une application client ou serveur, mais un système d'exploitation ou un matériel peuvent également être mis à l'épreuve.
On observe après coup comment le système réagit. Au mieux, il plante et l'on a découvert un bogue. Mais il se peut aussi que le système ait un énorme et soudain besoin de ressources mémoire ou que la charge du processeur bondisse dans le rouge. Tous deux prouvent que l'interprétation de nos données semi-valides nous permet d'avoir une influence sur son bon fonctionnement même s'il ne peut s'agir « que » d'une faiblesse de type déni de service. Dans tous les cas, on sauvegarde les données envoyées pour une analyse ultérieure et on recommence du début. On effectue la démarche aussi si le système n'a pas réagi anormalement à nos entrées. La figure I clarifie le processus complet.
Typiquement, on trouve à l'aide du fuzzing des faiblesses dans les fonctions de parsing ou de formatage. Mais l'on peut aussi, à l'aide de cette technique, découvrir des erreurs dans la gestion de la mémoire ou dans d'autres parties des programmes. Les vulnérabilités ainsi découvertes peuvent être alors utilisées pour acquérir le contrôle complet du programme testé. La création d'un code malicieux (typiquement un exploit) fonctionnel est l'étape suivante du fuzzing.
L'article de K. Kortchinsky de ce dossier aborde plus précisément la création de codes fiables. Bien sûr, toutes les faiblesses découvertes par fuzzing ne sont pas des vulnérabilités critiques, mais souvent certaines vulnérabilités rencontrées pourraient être exploitées par un individu malveillant. Un simple crash est déjà intéressant en lui-même, en particulier s'il s'agit par exemple de celui d'un système de détection d'intrusion ou d'un scanner anti-virus...
Dans la suite de cet article nous allons d'abord expliquer plus en détail la procédure fondamentale du fuzzing, ceci à l'aide d'un fuzzer pour client IRC: Nous vous présentons chaque étape et décrivons, à l'aide d'un exemple, comment l'on applique avec un programme les principes de la recherche de vulnérabilités.
Les faiblesses découvertes par nos soins dans le client IRC sont bien entendu présentées ; cependant nous n'indiquons pas si elles sont exploitables, ni comment elles le seraient. Ensuite, nous vous montrons grâce à quelques exemples supplémentaires de quelle manière le fuzzing peut être encore utilisé.
Cet article se conclut par une courte introduction à un framework qui permet de simplifier le fuzzing. À propos de Protos [Protos], certainement le projet le plus connu dans ce domaine, un article spécifique vous livrera des informations de fond tout en vous permettant de jeter un coup d'œil au travail de l'équipe.
Le fuzzing de clients IRC
Comme exemple didactique des principes du fuzzing, nous allons étudier un fuzzer pour clients IRC. Pour cela, observons tout d'abord rapidement le protocole IRC. Comme mentionné plus haut, le fuzzing suit globalement les spécifications du protocole pour coller en gros à la logique du programme. Alors qu'est exactement l'1RC et comment fonctionne ce protocole ? 1RC sont les initiales de « Internet Relay Chat » ; celui-ci est développé depuis 1988.
C'est un protocole d'échange de messages texte et les premières spécifications sont fixées dans la RFC 1459. La plupart des commandes et mécanismes qui y sont décrits sont encore valables ; cependant, les RFCs 2810-2813 proposent une version actualisée du protocole. Sans grande importance dans la pratique, puisque aucune implémentation IRC ne les supporte pleinement.
La structure du protocole IRC est comparable à celle des programmes de messageries instantanées comme Jabber. AOL Instant Messenger (AIM) ou ICQ, tout en montrant quelques différences. I RC est caractérisé par une architecture client- serveur : il y a donc un serveur central (ou un réseau de serveurs), auquel se connectent les clients.
La communication entre serveur et client est basée sur des messages et cela en clair ; un codage SSL est optionnel. Après le contact TCP 3-Way-Handshake s'échangent entre le client et le serveur des messages d'un maximum de 510 caractères (en excluant le retour chariot et le Line Feed) et qui doivent avoir cette structure : tout d'abord l'expéditeur (préfixe), ensuite la commande à proprement parler, puis d'éventuels paramètres supplémentaires.
La réponse du serveur à ce message d'un client est constituée de son préfixe, d'un code d'état de trois chiffres et du nom du client. L'utilisation du code d'état est similaire à d'autres protocoles en clair comme POP3, HTTP ou encore SMTP. Le client C envoie le premier message au serveur S:
-> S: MICK user
C -> 5: USER user 0 9 :realname
Le client s'enregistre à l'aide de ce message avec le pseudo désiré et donne également des informations sur l'utilisateur. Dans l'exemple suivant, nous voyons trois messages qui s'échangent lors du contact initial. Le préfixe de ce serveur dans ce cas est :'rc.matrix.org, un serveur du réseau Rizon. Suivent ensuite les différents codes d'état et le pseudo du client, de même que les informations relatives aux codes.
S -> C: :Irc.matr x.org 001 user :Welcome tu the Rizon Internet Relay Chat hetwork user
S -> C: :irc.rmatrix.org 002 user :Your hast is irc.matrix.org[irc.matrix. erg/6667], running version PleXusIRCa-2.0.9p1
S-> C: :irc.matrix.org on user :This server was created Sun Sep 25 2005 ut 01:33:15 GMT
Le serveur transmet d'autres informations au client, comme 251 (RPL_LUSERCL1ENT — le nombre d'utilisateurs (in)visibles sur le serveur) ou 254 (RPL_LUSERCHANNELS — le nombre actuel de canaux). Cette phase s'achève normalement avec le code 376 (RPL_ENDOFMOTD), qui montre la fin du Message ofthe Day.
Jusque-là, le serveur a envoyé plus de dix messages au client, que celui-ci doit parser, seulement afin de comprendre l'état du serveur. Ensuite, il peut également envoyer des données au client, que celui-ci devra encore assimiler.
Et c'est entre ces deux extrémités que nous nous plaçons avec notre fuzzer : nous envoyons à un client IRC des messages conformes au protocole, mais qui contiennent néanmoins de quoi l'amener à un plantage.
Dans ce qui suit, nous vous présentons la technique du fuzzing à l'aide du programme ircfuzz.c, de Ilja van Sprundel [ircfuzz05]. Ce programme montre très clairement comment un fuzzer est construit et quel est son principe de fonctionnement.
Comment donc allons-nous faire ? Tout d'abord, nous ouvrons une socket TCP auquel le client 1RC peut se connecter, en principe le port TCP 6667 ou le port TCP 7000. Ensuite, nous lançons une boucle d'une durée infinie, le fuzzing comme tel.
Dans cette boucle sont envoyées au client des données aléatoires pour tester si sa fonction de parsing tient bon, même avec des entrées inhabituelles. Nous utilisons ici le fait que le programmeur d'un client s'attend normalement à ce que le serveur lui transmette des données conformes au protocole — mais pourquoi n'y aurait-il pas de serveur mal intentionné ?
Avec la fonction accept ( ) nous démarrons une connexion et nous empêchons la socket de se bloquer avec futl(). Cela sert en particulier à ne pas laisser troubler la communication avec le client à tester par des fonctions de blocage d'entrée/sortie. Après le traitement des messages NICK et USER envoyés par le client commence véritablement le fuzzing à proprement parler.
En tout, nous testons cinq différentes sortes d'entrées
Cas 0: Une longue chaîne aléatoire, tant par son contenu que dans sa longueur
MM Cas I: Une chaîne typique pour utiliser la vulnérabilité Formatstring
MI Cas 2: Un grand nombre aléatoire d'arguments Cas 3: Une très longue chaîne alphanumérique
Cas 4: Une longue chaîne aléatoire de caractères
Le code ressemble à ceci :
void fuzz (int fd) {
char huf[100000];
int raw = rand () % 1000, end, i, a, al; if (!(rand () % 10))
raw = -raw;
switch (rand 0 % 5) {
case 0:    // long string
end = (rand () % 12000) + 10;
for (i = 0; i < end; i++)
do ( buf[i] = rand () % 256; 1
chie (buf[i] == '1n. Il buf[i] == '\r' II buf[i] == '10');
1
break;
case I:    frntbug
strcpy (buf, "%n%n%n%n%n%n%n%n%n%n%n%n%n%n%n%n");
break;
case 2:    /1 lots of arguments
a = (rand () % 230) + 20;
al = (rand () % 750) f a;
buf[0] = '10';
for(i=0;i<a;i++){
char t[100];
int j = 0;
far (j = 0; j < al; j++)
t[j] = arr[rand 0 % sizeof (arr)];
t[,1] = '10';
strcat (buf, t);
strcat (kif, " ");
1
break;
Case 3:    // long alpha str
end = (rand () % 12000) + 10;
for (i = 0; i < end; i++) (
buf[i] = arr[rand () % sizeof (arr)];
1
buf[i] = '10';
break;
cane 4:    // rendra stuff, rodoir length
end = (rand () % 2500);
sprintf (buf, ":aaa %d ", raw);
al = strlen (buf);
for (i = 0; i < end; i++)
buf[i + al] = rand () % 255;
buf[i + ai] = '1r';
buf[i + al + 1] = \n';
write (fi, but i + al + 2);
return;
1
if (rand 0 % 5)
netprintf (fd, ":%s %03d %s :Dots", gen_hostname (), raw, Nnick, but (rand 0 t 100) ? "1r1ir    "");
else if (rand () % 3)
netprintf (fd, "Cs %s %s :%s%s", gen_hostname I), gen_command (), Nnick, buf, (rand    % 100) ? "Irin" : "");
else
netprintf (fd, ":%s %s te ;1x01%s %s%s%s", gen_hostname 0, "PRIVMSG", Nnick, gen submsg    tuf, (rand (I % 20) ? "1x01" : "",
(rand 0 % 100) ?    :
return;
La fonction netprintf() transmet les données remises par send() à la socket et suit les indications de formatage. Les fonctions gen_hostneme(), gen_coinmand0 et gen_ submsg() créent les entrées de fuzzing pour les différentes parties de message prévues par le protocole IRC.
En particulier, nous voulons mettre en valeur l'implémentation des fonctions gen_submsg() et gen_hostname(), par lesquelles
seront générées aléatoirement des commandes typiques d'IRC ou un nom d'hôte :
char *submsg[] = {"OCC SENO'', "DCC CHAT", "DCC (MIT'', "DCC OFFER'', "XDCC "XOCC SENO", "FINGER'', "VERSION", "USERINFO", 'CLIENTINFO", "TIME", "FINGER",
"CDCC SENO', "CDCC LIST", 'CNC OMIT", "XOCC    "DCC LIST", "XOCC
OFFER", "XOCC CHAT', "WC 0FFER", "CDCC OFFER", "MC°, "XCCC", 'CEC", NULL };
char * gen_suhmsg () (
if (rand () % 26) {
return submsg[rand () % 25];
else (
return ""; 1/ test for empty string
1
char * gen_hostname () {
static char b[1500];
int len = rand () % 1500 / ((rand (1 % 3) + 1); rnemset (h, 'a', len);
b[len] = '10';
return b;
Maintenant, nous disposons de tous les éléments pour assembler notre fuzzer : dans une boucle infinie, nous recevons toutes les communications entrantes et préparons avec nos routines de fuzzing les messages que nous allons retourner au client.
Nous attendons à chaque fois que le client nous ait envoyé les messages NICK et USER, puis nous lui transmettons les codes de connexions IRC. Au cas où le client résiste lors de cette phase, d'autres données de notre fuzzer tentent de le mener au plantage. Le pseudo-programme ressemble à ceci :
if (nick && user {
init++;
netprintf (cfd, ":aaa 001 %s :a1r1n"
":aaa 002 %s :a1r1n" ":aaa 003 %s :alrin" ":aaa 004 %s :a1r1n" ":aaa 005 %s :a\r\n" ":aaa 251 %s :a1r1n" ":aaa 252 %s :a1r1n" ":aaa 253 %s :a1r1n" ":aaa 254 %s :a\r\n" ":aaa 255 Os :a1r1n"
.,:aaa 375 os :a\r\no
":aaa 372 %s :a1r1n"
":aaa 376 %s :a1r\n", Nnick, Nnick, Nnick, Nnick, Nnick, Nnick, Nnick, Nnick, Nnick, Nnick, Nnick, Nnick, Nnick);
1
if (finit) { fuzz(cfd);
1
La façon dont un client IRC réagit à des entrées erronées d'un
serveur IRC peut être testée avec l'aide de ce programme. Pour cela, on démarre le client IRC et on le configure de telle manière qu'il utilise local host bzw 12 7.0.21.1 comme serveur.
Le client doit essayer de se connecter à notre fuzzer et ainsi débute le processus de fuzzing en lui-même : notre fuzzer génère des entrées aléatoires et les envoie au client. Celui-ci tente de parser les données entrantes et dérape parfois : soit un plantage, ou bien un comportement bizarre, comme une consommation anormalement élevée de ressources.
Le tableau suivant démontre à quel point ces tentatives sont efficaces. Tous les clients IRC ci-contre ont pu être crashés par
cette méthode et ont montré diverses sortes de vulnérabilités. Seul lrssi a résisté jusqu'ici à toutes les tentatives de fuzzing. On a seulement observé chez ce client IRC quelques fuites de mémoire, de même qu'une augmentation constante de la consommation de ressources pendant le fuzzing.
Opera et d'autres encore sous Mac OS X n'ont guère résisté longtemps à des tentatives de fuzzing. h semblerait que l'implémentation sous Mac OS X éprouve le besoin d'être revue. On a pu, grâce à ces utilitaires, détecter également des vulnérabilités parmi les navigateurs disponibles sous Linux
TABLEAU1        xchat (2.4.1)    kvirc (3.2.0)    ircii(irc7-20040820)    eggdrop (1.6.17)
    mIRC (6.16)               
8itchX (1.1-final)                   
epic-4 (2.2)    ninja (1.5.9pre12)    ernech (2.8.5.1)    Virc (2.0 rc5)    TurboIRC (6)    leafchat (1.761)
iRC (0.16)    conversation (2.14)    colloquy (2.0 (2DI6))    snak (5.0.2)    ircle (3.1.2)    ircat (2.0.3)
darkbot (7f3)    bersirc (2.2.13)    Scrollz (1.9.5)    IM2    pirch98    Trillian (3.!)
Microsoft Comic Chat (2.5)    icechat (5.50)    centericq (4.20.0)    uirc (1.3)    weechat (0.1.3)    rhapsody (0.25b)
kmyirc (0.2.9)    bnirc (0.2.9)    bobot++ (2.1.8)    kwirc (0.1.0)    nwirc (0.7.8)    kopete (0.9.2)
Aperçu des dents ,RC« fuzzés ., avec succès                   
Certains clients IRC cibles ont un comportement intéressant pendant leur fuzzing. Xchat par exemple a provoqué ces messages d'erreur
$ xchat
*** MAT WARNING: Buffer overflow - shit server! *** XCHAT WAMIhG: Buffer overflow - shit server!
[...]
*** )(CHAT WARH1HG: Buffer overflow - shit server! Speicherzugriffsfehier (core dumped)
En cas de crash du client IRC, nous voulons bien stIr savoir quelles entrées l'ont provoqué. Il y a en principe deux possibilités pour le faire : ou nous journalisons tout en interne, le programme de fuzzing rédige lui-même un logfile. Ou bien nous procédons à un enregistrement externe, en l'occurrence ici avec l'utilitaire tethereal. Celui-ci a l'avantage dans ce cas de pouvoir se servir des données qui ont amené un client IRC à planter pour tester un autre client IRC sur cette même vulnérabilité. Ce qui peut être réalisé avec un utilitaire comme tcpreplay. Il faut néanmoins s'assurer de faire journaliser tethereal en mode Ringbuffer, car sinon les fichiers logs peuvent devenir énormes...
Le fuzzing d'autres applications clientes
En plus du fuzzing de clients IRC, on peut employer cette technique pour débusquer les vulnérabilités d'autres sortes de programmes. Un exemple connu est MangleMe de Michael Zalewski [mangleme04]. Ce programme conçoit de complexes sites Web, comprenant des erreurs typiques. Une implémentation en Python (htmler.py de nd[htmier.py04]) existe également. Nous l'avons un tantinet modifié dans l'optique de couvrir une zone d'entrées plus large. Ce programme réalise de manière aléatoire une page HTML difficile, qui sera ouverte dans un navigateur. Ces utilitaires ont permis de découvrir de nombreuses erreurs dans Internet Explorer ainsi que dans d'autres navigateurs. Ils sont encore utiles aujourd'hui... Ainsi, Safari, le navigateur standard de Max OS X. présente quelques vulnérabilités, comme le prouve un rapide test. (fig. 2)
Firefox 1.0.7, la dernière version de Firefox (Firefox 1.5 Beta 2), Mozilla 1.7.12, Konqueror 3.4 et Epiphany 1.7.6 ont été vaincus par le fuzzing.
Les erreurs provoquées ainsi vont du plantage (typiquement par SIGSEV) aux boucles sans fin jusqu'à la consommation exagérée de ressources mémoire. Voici, à titre d'exemple, le journal strace d'une tentative réussie de fuzzing sur Firefox
writev(3, [{112a1nL11120010H111200101361101v1011\ 012101013013741377".... 24},
r137710101010137713771377101377137713711013771377137710"..., 1060411, 2) = 10628 stat54("iusr/1ib/mozi11a-firefox/components/necko_caohe.xpt", {st_mode=S_ 1FREGI0644, st_size=2180, ...}) = 0
open("Jusr/lib/mozilia-firefoxicomponentstrecko_cacheApt', O_ROONLYILLARGEFILE) = 33 read(33, 4XPCCill\nTypeLerin132111210116101011012041010101101011'..., 2180)    2180
close(33)
gettimeofday({11325320513, 2012601, HALL) = 0
gettimecfday({1132532058, 2017881, HALL) = 0
5121E1V (Segmentation fau)t) A 0 (0) --- Kink('/home/thoi.mozillaifirefox/ufyrgvit.tho/lock')
rt_slaction(SIGSER {SIG_DFL), !.!ULL, 9) =
rt_sigprocmask(SIIUNBLOCK, [SEGV], HULL, 8) =
tgkill(17875, 17875, 512501V)    =
SIGSE9 (Segmentation failli) @ 0 (0) --- ++1- killed by SIGSEGV +++
On peut rapidement débusquer de nouvelles erreurs même avec un fuzzer légèrement modifié. Pour atteindre en pratique de semblables résultats, il suffit simplement de télécharger htmler.py de http://felinemenace.orgi—nd/htmler.py et de le modifier, par exemple en y rajoutant des tags ou des extensions, de tester par fuzzing d'autres caractères spéciaux, ou bien encore d'augmenter les valeurs de durée. Des possibilités supplémentaires de « fuzzer » un navigateur HTML sont, par exemple :
1111111« Fuzzer » l'en-tête HTTP, i.e. produire un en-tête HTTP défectueuse ;
« Fuzzer » le traitement du JavaScript, i.e. créer des applications JavaScript aléatoires ;
▪    « Fuzzer » le traitement du FTP (ftp://), de Usenet (news://)
ou de l'à propos (about:) ;
▪    « Fuzzer » les plugins, comme Java, RealPlayer, Flash ou
Shockwave.
D'autres applications peuvent également être mises à l'épreuve par le fuzzing. On modifiera pour cela des fichiers habituellement pris en charge par l'application, permettant ainsi d'observer la réaction du programme à ces données malveillantes. Grossièrement, on peut différencier deux formes de fuzzer : les intelligents et les simples.
Les fuzzer intelligents savent comment est constitué le format du fichier et peuvent suivre les particularités d'un protocole. Cela peut être le cas quand le format de fichier enregistre la longueur d'un champ donné comme entier — nous essaierons par fuzzing d'avoir précisément une influence sur ce champ et les données qu'il contient.
Les fuzzers simples au contraire ne connaissent pas les formats de fichiers et modifient aléatoirement des séries d'octets dans leurs entrées. Ce procédé relativement simple peut malgré tout, bien souvent, mener rapidement au succès escompté... Nous allons maintenant observer de plus près quelques-uns de ces fuzzers simples.
Comme lors du fuzzing des clients IRC, nous créons des données set-ni-valides, qui ressemblent beaucoup aux entrées normales, mais qui cependant diffèrent légèrement des spécifications. Cela peut être la modification aléatoire de quelques octets dans l'entête du fichier ou l'introduction de longues séquences d'octets dans le corps du fichier.
Nous allons, à titre d'exemple, employer la technique de la recherche locale : on démarre par un fichier d'entrée valide et modifie au hasard quelques octets au sein du fichier. Dans un deuxième temps, nous testons si le fichier manipulé amène le programme à des réactions inhabituelles. Si ce n'est pas le cas, une nouvelle série aléatoire d'octets est modifiée.
Un nombre déterminé d'essais est d'abord effectué — puis l'on recommence du début avec un fichier de départ valide. Si l'on découvre lors de la deuxième étape un comportement anormal du programme, alors le fichier est sauvegardé pour une analyse ultérieure.
Il peut également servir de base de départ à d'autres tentatives de fuzzing dans l'optique de détecter encore plus d'anomalies dans ce programme.
Un exemple permet d'illustrer la technique employée : nous allons observer dans ce qui suit des programmes qui traitent les fichiers mp3. Ici nous modifions au hasard une longue séquence d'octets dans un fichier mp3 intact. Nous ne nous limitons donc pas au test de l'en-tête mp3, de chaque {rames mp3 ou des tags ID3 à la fin du fichier, mais voulons éprouver dans son ensemble la structure du fichier.
Les résultats prouvent que certains lecteurs mp3 ont définitivement besoin de voir leur implémentation du parsing améliorée : lors du test, il n'a fallu que quelques minutes pour planter avec succès les programmes mpgl 23, mpg32 I et mocp. Le programme mp3-decoder s'est laissé également facilement « fuzzer » — quelques centaines d'essais ont ici suffi. Dans ce programme, une autre forme de crash a pu être provoquée :
$ mp3-decoder test2-2902.mp3
high Performance MPEG 1.0/2.0/2.5 Audio Player for Layer 1, 2, and 3, Version 11.59g (2002/03/23). Written and copyrights by Joe Drew.
Uses code from yarious people. See 'README' for more!
THIS SOFTWARE COMES WITH ABSOLUTELY HO WARRANTY! USE AT YOUR OWN RISK!
Playing MPEG stream from test2.2900,mp3
MPEG 1.0 layer III, 128 kbit/s, 44100 Hz mono [0:00] Decoding of test2-2900.mp3 finished. Playing MPEG stream from test2-2901.mp3 [0:00] Decoding of test2-2901.rnp3 finished. [..,]
Playing MPEG stream from test2-2908.mp3 [0:00] Decoding of test2-2908.mp3 finished. Playing MPEG stream from test2-2909.rip3
*** glibc detected *** double free or corruption (!prev?: 0x08056dd8 ***
Aborted
Malheureusement, Xmms ne s'est pas laissé crasher — il a survécu sans difficulté plusieurs jours d'affilée aux tests comprenant des milliers d'entrées différentes. Par cette forme simple de fuzzing, il a été impossible d'identifier des vulnérabilités chez Xmms, mais la conception d'un fuzzer plus intelligent aurait peut-être conduit au succès.
Un meilleur fuzzer créerait des fichiers mp3 conformes au protocole, mais incluant des vulnérabilités précises, comme par exemple un fichier mp3 valide mais comprenant des erreurs lors de la compression sans perte ou du codage Huffman...
Parallèlement aux lecteurs logiciels, le fuzzing de l'iPod ou d'un autre lecteur matériel est possible. Nous ne l'aborderons cependant pas dans le cadre de cet article — l'un de nos auteurs a déjà assez de problèmes avec son iPod :-)
Une procédure similaire est envisageable également pour d'autres formats multimédias. Les techniques présentées antérieurement ont été utilisées pour modifier aléatoirement différents formats de films et ainsi tester les vulnérabilités de programmes comme Mplayer.
Ce dernier, en particulier, se prête bien à ce genre de tests de
stress : grâce à l'option -va nul] et -ao null, il se transforme
simplement en un lecteur multimédia qui ne crée pas de sortie,
mais qui ne parse que les données d'entrée. Nous avons testé diverses sortes de formats d'entrée, par exemple des fichiers QuickTime et RealMedia. Dans ce programme, plusieurs fonctions ont été amenées à planter :
rlayer interruptee 1:1 signa': 11 in module: demux_open
* MPlayer interrupted by signal 11 in module: decode_yideo
* MPlayer interrupted by signal 11 in module: yideo_read_properties
En dehors de cela, les tests démontrent que la gestion de mémoire de Mplayer subit des fuites — le besoin en ressources augmente foncièrement à chaque nouveau test. Le fuzzing de Xine n'est de loin pas aussi simple : le fait que Xine ouvre apparemment toujours une fenêtre ralentit naturellement le fuzzing.
Et dans le cas d'entrées que le programme déclare comme défectueuses, une boite de dialogue prévient l'utilisateur. On peut oublier un fuzzing automatique de ce lecteur multimédia...
Par hasard, une simple erreur a été découverte dans un autre programme : aaxine. Celui-ci traite incorrectement les paramètres trop longs et plante si leur longueur dépasse les 1800 caractères (selon le système, bien que plus de 3000 les conduisent tous au crash) :
auxine 'perl -e "print 'A ' x 3090"' Segmentation fault
En dehors des programmes d'édition audio et vidéo, n'importe quelle sorte d'application peut être scrutée par une telle technique, entre autres : logiciels de retouche d'images, visionneuses PDF et suites Office, pour ne citer qu'eux.
Le fuzzing d'applications serveur
De même que pour les applications client, les vulnérabilités des applications serveur peuvent être recherchées par le biais de cette technique. Nous ne donnons ici qu'un court exemple de ce genre de test, puisque la méthode fondamentale ne diffère pas de celle exposée dans la section précédente.
Nous allons nous servir, à titre d'exemple de serveur à « fuzzer », de NTPd, le Network Time Protocol daemon. Et au lieu de le faire manuellement, nous allons employer cette fois un utilitaire qui possède les capacités nécessaires à cette méthode : Scapy (http://secdev.org/projectstscapy/).
Scapy est un utilitaire universel de manipulation de paquets réseaux. Ses possibilités dépassant de loin le cadre cet article, nous vous renvoyons à sa documentation [scapy05] et nous nous contentons de vous présenter brièvement ses atouts pour le fuzzing.
Depuis la version 1.0.0.42, scapy intègre les opérations de fuzzing, invoquées par la commande fiizz O. En guise d'exemple, on peut tester les vulnérabilités d'un serveur NTPd local avec ces quelques lignes :
>>> conf.L3socket.L3ReSocket
>» senCIP(Ost="127.0.0.1")/11DP(fifuzz(NTP()),looprl)
Il nous faut d'abord configure!. Scapy, de manière à ce que le kernel se charge par Raw-Sockets de la couche de liaison de données, c'est-à-dire que les paquets soient réellement envoyés par l'interface Loopback.
Puis nous envoyons à notre propre machine des paquets UDP valides, dont la charge utile consiste en paquets NTP aléatoires, Scapy assume ici le rôle d'un fuzzer intelligent, puisqu'il connaît la structure de NTP et remplit chaque champ de données semivalides dans ce protocole. Il est également possible de réduire l'étendue de l'investigation, en limitant par exemple grâce à fuzz(NTP(mode=3, version=3)) l'envoi de paquets client NTP à la version 3 du protocole.
Mais nos tests n'ont hélas pu réussir à crasher ni NTPd, ni OpenNTPd. Chez NTPd, on a pu cependant observer un comportement intéressant : si l'on supervise le daemon lors du fuzzing avec gdb ou strate, il peut rester stable pendant plusieurs jours. Dès qu'on y rattache ltrace, le daemon a toutes les chances de planter dans les 5000 prochains paquets — un fait qui est reproductible. La cause exacte de ce phénomène n'a pu encore étre déterminée, mais nous persévérons dans ce but :-)
Avec d'autres applications serveur, la procédure est similaire au cas précédent. Nous transmettons des données semi-valides au serveur et étudions comment celui-ci réagit à ces entrées. Cela s'applique aux serveurs HTTP (fuzzing des requêtes HTTP), NFS (génération aléatoire de paramètres) et à toutes les diverses sortes de serveurs. Une étude poussée de SSH a été réalisée grâce à SSHredder [SSHredderO2], laquelle a décelé plusieurs bogues dans les implémentations SSH.
Les frameworks de fuzzing
Mis à part les utilitaires spécifiques présentés auparavant, il existe également des frameworks génériques qui allègent considérablement les tâches de fuzzing. L'exemple suivant va nous permettre de vous présenter brièvement l'un de ces frameworks et d'évoquer quelques implémentations supplémentaires.
SPIKE de lmmunity [SPIKE] est un framework de fuzzing qui se prête particulièrement à l'évaluation d'applications serveur. Il est écrit en C et inclut un grand nombre d'outils, comme des scripts de test pour MSRPC, HTTP ou SMTP. SPIKE fonctionne d'après une méthode de blocs, ce qui procure certains avantages [Block04]. L'élément de base d'un tel projet est appelé un SPIKE. Il s'agit d'une description de haut niveau, complété de méta- informations de fuzzing. Observons maintenant ce que cela donne concrètement avec un serveur Web Sambar [Sambar04].
s_string("POST /search/results.stm HTTP/1,11r1 n");
s_string(lost: 1.1,SUDGEOPU\r\n"); C
s_strine'Cantent-Length; "I; s_blocksize_string("past",7); s_string("1r111r\n"); s_block_start("post"); 0
s_string("spage--Uindexnane.docs&query=");
s_string_variable("MEEP"); D
s_stririg("&stylerpage"); D
s_string("Irlerlo"); s_block_and("post");
s_string permet d'invoquer des chaînes d'une taille fixe et définit grossièrement la structure des données à envoyer. Avec s_bleck_ start/s_block_end, on démarre ou termine au choix un bloc. Dans ce bloc, nous définissons l'une des entrées comme chaîne d'une longueur variable (s_strimg variable), notre fuzzing se concentrant par conséquent sur cet unique paramètre.
Pour nous permettre de toujours envoyer une requête HTTPPOST valide, nous nous devons à chaque fois d'adapter notre Content-Lerigth si nous ne voulons pas nous faire rejeter immédiatement par le serveur Web.
Cela est rendu possible par la fonction s bi ocksize string, qui à la fin du bloc "post" introduit la longueur correcte. Un tel SPIKE suffit comme description, le frarnework pouvant se charger du fuzzing à proprement parler — dans notre cas c'est le paramètre query qui sera « fuzzé » par des chaînes de longueurs variables. L'utilisation de SPIKF permet donc de simplifier nos efforts: Après avoir réalisé une description de haut niveau des données, le framework se charge lui-même de conduire les tests.
II existe d'autres frameworks, chacun adapté à un cas de figure particulier. Voila un rapide survol et un descriptif des plus célèbres frameworks en la matière

Peach : Framework de fuzzing en Python avec quelques exemples de mise en situation
ffl Smudge (Software Mutilation Utility and Data Generation Engine): Implémentation proche de SPIKE en Python supportant un grand nombre de protocoles réseaux
SPIKEfile : Adaptation de SPIKE, pour le fuzzing de formats de fichiers
ffl Mangle : Proche de la méthode de fuzzing simple de formats
multimédias, en particulier les en-têtes de fichiers ;
COMbust : Fuzzer binaire de test d'objects COM.
1 rasse earnet ra5e. mla Du m'es, ta cette fol
bre ! *Me dans ere.uren.
Conclusion
Dans cet article nous vous avons présenté les principes fondamentaux du fuzzing. Nous avons essayé d'illustrer comment l'employer pour découvrir assez rapidement des vulnérabilités dans des programmes. Quelques exemples ont pu prouver que cette méthode est efficace et que l'on pouvait effectivement débusquer des faiblesses chez de nombreux clients. Ce qui cependant n'implique naturellement pas que chacun de ces bogues soit également une vulnérabilité critique — une analyse plus poussée de chaque anomalie est incontournable.
Les démonstrations de fuzzing d'applications client ou serveur présentées ici peuvent également être employées pour les tests d'autres systèmes : l'envoi par exemple de données semi-valides à un NetworkStack, la création aléatoire d'arguments d'appels système ou de librairies, le fuzzing d'un descripteur de fichier, ou encore l'émission aléatoire de signaux à un processus en cours ou bien la manipulation des variables d'environnement qu'un programme emploie. L'imagination n'a ici pas de limites — soyez créatif et suivez de nouvelles voies :-) Le fuzzing est plutôt une méthode qu'une implémentation concrète et pour chaque problème, il faut concevoir un fuzzer adapté (intelligent ou simple). Bien sûr, des frameworks génériques comme SPIKE ou Peach peuvent soulager vos efforts...
On peut employer le fuzzing pour une recherche rapide de vulnérabilités dans un programme. Par rapport à l'analyse binaire ou de code source, ce procédé est terriblement plus rapide et livre souvent d'étonnants résultats. Néanmoins, le fuzzing n'est pas non plus une panacée. Il y a des bogues que l'on détecte rapidement par cette technique, mais aussi certains qui sont impossibles à « fuzzer ». Dans l'analyse de code source, là aussi, on peut rapidement arriver au but, ou n'avoir atteint aucun résultat après des heures d'essais infructueux. La meilleure recette est donc une combinaison des deux procédures. En premier lieu, tenter de récolter avec le fuzzing les « fruits pendants » puis faire subir aux parties critiques d'un programme une recherche d'erreurs approfondie par analyse du code source ou des fichiers binaires.


Cordialement

L'équipe Parisdepannage.fr

Hors ligne

 

Pied de page des forums


Copyright Parisdepannage.fr


Fermer la fenètre