Zoom Icon

NewBies Simple KeyGenMe

From UIC

Simple KeygenMe

Contents


NewBies Simple KeyGenMe
Author: Zyrel
Email: marcolagalla@yahoo.it
Website: Unfair-Gamers
Date: 10/01/2011 (dd/mm/yyyy)
Level: Half brain is enough
Language: English Flag English.gif
Comments: Really simple, suitable for newbies.



Introduction

Directly from UIC's glossary the definition of KeygenMe:

« Programs written specifically to be cracked / reversed. Typically the only feature of crackme is its protection, which may take as a model existing protections (such as commercial programs), or more frequently offer something new and original.».

In this particular case we're not going to crack the program (or we'll do it just for completeness, but is not our goal) but instead we're going to "sniff" the code in order to deduce the algorithm that generates the correct serial.

Flag Italian.gif Notice: there is an italian version of this tutorial.



Tools & Files


URL or FTP program

For the occasion we're going to use a small program created by me: KeygenMe#1


Essay

Well, now I assume that you at least know the basics of assembly and at least one programming language (to create the keygen).

Ok, now start the tutorial itself, start the program just to have an idea of how it works:

KeyGenMe-1.png

Well what we see?

 1^ Textbox: not editable and shows us a number (integer).
 • 2^ Textbox: in which we are asked to enter our serial.
 • Button: used to check the serial.

There is no request for a nick or otherwise, then it seems logical to assume that the algorithm that generates the serial based on precisely that issue, well now we ask:Why just that number?(67 in the screen) , here is if you try to reopen the program you will find another number, always an integer, but each time different.

