Introduction:
Dans ce tutoriel on va voir de l'aléatoire entre deux nombres, de la gestion de liste noire, et on va faire un peu de code ripping pour notre keygen.
Les outils
Pour appliquer ce tutoriel il vous faudra...
OllyDbg v2.01 (http://Ollydbg.de/)
Detect It Easy (https://github.com/horsicq/DIE-engine/releases)
GerbView v9.20 (https://www.gerbview.com/download/gv9x86.exe)
I: Démarrage:
On installe le programme et on l'exécute pour voir comment ça se passe.
Un nag-screen apparaît, on a 30 jours d'évaluation gratuits.
On clique sur le bouton 'I've already bought a license' la fenêtre d'enregistrement s'ouvre, on rentre n'importe quoi puis on valide.
Une messagebox apparaît pour nous notifier que notre sérial est incorrect, OK.
The serial number you've entered is not valid. Please check it again.
On scanne l'exécutable principal (gerbview.exe) avec DiE (Detect It Easy) pour voir s'il est compressé, on ne dirait pas, super.
II: Trouver la routine d'enregistrement
Chargez l'application dans le débogueur.
On va regarder en premier les strings pour se faire une idée de ce fichier:
Clic droit>Search for>All referenced strings
Ctrl+F, ou bien: clique droit>Search for text
On rentre "serial number" dans le champ de recherche, on coche "Ignore case" puis on valide, on tombe ici:
On double-clique sur le premier string "Serial number" qui nous amène sur sa position: 012C9261.
Ensuite on remonte un peut cette procédure jusqu'a atteindre sont début, puis on y place un breakpoint.
Après on appuie sur F9 pour lancer le programme, ensuite on rouvre la fenêtre d'enregistrement et on essaye de valider un sérial erroné.
Le débogueur break immédiatement.
Il n'y a plus qu'a tracer pour voir ce qu'il se passe, allez hop!
III: Comprendre la routine d'enregistrement
On trace tranquillement à coup de F8 comme d'habitude.
On passe sur un appel d'API de mfc100u, puis un autre, on remarque que c'est des appels via ordinal.
Après le deuxième appel, ça semble passé notre sérial en argument dans une sous-routine, puis un peu plus bas on a un saut conditionnel.
Allons voir avec F7 quel est le rôle de cette routine.
III.a: Construction du serial
Une fois dans la routine, on continue de tracer avec F8.
Le programme copie notre sérial dans un buffer.
On continue, le contenu du sérial dans le buffer passe en majuscule avec wcsupr.
Puis le string du buffer est transmis dans une sous-routine, allons-y jeter un oeil.
Cette sous-routine récupère la longueur de notre sérial.
Ensuite on a une vérification de longueur, si la taille est de zéro on sort, sinon on continue
Puis on arrive dans une boucle qui vérifie que le dernier caractère de notre sérial n'est pas un espace.
Si oui, le programme enlève l'espace, et reboucle pour vérifier qu'il n'y en a pas un autre.
Une fois cette fonction "anti-espace" passée, on sort de la routine.
Une fois de retour, on arrive sur de multiples comparaisons de caractères.
En premier on a MOVZX EDX, WORD PTR SS:[local.48]. Cela place l'adresse de notre buffer sérial dans EDX.
Sur la ligne suivante, on compare la première lettre du buffer contre 47. (C'est de l'hexa, la lettre: G)
Si c'est bon, on continue, sinon le saut conditionnel nous pousse vers la sortie.
On va forcer le flag dans la fenêtre des registres pour continuer de parcourir cette partie et voir ce qu'il demande d'autre.
Tout de suite après on a un autre MOVZX mais avec EAX, toujours avec un pointeur sur notre sérial mais avec [local.48]+2.
+2 et non pas +1 pour le caractère suivant, car notre string est en unicode!.
Cette fois-ci il regarde si la seconde lettre correspond a 'B'.
Ensuite il regarde si la troisième lettre correspond a '5'.
Puis si la quatrième lettre du sérial contient un tiret '-'.
Puis si la septième lettre du sérial contient un tiret '-'.
Puis si la treizième lettre du sérial contient un tiret '-'.
Une fois toutes ses vérifications effectuées, la longueur de notre sérial est encore récupérée puis comparée à 15 (21 en décimal)
Après ça, les passes de validité de la structure du sérial sont terminées.
On sait maintenant que notre sérial doit avoir la forme: GB5-XX-YYYYY-ZZZZZZZZ
III.b: L'algorithme
Le JAE nous envoi directement dans une sous-procédure, pas le temps de niaiser!
Cette procédure est une boucle qui crée une 'table' de 1024 bytes, elle servira pour plus tard dans la routine.
en fin de boucle on remarquera: MOV DWORD PTR DS:[EAX*4+7BBA50],ECX
7BBA50 étant l'offset ou démarre la création de la table, et EAX le compteur qui lui permet de se déplacer dedans pour écrire au fur et à mesure.
On va juste retenir 7BBA50 dans un coin de notre tête.
On sort de cette sous-routine puis on continue de tracer et on arrive sur la vérification de la première partie de notre sérial 'XX'
La routine s'attend a rencontrer un décimal supérieur ou égale à 128 (0x80 en hex), contre 'XX' stocké dans EDX.
'XX' n'étant pas dans la table hexadécimale, EDX contiendra zéro.
Un exemple de première partie valide pourrait être: GB5-FF-YYYYY-ZZZZZZZZ ou bien simplement GB5-80-YYYYY-ZZZZZZZZ
On continue de tracer, et on arrive sur la vérification de 'YYYYY'.
Alors ici pour passer la vérification on doit être supérieur ou égal à 10000 (0x2710 en hex).
Contrairement à la première partie, on n'a pas l'utilisation de wcstoul, donc pas d'hex dans cette partie du sérial, le programme attend du décimal.
Un exemple de deuxième partie valide pourrait être: GB5-FF-99999-ZZZZZZZZ
Ou bien simplement GB5-80-10000-ZZZZZZZZ
Ensuite notre deuxième partie est comparée à une blacklist.
Si notre numéro correspond à ceux de cette liste, on est éjecté:
10218, 10224, 11297, 11396, 11597, 20255, 65205, 65619, 65620, 66563, 66564, 97387
Juste après on observe une sous-procédure avec trois arguments, dont notre sérial en Arg2, ainsi que 0x0C (12) en hard pour Arg3.
Rentrons dans cette routine.
Cette routine traite individuellement les 12 premiers caractères du sérial pour calculer la dernière partie.
On a des opérations bit à bit comme AND, XOR, et du décalage droite/gauche avec SHR, SHL.
Le résultat influe sur la position dans la table, qui donnera notre sérial final.
Une fois sorti de cette procédure, on retrouve un peu plus bas wcstoul, donc de l'hex.
Puis une comparaison entre notre dernière partie de notre sérial et le résultat du dernier calcul.
Si ce n'est pas bon, on ne prend pas le jump, eax passe a zéro via un xor et on sort.
Maintenant de retour dans notre procédure du début, on comprend que si notre sérial était correct ce jump aurait été pris.
IV: Faire un Keygen
Il n'y a plus qu'a.
On n’a pas trainé sur le fonctionnement de la génération de la table, on va juste riper la routine ainsi que la dernière partie pour la génération du troisième code.
La dernière fois, sur mon tutoriel du keygenning m*I*R*C j'avais utilisé asm2clipboard pour faire ça, pour changer on va utiliser IDA.
C'est relativement simple, ouvrer gerbview.exe dans IDA puis depuis le menu: File > Produce file > Create ASM file.
Ou bien simplement ALT+F10.
Ensuite il n'y a plus qu'à copier la partie qui nous intéresse dans notre keygen et faire les raccords.
keygen.asm:
.486 .model flat, stdcall option casemap :none ; case sensitive include \masm32\include\windows.inc include \masm32\include\user32.inc include \masm32\include\kernel32.inc include \masm32\macros\macros.asm includelib \masm32\lib\user32.lib includelib \masm32\lib\kernel32.lib DlgProc PROTO :DWORD,:DWORD,:DWORD,:DWORD .const ; Dialog members IDB_GEN equ 1002 IDB_QUIT equ 1003 IDC_SERIAL equ 1004 IDC_DATE_STATIC equ 1005 ; Constants LIMIT_LOW_Y equ 10000 LIMIT_HIGH_Y equ 99999 MASK_Y equ 00001FFFFh OFFSET_BUFFER_Y equ 4 CHUNK_SIZE_Y equ 5 LIMIT_LOW_X equ 128 LIMIT_HIGH_X equ 255 MASK_X equ 00000007Fh OFFSET_CODE_X equ 0 OFFSET_BUFFER_X equ 6 CHUNK_SIZE_X equ 2 Destination equ word ptr -0C0h ; Initialized data section .data ; App detail szSerialDialog db "Serial Number",0 szClassSerial db "#32770",0 ; Dialog details szTitle db "Software Companions GerbView v9.20 *keygen*",0 szIDBExit db "cLOSE!",0 szIDBGen db "gENERATE!",0 szDateStatic db "o9/11/2021",0 hexadecimal_digits db "0123456789ABCDEF",0 Forbidden_Numbers DWORD 10218, 10224, 11297, 11396, 11597, 20255, 65205, 65619, 65620, 66563, 66564, 97387, (-1) SerialHardPart db "GB5-",0 ; Uninitialized data section .data? Buffer db 20 dup (?) hInstance dd ? timeData dd ? windhand dd ? ; Window handle of the registration dlg szserialtemp db 50 dup (?) HexBuffer db 20 dup (?) DecBuffer db 10 dup (?) RandomHexFinal db 20 dup (?) RandomDecFinal db 10 dup (?) seedY dd ? seedX dd ? presentY dd ? presentX dd ? valueY dd ? valueX dd ? dword_7EBA50 dd ? ; Program code section .code start: ; This function is the entrypoint of the program. invoke GetModuleHandle,NULL mov hInstance,eax invoke DialogBoxParam,hInstance,101,0,addr DlgProc,0 invoke ExitProcess,eax CopyChunk proc chunkSize:DWORD, destination:DWORD, source:DWORD ; This procedure copies a chunk of data of given size from a source ; in memory to a destination. push esi push edi mov esi,source mov edi,destination mov ecx,chunkSize CopyChunk_Loop: cmp ecx,0 jz CopyChunk_End sub ecx,1 mov al,[esi] mov [edi],al add esi,1 add edi,1 jmp CopyChunk_Loop CopyChunk_End: CopyChunk_return: pop edi pop esi ret CopyChunk endp WriteDecimal proc integer:DWORD, buffer:DWORD LOCAL numberTen:DWORD ; This procedure produces a string with the decimal representation of ; an unsigned 32-bit integer into the provided buffer, padded with zeros mov [numberTen], 10 mov ecx,buffer mov al,'0' mov edx,9 WriteDecimal_ForwardLoop: cmp edx,0 jz WriteDecimal_ForwardEnd sub edx,1 mov [ecx],al add ecx,1 jmp WriteDecimal_ForwardLoop WriteDecimal_ForwardEnd: xor al,al mov [ecx],al mov eax,integer WriteDecimal_BackwardLoop: sub ecx,1 xor edx, edx div numberTen add dl,'0' mov [ecx],dl cmp eax,0 jnz WriteDecimal_BackwardLoop WriteDecimal_BackwardEnd: WriteDecimal_return: ret WriteDecimal endp WriteHex proc integer:DWORD, buffer:DWORD ; This procedure produces a string with the hex representation of an ; unsigned 32-bit integer into the provided buffer, padded with zeros mov ecx,buffer mov al,[hexadecimal_digits] mov edx,8 WriteHex_ForwardLoop: cmp edx,0 jz WriteHex_ForwardEnd sub edx,1 mov [ecx],al add ecx,1 jmp WriteHex_ForwardLoop WriteHex_ForwardEnd: xor al,al mov [ecx],al mov eax,integer push ebx lea ebx,[hexadecimal_digits] WriteHex_BackwardLoop: sub ecx,1 mov edx,eax and edx,00Fh shr eax,4 mov dl,[ebx+edx] mov [ecx],dl cmp eax,0 jnz WriteHex_BackwardLoop WriteHex_BackwardEnd: pop ebx WriteHex_return: ret WriteHex endp NextRandomX proc ; This procedure calculate the first part of the serial mov edx, seedX NextRandomX_Loop: add presentX, edx mov eax, presentX and eax, MASK_X add eax, LIMIT_LOW_X cmp eax, LIMIT_HIGH_X ja NextRandomX_Loop NextRandomX_Return: mov [valueX], eax ret NextRandomX endp NextRandomY proc ; This procedure calculates the second part of the serial ; And check it against a list of forbidden numbers. mov edx,seedY NextRandomY_Loop: add presentY,edx mov eax,presentY and eax,MASK_Y add eax,LIMIT_LOW_Y cmp eax,LIMIT_HIGH_Y ja NextRandomY_Loop lea ecx,[Forbidden_Numbers] NextRandomY_ForbiddenLoop: mov edx,[ecx] cmp edx,0 jl NextRandomY_ForbiddenEnd cmp edx,eax jz NextRandomY_ForbiddenLoop_Reset add ecx,4 jmp NextRandomY_ForbiddenLoop NextRandomY_ForbiddenLoop_Reset: mov edx,seedY jmp NextRandomY_Loop NextRandomY_ForbiddenEnd: NextRandomY_End: NextRandomY_return: mov [valueY],eax ret NextRandomY endp DlgProc proc hWin :DWORD, uMsg :DWORD, wParam :DWORD, lParam :DWORD .if uMsg == WM_INITDIALOG ; Set the dialog controls texts. Done here in the code instead of resource ; file to reduce the required bytes (strings in the rc file are UNICODE not ANSI) invoke SetWindowText,hWin,addr szTitle ; Set the window title text invoke SetDlgItemText,hWin,IDB_GEN,addr szIDBGen invoke SetDlgItemText,hWin,IDB_QUIT,addr szIDBExit invoke SetDlgItemText,hWin,IDC_DATE_STATIC,addr szDateStatic ; Initialize the random seeds invoke GetSystemTimeAsFileTime,addr timeData lea eax,[timeData] mov eax,[eax] or eax,1 mov seedX,eax ror eax,8 or eax,1 mov seedY,eax ; Initialize the present data for X and Y xor eax,eax mov presentX,eax mov presentY,eax .elseif uMsg == WM_COMMAND .if wParam == IDB_GEN invoke lstrcat,addr szserialtemp,addr SerialHardPart INVOKE NextRandomX INVOKE WriteHex, valueX, ADDR HexBuffer INVOKE CopyChunk, CHUNK_SIZE_X, ADDR RandomHexFinal, ADDR HexBuffer + OFFSET_BUFFER_X invoke lstrcat,addr szserialtemp,addr RandomHexFinal invoke lstrcat,addr szserialtemp,chr$('-') ; This is generating a number, ; In this example it can be any number superior to 10000 and inferior to 99999. ; The number is then checked against a list of forbidden numbers. ; This should output a 5 chars long result like: 46257 invoke NextRandomY invoke WriteDecimal,valueY,addr DecBuffer invoke CopyChunk,CHUNK_SIZE_Y,addr RandomDecFinal,addr DecBuffer+OFFSET_BUFFER_Y invoke lstrcat,addr szserialtemp,addr RandomDecFinal invoke lstrcat,addr szserialtemp,chr$('-') ; We generate the table. call sub_6394B0 ; We generate the last part. push 0Ch lea eax, [szserialtemp] push eax push 0 call sub_639530 ; Retrieve EAX. invoke WriteHex,EAX,addr Buffer invoke lstrcat,addr szserialtemp,addr Buffer ; Send serial to app invoke FindWindow,addr szClassSerial,addr szSerialDialog .if eax mov windhand, eax invoke SendDlgItemMessageA,windhand,1140,WM_SETTEXT,0,addr szserialtemp .endif invoke SetDlgItemTextA,hWin,IDC_SERIAL,addr szserialtemp invoke RtlZeroMemory,addr szserialtemp,sizeof szserialtemp invoke RtlZeroMemory,addr Buffer,sizeof Buffer .elseif wParam == IDB_QUIT invoke EndDialog,hWin,0 .endif .elseif uMsg == WM_CLOSE invoke EndDialog,hWin,0 .endif xor eax,eax ret DlgProc endp sub_6394B0 proc near ; This procedure generate the table var_C= dword ptr -0Ch var_8= dword ptr -8 var_4= dword ptr -4 push ebp mov ebp, esp sub esp, 0Ch mov [ebp+var_4], 0 jmp short loc_6394C8 loc_6394BF: mov eax, [ebp+var_4] add eax, 1 mov [ebp+var_4], eax loc_6394C8: cmp [ebp+var_4], 100h jge short loc_639525 mov ecx, [ebp+var_4] shl ecx, 18h mov [ebp+var_8], ecx mov [ebp+var_C], 0 jmp short loc_6394EC loc_6394E3: mov edx, [ebp+var_C] add edx, 1 mov [ebp+var_C], edx loc_6394EC: cmp [ebp+var_C], 8 jge short loc_639516 mov eax, [ebp+var_8] and eax, 80000000h jz short loc_63950C mov ecx, [ebp+var_8] shl ecx, 1 xor ecx, 4C11DB7h mov [ebp+var_8], ecx jmp short loc_639514 loc_63950C: mov edx, [ebp+var_8] shl edx, 1 mov [ebp+var_8], edx loc_639514: jmp short loc_6394E3 loc_639516: mov eax, [ebp+var_4] mov ecx, [ebp+var_8] mov dword_7EBA50[eax*4], ecx jmp short loc_6394BF loc_639525: mov esp, ebp pop ebp retn sub_6394B0 endp sub_639530 proc near ; This procedure calculates the third (last) part of the serial var_8= dword ptr -8 var_4= dword ptr -4 arg_0= dword ptr 8 arg_4= dword ptr 0Ch arg_8= dword ptr 10h push ebp mov ebp, esp sub esp, 8 mov [ebp+var_8], 0 jmp short loc_639548 loc_63953F: mov eax, [ebp+var_8] add eax, 1 mov [ebp+var_8], eax loc_639548: mov ecx, [ebp+var_8] cmp ecx, [ebp+arg_8] jge short loc_639585 mov edx, [ebp+arg_0] shr edx, 18h mov eax, [ebp+arg_4] movzx ecx, word ptr [eax] xor edx, ecx and edx, 0FFh mov [ebp+var_4], edx mov edx, [ebp+arg_4] add edx, 1 mov [ebp+arg_4], edx mov eax, [ebp+arg_0] shl eax, 8 mov ecx, [ebp+var_4] xor eax, dword_7EBA50[ecx*4] mov [ebp+arg_0], eax jmp short loc_63953F loc_639585: mov eax, [ebp+arg_0] mov esp, ebp pop ebp retn sub_639530 endp end start
keygendlg.rc:
;This Resource Script was generated by WinAsm Studio. #define IDB_GEN 1002 #define IDB_QUIT 1003 #define IDC_SERIAL 1004 #define IDC_DATE_STATIC 1005 101 DIALOGEX 10,10,184,43 FONT 8,"MS Shell Dlg" STYLE 0x80c80880 EXSTYLE 0x00000000 BEGIN CONTROL "",IDB_QUIT,"Button",0x10010000,127,28,51,13,0x00000000 CONTROL "",IDC_SERIAL,"Edit",0x50010801,3,9,177,12,0x00000200 CONTROL "",IDB_GEN,"Button",0x10010000,70,28,51,13,0x00000000 CONTROL "",IDC_DATE_STATIC,"Static",0x58000000,3,34,44,9,0x00000000 END
make.bat:
@echo off \masm32\bin\rc /v keygendlg.rc \masm32\bin\ml.exe /c /coff /Cp /nologo keygen.asm \masm32\bin\link.exe /SUBSYSTEM:WINDOWS /RELEASE /VERSION:4.0 /OUT:keygen.exe keygen.obj keygendlg.res del keygendlg.res del keygen.obj pause
Une fois compilé, ça ressemble à ça:
On test une clé générée.
Et voilà c'est fini !
Xylitol, o9/11/2021
Copyright (C)- xtx Team (2021)