ÉÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ» º KeygenMe hccc #5 º ÈÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍͼ Outils utilises : ~~~~~~~~~~~~~~~~~ - un debugger (Soft-Ice, j'ai pas encore essaye TRW 2000) - un desassembleur pour lire le code a tete reposee (W32dasm pour ses Strings Data plus explicites que les Names d'Ida) - un Editeur Hexadecimal (UltraEdit parce que je j'aime bien ce prog) - un crayon (ou plusieurs a vous de voir) du papier (c'est pratique pour noter les adresses leurs corespondances) Bon tout est pret on peut y aller. Lancons le prog pour voir et il commence par nous insulter en disant qu'il a detecte SoftIce. Comme j'ai lu le .txt qui l'accompagnait et j'ai vu que c'etait un 'petit' Anti SoftIce, on ne va pas aller chercher FrogsIce pour ci peu, donc on fait une copie de sauvegarde du prog et on le desassemble. Ca va vite, il est pas gros. On trouve deux Strings Data interessantes qui nous ammenent au listing suivant : * Possible StringData Ref from Data Obj ->"Vilain Gar" ; Titre de la boite de msg | :0040134A 6804614000 push 00406104 * Possible StringData Ref from Data Obj ->"Softice Detected ;(" ; txt de la boite de msg | :0040134F 68F0604000 push 004060F0 :00401354 8B4D08 mov ecx, dword ptr [ebp+08] :00401357 51 push ecx * Reference To: USER32.MessageBoxA, Ord:01BEh | :00401358 FF15B8504000 Call dword ptr [004050B8] ; appel de la boite :0040135E E98F000000 jmp 004013F2 ; on continue... on reomonte un peu et on trouve : :00401317 B443 mov ah, 43 ; service 43h :00401319 CD68 int 68 ; interuption 68h :0040131B 663D86F3 cmp ax, F386 ; F386 = Magic Number :0040131F 7405 je 00401326 ; ax = Magic Number => debugger? present! :00401321 E9CC000000 jmp 004013F2 ; on continue... * Referenced by a (U)nconditional or (C)onditional Jump at Address: ; nous ramene au code precedant et a sa |:0040131F(C) ; MessageBox | :00401326 68EA030000 push 000003EA :0040132B 8B5508 mov edx, dword ptr [ebp+08] :0040132E 52 push edx * Reference To: USER32.GetDlgItem, Ord:0102h | :0040132F FF15C0504000 Call dword ptr [004050C0] :00401335 A3A06B4000 mov dword ptr [00406BA0], eax :0040133A 6A00 push 00000000 :0040133C A1A06B4000 mov eax, dword ptr [00406BA0] :00401341 50 push eax * Reference To: USER32.EnableWindow, Ord:00B7h | :00401342 FF15BC504000 Call dword ptr [004050BC] :00401348 6A00 push 00000000 Pour ceux qui n'ont jamais eu a faire a un Anti-XXX sachez qu'il existe une multitude de moyens de savoir si un debugger est present ou non. Certains cible tous les debuggers (ex: code ci-dessus), d'autres seulement certains (souvent Soft-Ice). Deux solutions existent alors soit utiliser FrogsIce (merci +Frog's Print) qui se charge de cacher le debugger, soit eliminer la protection. Ici on va choisir la deuxieme solution car il n'y a qu'un seul endroit où le prog essaie de detecter un eventuel debugger. C'est un appel a l'Int 68h, sevice 43h renvoie F386 dans ax si un debugger est present. Pour l'instant on va patcher le prog assez sauvagement en Nopant je saut conditionel, 7405 ->9090 ; je +05 -> no operation. Ce tutoriel ne vous expliquera pas comment faire un memory patcher car j'ai pas encore bien assimile le code contenu dans le tut de christal (cf hccc #5) et je n'aime pas utiliser un code que je ne comprend pas parfaitement. Maintenant qu'on peut lancer le prog sans probleme, voyons ce qui se passe. Tout d'abord le bouton 'Register' n'est plus grise, on peut donc commencer a faire des essais. Si on n'entre pas de nom et/ou de serial le prog nous le signale ainsi que si le nom comporte moins de 4 caracteres. Lors du premier desassemblage, j'ai vu une String Data : "%d-%d-69", pour ceux qui ne connaissent pas le C, c'est une chaine de formatage, le %d veut dire un nombre entier fourni en parametre. C'est a dire que le serial risque d'etre de la forme n1-n2-69 où n1 et n2 sont des nombre calcules par le prog. Pour voir ce que le prog faisait des entrees saisies j'ai pose un Breakpoint sur l'Api GetDlgItemTextA. Soft-Ice break deux fois et on s'apercoit que le prog met mon nom en : 00406A5C et mon serial en 00406B04. ensuite on se retrouve dans le code suivant : 004010B7 call ds:GetDlgItemTextA 004010BD movsx edx, byte_406A5C // met la premiere lettre du nom dans edx 004010C4 test edx, edx // effectue un ET logique, est-elle egale a 0 004010C6 jnz short loc_4010E3 // non ? on saute 004010C8 push 0 // oui, alors le nom ne contient pas de caractere 004010CA push offset aKeygenme ; "KeyGeNMe" // affiche une MessageBox qui vous dit d'entrer un nom 004010CF push offset aVousDevezEntre ; "Vous devez entrer un nom" 004010D4 mov eax, [ebp+arg_0] 004010D7 push eax 004010D8 call ds:MessageBoxA 004010DE jmp loc_401312 [Cette partie du code provient d'IDA qui est (a mon gout) plus pratique pour etudier le code, mais il est assez long pour desassembler.] C'etait le test pour savoir s'il y a un nom d'entre, le test du serial suit, puis le test du nombre de caracteres. Ensuite on arrive a une partie du code qui nous interesse un peu plus : 00401144 loc_401144: ; CODE XREF: sub_401000+127j 00401144 push offset aKeygenme_0 ; "KeygenMe" 00401149 push offset byte_406A5C // adresse du Nom 0040114E push offset aHccc ; "Hccc" 00401153 push offset aSSS ; "%s%s%s" 00401158 push offset unk_406B38 0040115D call _sprintf 00401162 add esp, 14h 00401165 mov dword_406A50, 0 0040116F jmp short loc_401180 Voici le grand interet d'IDA, les fonctions standard du C sont nomees dans le Dead Listing, ce qui est bien pratique plutôt que de tracer un 'CALL 00401470' on sait que c'est la fonction 'sprintf'. Toujours pour ceux qui ne conaissent pas le C, cette fonction va mettre dans une chaine de caractere le contenu d'une chaine de formatage. dans ce cas "%s%s%s" signifie que la fonction va mettre bout a bout 3 chaines de caracteres qui lui seront fournis en parametre. Dans le code original la fonction a ete appelee comme suit : sprintf(destination, "%s%s%s", "Hccc", Nom, "KeygenMe"); Les parametres sont mis sur la pile (push) dans l'ordre inverse. Resultat de cet appel : l'adresse 00406B38 contient la chaine "HcccNomKeygenMe". C'est cette chaine qui va servir a la generation du serial. Continuons l'analyse de ce prog et on tombe juste apres sur les boucles (y en a deux) de generation. Voici le code commente : Boucle 1: * Referenced by a (U)nconditional or (C)onditional Jump at Address: |:004011E5(U) | :00401171 mov ecx, dword ptr [00406A50] ;index -> ecx } :00401177 add ecx, 00000001 ;ecx + 1 -> ecx } index + 1 -> index :0040117A mov dword ptr [00406A50], ecx ;ecx -> index } * Referenced by a (U)nconditional or (C)onditional Jump at Address: |:0040116F(U) | :00401180 mov edx, dword ptr [00406A50] ; index -> edx :00401186 movsx eax, byte ptr [edx+00406B38] ; *"HcccNomKeygenMe" + index -> eax :0040118D test eax, eax ; dernier caractere ? :0040118F je 004011E7 ; oui, saute en 004011E7 (sort) :00401191 mov ecx, dword ptr [00406A50] ; non, index -> ecx :00401197 movsx edx, byte ptr [ecx+00406B38] ; *"HcccNomKeygenMe" +index -> edx :0040119E mov dword ptr [00406A94], edx ; edx -> carac :004011A4 mov eax, dword ptr [00406A94] ; carac -> eax :004011A9 add eax, 00000009 ; eax + 9 -> eax } :004011AC mov dword ptr [00406A94], eax ; } carac + 9 -> carac :004011B1 mov ecx, dword ptr [00406A94] ; carac + 9 -> ecx :004011B7 imul ecx, 0000000E ; ecx * 14 -> ecx :004011BA mov dword ptr [00406A94], ecx ; (carac + 9) * 14 -> carac :004011C0 mov eax, dword ptr [00406A94] ; carac -> eax :004011C5 cdq ; converti un double word en quad word, met edx a 0 :004011C6 and edx, 00000003 ; reste de edx/4 -> eax :004011C9 add eax, edx ; (carac + 9) * 14 + edx%4 :004011CB sar eax, 02 ; ((carac + 9) * 14 + edx%4) / 4 -> eax :004011CE mov dword ptr [00406A94], eax ; eax -> carac :004011D3 mov edx, dword ptr [00406B00] ; count -> edx :004011D9 add edx, dword ptr [00406A94] ; count + edx -> edx :004011DF mov dword ptr [00406B00], edx ; edx -> count :004011E5 jmp 00401171 ; boucle boucle 2: * Referenced by a (U)nconditional or (C)onditional Jump at Address: |:0040118F(C) | :004011E7 mov dword ptr [00406A50], 00000000 ; index = 0 :004011F1 jmp 00401200 * Referenced by a (U)nconditional or (C)onditional Jump at Address: |:00401242(U) | :004011F3 mov eax, dword ptr [00406A50] :004011F8 add eax, 00000001 :004011FB mov dword ptr [00406A50], eax * Referenced by a (U)nconditional or (C)onditional Jump at Address: |:004011F1(U) | :00401200 mov ecx, dword ptr [00406A50] ; index -> ecx :00401206 movsx edx, byte ptr [ecx+00406B38] ; *"HcccNomKeygenMe" + index -> edx :0040120D test edx, edx ; dernier caractere ? :0040120F je 00401244 ; oui, sort de la boucle :00401211 mov eax, dword ptr [00406A50] ; non, index -> eax :00401216 movsx ecx, byte ptr [eax+00406B38] ; *"HcccNomKeygenMe" + index -> ecx :0040121D mov dword ptr [00406A94], ecx ; ecx -> var1 :00401223 mov edx, dword ptr [00406A94] ; var1 -> edx :00401229 xor edx, 00000009 ; edx XOR 9 -> edx :0040122C mov dword ptr [00406A94], edx ; (*"HcccNomKeygenMe" + index) XOR 9 -> var1 :00401232 mov eax, dword ptr [00406A98] ; count -> eax :00401237 add eax, dword ptr [00406A94] ; var1 + count -> eax :0040123D mov dword ptr [00406A98], eax ; var1 + count -> count :00401242 jmp 004011F3 ; boucle Utilisation des boucles * Referenced by a (U)nconditional or (C)onditional Jump at Address: |:0040120F(C) | :00401244 mov ecx, dword ptr [00406A98] ; count boucle 2 :0040124A push ecx :0040124B mov edx, dword ptr [00406B00] ; count boucle 1 :00401251 push edx * Possible StringData Ref from Data Obj ->"%d-%d-69" | :00401252 push 00406138 ; "%d-%d-69" :00401257 push 00406A9C ; buffer pour stocker le serial :0040125C call 00401470 ; fonction 'sprintf' Apres avoir etudier le listing precedant on voit que serial = count1-count2-69. Comme s'etait prevu! Au passage, pour voir le serial, il est stocke en 00406A9C, donc sous Soft-Ice un 'd 00406A9C' affichera le bon serial. Maintenant le but pour faire le Keygen et de refaire : la chaine "HcccNomKeygenMe" et les deux boucles. Pour le language au choix, en ASM c'est chiant mais on peut faire du copier/coller, perso je prefere le C. Voici donc le code du keygen en C : #include void main(void) { int count1 = 0, count2 = 0, temp, temp2; // variables numeriques char *nom, *chaine, *serial; // chaines de caracteres printf("\nVotre nom : "); // affichage d'une chaine scanf("%s", &nom); // saisie du nom sprintf(chaine, "Hccc%sKeygenMe", &nom); // creation de la chaine "HcccNomKeygenMe" avec sprintf... for (int index = 0; ;index++) // 1ere boucle { if (!chaine[index] = NULL) // dernier caractere (NULL) ? break; // sort de la boucle temp = chaine[index]; temp += 9; // temp = temp + 9 temp *= 14; // temp = temp * 14 temp /= 4; // temp = temp / 4 temp2 = 0; // edx toujours a zero (?) temp += temp2; count1 += temp; // compteur = compteur + temp } // on boucle for (i = 0; ; i++) // boucle 2 { if (!chaine[index]) // dernier caractere ? break; // on sort de la boucle temp = chaine[index]; asm xor temp, 0x00000009 // je ne sais pas pourquoi mais le xor du c donne de faux resultats //temp = temp ^ 9; // temp = temp XOR 9 equivalent en asm de la ligne au dessus count2 += temp; // compteur2 = compteur + temp } // on boucle sprintf(serial, "%d%d-69", count1, count2); // ca vous rappele rien ? printf("Votre serial est : %s", serial); // affichage du serial } On a maintenant un Keygen qui semble marche (je ne l'ai jamais pris en default), mais maintenant que l'on sait où est place le bon serial, ne pourait-on pas, lorsque le serial est faux, afficher un MessageBox indiquant le bon serial ? Avec un minimum de base en reverse c'est faisable. tout d'abord, il faut trouver de la place pour ecrire notre code qui n'est pas tres long : 4 push pour les parametres de la fonction MessageBoxA, 1 call pour l'appel et un saut. Il faudra peut-etre reconstituer les instruction qui auront ete ecrasees lors du patch. Pour trouver de la place, il faut savoir que lorsqu'une section n'est pas complete, le compilateur la rempli generalement de 0. C'est l'endroit que nous allons choisir, avant de s'y installer, il faut regarder si cette zone de l'executable n'est pas utilisee par le programme lors de son fonctionnement (pour des variables, ...). On va voir en 6350 qui va corespondre a l'adresse 00406350 en memoire. Bosons un BPR (BreakPoint on memory Range) entre 00406350 et 00406380 (une zone de 48 octets devrait suffire) : BRP 00406350 00406380 RW Soft-Ice ne break pas, il semble effectivement ne rien avoir dans cette zone. Ecrivons alors notre patch. Voici le prototype de la fonction MessageBox int MessageBox( HWND hWnd, // handle of owner window LPCTSTR lpText, // address of text in message box LPCTSTR lpCaption, // address of title of message box UINT uType // style of message box ); push 0 // style de boite push 004061B4 // adresse du titre KeygenMe push 00406A9C // bon serial push 0 // cette nouvelle fenetre n'apartient a rien call MessageBoxA ?? ?? // instructions eventuellement ecrasees par le detour vers le patch jmp 0040???? // retourne d'où l'on vient Maintenant on veut placer notre boite juste apres la boite "Tu t'es plante". Voyons comment ca se presente : :004012F0 6A00 push 00000000 * Possible StringData Ref from Data Obj ->"KeyGeNMe" | :004012F2 68B4614000 push 004061B4 * Possible StringData Ref from Data Obj ->"Tu t'es plant" | :004012F7 6814614000 push 00406114 :004012FC 8B4D08 mov ecx, dword ptr [ebp+08] :004012FF 51 push ecx * Reference To: USER32.MessageBoxA, Ord:01BEh | :00401300 FF15B8504000 Call dword ptr [004050B8] // ici me semble etre une bonne idee :00401306 EB0A jmp 00401312 * Referenced by a (U)nconditional or (C)onditional Jump at Address: |:004012EE(C) | :00401308 EB08 jmp 00401312 * Referenced by a (U)nconditional or (C)onditional Jump at Address: |:00401051(C) | :0040130A 6A00 push 00000000 * Reference To: KERNEL32.ExitProcess, Ord:007Dh | :0040130C FF1564504000 Call dword ptr [00405064] * Referenced by a (U)nconditional or (C)onditional Jump at Addresses: |:00401064(U), :004010DE(U), :00401104(U), :0040113F(U), :004012E5(U) |:00401306(U), :00401308(U) | :00401312 E9DB000000 jmp 004013F2 La on va faire un detour par notre patch en 00406350 :00401300 E94B500000 jmp 00406350 // ici me semble etre une bonne idee :00401305 90 nop // pour compenser la difference de taille entre les instructions :00401306 EB0A jmp 00401312 call MessageBoxA // instruction ecrasee push 0 // style de boite push 004061B4 // adresse du titre KeygenMe push 00406A9C // bon serial push 0 // cette nouvelle fenetre n'apartient a rien call MessageBoxA jmp 004013F2 // effectue le saut que notre detour a remplace. Maintenant que nous savons tout, posons un Bpx en 00401300 pour aller assembler tout cela en memoire call MessageBoxA E8CBDDB4BF push 0 6A00 push 004061B4 68B4614000 push 00406A9C 689C6A4000 push 0 6A00 call MessageBoxA E8CBDDB4BF jmp 00401312 E9A5AFFFFF ; les sauts sont relatifs a l'adresse de depart du saut Maintenant c'est le prog qui sert de Keygen. Cette methode peut s'averer plus simple si l'algorithme de generation de serial est complexe, bien cache ou les deux! Kleph - !!!@???.?!?