Perfect load the target (I'll often call in this way the KeygenMe for the rest of the tutorial) in Olly and let's stop to take a look at the code:

00401240 K> $  55             PUSH EBP
00401241    .  89E5           MOV EBP,ESP
00401243    .  83EC 08        SUB ESP,8
00401246    .  C70424 0200000>MOV DWORD PTR SS:[ESP],2
0040124D    .  FF15 3C614000  CALL DWORD PTR DS:[<&msvcrt.__set_app_type>]           ;  msvcrt.__set_app_type
00401253    .  E8 A8FEFFFF    CALL Keygen.00401100
00401258    .  90             NOP
00401259    .  8DB426 0000000>LEA ESI,DWORD PTR DS:[ESI]
00401260    $  55             PUSH EBP
00401261    .  8B0D 54614000  MOV ECX,DWORD PTR DS:[<&msvcrt.atexit>]                ;  msvcrt.atexit
00401267    .  89E5           MOV EBP,ESP
00401269    .  5D             POP EBP                                                ;  kernel32.7671D0E9
0040126A    .  FFE1           JMP ECX
0040126C       8D7426 00      LEA ESI,DWORD PTR DS:[ESI]
00401270    .  55             PUSH EBP
00401271    .  8B0D 48614000  MOV ECX,DWORD PTR DS:[<&msvcrt._onexit>]               ;  msvcrt._onexit
00401277    .  89E5           MOV EBP,ESP
00401279    .  5D             POP EBP                                                ;  kernel32.7671D0E9
0040127A    .  FFE1           JMP ECX

This is the entrypoint of the program, but this part does not concern us, rather shaken down:

 • Here we see a call to CreateWindowExA:
00401344   |> \C74424 2C 0000>MOV DWORD PTR SS:[ESP+2C],0                            ; |
0040134C   |.  8B45 08        MOV EAX,[ARG.1]                                        ; |
0040134F   |.  894424 28      MOV DWORD PTR SS:[ESP+28],EAX                          ; |kernel32.BaseThreadInitThunk
00401353   |.  C74424 24 0000>MOV DWORD PTR SS:[ESP+24],0                            ; |
0040135B   |.  C74424 20 0000>MOV DWORD PTR SS:[ESP+20],0                            ; |
00401363   |.  C74424 1C 9600>MOV DWORD PTR SS:[ESP+1C],96                           ; |
0040136B   |.  C74424 18 9001>MOV DWORD PTR SS:[ESP+18],190                          ; |
00401373   |.  B8 00000080    MOV EAX,80000000                                       ; |
00401378   |.  894424 14      MOV DWORD PTR SS:[ESP+14],EAX                          ; |kernel32.BaseThreadInitThunk
0040137C   |.  B8 00000080    MOV EAX,80000000                                       ; |
00401381   |.  894424 10      MOV DWORD PTR SS:[ESP+10],EAX                          ; |kernel32.BaseThreadInitThunk
00401385   |.  C74424 0C 0000>MOV DWORD PTR SS:[ESP+C],0CF0000                       ; |
0040138D   |.  C74424 08 0040>MOV DWORD PTR SS:[ESP+8],Keygen.00404000               ; |ASCII "Keygen Me  #1"
00401395   |.  C74424 04 0030>MOV DWORD PTR SS:[ESP+4],Keygen.00403000               ; |ASCII "WindowsApp"
0040139D   |.  C70424 8801000>MOV DWORD PTR SS:[ESP],188                             ; |
004013A4   |.  E8 A70B0000    CALL <JMP.&USER32.CreateWindowExA>                     ; \CreateWindowExA
 • Here we find an interesting part, with two calls, at rand() and srand()
004013AC   |.  8945 F4        MOV [LOCAL.3],EAX                                      ; |||kernel32.BaseThreadInitThunk
004013AF   |.  C70424 0000000>MOV DWORD PTR SS:[ESP],0                               ; |||
004013B6   |.  E8 F50A0000    CALL <JMP.&msvcrt.time>                                ; ||\time
004013BB   |.  890424         MOV DWORD PTR SS:[ESP],EAX                             ; ||kernel32.BaseThreadInitThunk
004013BE   |.  E8 FD0A0000    CALL <JMP.&msvcrt.srand>                               ; |\srand
004013C3   |.  E8 D80A0000    CALL <JMP.&msvcrt.rand>                                ; |[rand

Well here we understand how the number that is generated (as I anticipated) the algorithm is based. Then we deduce that uses the srand () and rand () using as seed the first time () function, there is a call to rand () again below but we are not interested.

Run with F9 and try to enter a serial at random, I will use the good old 1234567890 ;) There appears a messagebox that warns us that the serial is not correct, Well I think that the program can be done more or less like this:

// we see only the part that interests us
unsigned long s_fake = 1234567890; // our serial (wrong)
if (s_fake == generate()) {
MessageBox (NULL, "The serial is correct","Great work", MB_OK);
}
else {
MessageBox (NULL, "The serial is wrong","Try again", MB_OK);
}

Well then do not waste time ;) Topping up your target on Olly pressing CTRL + F2 and Commandline type:


bpx MessageBoxA
 N.B. who did not have the plugin CmdLine can find it   here.

In this way we placed a bp (BreakPoint) on all calls to that API (attention to character cases, the cmdline is case-sensitive), now re run pressing F9 and add with our fictitious serial (for me 1234567890), just before the MsgBox appears to tell us that our serial is wrong with the debugger break us here:

004017D7   |.  E8 E4070000    CALL <JMP.&USER32.MessageBoxA>                         ; \MessageBoxA

If you look just above you will see:

0040178A       3B45 F8        CMP EAX,DWORD PTR SS:[EBP-8]                           ;  EAX contains our serial and EBP points to the correct serial
0040178D   |.  75 29          JNZ SHORT Keygen.004017B8                              ; |conditional jump if the Z flag is set to 0 jumps to 004017B8
0040178F   |.  C74424 0C 0000>MOV DWORD PTR SS:[ESP+C],0                             ; |
00401797   |.  C74424 08 E140>MOV DWORD PTR SS:[ESP+8],Keygen.004040E1               ; |ASCII "Ottimo lavoro" Great work
0040179F   |.  C74424 04 EF40>MOV DWORD PTR SS:[ESP+4],Keygen.004040EF               ; |ASCII "Seriale Corretto" The serial is correct
004017A7   |.  C70424 0000000>MOV DWORD PTR SS:[ESP],0                               ; |
004017AE   |.  E8 0D080000    CALL <JMP.&USER32.MessageBoxA>                         ; \MessageBoxA
004017B3   |.  83EC 10        SUB ESP,10
004017B6   |.  EB 27          JMP SHORT Keygen.004017DF
004017B8   |>  C74424 0C 0000>MOV DWORD PTR SS:[ESP+C],0                             ; |
004017C0   |.  C74424 08 0041>MOV DWORD PTR SS:[ESP+8],Keygen.00404100               ; |ASCII "Tenta ancora ;)" Try again
004017C8   |.  C74424 04 1041>MOV DWORD PTR SS:[ESP+4],Keygen.00404110               ; |ASCII "Seriale Sbagliato" The serial is wrong
004017D0   |.  C70424 0000000>MOV DWORD PTR SS:[ESP],0                               ; |
004017D7   |.  E8 E4070000    CALL <JMP.&USER32.MessageBoxA>                         ; \MessageBoxA

