Yoda's Crypter 1.2 Unpacking
par Kharneth
 
Outils utilisésPublicCible
 - OllyDbg 1.09d + plugins
      IsDebugPresent
      OllyDump
 - LordPE
 - Calculatrice
 - Papier, Crayon, Cerveau 5.0
 Débutant avancé en Cracking
ayant de bonnes connaissances en programmation
 yC_Full_HW
 
1 - Introduction 

      C'est la première fois que je m'attaque à l'étude d'un crypteur d'exécutable donc il se peut que des erreurs se soient glissées dans ce texte. Si tel est le cas, n'hésitez pas à me le signaler.
      Pour étudier ce packer, j'ai crypté un simple Hello World en C, histoire que le programme soit très léger. J'ai également activé toutes les options disponibles après une première étude sans aucune option. Cela ne change rien puisque le code du loader est le même quelques soient les options sélectionnées, celles-ci étant testées à l'exécution.

      On remarque d'abord que le programme est passé de 5Ko à 8Ko. Un coup d'oeil dans un PE Editor nous permet de voir qu'une section (yC) a été ajouté à la fin des autres. L'Entry Point se trouvant dans celle-ci (5060), elle contient surement le Loader.

      On va donc regarder ça de plus près avec OllyDbg.

2 - OllyDbg 

      D'abord, OllyDbg nous prévient que l'Entry Point se trouve en dehors du code et que le programme est surement crypté ou compressé.

      Première chose frappante, on voit que le code est bourré de CCA! On remarque aussi que cela ne perturbe pas plus que ça OllyDbg! :o) Si ce n'est pas le cas, il faut cocher Extend code section to include extractor dans les options SFX, puis recharger le programme. On note également que tout le code est crypté à part cette boucle qui va se charger de décrypter une première partie du Loader. On va donc laisser cette boucle s'exécuter.
      On place un Hardware Breackpoint (avec un bp normal F2 l'opcode CC aurait été décrypté à la place de 46) sur l'instruction juste après le LOOPD en 004050AE puis F9.
      Pour afficher un code compréhensible, on va forcer OllyDbg à l'analyser en faisant Ctrl-A. Le code maintenant décrypté, on peut enlever le Hardware BreakPoint car on en a plus besoin et qu'ils sont limités à 4.

      On arrive sur un CALL qui va calculer le CheckSum du Loader. La routine additionne simplement les octets à partir de l'Entry Point jusqu'en 0040568A. Puis la valeur est sauvegardée.

      Ensuite, le Loader controle si l'option Exit if SoftIce is loaded a été cochée. Si oui, la "méthode BCHK" (pour plus de précisions, lire le tut de Christal sur BlindRead) est employée sinon on saute à la suite. Pour la contourner, il suffit de forcer le saut pour faire croire que l'option n'a pas été sélectionnée en remplaçant le JE en 004050DE par un JMP. Mais là on s'en fout, on a pas SoftIce! :op

      Le programme récupère les adresses de LoadLibraryA et GetProcAddress en remontant jusqu'à l'IAT à partir de l'entête PE. Ces adresses ayant été inscrites par le Loader de Windows au chargement du programme. Ces 2 fonctions lui permettent de récupérer les adresses de plusieurs API qui seront utilisées plus tard.

      Le Loader vérifie maintenant la sélection de l'option Anti Process Dumping. Si c'est le cas, il récupère l'adresse du PEB, puis remonte jusqu'à l'adresse de ImageSize pour remplacer sa valeur (7000) par 1000! (pour plus de précisions, lire le tut de Pulsar & Christal sur PEShield 2 ainsi que REAL Win32 GENERIC SHELLCODE par ThreaT & Crazylord qui décrit entre autre la structure du PEB). Comme précédemment, il suffit de remplacer le JE par un JMP pour sauter cette protection.

      Après avoir récupéré la valeur de SizeOfHeaders, le Loader modifie les droits d'accès en écriture sur l'entête PE grace à l'API VirtualProtect.

      Ensuite, le programme controle si l'on a coché l'option Exit in the case of a bad CRC. En fait, il va simplement ouvrir le fichier et calculer le CheckSum à l'aide de la même routine que vue au début. Il sauvegarde le résultat puis saute vers la suite du programme.

      Après avoir libéré la mémoire précédemment allouée pour calculer le CheckSum du fichier, le Loader décrypte les sections de l'exe, puis continue l'exécution en 00405416.

      Rien de spécial sur cette partie. Les noms des sections sont récupérés à partir de l'entête PE, puis les sections sont décryptées si leur nom ne commence pas par rsrc, .rsr, relo, .rel, yC ou .eda. La boucle s'effectue tant qu'il y a des sections.

      Voilà une partie intéressante où l'on voit l'OEP en clair! Son RVA a été enregistré lors du cryptage de l'exe. Il est maintenant crypté puis sauvegardé ainsi que la fonction qui le décryptera avant de sauter.

      Le Loader récupère le CheckSum du fichier calculé un peu plus tôt puis, s'il a effectivement été calculé, le compare avec le CheckSum correct. S'ils sont différents, le programme quitte. Il n'y a pas de raison pour qu'ils soient différents puisque l'on n'a rien modifié dans le fichier.

      Lors du cryptage de l'exe, le Crypter a sauvegardé puis détruit l'ImageImportDescriptor. Le Loader va donc récupérer cette structure, compter le nombre de pointeur présent dans l'IAT d'origine du programme, pour finalement allouer une zone mémoire en fonction du nombre d'API trouvé.

      Pour chaque DLL, le Loader décrypte le nom de celle-ci puis récupère son adresse grâce à LoadLibraryA(). Ensuite, il vérifie que l'option Delete Import Information est sélectionnée pour effacer le nom de la DLL dans l'Import Table originale. Pour éviter ça, il suffit encore une fois de remplacer le JE par un JMP.

      Une fois l'adresse de la DLL récupérée, le Loader décrypte le nom de chaque API rattachée puis récupère son adresse grâce à la fonction GetProcAddress(). Comme pour la DLL, si l'option Delete Import Information est sélectionnée, le nom de l'API est effacé. Sauf si on remplace le JE par un JMP.

      Maintenant que l'adresse de l'API est connue, le Loader l'enregistre dans l'IAT d'origine. Puis il teste si l'option API Redirection est sélectionnée. Si oui, il remplace dans l'IAT l'adresse de l'API par l'adresse d'un JMP API. On empèche toujours ça en remplaçant le JE par un JMP. La boucle s'effectue tant qu'il y a des API ou des DLL à décrypter.

      Ensuite, le Loader vérifie que l'option Erase PE Header a été activée puis récupère le SizeOfHeaders et remplace chaque octet, à partir de l'ImageBase, par des 0. En rempaçant le JE par un JMP, on garde le PE Header intacte.

      Le CheckSum du Loader est de nouveau calculé, puis comparé avec celui calculé au début. S'ils différent, le programme quitte. Une fois encore, en remplaçant le JE par un JMP, le problème est réglé.

      Une boucle décrypte la seconde partie du Loader.

      Le Loader récupère l'adresse de l'API IsDebuggerPresent() puis l'exécute. Un petit coup du plugin "IsDebugPresent" et on peut passer à la suite.

      Voici une nouvelle routine de détection de SoftIce qui ne nous concerne toujours pas puisque l'on a toujours pas SoftIce!

      On enchaine avec 2 boucles qui vont effacer le Loader en remplaçant chaque octet par 0. Ne laissant que cette routine ainsi que la fonction de décryptage de l'OEP.

      Et finalement, ces dernières instructions créent le SEH avant de générer une exception puisque le programme veut accéder à l'adresse 0. (Pour plus d'info sur les SEH, lire Win32 Exception handling for assembler programmers de Jeremy Gordon).

      Voici l'Except Handler chargé de gérer l'exception. Il décrypte l'OEP puis remplace l'adresse qui a provoqué l'erreur (00405769) par l'OEP. Ainsi le programme continuera son exécution à partir de l'instruction suivant l'OEP.

      Maintenant, pour créer le dump, on trace tranquillement avec F8 jusqu'en 00405769 pour générer l'erreur, puis Shift F8 pour continuer jusqu'au CALL ntdll.ZwContinue qui nous envoie en 004011D5 où l'on peut enfin créer notre Dump. On va simplement utiliser OllyDump en sélectionnant Rebuild Import Method2 et sans oublier de modifier l'OEP par 004011D4. Et c'est tout! :o)

      Voilà, le dump fonctionne parfaitement sous Windows 98, 2000 et XP. Par contre, lorsqu'on l'ouvre sous OllyDbg, il nous affiche l'erreur "Unable to open or read executable file 'path/dump.exe' " mais il l'ouvre quand même apparemment normalement!
      Lorsqu'OllyDump a créé le fichier, il a rajouté une section NewIID qui contient uniquement les Import_Image_Descriptor. Il suffit de copier ces structures au début de l'Import Table original (ici à l'Offset 3000) et de modifier l'adresse de l'Import Table en 3000 pour que tout rentre dans l'ordre.

Kharneth 

The snake is long, seven miles
Ride the snake...he's old, and his skin is cold


Merci à toutes les personnes qui se battent pour que l'Information soit accessible à tous!