Date de publication : 20 avril 2008 00h40
Auteur : BeatriX
La solution que je propose ici pour "nettoyer" le binaire de l'obfuscation est semi-automatique. Toute la partie concernant la prospection et la prise de décision est réalisée à la main. La partie laborieuse de suppression de junk est réalisée par un émulateur. L'analyse menée sur ce binaire est en fait un ensemble d'attaques 'locales' portées à différents endroits choisis avec soin. Wroblewski les appelle des 'program points'. Ces program points sont en fait de "vraies" instructions qui nous servent de point d'appui pour découvrir de nouvelles instructions du code source. L'étude effectuée autour de chaque program point est ainsi qualifiable de "locale".
Le plus difficile dans ce genre d'approche est de déterminer les "bons" program points qui permettront de nettoyer un maximum de code en un minimum d'effort. De plus, il va sans dire que plus les program points sont nombreux et plus l'analyse sera longue. Nous allons voir ici que ce nombre est assez réduit mais suffisamment conséquent pour s'amuser un peu.
Comme je viens de le dire, déterminer les program points est la phase critique de l'analyse. Il faut trouver de "bons" program points ! Dans ce genre de recherche, nous sommes confronté au problème de ce que j'ai baptisé les insertions de code de type "stealth constructs". Il s'agit d'une technique d'obfuscation qui consiste à insérer du code qu'il est difficile de distinguer du code source d'origine. Une analyse spécifique nous permet de distinguer les vrais des faux.
Si par exemple nous rencontrons un "cmp ebx, eax" comme program point potentiel, il faudra vérifier qu'il a un lien avec ce que nous analysons. Il faudra chercher à savoir d'où vient "ebx" et "eax" et il faudra aussi vérifier si la comparaison débouche sur quelque chose d'utile. Si nous rencontrons un "mov [49379Ch], eax", il faudra vérifier que l'espace mémoire rempli est bien utilisé quelque part par le programme. Dans ce binaire, cette phase a été facile à négocier puisque les "stealth constructs" se résument à une seule insertion.
Sans m'en rendre compte, pour trouver des program points, je suis parti de deux hypothèses fortes. La première a presque été suggérée par les consignes laissées par Yolejedi :
Ceci m'a amené à formuler la seconde hypothèse dans la foulée.
Ceci signifie que certaines instructions ou familles d'instructions sont utilisées dans le code d'origine et sont absentes de l'obfuscation. Dans notre cas, il se trouve que le différentiel est assez important. J'ai donc commencé ce travail de recherche des différences en logguant certaines instructions ou familles d'instructions.
Recherche d'instructions rares
Les premiers program points auxquels on peut penser sont les appels aux fonctions de l'api qui ne sont pas du tout utilisés par l'obfuscation. Chaque fonction utilise des paramètres poussés sur la pile. L'analyse locale ici cherchera à déterminer les lignes de code qui "poussent" ces fameux paramètres.
Les seconds program points incontournables sont les instructions qui modifient le contenu d'emplacements mémoire. Par exemple, un :
0056EED9 mov dword ptr [49379Ch], eax
nous amènera à chercher l'origine de la valeur contenue dans eax afin de déterminer la définition du dword situé en 49379Ch. De la même façon que les appels à l'API, l'obfuscation n'utilise pas ce genre d'instruction.
Les troisièmes program points concernent les instructions de tests ou de comparaisons. Par exemple, un :
0045A3A9 cmp ebx, eax
nous amènera à réaliser une analyse locale visant à déterminer l'origine des valeurs contenues dans ebx et eax afin de comprendre le sens de cette comparaison. Encore une fois, c'est une famille d'instructions qui n'est pas utilisée dans l'obfuscation (sauf une fois en tant que stealth code).
Une fois cette propection réalisée, on peut essayer de faire une recherche plus systématique d'instructions rares en faisant le pari que si elles sont rares, c'est qu'elles ne sont pas utilisées par le moteur d'obfuscation. Pour exemple, je me souviens que durant l'analyse des instructions d'un défi appelé le K5 (de Kaine), l'émulateur m'avait signalé une instruction unique utilisée : ENTER 0,0 ! Il se trouve que c'était l'entrée du length disassembler engine de zombie (LDE 1.06).
Dans ce binaire, les program points proposés ci-dessus sont de type "rares" et sont à 99 % issus du code source. Une seule insertion de type "stealth constructs" a été ajoutée dans la routine de vérification du serial comme nous le verrons plus tard.
Je dispose sur l'émulateur d'un module de comptage des instructions. Voici ce que nous obtenons sur la routine de vérification du serial :
======================================== = = one-byte instructions frequences = ======================================== add Ev, Gv = 4 or Gb, Eb = 8 <-------- code source sub Ev, Gv = 3 xor Ev, Gv = 4 cmp Gv, Ev = 1 <-------- code source push ecx = 7 push edi = 9 pushad = 1 <--------- code source popad = 1 <--------- code source mov Gb, Eb = 8 <--------- code source mov cl, Ib = 7 <--------- code source mov eax, Iv = 1 <--------- code source mov ecx, Iv = 1 <--------- code source mov ebx, Iv = 1 <--------- code source mov ebp, Iv = 1 <--------- code source mov edi, Iv = 1 <--------- code source ======================================== = = Group 1 instructions frequences = ======================================== and Ev, Ib = 1 cmp Ev, Ib = 1 <--------- code source ======================================== = = Group 2 instructions frequences = ======================================== shl Ev, 1 = 1 shr Ev, 1 = 1 ======================================== = = Group 3 instructions frequences = ======================================== div Ev = 1 <--------- code source ======================================== = = Group 5 instructions frequences = ======================================== push Ev = 1
Vous pouvez constater qu'une grande partie des instructions retenues fait partie du code source et non de l'obfuscation.
Analyse différentielle du control-flow
La technique précédente permet de débusquer des instructions agissant sur le data-flow mais ne permet pas de rendre compte de la structure du binaire. Les procedures, les boucles nous échappent même si on entrevoit parfois des portions de code qui se répètent. Il est donc nécessaire de se concentrer sur les valeurs prises par EIP et sur les instructions de branche ainsi que sur certains grands classiques.
Mesure du delta
On peut partir du principe que si EIP est augmenté ou diminué d'une valeur supérieure à une valeur seuil fixée a priori, c'est qu'il s'est produit un saut "réel". La valeur seuil permet de se prémunir des instructions de branche de l'obfuscation qui souvent ne réalisent pas des "sauts" très significatifs. Cette mesure du différenciel entre deux valeurs consécutives de EIP s'appelle communément mesure du delta. Voici par exemple ce genre d'analyse réalisée sur la routine de vérification du serial qui rappelons-le, pour un serial de 7 caractères, va exécuter plus de 29000 instructions.
===================================================== = = Log des instructions de branche (delta > 1000h) = sur routine de vérification du serial = ===================================================== 00412283 call 40103Ch 0040103C jmp near dword ptr [59E02Ch]=========> IsWindowEnabled 00421FCC call 40101Eh 0040101E jmp near dword ptr [59E030h]=========> EnableWindow 004232AE jne 420E30h 00421FCC call 40101Eh 0040101E jmp near dword ptr [59E030h]=========> EnableWindow 004232AE jne 420E30h 00426039 je 43264Ah 00437D69 call 401036h 00401036 jmp near dword ptr [59E028h]=========> GetWindowTextA 0043E009 je 449C3Ch 00445E7D jne 441B72h 00445E7D jne 441B72h 00445E7D jne 441B72h 00445E7D jne 441B72h 00445E7D jne 441B72h 00445E7D jne 441B72h 00445E7D jne 441B72h 00445E7D jne 441B72h 0044859C jmp 43B7ABh 0043E009 je 449C3Ch 00445E7D jne 441B72h 00445E7D jne 441B72h 00445E7D jne 441B72h 00445E7D jne 441B72h 00445E7D jne 441B72h 00445E7D jne 441B72h 00445E7D jne 441B72h 00445E7D jne 441B72h 0044859C jmp 43B7ABh 0043E009 je 449C3Ch 00445E7D jne 441B72h 00445E7D jne 441B72h 00445E7D jne 441B72h 00445E7D jne 441B72h 00445E7D jne 441B72h 00445E7D jne 441B72h 00445E7D jne 441B72h 00445E7D jne 441B72h 0044859C jmp 43B7ABh 0043E009 je 449C3Ch 00445E7D jne 441B72h 00445E7D jne 441B72h 00445E7D jne 441B72h 00445E7D jne 441B72h 00445E7D jne 441B72h 00445E7D jne 441B72h 00445E7D jne 441B72h 00445E7D jne 441B72h 0044859C jmp 43B7ABh 0043E009 je 449C3Ch 00445E7D jne 441B72h 00445E7D jne 441B72h 00445E7D jne 441B72h 00445E7D jne 441B72h 00445E7D jne 441B72h 00445E7D jne 441B72h 00445E7D jne 441B72h 00445E7D jne 441B72h 0044859C jmp 43B7ABh 0043E009 je 449C3Ch 00445E7D jne 441B72h 00445E7D jne 441B72h 00445E7D jne 441B72h 00445E7D jne 441B72h 00445E7D jne 441B72h 00445E7D jne 441B72h 00445E7D jne 441B72h 00445E7D jne 441B72h 0044859C jmp 43B7ABh 0043E009 je 449C3Ch 00445E7D jne 441B72h 00445E7D jne 441B72h 00445E7D jne 441B72h 00445E7D jne 441B72h 00445E7D jne 441B72h 00445E7D jne 441B72h 00445E7D jne 441B72h 00445E7D jne 441B72h 0044859C jmp 43B7ABh 0043E009 je 449C3Ch 00453D0A jne 456C56h 0045B7A4 ja 46B40Fh 0046965E jmp near ecx 004731E0 call 401060h 00401060 jmp near dword ptr [59E044h]=========> SetDlgItemTextA : --------< Perdu ! >--------
On voit apparaître tous les appels aux fonctions de l'API. Au passage, on voit que le binaire utilise ce qu'on appelle un trampoline pour réaliser ces appels (compilation médiocre de masm32 :) ). On voit également apparaître des boucles avec des série de "jne". On voit même un "jmp ecx" ! Tout ceci est une bonne base pour compléter une analyse de data-flow sur cette routine. Il est à noter que cette analyse nous permet d'obtenir à 98 % des instructions du code d'origine.
Dans un premier temps, on remarque que le binaire exécute deux fonctions :
0056DE51 call 401078h =========> GetModuleHandleA : Main 005761FC call 401012h =========> DialogBoxParamA
Voici donc nos deux premiers program points sur lesquels vont porter notre première attaque. Avant l'appel à chacune de ces deux fonctions, le programme va pousser sur la pile les paramètres utiles. Il va également s'amuser à noyer ces informations dans un junk code complexe. Nous allons ainsi nettoyer le code en se focalisant sur les modifications de ESP. Si on commence par chercher à logguer toutes les instructions qui modifient le registre esp avant l'appel à la première fonction, on obtient ceci :
00565BB2 call 565BB8h 00565BC9 push ecx 00565BCA call 565BD0h 00565BD0 pop ebp 00565BE4 call 565BEAh 00565BEA pop ecx 00566203 push 4AF35Ch 00566208 push dword ptr fs:[8h] 0056620F pop ecx 00566215 push dword ptr fs:[8h] 0056621C pop ecx 00566223 push esp 00566224 pop esi 0056686D push dword ptr fs:[8h] 00566874 pop ecx 00566877 pop ecx 00566878 push esp 00566879 pop ebp 0056687D pop ebp 00566880 pop edi 00566EFE push ebp 005675D0 push dword ptr fs:[0h] 005675E1 push dword ptr fs:[8h] 005675E8 pop ecx 005675FE call 567604h 00567604 pop ecx 00567C2B push dword ptr fs:[4h] 005682BD push dword ptr fs:[8h] 005682C4 pop eax 005682C7 pop edx 005682CF pop ebp 005682D3 pop eax 005689A6 push 5801CFh 00568FC3 push 59752Ch 00568FD9 push ebx 00569617 push dword ptr fs:[4h] 00569624 push dword ptr [esp+4h] 00569628 pop dword ptr [esp] 0056962B push dword ptr fs:[8h] 00569632 pop ebx 00569637 push dword ptr fs:[8h] 0056963E pop ebp 00569649 pop ebp 0056964A call 569650h 00569650 pop esi 00569656 pop edi 0056965A pop esi 00569E8D push dword ptr fs:[eax] 00569E90 call 569E96h 0056A537 push edi 0056A543 push esp 0056A544 pop edi 0056A553 call 56A559h 0056A559 pop edi 0056A55F push dword ptr fs:[4h] 0056ACFE push esp 0056ACFF pop edx 0056AD13 call 56AD19h 0056AD19 pop ecx 0056AD30 push esp 0056AD31 pop edi 0056AD41 push esp 0056AD42 pop esi 0056B20C pop ebp 0056B213 pop ecx 0056B217 pop edi 0056BD96 push 4752A9h 0056BDA0 push eax 0056C296 push 4DB30Ch 0056C29B call 56C2A1h 0056C2A1 pop esi 0056C2B3 pop ebp 0056C2BA pop edi 0056C745 pop esi 0056C763 push 0h 0056C765 push edi 0056CE2D lea esp, dword ptr [esp-4h] 0056D2EE call 56D2F4h 0056D2F4 pop edi 0056D2FD push 487E4Fh 0056D8B9 pop esi 0056D8C0 pop ecx 0056D8C4 pop edx 0056DE51 call 401078h =========> GetModuleHandleA : Main
Bien que cette version du code soit nettement moins lourde que la version complète (nous n'avons que 83 instructions qui modifient esp sur un total de 787 instructions exécutées !), elle reste encore très confuse. Nous pouvons alors coder un petit filtre supplémentaire qui va simplement logguer les instructions équivalentes à un "push" mais qui va en même temps observer les valeurs de esp. Si après avoir loggué un "push", esp est augmenté de 4, on supprime ce dernier "push" des logs enregistrés. Dès qu'on atteint notre program point (en 56DE51), on affiche les logs enregistrés. Voici ce que nous obtenons pour le code précédent :
005689A6 push 5801CFh 00569E8D push dword ptr fs:[eax] 0056C763 push 0h 0056DE51 call 401078h =========> GetModuleHandleA : Main
On voit donc que le paramètre de la fonction observée est poussé en 56C763. On voit également la mise en place d'un SEH dont le handler se situe en 5801CFh.
De même, en appliquant ce petit filtre sur la seconde fonction, on obtient ceci :
00570171 push 0h 00571861 push 494B9Bh 00572C0A push 0h 00573C0E push 65h 00574E46 push eax 005761FC call 401012h =========> DialogBoxParamA
Pour information, avant ce dernier nettoyage, le programme exécute 760 instructions ! On voit de ce fait que la WinProc démarre en 494B9Bh.
Vous pensez déjà que tout ceci est bien inutile vu qu'on peut obtenir les mêmes informations en posant deux BP sur ces fonctions et en regardant les valeurs poussées sur la pile. Il est vrai que dans les deux exemples proposés, cette technique n'a aucun intérêt vu que tous les paramètres poussés sont des constantes. Il n'en est pas de même pour toutes les fonctions de ce binaire. Voyez plutôt l'exemple suivant :
00495F3F pushad 004B71E7 push 406A43h 004B8AEC push dword ptr [4937A0h] 004BA00B call 40106Ch =========> SetWindowTextA : YO-bfuscator_I :) 004BB9D2 push 67h 004BCDA3 push dword ptr [49379Ch] 004BE7C2 call 401042h =========> LoadBitmapA 004C147C push 68h 004C28DB push dword ptr [49379Ch] 004C3E20 call 401042h =========> LoadBitmapA ...
Dans les cas proposés ici, on voit le contenu d'espaces mémoire poussés sur la pile. Grâce à cette technique, nous récupérons facilement les adresses de ces emplacements ce qui nous donne une information précieuse sur la nature de l'emplacement en question.
Pour conclure sur cette technique d'attaque, je signale que j'ai réalisé le filtrage à l'aide de ma librairie de désassemblage BeaEngine 3.0. Le filtrage effectué est le suivant :
MonDisasm.Instruction.ImplicitModifiedRegs & REG4
MonDisasm.Argument1.ArgType & REG4
En d'autres termes, on loggue les instructions qui modifient implicitement le registre REG4, c'est-à-dire ESP, et on loggue également les instructions qui modifient explicitement le REG4, c'est-à-dire celles qui disposent de ESP comme premier argument dans la syntaxe d'une instruction intel suivante :
OPERATION + ARGUMENT1 + ARGUMENT2
L'argument 1 est souvent associé à l'opérande destination qui va subir la transformation.
Remarque importante :
Dans cette méthode, on ne peut pas voir réellement comment sont stockés les paramètres sur la pile. Voici un petit exemple qui nécessite un travail supplémentaire :
push eax mov dword ptr [esp], ebx call GetModuleHandleA
C'est clairement une mutation du code standard suivant :
push ebx call GetModuleHandleA
Malheureusement, si nous n'appliquons qu'un data-flow sur esp, nous n'obtenons que ceci :
push eax call GetModuleHandleA
Il est donc nécessaire de comparer les valeurs sur la pile au moment de l'appel à la fonction avec les valeurs poussées ! C'est un travail à ne pas négliger dans l'absolu. Dans le cas de cette étude, le code source n'étant pas muté, nous ne rencontrerons pas cette situation.
Cette attaque porte cette fois sur les deux autres types de program points mentionnés précédemment. Le principe est assez simple. On demande à l'émulateur d'exécuter le programme à l'envers à partir du program point choisi tout en scrutant le contenu des registres et de la pile. On peut ainsi pister une valeur et remonter jusqu'à sa source tout en logguant les instructions qui opèrent sur cette valeur. On obtient ainsi ce qui s'appelle un SLICE. L'obfuscation ne resiste quasiment pas à une telle attaque. Je vais exposer cette approche à travers deux exemples.
Exemple 1
Si on demande de logguer à partir de l'entry point les instructions qui modifient les contenus d'espace mémoire, on obtient ceci :
0056B83C mov dword ptr fs:[eax], esp 0056DE51 call 401078h =========> GetModuleHandleA : Main 0056EED9 mov dword ptr [49379Ch], eax 005761FC call 401012h =========> DialogBoxParamA
Nous avons obtenu ce filtrage en demandant à BeaEngine 3.0 de logguer les instructions qui vérifient les conditions suivantes :
(MonDisasm.Argument1.Argtype & MEMORY_TYPE) && (MonDisasm.Argument1.Memory.BaseRegister != REG4)
On demande en fait à BeaEngine de logguer les instructions qui ont pour opérande destination (argument1) un MEMORY_TYPE qui n'est pas pointé par ESP (BaseRegister != ESP). Cette dernière condition n'est pas indispensable a priori mais s'avère très utile pour éviter les accès à la pile très nombreux.
La première affectation concerne la mise en place du SEH et confirme ce que nous avons constaté précédemment. Juste après l'appel à la fonction GetModuleHandleA, on voit que le contenu de eax est stocké en 49379Ch. Nous allons donc partir de ce program point et appliquer un back-stepping automatique pour pister le contenu de eax. Pour information, 96 instructions séparent l'appel à GetModuleHandleA de notre program point.
Voici le slice que nous obtenons au premier jet :
backward slicing à partir de 0056EED9 0056DE51 call 401078h =========> GetModuleHandleA : Main 0056DE56 xchg eax, esi 0056E40C inc esi 0056E8EE mov edi, esi 0056E8FF xchg ebp, edi 0056EECF xchg eax, ebp 0056EED8 dec eax 0056EED9 mov dword ptr [49379Ch], eax
La séquence ne contient que 6 instructions sur les 96 exécutées en réalité. C'est un très bon rendement a priori. Cependant, si on y regarde de plus près, on constate qu'en dehors du "inc esi" et du "dec eax", toutes les instructions font partie des instructions de transfert de données (DATA TRANSFER INSTRUCTIONS) et ne sont là que pour essayer de nous perdre un peu plus. On va donc demander au back-stepping de refaire le travail en ne logguant que les instructions qui font vraiment quelquechose, c'est-à-dire les ARITHMETIC INSTRUCTIONS, LOGICAL INSTRUCTIONS, SHIFT AND ROTATE INSTRUCTIONS. Nous obtenons un log bien évidemment plus court :
backward slicing à partir de 0056EED9 sans DATA_TRANSFER INSTRUCTIONS 0056DE51 call 401078h =========> GetModuleHandleA : Main 0056E40C inc esi 0056EED8 dec eax 0056EED9 mov dword ptr [49379Ch], eax
L'obfuscation qui reste ici, c'est à dire un "inc/dec" ne nous pose plus de difficulté même si les deux instructions travaillent sur des registres différents. Quoiqu'il arrive, le contenu de esi ira dans eax par un jeu de tranfert complexe. Nous pouvons conclure que finalement le contenu de eax dans notre program point n'est rien d'autre que la valeur retournée par GetModuleHandleA ! Tout ça pour ça !!
Exemple 2
Si on demande de logguer à partir de l'entrée de la WinProc les instructions de comparaison , on obtient ceci :
0049AE89 cmp esi, 110h <-- WM_INITDIALOG 0049BCF3 cmp esi, 111h <-- WM_COMMAND 005286DB cmp ebp, 1h 00412283 call 40103Ch =========> IsWindowEnabled 00437D69 call 401036h =========> GetWindowTextA 0045A3A9 cmp ebx, eax 004731E0 call 401060h =========> SetDlgItemTextA : --------< Perdu ! >-------- 0052A1E8 cmp ebp, 2h 0049CF40 cmp esi, 136h <-- WM_CTLCOLORDLG 0049E2B9 cmp esi, 138h <-- WM_CTLCOLORSTATIC 0049F749 cmp esi, 133h <-- WM_CTLCOLOREDIT 004A0817 cmp esi, 135h <-- WM_CTLCOLORBTN 004A22A2 cmp esi, 10h
Si on y regarde de près, on remarque un "cmp ebx, eax" fort intéressant ! ( a priori). Il se trouve juste derrière l'appel à la fonction GetWindowTextA... Si j'ose dire, on a une sensation du bon gros crackme de base qui récupère le serial , qui fait des calculs et qui fait un gros "CMP EBX, EAX". Nous verrons plus loin qu'il n'en est rien et que c'est un petit piège de YoleJedi pour nous faire chercher au mauvais endroit :) Bref, nous pouvons quand même chercher l'origine des valeurs contenues dans ebx et eax. Cette recherche va s'avérer très fructueuse puisqu'elle va nous dévoiler la routine de vérification du serial saisi.
Si on effectue un back-stepping avec filtrage des DATA_TRANSFER INSTRUCTIONS à partir de 45A3A9h, on obtient ceci :
****************************************** * * Backward Slicing + FILTRE ANTI TRANSFERT à partir de 45A3A9 sur ebx * * ****************************************** nbre d'instructions exécutées entre 43901A et 45A3A9 : 29032 nbre d'instructions retenues après back-stepping + anti-transfer : 610 Taux de réduction : 98 % 0045A3A9 cmp ebx, eax 00457F28 inc ebx 00457F26 not ebx 00457209 neg dword ptr [esp] 00456C43 ror ebx, 63h 004566AA rol eax, 0E3h 00455896 inc ebx 0045518B not ebx 00455188 sub ebx, 1h 00454AFD neg ebp 0045376A xor edx, 4CC12Ch 00453274 xor ebx, 4CC12Ch 00452C61 ror ebx, 98h 00451E84 rol edx, 0D8h 00451A07 ror ebx, 5h 00451A06 dec ebx 00451A04 neg ebx 00451359 not dword ptr [esp] 004506D6 or ebx, eax 004506C6 ror ebx, 62h 0044FA8F ror edx, 3Eh 0044F55E rol ebx, 0C9h 0044EEA3 ror ecx, 0A9h 0044E026 sub ebx, 1h 0044D8FA inc ebx 0044D231 inc ebx 0044C55C dec edi 0044BAB7 neg edx 0044BAB6 inc edx 0044B431 not dword ptr [esp] 0044AD47 xor ebx, 0AB979C82h 0044AD25 inc ebx 0044AD23 not ebx 0044A26E neg esi 0043DFF2 rol ebx, 0AFh 0043D0DF rol ebx, 0D1h 0043C4D0 neg esi 0043C4B1 neg ecx 00448593 ror ebx, 0Ah 00447932 ror ebx, 56h 004472B5 dec ebx 004472B3 neg ebx 00445E83 not ebx 004459C7 add ebx, 4AB2B6h 00444C27 sub edx, 4AB2B6h 004444FB ror ebx, 1h 00443F5A dec ebx 00443392 inc ebx 00442D3B xor bl, al 00442D37 rol ebx, 9Fh 0044218D ror ebx, 7Fh 004459C7 add ebx, 4AB2B6h 00444C27 sub edx, 4AB2B6h 004444FB ror ebx, 1h 00443F5A dec ebx [....] 0043C4D0 neg esi 0043C4B1 neg ecx 0043B7A1 not ebx 0043B79E add ebx, 0FFFFFFFFh 0043A95D neg ebx 0043A312 xor ebx, 539B5Dh 00439742 xor ebx, 539B5Dh 0043901A mov ebx, 6964654Ah
L'origine de ebx est donc située en 43901A où ebx est affecté de la chaine "ideJ". Même si nous sommes passés de 29000 instructions exécutées à 610, on constate que l'obfuscation reste assez présente mais facile à comprendre. En réalisant un petit filtrage spécifique à l'obfuscation présente ici (j'appelle ça une suppression manuelle), on obtient ceci :
****************************************** * * Backward slicing + FILTRE ANTI TRANSFERT à partir de 45A3A9 sur ebx * * ****************************************** nbre d'instructions exécutées entre 43901A et 45A3A9 : 29032 nbre d'instructions retenues après back-stepping + filtre anti-transfer : 610 nbre d'instructions retenues après back-stepping + filtre anti-transfer + suppression manuelle : 118 Taux de réduction : 99,6 % 0045A3A9 cmp ebx, eax 00455896 inc ebx 00451A07 ror ebx, 5h 004506D6 or ebx, eax 0044AD47 xor ebx, 0AB979C82h 004444FB ror ebx, 1h 00442D3B xor bl, al 004444FB ror ebx, 1h 00442D3B xor bl, al 004444FB ror ebx, 1h 00442D3B xor bl, al 004444FB ror ebx, 1h 00442D3B xor bl, al 004444FB ror ebx, 1h 00442D3B xor bl, al 004444FB ror ebx, 1h 00442D3B xor bl, al 004444FB ror ebx, 1h 00442D3B xor bl, al 004444FB ror ebx, 1h 00442D3B xor bl, al 004444FB ror ebx, 1h [.....] 00442D3B xor bl, al 004444FB ror ebx, 1h 00442D3B xor bl, al 0043901A mov ebx, 6964654Ah
A ce stade de l'analyse, il ne reste plus qu'à comprendre d'où vient "al" dans les "xor" successifs. On verra que ces opérations répétées cachent de simples boucles et que 'al' n'est rien d'autre que les caractères du serial pris un par un.
Voilà dans son ensemble la démarche suivie pour analyser ce petit binaire. Je conclue donc en rappelant les différentes étapes de la recherche :
Nous allons pouvoir nous intéresser au crackme à proprement parlé maintenant. Dans un souci de clareté, j'ai décomposé le travail en deux parties :
Première partie :Je donne un aperçu du fonctionnement du crackme et je donne également le code source pour masm32 juste histoire de prouver que ce que je viens d'écrire n'est pas complètement inutile.
Deuxième partie :Ma méthode d'analyse fait de la discrimination positive, c'est-à-dire qu'elle ne travaille que sur le code utile et non l'obfuscation. Nous comblerons en partie ce manque en analysant l'obfuscation proposée selon des critères annoncés par Wroblewski dans son article "General Method Of Program Code Obfuscation".
Copyright (C)- FRET (2008)