I commented the code in the important points, but the thing that interests us most at the moment is that:

0040178A       3B45 F8        CMP EAX,DWORD PTR SS:[EBP-8]                           ;  EAX contains our serial and EBP points to the correct serial
0040178D   |.  75 29          JNZ SHORT Keygen.004017B8                              ; |conditional jump if the Z flag is set to 0 jumps to 004017B8

Now we open a small bracket:

The Cracking

I believe that you have already guessed, but if we reverse that jump we make that if a serial was the wrong targets would appear to confirm the MsgBox (You want to try? try to set the zero flag to 1, you will see that not make the leap and you will see just MsgBox) I will not dwell much because it is not our purpose to go straight to answer:

First method: replace jnz (Jump if Not Zero) with a JE (Jump if Equal)
   •Second method: jnz replaced with a JMP or a jump to incodizionato 0040178F, MsgBox Confirmation
   •Third method: noppare all doing well up the MsgBox

Frankly, I prefer the first method, then we go for a small patch of just having one byte as follows:

0040178D      /74 29          JE SHORT Keygen.004017B8                               ;  conditional jump

In fact, in the box below:

Jump is NOT taken

Now to make the changes permanent you go read the tutorial on how to do manual patching or for the first (if you already know how to do it manually)Right click-> Copy to executables-> All Modifications-> Save.


Identification and study of the algorithm:

Now we enter the real part of our tutorial: analysis of the algorithm. We left here:

0040178A       3B45 F8        CMP EAX,DWORD PTR SS:[EBP-8]                           ;  EAX contains our serial and EBP points to the correct serial

If even more shaken up, just below theGetDlgItemInt you will find this CALL:

00401757   |.  E8 BA000000    CALL Keygen.00401816

Hmm...a call in the function that reads the value (number) of the textbox... close to the conditional jump...Well I would say that it may be interesting, so we place a CALL bp with the F2 and run entering the serial, stop us on the CALL, pressing F7 we step into the CALL and we brought up a routine that, I play my slippers is that the generation of the serial:

00401816   /$  55             PUSH EBP
00401817   |.  89E5           MOV EBP,ESP
00401819   |.  83EC 0C        SUB ESP,0C
0040181C   |.  8B45 08        MOV EAX,DWORD PTR SS:[EBP+8]
0040181F   |.  0FAF45 08      IMUL EAX,DWORD PTR SS:[EBP+8]
00401823   |.  8945 F8        MOV DWORD PTR SS:[EBP-8],EAX
00401826   |.  8B45 08        MOV EAX,DWORD PTR SS:[EBP+8]
00401829   |.  0FAF45 F8      IMUL EAX,DWORD PTR SS:[EBP-8]
0040182D   |.  8945 F4        MOV DWORD PTR SS:[EBP-C],EAX
00401830   |.  8B45 F8        MOV EAX,DWORD PTR SS:[EBP-8]
00401833   |.  0FAF45 F4      IMUL EAX,DWORD PTR SS:[EBP-C]
00401837   |.  0345 08        ADD EAX,DWORD PTR SS:[EBP+8]
0040183A   |.  8945 FC        MOV DWORD PTR SS:[EBP-4],EAX
0040183D   |.  8B45 FC        MOV EAX,DWORD PTR SS:[EBP-4]
00401840   |.  C9             LEAVE
00401841   \.  C3             RETN

