Un curieux PDF
Par Nicolas COLLERY, vendredi 22 août 2008 à 10:07 :: General :: #256 :: rss
(Rédacteurs : Nicolas Collery - Sylvain Sarméjeanne - Fabien Périgaud ; Relecture : Thomas Gayet)
Introduction
Les attaques basées sur des pages Web légitimes modifiées, en incluant tout un lot de vulnérabilités spécifiques aux applications des postes clients (navigateurs, lecteur multimédias) s'inscrivent dans la durée et nécessitent du temps d'analyse.
Dernièrement, nos recherches concernant l'étude de vulnérabilités exploitées "dans la nature" nous ont mené vers le répertoire ouvert d'un site Web contenant quatre fichiers, dont nous allons maintenant partager l'analyse.
Analyse des fichiers
Regardons dans un premier temps le fichier ie.php :
Il s’agit d’un gros bloc de données, incompréhensible à première vue.
Nous pourrions le modifier à la main pour le rendre plus lisible mais il existe un outil JsDecoder qui va nous simplifier la tâche :
Ce code a visiblement été "obfusqué" pour éviter que des outils de recherche automatiques ou de protections basiques ne détectent le contenu. Ceci rend évidemment la tâche des chercheurs un peu plus difficile.
Afin d’en découvrir le contenu, nous allons utiliser le moteur JavaScript de Mozilla, SpiderMonkey, qui va interpréter ce code. Comme l’objet ‘document’ présent dans le code dans ‘document.write(r);’ n’existe pas par défaut, nous allons devoir ruser. En effet, ‘write’ va écrire dans ‘document’, le contenu de ‘r’.
- Une modification pour SpiderMonkey existe sous le nom de Caffeine Monkey et intègre la création de ‘document’.
- Didier Stevens propose également d’appliquer sa propre modification.
- Déclarons manuellement dans SpiderMonkey notre objet et certaines de ces méthodes.
Pour déclarer l’objet ‘document’, ainsi que la méthode ‘write’, José Nazario nous propose de créer une nouvelle fonction :
function MyDoc () { function write(x) { print(x); } }
et d’attribuer cette fonction à l’objet ‘document’.
document = new MyDoc();
Nous pouvons aussi simplement déclarer les deux en même temps, tout en redirigeant l’affichage de ‘write’ sur la console grâce à la commande suivante :
document={write:print}
Le script semble encore "obfusqué" par l'ajout de-ci et de-là des +nuc+ vides que nous retirons facilement.
Nous voilà au cœur du problème !
Ce script va tenter d’exploiter une faille pourtant datée de 2006, corrigée depuis bien longtemps par le bulletin Microsoft - MS06-014.
Plus d’informations sur cette vulnérabilité sont disponibles dans notre
bulletin Lexsi 6876.
L'exploit va alors tenter dans un premier temps d'exécuter ..\\S87ekhV.exe qui n’existait pas lors de cette analyse, ou dans un deuxième temps, ouvrir le fichier pdf.pdf via une iframe :
<iframe src="spl/pdf.pdf" width=1 height=1 style="display:none"></iframe>
Lors de l’analyse de l'arborescence, nous avions détecté un autre fichier, nommé op.php. Celui est simple, il contient :
Donc dans les deux cas, qu’il s’agisse de ie.php ou op.php, le fichier pdf.pdf semble contenir une autre charge utile, puisque les pages contenant les codes d'exploitation veulent le faire ouvrir.
Le document PDF malveillant
Il s'agit d'un fichier PDF apparemment vide (une page blanche).
Dans les "strings" du pdf, seule la partie suivante pourrait présenter un intérêt, mais le flux ("stream") est compressé. Nous n’en savons pas plus pour le moment.
stream JNjA X=Xv !o@Y 78g= n]PX OU|% ^[sFA^ "s5. F+RK {ebs 1b;WY ^gY[ 2;cX nu\j }vyW i6O 3 Bk=' endstream
Nous allons donc utiliser pdf toolkit pour décompresser cette partie :
$ pdftk pdf.pdf output pdf.pdf.decoded uncompress
De la sorte un fichier contenant maintenant en clair les parties précédemment compressées est créé. Les "strings" nous révèlent bien le flux que nous avions au dessus. C’est un nouveau script JavaScript :
Plaçons ce script dans un nouveau fichier :
$ cat pdf.pdf.decoded | grep function > pdf.js
Dans le script, nous aurons pris soin de remplacer la fonction "eval" par "print" pour éviter l'exécution du résultat et donc pour simplement le faire afficher (via sed ou vi par exemple : s/eval/print/g).
Invoquons spidermonkey via "js" pour afficher un shell (copier/coller), ou via :
$ js -f pdf.js
Nous avons découpé ce script en trois blocs. Nous n'allons pas décortiquer l'intégralité du script pas à pas ; procédons cependant à quelques observations :
Le code vérifie la version d'Adobe Acrobat Reader (visionneuse PDF la plus classique). Le reste du script ne s'exécutera que si la version d'Acrobat Reader est soit inférieure à 8.1.2, soit inférieure ou égale à 7, mais dans les cas d'une sous version inférieure à 1.
Ce bloc va créer une énorme chaine de caractère et va la placer dans un "collabStore" grâce aux méthodes "Collab methods" (partie spécifique, propre au PDF d'Adobe).
Les versions mentionnées ci-dessus se trouvent être vulnérables à un débordement de tampon dans le tas.
La référence de cette vulnérabilité est connue sous le nom CVE-2007-5659 et plus spécifiquement dans notre base de données sous l'identifiant
9659.
Cette chaine (d'environ 200 ko), trop grande pour tenir dans la zone mémoire allouée va écraser l'adresse de retour initialement prévue par l'adresse 0x0C0C0C0C. Cette adresse pointe sur une zone du tas du processus.
La fonction suivante, appelée avant l'exploitation, va se charger de "mettre en forme" le tas afin qu'il s'accorde aux besoins de l'attaquant.
Cette fonction se charge de remplir le tas avec de nombreuses instances du shellcode précédé de quelques milliers de NOP (opération nulle). Grâce à cette méthode, que l'on nomme heap spraying, la probabilité de retomber dans une zone de NOPs conduisant au shellcode est très forte.
L'exploitation que l'on a vu plus haut se chargeant de nous rediriger quelque part dans le tas, la probabilité de réussite de l'attaque est conséquente.
C'est le shellcode qui nous intéresse particulièrement. Le script se charge de placer le bloc suivant au bon endroit dans la mémoire :
Retirons les caractères inutiles, c'est à dire concaténons le tout :
Modifions le un peu afin qu'il devienne plus lisible pour nous.
- Eliminons le %u
- Inversons les octets de poids fort et ceux de poids faible (ex: %u0feb => eb0f)
- Transformons en hexadécimal (via une petite moulinette)
- Sauvegardons ce résultat au format binaire
Nous pourrions donner plus d'explications à ce sujet pour le faire à la main mais iDefense l'a automatisé avec shellcode2exe.
Le fichier généré puis téléchargé contient alors :
Analyse du Shellcode
Quelques instructions après le début du shellcode, nous arrivons dans une boucle chargée d'effectuer un XOR avec le reste du code, celui-ci étant pour le moment illisible :
Une fois la boucle passée, le code semble plus propice à l'analyse :
Intéressons nous à la méthodologie employée pour récupérer les adresses des fonctions de l'API Windows qui vont être utilisées pour mener à bien l'infection.
Le shellcode commence tout d'abord par la récupération de l'adresse du PEB (Process Environment Block), dans lequel se trouvent de nombreuses informations à propos du processus en cours. Pour cela, il charge l'adresse lisible à FS[30] dans le registre EAX.
Il s'en suit alors la récupération de l'adresse de PEB_LDR_DATA (PEB Loader Data), qui contient les données qui nous intéressent. Celles-ci se situant à l'offset 0xC de la structure PEB, on charge dans EAX la valeur située à EAX+0xC.
Dans la structure PEB_LDR_DATA se trouve une liste doublement chainée, nommée InInitializationOrderModuleList, de structures LDR_DATA_TABLE_ENTRY contenant des informations sur les modules chargés et initialisés au lancement du processus. C'est dans cette liste que nous pourrons trouver l'adresse de base de la bibliothèque Kernel32.dll, nous permettant ainsi d'obtenir les adresses de nombreuses fonctions de l'API Win32.
Lors du lancement du processus, les trois premiers modules chargés en mémoire sont les suivants :
- le processus
- ntdll.dll
- kernel32.dll
Seules les deux bibliothèques ont besoin d'être initialisées et se trouvent donc dans notre structure.
Il nous suffit donc d'accéder à la deuxième entrée de la liste. La première entrée est située à l'offset 0x1C de la structure PEB_LDR_DATA, on charge donc dans ESI la valeur située à EAX+1C, puis l'adresse de la seconde entrée est assignée à EAX. Il est ensuite possible de lire la valeur se trouvant à l'adresse EAX+8, correspondant à l'image base de Kernel32.dll.
Cette technique a été expliquée en 2002 par le groupe Last Stage of Delirium dans le livre blanc Win32 Assembly Components, dont une copie est disponible sur le dépôt d'Ivanlef0u.
Les structures ont été documentées, cette fois-ci en français, dans un très bon papier de la FRET.
Une fois l'adresse de Kernel32.dll obtenue, le shellcode va chercher les adresses des fonctions suivantes :
- LoadLibraryA
- WinExec
- DeleteFileA
- ExitThread
- GetSystemDirectoryA
en parcourant la liste des exports de Kernel32.dll.
Une fois l'adresse de la fonction LoadLibraryA() connue, celui-ci va maintenant charger en mémoire la bibliothèque URLMon, et rechercher l'adresse de la fonction URLDownloadToFileA, afin de préparer le téléchargement du malware.
La fonction GetSystemDirectoryA() est ensuite utilisée pour récupérer le chemin du répertoire system32, auquel est accolé le nom de fichier ~.exe, lequel est immédiatement supprimé.
Le malware est alors téléchargé vers %SYSTEM%\~.exe par le biais de la fonction précédemment recherchée, puis exécuté par un appel à WinExec(), avant que le shellcode ne se termine avec ExitThread().
L'infection a eu lieu. Voyons maintenant ce que cache le binaire exécuté.
Analyse du malware
La fonction main() du malware a le flot d'exécution suivant :
On observe :
- en bleu un appel à la fonction 403930 qui effectue un décodage XOR de son argument, suivi d'un appel à 4039db pour créer un mutex avec CreateMutexA() dont le nom précédemment XORé est "It's Me!"
- en vert un appel à CreateThread() pour créer un nouveau thread depuis 403b8b, adresse à laquelle nous posons un breakpoint
Avant la création du thread, on note un second appel à 403930 pour remettre la chaine de caractères dans son état XORé initial ; ceci empêche de retrouver le nom du mutex par un simple dump du processus.
La fonction du thread est très linéaire. Comme précédemment, on observe que les chaines de caractères sont XORées juste avant puis juste après leur utilisation, toujours grâce à la fonction 403930. Avant :
Après :
Puis les chaines sont de nouveaux encodées.
Que fait le malware ? A l'aide de la fonction SHGetValueA(), il commence par chercher la valeur contenue dans la clé HKLM\SOFTWARE\WebMoney\Path :
Il utilise ensuite PathFileExitsA() pour déterminer si le contenu de Path est un fichier qui existe.
Si le contenu de cette clé n'existe pas, la même recherche est effectuée dans HKCU\SOFTWARE\WebMoney\Path. En dernier recours, il détermine l'existence de C:\Program Files\WebMoney.exe. Si rien n'est trouvé, le programme récupère la variable $COMSPEC (contenant le chemin complet vers l'interprète de commandes) avec GetEnvironmentVariableA(), avant d'exécuter ShellExecuteExA() sur une structure lançant $COMSPEC /c echo Waiting... | del /q C:\cheminversmonmalware.exe.
Si un chemin correct vers WebMoney est trouvé, la clé HKCU\SOFTWARE\WebMoney\Certificates est supprimée avec SHDeleteKeyA().
Puis le malware crée le fichier mprapi.dll dans le répertoire contenu dans la clé Path, où il recopie un binaire depuis 401130 :
Il fait de même avec setupapi.dll (recopié depuis 402930).
Puis il recherche avec FindFirstFile() les fichiers *.groups situé dans Path, puis les supprime avec DeleteFileA() :
Enfin, le malware termine comme dans le cas précédent.
Conclusion
Cette attaque fait partie intégrante d'un pack Neosploit, sorte de kit tout-en-un pour attaquants, dont la fin du support était annoncée pour le mois de Juillet. Dans l'attaque analysée, seules trois vulnérabilités sont utilisées, mais n'oublions pas que la version "complète" en contient une dizaine.



























