Obfuscation, la bête noir des reversers - Big
Transcription
Obfuscation, la bête noir des reversers - Big
Obfuscation, la bête noir des reversers Squallsurf Decembre 2008 Sous licence Creative Commons CC-BY-SA Contents 1 Introduction 2 2 Le polymorphisme, ou le chiffrement 2.1 Introduction et historique . . . . . . 2.2 Exemple de polymorphisme . . . . . 2.3 Améliorations . . . . . . . . . . . . . par l’aléatoire . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 Le métmorphisme, ou la modification par 3.1 Introduction et explications sommaires . . 3.2 Analyse des faits et théorie avancée . . . . 3.3 Limites d’une telle technique . . . . . . . équivalence . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 3 3 5 6 6 6 9 4 La permutation de code, ou la furtivité par le mélange 10 4.1 Introduction et problèmatique . . . . . . . . . . . . . . . . . . . . 10 4.2 Théorie et pratique . . . . . . . . . . . . . . . . . . . . . . . . . . 10 4.3 Points forts, faiblesses et améliorations . . . . . . . . . . . . . . . 12 5 Outroduction : vers l’infini et au dela !! 13 6 Liens et remerciements 14 1 1 Introduction Premièrement sachez que ce document n’as aucunement la prétention de répondre à toutes les questions imaginables sur la modification dynamique de code, il faudrait une bible de la taille des manuels intel pour y répondre un tant soit peu correctement. :-) Ce document essaieras donc simplement de vous expliquer les différentes formes de modification dynamique, leurs méthodes ainsi que les différentes ressources disponible à ce sujet. Avant d’entrée dans le vif du sujet, il me semble judicieu de définir les termes qui seront ci-dessous employés afin d’éviter toute confusion. Les définitions nous permettrons de donner un sens à un simple mot ainsi les termes employés ne sont peut-être pas les bons, cependant l’idée derrière ceux-ci seras expliquer clairement et vous pourrez nommer comme bon vous semble une certaine technique. Un moteur polymorphique est un programme permettant de modifier aléatoirement la clef qui serviras à chiffrer ET déchiffrer chaque nouvelle génération d’un programme. Un moteur métamorphique ne ressemble aucunement aux moteurs polymorphiques vus précédemments. Leurs but est de généré des instructions ayant la même sémantique que les précédentes. Un moteur de (per)mutations est un programme permettant de modifier la structure d’une "cible" tout en gardant les résultats initiallement atteinds. C’est à dire que la modification de l’ordre du code n’affecteras pas le bon fonctionnement du programme cible. Une signature est une suite de bytes permettant d’identifier un programme ou une de ses fonctions. Dans le cas de l’identification d’un programme entier on prendras généralement un endroit clés et spécifique à ce programme, tel que le coeur du virus dans le cas d’un malware(non protégé) ou un layer d’un protector (protégé). 2 2 Le polymorphisme, ou le chiffrement par l’aléatoire 2.1 Introduction et historique Les premiers articles traitant du polymorphisme date du début des années 90, certains plus techniques que d’autres, ils restaient toute fois relativement vague, à l’image de cette technique. Bien que très basique et maintenant obsolète, le polymorphisme reste utilisée par les virus afin de retarder l’inévitable : leur détection. En théorie, un moteur polymorphique n’est rien de plus qu’une fonction créant un ou plusieurs nombres pseudo-aléatoire qui vont servir de clefs de chiffrement lors de la génération d’un ou plusieurs layers de (dé)chiffrements (Souvent, un seul layer est utilisé et est prédéfinis par le coder du virus). En terme plus clair, voici ce qu’un moteur polymorphique doit accomplir : • Générer un nombre pseudo-aléatoire et le stocker dans X • Générer la boucle (layer) de chiffrement de la nouvelle génération du virus • Inclure dans la boucle de chiffrement la clef X à utiliser • Générer la boucle de déchiffrement correspondante • Chiffrer la nouvelle génération du virus à l’aide de X • Y ajouter la boucle de déchiffrement 2.2 Exemple de polymorphisme Voici un exemple simple de moteur polymorphique en asm : c a l l GetPseudoRandomNumber ; Retourne l e nombre pseudo a l é a t o i r e dans eax CryptLoopBegin : mov e s i , V i r u s C o r e S t a r t A d d r ; Début du c o e u r du v i r u s , l a p a r t i e à c h i f f r e r mov ecx , V i r u s C o r e S i z e ; R é c u p é r a t i o n de l a t a i l l e de code à c h i f f r e r CryptLoop : add b y t e p t r [ e s i+e c x ] , eax dec b y t e p t r [ e s i+e c x ] s h r b y t e p t r [ e s i+e c x ] , eax x o r b y t e p t r [ e s i+e c x ] , eax dec e c x l o o p CryptLoop ; Tant que e c x e s t d i f f é r e n t de 0 , s a u t e v e r s CryptLoop CryptLoopEnd : c a l l SetDecryptionLayerInNewVirusGen Ici, nous avons un layer de chiffrement basé sur un nombre pseudo-aléatoire encore présent dans eax au moment du call final. Ce layer étant très basique et le choix des opérations arithmétiques utilisées restant le votre, je ne vais pas détailler plus son fonctionnement. (Attention toutefois à choisir des opérations possédants un inverse : add/sub;shr/shl;dec/inc;etc...). Regardons maintenant le rôle de la fonction SetDecryptionLayerInNewVirusGen : 3 SetDecryptionLayerInNewVirusGen : push eax mov e s i , o f f s e t DecryptLoopBegin mov ecx , o f f s e t DecryptLoopEnd sub ecx , e s i add ecx , 12 c a l l CreateStubForDecryptionLoop rep mov mov mov movsb dword dword dword ; Récupère l a t a i l l e du l a y e r dde d é c r y p t i o n ; A jo u t e à l a t a i l l e du l a y e r , 3 dwords ; i n p u t : e c x==t a i l l e du l a y e r ; o u tp u t : e d i=addr du s t u b ; r e m p l i s l e s t u b a v e c l e l a y e r de d é c r y p t i o n p t r [ e d i ] , eax ; mets l a c l e f à l a f i n du s t u b p t r [ e d i + 4] , VirusCoreAddr ; p u i s l ’ a d r e s s e du c o e u r du v i r u s p t r [ e d i + 8] , V i r u s C o r e S i z e Rien de bien compliqué, passons plutôt à la fonction de déchiffrement : DeryptLoopBegin : mov e s i , o f f s e t DecryptLoopEnd mov eax , dword p t r [ e s i ] mov ecx , dword p t r [ e s i +8] mov e s i , dword p t r [ e s i +4] DecryptLoop : x o r b y t e p t r [ e s i+e c x ] , eax s h l b y t e p t r [ e s i+e c x ] , eax i n c b y t e p t r [ e s i+e c x ] sub b y t e p t r [ e s i+e c x ] , eax dec e c x l o o p DecryptLoop jmp e s i DecryptLoopEnd : dd 0 dd 0 dd 0 ; Récupèr l ’ a d r e s s e où s o n t s o t c k é s l e s d o n n ée s ; Récupère l a c l e f ; La t a i l l e du c o e u r du v i r u s ; l ’ addr du début du v i r u s ; O p é r a t i o n s a r i t h m é t h i q u e s i n v e r s e s de c e l l e s ; u t i l i s é e s d ur an t l ’ e n c r y p t i o n ; Saute v e r s l e début du c o e u r du v i r u s ; 3 DWORDs v i d e s , un pour l a c l e f ; un a u t r e pour l ’ a d r e s s e du début du c o e u r du v i r u s ; e t un d e r n i e r pour l a t a i l l e à d é c h i f f r e r Ce moteur étant plus que sommaire, je me permet de le diffuser sans me soucier des difficultés théoriques (maintenant largement dépassées) qu’il pourrait apporter en matière de recherche anti-virales.Essayons donc de voir ce que l’on pourrait améliorer. 4 2.3 Améliorations Pour renforcer l’éfficacitée de ce genre de moteur on pourrais : • Générer dynamiquement le layer de déchiffrement Bien que cette méthode entraine une augmentation de la taille du moteur polymorphique, elle rends le déchiffrement du coeur du virus par un logiciel anti-virus plus lente. • Ajouter des anti-* et du junkcode Tout ce qui peu retarder l’analyse d’un virus est bon à prendre si l’on veut que son code survive quelques temps dans la jungle d’un ordinateur où les soldats vous traquerons jusqu’à ce que votre espèce disparaisse en s’aidant de tankAV ou toute autre arme qu’ils jugeront utiles. • Générer plusieurs layers pour (dé)crypter le coeur du virus Toujours le même but, retarder l’inévitable. • Générer un layer différents pour chaque nouvelle génération de virus Générer un layer différent pour chaque nouvelle génération de virus rends sa reconaissance par signature beaucoup plus difficile. Evoluons ... 5 3 3.1 Le métmorphisme, ou la modification par équivalence Introduction et explications sommaires Les créateurs de virus ont longtemps cherchés à contrer les méthodes de détection par signature, il a souvent été constaté qu’une nouvelle variante d’un virus bien connu avait été modifié de quelques instructions et pouvait outre passer ainsi cette technique de détection par signature. Or modifier un programme d’une centaine d’instructions par leurs équivalence demande beaucoup de temps, ce qui est en sois très rébarbatif, d’où la naissance de moteurs métamorphiques qui se chargent de se travail comme des grands. D’après les travaux que j’ai pu trouvé à ce sujet, la majorité des moteurs métamorphique utilisent des algorithmes relativement poussés concernant la forme binaire des instructions, ici nous utiliserons une table de correspondance pour faciliter la tâche, cependant cette technique, reste limitée par le nombres de correspondances entrées et est donc bien inférieur à celle généralement utilisé. Les moteurs métamorphiques sont donc en réalitée des programmes permettant de modifier les instructions d’un programme afin de changer sa suite d’opcodes tout en gardant les résultats initiallement escomptés.Donc, cela revient à lire une instruction, à l’analyser, puis à la remplacer par une (ou plusiers) instruction(s) ayant le même effets. 3.2 Analyse des faits et théorie avancée Le polymorphisme nous permet d’empêcher, ou du moins de retarder, la possibilitée de création d’une signature concernant le coeur du virus. Cependant le layer de chiffrement resteras le même, et il est ainsi possible d’en créer une signature, et donc d’identifier le virus. L’évolution du polymorphisme vers le métamorphisme va nous permettre de palier à cette faiblesse non-négligeable. Récapitulons, nous avons besoin d’empêcher la création d’une signature pour le layer de chiffrement. Pour cela nous disposons du métamorphisme qui va nous permettre de changer des opcodes par leurs équivalent à chaque nouvelle génération du virus et ainsi de rendre théoriquement impossible la génération d’une signature. 6 Pour créer un moteur métamorphique nous aurons besoin : • De connaitre la taille de l’opcode à modifier et d’avoir des informations sur l’instructions (désassemblage) Nom : BeaEngine . T a i l l e : 294 336 o c t e t s . Supporte : t o u t e s l e s i n s t r u c t i o n s i n t e l r é f é r e n c é e s dans l e s manuels i n t e l de novembre 2006 e t notamment l e s FPU, MMX, SSE , SSE2 , SSE3 , SSSE3 , SSE4 . 1 , SSE4 . 2 e t VMX. Auteur : BeatriX . Equipe : French R e v e r s e E n g i n e e r i n g Team . • D’un tableau contenant certaines instructions et leurs équivalents utilisable ainsi que leurs tailles. Je limiterais le nombre d’opcodes référencés dans ce tableau au strict minium pour notre exemple. Vous pouvez en trouver déjà conçus, ou le créer vous même, c’est à votre guise. Voici un schéma grossier résument ce qu’un moteur basique de polymorphisme avancée devrais réaliser : 7 1. Récupération de la taille de l’instruction à analyser et d’informations à son sujet 2. Récupération de l’instruction 3. Recherche d’instructions équivalentes dans le tableau d’équivalences 4. Vérification de la taille de l’instruction équivalente 5. Si la taille est supérieur à celle de l’instruction première, on cherche une autre instructions 6. Si la taille est inférieur ou égal à celle de l’instruction première, on modifie le layer et on pad de nop si nécessaire Voici le résultat attendu sur une fonction de type DeltaOffset, servant à récupérer l’adresse de Kernel32.dll en mémoire sans utiliser l’API LoadLibrary. DeltaOffset original start : mov e s i , dword p t r [ e s p ] and e s i , 0FFFFF0000h DeltaOffset : sub e s i , 1000 h cmp word p t r [ e s i ] , 5A4Dh jne DeltaOffset mov hKrn32 , e s i DeltaOffset métamorphisé : Generation 1 start : mov eax , dword p t r [ e s p ] push eax and dword p t r [ e s p ] , 0FFFFF0000h DeltaOffset : sub dword p t r [ e s p ] , 1000 h mov ecx , dword p t r [ e s p ] cmp word p t r [ e c x ] , 5A4Dh jne DeltaOffset l e a eax , hKrn32 pop dword p t r [ eax ] Etc ... Un énorme point faible du métamorphisme est le fait que le code augmente de taille de façon flagrante entre chaque génération (dans le cas d’un virus). La premiere solution envisageable est de remplacer opcode par un autre opcode équivalent et de taille inférieur. Qui-t-à padder l’opcode à l’aide de NOP (No OPeration) jusqu’à ce que la taille du second opcode+nop soit ségal à celle du premier. Un peu brute comme méthode n’est-ce pas ? Les pioners 8 en matière de métamorphisme font partit de 29A un fameux groupe de créateur de virus, on peut citer Z0mBiE, Vecna et The Mental Driller, utilisaient une toute autre technique pour pallier à ce problème, le "shrinking". Cette méthode consiste à dé-métamorphiser le code de la génération précédente du virus afin de repartir d’un code apparemment sain pour éviter la grossissement du code. Chaque génération avait une taille approximativement égal et était majestuesement différente. DeltaOffset métamorphisé : Generation 2 utilisation d’un shrinker start : pop e s i push e s i mov eax , 0FFFFF0000h and e s i , eax DeltaOffset : add e s i , −1000h push e s i cmp word p t r [ e s p ] , 5A4Dh pop e s i jne DeltaOffset push e s i pop hKrn32 Le moteur métamorphise non pas les instructions du Delta Offset de la génération 1 mais un code ressemblant au code original mais "compressé" (plusieurs instructions réduites à 1 par exemple,nommé généralement de l’optimisation). De ce fait le grossissement du code que peut entrainer notre moteur est très réduit du fait que le métamorphisme ne se base pas sur le déjà code obfusqué du Delta Offset de la génération 1. Au départ de l’écriture de ce document je pensais fournir un exemple concret de moteurmétamorphique, cependant j’ai jugé plus raisonnable de ne faire part que de la théorie et de vous laisser le soin de chercher par vous même un exemple. De plus mon moteur étant loin d’être un bon exemple, ca va aider tout le monde :-) 3.3 Limites d’une telle technique Bien que la modification du code soit évoluée, elle reste facilement mise en défaite après l’étude du moteur polymorphique et le recoupement des données utilisé durant le métamorphisme. Evoluons ... 9 4 La permutation de code, ou la furtivité par le mélange 4.1 Introduction et problèmatique Essayons de voir les choses différemment, si les signatures se basent sur une suite d’opcodes, à la place de changer les opcode, il serrait peut-être judicieux de changer leurs ordres tout en gardant le même résultat afin, encore une fois, d’empêcher la création d’une signature de manière aisé. En se basant sur le fait qu’on souhaite changer l’ordre des opcodes, on peut penser à plusieurs manières de le faire : • Changer l’ordre de chaque opcode (si possible) Modifier l’ordre tout en gardant le même résultat, et donc obéir à certaines règles prédéfinies et stricts, laissant donc peu de place aux fantaisies mais n’ayant pas moins d’efficacitée face à beaucoup d’émulateur. • Changer l’ordre de groupes d’opcodes Et donc avoir différentes redirection de l’execution du programme pour qu’elle diffère de l’original, ainsi les opcodes ne seront plus écrit dans le même ordre à l’intérieur du fichier mais l’execution seras exactement la même. Les règles sont moins stricts mais un travail de retouche des adresses inscrites dans le programme et du PE seras à executer pour ne pas provoquer d’erreurs malencontreuses. De plus, de pâr le fait d’insérer des redirections, la taille du programme augmenteras (plus ou moins celon le nombres de groupes d’opcodes). 4.2 Théorie et pratique Je me dois de vous rapeller que ce document ne présente aucunement de manière suffisamment satisfaisante toutes les différentes manières de modifié dynamiquement du code. J’ai fait certains choix sur les options présentées et sur le degré de précision que j’estimais nécesaire de donner. Il en va que finalement, je préfère restait relativement vague et ne fournir aucun code utile : au final, les personnes n’ayant pas les compétences nécessaire de coder se genre de moteur n’ont pas envie que je leur donne des codes tout fait (du moins c’est ce que j’ose espérer). Ici, nous choisirons premièrement d’étudier la première des deux options suscitées et ensuite de nous pencher brièvement sur la deuxième. Changer l’ordre de chaque opcode est, comme je vous l’expliquais, très contraint. Il n’y a que des règles plus ou moins poussées qui le permettent et quasiment aucune place pour de l’aléatoir. Premièrement, attardons nous sur l’analyse des opérandes Source et Destination d’instructions. mov eax , ebx 10 soit Destination = Source Ainsi, pour l’instruction MOV, la première opérande seras la destination et la seconde la source. Il en va de même pour quasiment toutes les instructions de déplacement de données. Il en va de même pour les instructions arithmétiques simples (ne pas y inclure (I)MUL et (I)DIV), ici on prendras pour exemple ADD, la Destination est encore la première opérande, seulement la Source comprends en réalité les deux opérandes : add eax , ebx soit Destination = Destination + Source Il n’est pas difficile, avec un peu de recherche et d’expérience, de faire l’analyse de la majorité des instructions générales. Ainsi, à l’aide de règles plus ou moins complexe (dépendant directement du niveau de complexité de l’obfuscation voulu) d’obfuscation, la permutation d’opcodes s’effectue relativement aisément (pour un premier jet tout du moins). Par exemple, étudions ce bout de code et essayons de le permuter simplement mov eax , 42 mov ecx , 3 add eax , e c x sub eax , 2 xchg ebx , eax dec e c x movzx eax , cx En nommant l’instruction actuelle comme Instr1 comportant une opérande source Source1 et une opérande destination Dest1 et l’instruction suivant Instr2 (avec les opérandes Source2 et Dest2), on peut permuter des groupes de 2 instructions aisément. Regardons à quoi ressemblerait une règle de permutation permettant d’avoir un résultat convenable. [title=Tiré de "Permutation conditions" de Z0mbie] S i Dest1 != Dest2 OU Dest1 != S o u r c e 2 OU Dest2 != S o u r c e 1 Permutation ( I n s t r 1 , I n s t r 2 ) FinSi ProchaineInstr () 11 Ce qui donne comme résultat, après plusieurs passage : mov ecx , 3 mov eax , 42 add eax , e c x dec e c x sub eax , 2 xchg ebx , eax movzx eax , cx 4.3 Points forts, faiblesses et améliorations Le gros points fort de la permutation de code, à l’inverse du métmorphisme, est que une fois modifié, le code garde la même taille qu’à l’origine. Hélas, bien que cette technique soit puissante elle dépends beaucoup des instructions et de l’ordre de celles-ci. Par exemple, pour une fonction de cryptographie relativement avancée, la mutation ne seras que minime et effectué, au mieux, aux deux extrémités de la fonction : la création d’une signature sur cette fonction seras donc relativement aisé. Evoluons ... 12 5 Outroduction : vers l’infini et au dela !! Ces techniques sont souvent utilisées séparemment, ce qui est extrêment dommage. Cependant on voit aujourd’hui apparaitre des malwares posédant une très bonne défense qui se base sur l’obfuscation et d’autres astuces antireversing. D’ici peut on seras très certainement ammené à étudier des malwares comportant un moteur métamorphique et un moteur de permutations (C’est le cas de MetaPHOR, seulement je le considére plus comme un PoC, un très beau travail technique, que comme un malware), le tout couplé à des centaines de layers, du junkcode et autres techniques folkloriques du reverse-engineering ;) Devoir analyser un worm comportant cette défense est chose hardue, et le détecter de façon automatique l’est encore plus, cela représenterais une menace très importante si le tout est bien ficellé. L’évolution vers d’autres techniques d’obfuscations ne se feras qu’avec de nouvelles idées et technologies, on pourrait par exemple se baser sur les hypervisor pour imaginer un déchiffrement furtif (à l’heure actuelle). Ou encore généré la clef de chiffrement des layers en fonctions du contenu de zones mémoires choisient aléatoirement, ou du contenu de la mémoire vidéo, qui sait... Les possibilitées sont quasi infinis, la simple restriction est l’imagination et le courage du codeur. 13 6 Liens et remerciements Merci à Kaine(God certified ?), Neitsa, BeatriX, Baboon, Virtualabs, sh4ka, Guett@, 0vercl0Ck, Natsouillette ainsi que la majorité des zigotos que j’ai pu rencontré depuis mes débuts dans ce monde qui me bouffe tout mon temps ;] Liens : Je vous conseille de jeter un oeil aux liens suivant si le domaine vous intéresse et que vous souhaitez le découvrir plus en profondeur. - http://www.29a.net/ - http://vx.netlux.org/lib/ - http://vx.netlux.org/lib/vmd03.html http://vx.netlux.org/lib/agw00.html http://vx.netlux.org/lib/ayt01.html http://vx.netlux.org/lib/ajm02.html http://vx.netlux.org/lib/vzo10.html http://vx.netlux.org/lib/vzo18.html http://vx.netlux.org/lib/vzo20.html http://vx.netlux.org/lib/vmd01.html http://vx.netlux.org/lib/vzo06.html http://vx.netlux.org/lib/vzo43.html http://vx.netlux.org/lib/vzo15.html http://vx.netlux.org/lib/vmn03.html Analyse de virus : - http://www.openrce.org/articles/full_view/29 - http://www.openrce.org/articles/full_view/27 Contrer l’obfuscation : - http://vx.netlux.org/lib/adb00.html - http://vx.netlux.org/lib/aww00.html 14