As you can see does nothing more than to do some simple calculations with the value in EAX, which is precisely the number random by the program shown in the textbox disabled. In fact, the value returned by an API is always contained in EAX (from GetDlgItemInt). For further proof typedin EAX and you will see that CmdLine contains precisely that number.

I would say that the code is clear, however, Let's see the basic traits:

0040181F   |.  0FAF45 08      IMUL EAX,DWORD PTR SS:[EBP+8]
00401829   |.  0FAF45 F8      IMUL EAX,DWORD PTR SS:[EBP-8]
00401833   |.  0FAF45 F4      IMUL EAX,DWORD PTR SS:[EBP-C]
00401837   |.  0345 08        ADD EAX,DWORD PTR SS:[EBP+8]

That translated into pseudo-code would be:

0040181F multiplies eax by itself, then eax ^ 2
00401829 multiplies eax * (eax ^ 2) ^ 3 then eax
00401833 multiplies (eax ^ 2) * (eax ^ 3) ^ 5 then eax
00401837 add (eax ^ 5) + eax

So the deliver that generates the serial is:(EAX ^ 5) + Eax where eax is the random number. But here lies the only difficulty with this pseudo crackme, the result of calculations is treated as and when dword eax is greater than 84 (which happens often) then Eax exceeds 0xFFFFFFFF ^ 5, which generates overflow errors that make it invalid in the eyes the program even if the serial potentially correct.

A keygen is precisely follow the calculations made by the program, then: Eax * Eax * Eax *(Eax ^ 2) + Eax and thus avoid an overflow, even more correctly the most appropriate solution would be:serial = ((Eax ^ 5) and & HFFFFFFFF + Eax) and & HFFFFFFFFwith two logical And.

The KeyGen

Finally our beloved keygens ;) I will present it in two languages: C + + and ASM.

#include <iostream>
using std::cout;
using std::cin;

int main() {
    int eax;
    unsigned long serial;
    cout << "Enter the number returned by the program: ";
    cin >> eax;
    serial = eax * eax;
    serial = serial * eax;
    serial = serial * (eax*eax);
    serial = serial + eax;
    cout << "\nThe correct serial: " << serial << "\n";
    system("pause");
    return 0;
}

Asm:

.486
.model  flat, stdcall
option  casemap :none   ; case sensitive


include windows.inc

uselib  MACRO   libname
        include         libname.inc
        includelib      libname.lib
ENDM

uselib  user32
uselib  kernel32

;prototype declaration of the functions of the program
DlgProc         PROTO :DWORD,:DWORD,:DWORD,:DWORD       ;window function
MakeSerial      PROTO :DWORD                                                    ;function generation serial
ErrorProc               PROTO                                                                   ; error function for placing the code

; setting names for the objects of the window
IDC_TXTCODE     equ     1001
IDC_TXTSERIAL   equ     1002
IDC_CMDGEN              equ     1003
IDC_CMDEXIT             equ     1004

;dati non inizializzati
.data?
hInstance               DWORD ? ;dd can be written as dword
lptranslated    DWORD ? ;variable to see if the code is' valid
hWindow         DWORD ? ;replicated for the MessageBox window handle

;dati inizializzati
.data
szerrortext                     db "Value Code incorrect or missing!",0
szerrorcaption                  db "ERROR",0

.code
ErrorProc proc
        ;deletes both the code that the serial
        invoke SetDlgItemText,hWindow,IDC_TXTCODE,NULL
        invoke SetDlgItemText,hWindow,IDC_TXTSERIAL,NULL
        ;error message
        invoke MessageBox,hWindow,ADDR szerrortext,ADDR szerrorcaption,MB_ICONERROR
        Ret
ErrorProc EndP

start:
        invoke  GetModuleHandle, NULL ;exe back to the imagebase (handle) to be passed to create the window
        mov     hInstance, eax
        invoke  DialogBoxParam, hInstance, 101, 0, ADDR DlgProc, 0 ;creation window
        invoke  ExitProcess, eax ;exit the process
