Crackme GOLD de CoOlViPeR
By Christal
CoOlViPeR Crackme Gold
ProcDump va planter parce que la valeur qu'il recoit en 004056B6 ne correspond à rien tant que l'application cible n'est pas en cours d'exécution. Un "d EDI" en 004056A9 va montrer que le programme pointe sur le début du MZ Header: 00000000 4D5A 9000 0300 0000 0400 0000 FFFF 0000 B800 0000 0000 0000 MZ...................... 00000018 4000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 @....................... 00000030 0000 0000 0000 0000 0000 0000 1B29 0000 0E1F BA0E 00B4 09CD .............).......... 00000048 21B8 014C CD21 5468 6973 2070 726F 6772 616D 2063 616E 6E6F !..L.!This program canno Et qu'à l'adresse ESI (0058C000) + 3C il y a un 291B (EAX). Ce champ est indispensable pour Windows, il s'agit du DWORD e_lfanew, le dernier du MZ Header, indiquant à quel offset le PE Header de l'exécutable est situé. Nomalement, cette valeur devrait être C0, et pointer sur l'offset où se trouve PE (le début du PE Header), que l'on trouve quelques lignes plus bas: 000000C0 5045 0000 4C01 0000 9869 0342 0000 0000 0000 0000 E000 0F00 PE..L....i.B............
000001B0 0000 0000 0000 0000 2E74 6578 7400 0000 0080 0000 0010 0000 .........text........... 000001C8 0000 0000 0004 0000 0000 0000 0000 0000 0000 0000 8000 00E0 ........................ 000001E0 2E64 6174 6100 0000 0020 0000 0090 0000 0020 0000 0004 0000 .data.... ....... ...... 000001F8 0000 0000 0000 0000 0000 0000 4000 00E0 2E72 7372 6300 0000 ............@....rsrc... 00000210 0010 0000 00B0 0000 0006 0000 0024 0000 0000 0000 0000 0000 .............$.......... 00000228 0000 0000 4000 00C0 0000 0000 0000 0000 0000 0000 0000 0000 ....@................... Epluchons un peu: Section Header (section .text) BYTE Name[IMAGE_SIZEOF_SHORT_NAME] ( 2E74 6578 7400 ) DWORD PhysicalAddress DWORD VirtualSize ( 0080 0000 ) DWORD VirtualAddress ( 0010 0000 ) DWORD SizeOfRawData ( 0000 0000 ) DWORD PointerToRawData ( 0004 0000 ) DWORD PointerToRelocations ( 0000 0000 ) DWORD PointerToLinenumbers ( 0000 0000 ) WORD NumberOfRelocations ( 0000 ) WORD NumberOfLinenumbers ( 0000 ) DWORD Caracteristiques ( 8000 00E0 ) -> E00000080 Ici, rien d'anormal... Alors que peut il bien y avoir à l'offset 291B, sachant que sans les informations du header, l'application plantera immédiatement. Dans le cas de ce crackme, le PE Header a été détourné vers l'offset 291B, à fin du code: Name: VS: 00008000 VA: 00001000 RS: 00000000 RA: 00000400 C: C0000040 Name: VS: 00002000 VA: 00009000 RS: 00002000 RA: 00000400 C: C0000040 Name: VS: 00001000 VA: 0000B000 RS: 00000600 RA: 00002400 C: C0000040 Pas de Nom de section, le minimum de chez minimum, mais on retrouve des points communs 0000291B 50 4500 004C 0103 0098 6903 4200 PE..L....i.B. etc... 00002A00 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 ........................ 00002A18 0000 0000 8000 0000 1000 0000 0000 0000 0400 0000 0000 0000 ........................ 00002A30 0000 0000 0000 0040 0000 C000 0000 0000 0000 0000 2000 0000 ....... ............ ... 00002A48 9000 0000 2000 0000 0400 0000 0000 0000 0000 0000 0000 0040 .... ..................@ 00002A60 0000 C000 0000 0000 0000 0000 1000 0000 B000 0000 0600 0000 ........................ 00002A78 2400 0000 0000 0000 0000 0000 0000 0040 0000 C000 0000 0000 $..............@........ 00002A90 0000 0000 0000 ...... Pour résumer, CoOlViPeR a détourné l'adresse de début du PE Header, et a créé un nouvel Header qu'il a placé à la fin de la section .rsrc, puis il a supprimé le nom des sections et modifié les caractéristiques de celles ci: Généralement, pour empêcher le break et le désassemblage de la section, il suffit de mettre les caractéristiques suivantes : 0x00000040 IMAGE_SCN_CNT_INITIALIZED_DATA 0x40000000 IMAGE_SCN_MEM_READ OR 0x80000000 IMAGE_SCN_MEM_WRITE -------------------------------------------- 0xC0000040 En inversant le principe: Chaque caractéristique à une valeur. Si vous voulez avoir deux caractéristiques,
vous faites : caract1 OR caract2 ... 0x00000020 IMAGE_SCN_CNT_CODE 0x20000000 IMAGE_SCN_MEM_EXECUTE 0x40000000 IMAGE_SCN_MEM_READ OR 0x80000000 IMAGE_SCN_MEM_WRITE ----------------------------------- 0xE0000020 Wdasm va rencontrer les mêmes difficultés que ProcDump face à de telles modifications. Seul le lancement du CrackMe permet de rendre "actif" le détournement du PE. Mais est ce vraiment satisfaisant? Pas totalement. Le mieux est encore de remettre de l'ordre dans le Chaos de CoOlViPer.... Et à commencer par rediriger le programme vers son VRAI PE Header, puis (et votre oeil exercé ne l'aura pas loupé), REINSCRIRE LE NOMBRE DE SECTIONS du CRACKME, à savoir 03... 00000030 0000 0000 0000 0000 0000 0000 C0000 0000 0E1F BA0E 00B4 09CD .............)......... et 000000C0 5045 0000 4C01 0300 9869 0342 0000 0000 0000 0000 E000 0F00 PE..L....i.B............ Mais il y a bien plus simple, il suffit d'utiliser un autre Dumper comme ProcInfo,
et le tour est joué... EB 01 50 -> 38 occurences EB 02 8B EC -> 43 occurences EB 03 CD 20 C7 -> 41 occurences EB 03 E9 21 47 -> 35 occurences EB 04 EB CD 20 EB -> 26 occurences Les anti SoftIce: :00401036 PUSH EBP :00401037 MOV EBP,ESP :00401039 ADD ESP,-4C :00401040 SIDT FWORD PTR [0040338C] ; stock IDT dans SEH3 :0040104C MOV DWORD PTR [EBP-30], 00000030 :00401058 MOV DWORD PTR [00403388],004017C5 ; @ Sice Detected dans SEH2 :00401068 MOV DWORD PTR [EBP-2C], 00000003 :00401073 MOV DWORD PTR [EBP-28], 0040126F :0040107F MOV DWORD PTR [00403392],00401170 ; @ No problem dans SEH1 :0040108E MOV DWORD PTR [EBP-24], 00000000 :0040109B MOV BYTE PTR [00401214], 00 ; auto patch du programme ! :004010A7 MOV DWORD PTR [EBP-20], 0000001E :004010B3 PUSH EAX ; adresse du MZ header sur la pile :004010B8 INC EDI :004010B9 MOV EAX,FS:[00000000] ; récupère l'état de la Pile SEH original :004010C5 MOV [00403384],EAX ; et le sauve en mémoire SEH0 :004010CE POP EAX ; récupère l'adresse du MZ header :004010D4 PUSH DWORD PTR [EBP+08] :004010DA POP DWORD PTR [EBP-1C] :004010E2 MOV DWORD PTR [EBP-10], 00000008 :004010EF MOV DWORD PTR FS:[0], 00403384 ; adresse de la mémoire SEH0 dans la pile SEH :004010FD MOV DWORD PTR [EBP-08], 00403000 :00401107 PUSH 68 ; identificateur de l'Icône :00401109 PUSH DWORD PTR [00403074] ; handle de l'application :0040110F CALL LoadIconA :00401119 MOV [EBP-18],EAX ; sauve l'handle de l'icône :00401121 MOV DWORD PTR [EBP-04], 00000000 :0040112D PUSH 00007F00 ; identificateur du curseur :00401132 PUSH 00 ; curseur standard :00401134 CALL LoadCursorA :0040113F MOV [EBP-14],EAX :00401146 LEA EAX,[EBP-30] ; adresse de la structure DATA :00401149 PUSH EAX :0040114A CALL RegisterClassExA :00401154 PUSHAD :00401158 MOV [00403396],ESP :00401163 SUB EAX,EAX :0040116A DIV EAX ; provoque une EXCEPTION Le DIV EAX semble ne pas pouvoir être évité. :004017C5 NOP :004017C9 XOR EAX,EAX :004017D0 MOV EDI,[0040338E] ; EDI = 800A8000 (IDTbase) :004017DB MOV AL,[EDI+0E] ; AL = 4 -> présence d'un débuggeur (Magic Number) :004017E3 OR AL,AL ; AL reste à 4 -> à remplacer par xor AL,AL :004017E9 JZ 00401851 ; et on sautera vers la bonne procédure :004017EE MOV EDI,USER32!MessageBoxA ; charge l'adresse de l'API dans EDI :004917F MOV EDI,[EDI+02] :00401800 MOV EDI,[EDI] :00401806 MOV EAX,[EDI] :0040180D AND EAX,000000FF :00401817 CMP EAX,000000CC :00401821 JNZ 00401831 :00401827 PUSH 00 :00401829 CALL KERNEL32!ExitProcess :00401831 PUSH 40 :00401833 PUSH 00403014 :00401838 PUSH 0040304C ; Numéga SoftIce Detected :0040183D PUSH 00 :0040183F CALL USER32!MessageBoxA :0040184A PUSH 00 :0040184C CALL KERNEL32!ExitProcess :00401851 NOP ; Glop Glop ! :00401855 ADD EAX,00403392 ; ajoute le contenu de AL à l'@ stockée en 00403392 :0040185F PUSH DWORD PTR [EAX] ; pousse l'adresse de retour sur la pile :00401864 RET En l'occurence, l'adresse stocké dans 00403392 est 00401170 (adresse Glop!),
une des adresses servant à initialiser la SEH, et celle stockée dans 00403392+4 est 0063FDB8, l'état
de la Pile SEH original. :00401058 MOV DWORD PTR [00403388],004017C5 ; -> en remplaçant 004017C5 par 00401170 en 0040338E se trouve stocké l'IDTbase (rappellez vous le SIDT FWORD PTR
[0040338C] -> 0040338E = 0040338C+2).
:800A8000 00281350 C0008E00 00284B0F C004EE00 P.(......K(..... :800A8010 00284B1E C0048E00 00284B2D C004EE00 .K(.....-K(..... :800A8020 00281380 C000EE00 00281390 C000EE00 ..(.......(..... L'IDT réserve 8 bytes pour chaque interruption differente ___________Lowword de la routine de l'int (n+1) | ____________ high word de la routine (n+1) _|__ __|_ XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX ---- ---- Lowword de la routine___| |_____high word qui gere l'int n
:004016C7 PUSH 00 :004016C9 PUSH 00403065 ; push \\.\SICE :004016CE CALL KERNEL32!_lopen ; _lopen = version 16 bits de CreateFileA :004016D8 INC EAX ; si Sice est absent EAX = -1 :004016DE JZ 0040174A ; et dans ce cas on revient en 00401673 :004016E3 MOV EDI,USER32!MessageBoxA ; petite technique à CoOlViPeR :004016EE MOV EDI,[EDI+02] ; pour afficher "discrétement" une MessageBox :004016F4 MOV EDI,[EDI] ; un BPM MessageBoxA permet d'obtenir un break :004016FA MOV EAX,[EDI] ; alors qu'un BPX API échoue. :00401701 AND EAX,000000FF ; On coutourne le MeltIce en remplaçant SICE :0040170B CMP EAX,000000CC ; par MICE avec un HexEditeur. :00401713 JNZ 00401727 ; -> vers affichage de la box :0040171B PUSH 00 :0040171D CALL KERNEL32!ExitProcess :00401727 PUSH 40 :00401729 PUSH 00403014 :0040172E PUSH 0040304C ; Numega SoftIce detected :00401733 PUSH 00 :00401735 CALL USER32!MessageBoxA :0040173E PUSH 00 :00401740 CALL KERNEL32!ExitProcess :0040174A RET Et juste en dessous, dans la foulée, vous pouvez repérez de suite
une INT68. :0040174F MOV AH,43 :00401755 INT 68 ; l'INT 68 à nopper :0040175A CMP AX,F386 :00401763 JNZ 004017C4 etc... :004017C4 RET Vala, vala... :00401450 PUSH 00000100 :00401455 PUSH 0040317C ; buffer de stockage du Name :0040145A PUSH 69 :0040145C PUSH DWORD PTR [00403070] :00401462 CALL USER32!GetDlgItemTextA ; saisie du 1er champ (Name) :0040146D PUSH 00000100 :00401472 PUSH 00403078 ; buffer de stockage du Serial :00401477 PUSH 6A :00401479 PUSH DWORD PTR [00403070] :0040147F CALL USER32!GetDlgItemTextA ; saisie du 2d champ (Serial) :00401488 PUSH 00403078 ; serial poussé sur la pile :0040148D CALL KERNEL32!lstrlen ; calcul de sa longueur :00401497 CMP EAX,06 ; si inférieur à 6 digits :0040149F JL 00401682 ; Go Out :004014A8 PUSH 0040317C ; Name poussé sur la pile :004014AD CALL KERNEL32!lstrlen ; calcul de sa longueur :004014B7 CMP EAX,04 ; si inférieur à 4 (important!) :004014BF JL 00401682 ; Go Out CoOlViPeR nous truande un peu (et c'est de bonne guerre!)... 00403078 -> overflow (A) 0040307C -> séparateur (B) 0040307D -> autopatch 1 (C) 00403081 -> autopatch 2 (D) 00403084 -> checksum (E) :004014C8 XOR EBX,EBX ; ebx = 0 :004014CD XCHG EAX,ECX :004014D1 XOR EAX,EAX ; eax = 0 :004014D6 MOV ESI,0040317C ; esi = Name entré :004014DF LODSB ; charge un caractère dans EAX :004014E4 ADD EBX,EAX ; l'ajoute à EBX :004014EA LOOP 004014DB ; et recommence jusqu'à épuisement A la sortie de cette routine, EBX contient la somme ASCII des caractères du Name entré. :004014F1 MOV EAX,[0040307D] ; EAX = point sur le Dword du 6ème caractère :004014FB ADD EBX,EAX ; ajouté à la somme ASCII du Name :00401502 MOV EAX,[0040302E] ; EAX = "clé secrète" -> 582105F5 :0040150C MUL EBX ; clé x somme ASCII :00401513 MOV [00401563],EAX ; autopatch du programme Ces quelques lignes sont riches... 0030:0040302E F5 05 21 58 47 4F 4C 44-56 65 72 73 69 6F 6E 20 ..!XGOLDVersion 0030:0040303E 45 6E 72 65 67 69 73 74-72 E9 65 20 21 00 4E 75 Enregistr.e !.Nu 0030:0040304E 6D 65 67 61 20 53 6F 66-74 49 63 65 20 64 E9 74 mega SoftIce d.t 0030:0040305E 65 63 74 E9 20 21 00 5C-5C 2E 5C 53 49 43 45 00 ect. !.\\.\SICE. Le résultat de la multiplication est stocké dans EAX, et le débordement
(OverFlow) de celle ci dans EDX.
Description: Multiplie la valeur non signée de l'Accumulator (A) par une
valeur non signée stockée dans un des registres (B). Le byte le moins significatif (bit de poids
faible) est replacé dans l'Accumulator et le plus significatif (bit de poids fort) dans le registre "B".
:0040156C MOV EAX,[00403078] ; eax = 4 premiers caractères du serial entré (le 1er DWORD) :00401577 CMP EAX,EDX ; comparé au débordement de la multiplication :0040157D JNZ 0040166E ; et si la comparaison n'est pas favorable -> Go Out! Nous avons donc d'une part le résultat direct de la multiplication (stocké
dans EAX) qui va faire planter le programme, et d'autre par le débordement de cette même multiplication
qui va être comparé aux 4 premiers caractères du serial. EAX EBX EAX EBX EDX 2 MUL 3 = 6 3 0 -2 MUL -3 = 6 -3 -5 -4 MUL -5 = 20 -5 -9 La multiplication fonctionne comme une opération en valeur absolue pour le
résulat dans EAX, EBX conserve sa valeur d'origine, EDX reçoit la somme de EAX + EBX (du moins pour
les petites valeurs). ( somme du nom + car6789 ) * 582105F5h = 90909090h ( 294h + car6789 ) * 582105F5h = 90909090h 025f6450h * 582105F5h = 90909090h 025f6450h - 294h = 025F61BCh <- caractere 6,7,8,9 du serial pour iorior Dans ce cas, le serial sera du genre, pour le moment, XXXXXBC615F02XXXXXX EAX MUL EBX = EAX EBX EDX 582105F5 025f6450 90909090 025f6450 00D118D5 Le serial commence à prendre forme: D518D100 2D BC615F02 XXXXXXXX XXXX -------- -- -------- -------- ---- A B C D E A est égal à l'overflow de la multiplication (EDX) EAX MUL EBX = EAX EBX EDX 582105F5 7E7AF671 0F0F0F25 7E7AF671 2B8A9582 7E7AF671 - 035A (la somme du Name que j'ai entré) = 7E7AF317 82958A2B 2D 17F37A7E XXXXXXXX XXXX -------- -- -------- -------- ---- A B C D E Second problème, mon AND 0F0F0F0F fonctionne très bien dans le programme
nettoyé de son codage polymorphe, mais fiche la pagaille dans la version en CCA. Une autre "combinaison"
finira par me donner une séquence admise par le code smc... :00401590 XOR EDX,EDX ; EDX = 0 :00401597 MOV EAX,[0040317C] ; EAX = Name entré :004015A1 AND EAX,0F0F0F0F ; And Logique :004015A9 MOV EBX,[00403081] ; EBX = 3ème DWORD pointant sur D :004015B3 SUB EBX,EAX ; EBX - EAX :004015BA MOV [004015C4],EBX ; AutoPatch2 -> le résultat "recode" le crackme :004015C4 JMP 004015C8 ; pointe pour le moment vers un NOP :004015CD OR DL,DL ; l'état de EDX est testé (0 ou non ?) :004015D2 JZ 0040166E ; si Pas Glop va vers la sortie Cette fois ci, pour éviter un autre recodage sauvage du Crackme, c'est en
jouant avec le SUB EBX, EAX (EBX contenant la valeur du Dword D) que l'on va pouvoir obtenir un 90909090. C'est
également ici que iorior m'a donné un coup de main, je m'obstinais à vouloir parvenir à
un 90909090 en voulant jouer sur le AND 0F0F0F0F.... 82958A2B 2D 17F37A7E 45989299 XXXX -------- -- -------- -------- ---- A B C D E Le AND 0F0F0F0F sur les 4 premiers caractères (1er DWORD) du Name m'a donné
09020803h. :004015DD XOR EAX,EAX ; EAX = 0 :004015E5 XOR EBX,EBX ; EBX = 0 :004015EC MOV ESI,00403078 ; ESI = début du sérial :004015F6 MOV ECX,EDX ; ECX récupère le résultat de EDX :004015FC ADD ECX,05 ; ECX +5 = nombre de caractères à traiter :00401602 LODSB ; charge la valeur ASCII pointée par EAX :00401608 ADD EBX,EAX ; l'ajoute à EBX, jusqu'à épuisement :0040160F LOOP 00401602 ; des 5 premiers caractères du serial :00401614 AND BX,F0F0 ; AND logique sur le résultat :0040161F ROR BX,02 ; Rotation à droite de 2 caractères :00401628 ADD BX,3030 ; on y ajoute 3030 :00401631 CMP [00403085],BX ; et comparaison avec la partie E du serial :0040163B JNZ 0040166E ; si Pas Glop -> Go Out! Je pense que les commentaires sont clairs. 82958A2B 2D 17F37A7E 45989299 3430 -------- -- -------- -------- ---- A B C D E La partie est gagnée: :00401643 PUSH 40 ; Si Glop Glop ! :00401645 PUSH 00403014 ; Message Box -> Version Enregistrée :0040164A PUSH 00403036 :0040164F PUSH 00 :00401651 CALL MessageBoxA :0040165A MOV EAX,[00403384] :00401664 MOV FS:[00000000],EAX ; rétablie le SEH Routine de sortie Go Out: :0040166E CALL 004016C3 ; vers le test MELTICE :00401676 MOV ESP,[00403396] :00401682 POPAD :00401683 JMP 004016A9 ; vers XOR EAX,EAX / RET -> sortie sans message Conclusion: Le KeyGenerator: Il est très facile à faire, dans la mesure ou plusieurs éléménts du serial sont des constantes:
merci à CoOlViPeR et iorior |
Bonne Journée