Yoda's Crypter 1.2 Unpacking par Kharneth | |||
---|---|---|---|
Outils utilisés | Public | Cible | |
- 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. ![]() 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 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. | |||
Kharneth | |||
The snake is long, seven miles Ride the snake...he's old, and his skin is cold | |||
![]() |