; -----------------------------------------------------------------------
DlgProc proc    hWin    :DWORD,
                uMsg    :DWORD,
                wParam  :DWORD,
                lParam  :DWORD

        .if     uMsg == WM_COMMAND ;if you press a button ...
                .if     wParam == IDC_CMDGEN ;if you press the button generates ...
                        mov eax, hWin
                        mov hWindow, EAX
                        invoke GetDlgItemInt,hWin,IDC_TXTCODE,ADDR lptranslated,FALSE ;if it exists, takes the entire code from the text box
                        .if lptranslated == TRUE
                                invoke MakeSerial, EAX ;calculates the serial
                                invoke SetDlgItemInt,hWin,IDC_TXTSERIAL,EAX,FALSE ;writes the value in the textbox
                        .else
                                invoke ErrorProc ;calls the error message
                        .endif
        .elseif wParam == IDC_CMDEXIT ;if you press the exit button...
                        invoke EndDialog,hWin,0
                .endif
        .elseif uMsg == WM_CLOSE ;if you select X
                invoke  EndDialog,hWin,0
        .endif

        xor     eax,eax
        ret
DlgProc endp

MakeSerial Proc code:DWORD
        ;starts to calculate the serial only 2 multiplications and an addition, there is nothing to explain
        mov eax, code
        imul eax, eax
        mov edx, eax
        mov eax, code
        imul eax, edx
        mov ecx, eax
        mov eax, edx
        imul eax, ecx
        add eax, code
        Ret
MakeSerial EndP

end start


Endnotes

And so we come the end of my first suits up Quequero:) Well I hope to be able to write everything clearly and not too Pallos for any questions or concerns you can contact me via mail, via the bore of the UIC pm on UG, wherever you like:)

A special thanks to Evolution for the source in ASM Keygen (which resolved in 40 seconds flat xd) and for some clarifications. Zyrel.


Disclaimer

I documenti qui pubblicati sono da considerarsi pubblici e liberamente distribuibili, a patto che se ne citi la fonte di provenienza. Tutti i documenti presenti su queste pagine sono stati scritti esclusivamente a scopo di ricerca, nessuna di queste analisi è stata fatta per fini commerciali, o dietro alcun tipo di compenso. I documenti pubblicati presentano delle analisi puramente teoriche della struttura di un programma, in nessun caso il software è stato realmente disassemblato o modificato; ogni corrispondenza presente tra i documenti pubblicati e le istruzioni del software oggetto dell'analisi, è da ritenersi puramente casuale. Tutti i documenti vengono inviati in forma anonima ed automaticamente pubblicati, i diritti di tali opere appartengono esclusivamente al firmatario del documento (se presente), in nessun caso il gestore di questo sito, o del server su cui risiede, può essere ritenuto responsabile dei contenuti qui presenti, oltretutto il gestore del sito non è in grado di risalire all'identità del mittente dei documenti. Tutti i documenti ed i file di questo sito non presentano alcun tipo di garanzia, pertanto ne è sconsigliata a tutti la lettura o l'esecuzione, lo staff non si assume alcuna responsabilità per quanto riguarda l'uso improprio di tali documenti e/o file, è doveroso aggiungere che ogni riferimento a fatti cose o persone è da considerarsi PURAMENTE casuale. Tutti coloro che potrebbero ritenersi moralmente offesi dai contenuti di queste pagine, sono tenuti ad uscire immediatamente da questo sito.

Vogliamo inoltre ricordare che il Reverse Engineering è uno strumento tecnologico di grande potenza ed importanza, senza di esso non sarebbe possibile creare antivirus, scoprire funzioni malevole e non dichiarate all'interno di un programma di pubblico utilizzo. Non sarebbe possibile scoprire, in assenza di un sistema sicuro per il controllo dell'integrità, se il "tal" programma è realmente quello che l'utente ha scelto di installare ed eseguire, né sarebbe possibile continuare lo sviluppo di quei programmi (o l'utilizzo di quelle periferiche) ritenuti obsoleti e non più supportati dalle fonti ufficiali.