NewBies Simple KeyGenMe ita
From UIC
Simple KeygenMe
Contents |
| NewBies Simple KeyGenMe ita | |
|---|---|
| Author: | Zyrel |
| Email: | marcolagalla@yahoo.it |
| Website: | Unfair-Gamers |
| Date: | 10/01/2011 (dd/mm/yyyy) |
| Level: |
|
| Language: | Italian |
| Comments: | Veramente semplice, adatto ai newbies. |
Introduzione
Ciao a tutti, questo è il primo tutorial che pubblico su quequero, ed è rivolto (come avrete capito dal titolo) agli utenti che hanno appena deciso di intrapprendere questa strada. Non mi dilungo ulterioriormente, limitandomi a citare direttamente dal glossario della UIC la definizione di cosa è un KeygenMe:
« Programma scritto appositamente per essere crackato/reversato. Tipicamente l'unica funzionalità del crackme è proprio la sua protezione, che può prendere a modello protezioni esistenti (come quelle dei programmi commerciali) o più frequentemente proporre qualcosa di nuovo ed originale. I crackme sono scritti da reverser e si indirizzano ad altri reverser, ed hanno scopi puramente didattici ».
In questo specifico caso noi non andremo a crackare il programma (o meglio lo faremo giusto per completezza, ma non è quello il nostro obiettivo) facendo sì che ritenga ogni seriale corretto, ma andremo invece a "sniffare" il codice per poter dedurre l' algoritmo che genera il seriale corretto.
Tools & Files
URL o FTP del programma
Per l' occasione andremo ad usare un piccolo programma creato da me: KeygenMe#1
Essay
Bene, ora io darei per scontato che voi conosciate almeno le basi dell' assembly e almeno un linguaggio di programmazione (per creare il keygen).
Ok, inizia il tutorial vero e proprio, avviamo il programma tanto per farci un idea di come funziona:
Bene cosa notiamo? Dunque:
• 1^ Textbox: non editabile che ci mostra un numero (intero). • 2^ Textbox: in cui ci viene chiesto di inserire il nostro seriale. • Bottone: serve per il check.
Non c'è nessuna richiesta di un nick o altro, quindi mi pare logico pensare che l' algoritmo che genera il seriale si basi proprio su quel numero; bene ora ci chiediamo: perchè proprio quel numero? (67 nello screen), ecco se provate a riaprire il programma vi troverete un altro numero, sempre intero, ma ogni volta diverso.
Perfetto carichiamo il target (chiamerò spesso così per il resto del tutorial il KeygenMe) in Olly e fermiamoci a dare un occhiata al listato:
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
Questo è l' entrypoint del programma, ma a noi questa parte non interessa, piuttosto scrollate verso il basso:
• Qui vediamo una chiamata alla CreateWindowExA:
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
• Qui troviamo una parte interessante, con due chiamate, ad rand() e srand()
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
Bene da qui capiamo come viene generato il numero su cui (come vi ho anticipato) si basa l' algoritmo. Deduciamo quindi che utilizza le funzioni srand() e rand() utilizzando come seme delle prima la funzione time(), troviamo una chiamata alla rand() nuovamente più sotto ma non ci interessa.
Benissimo runniamo con F9 e proviamo ad inserire un seriale a casaccio, io userò il buon vecchio 1234567890 ;) Ci compare una MessageBox che ci avverte che il seriale non è corretto, bhè io penso che il programma possa essere fatto più o meno così:
unsigned long s_fake; // nostro seriale che inseriamo (sbagliato)
if (s_fake == generate()) {
MessageBox (NULL, "Il seriale è corretto","Ottimo lavoro", MB_OK);
}
else {
MessageBox (NULL, "Il seriale è sbagliato","Tenta ancora", MB_OK);
}
Bene quindi non perdiamo tempo ;) Ricarichiamo il target su Olly premendo CTRL+F2 e nella Commandline digitiamo:
N.B. chi non avesse il plugin della CmdLine può trovarlo Qui.
In questo modo abbiamo piazzato un bp (BreakPoint) su tutte le chiamate alla suddetta API (attenzione alle maiuscole/minuscole, la cmdline è case-sensitive), ora runniamo nuovamente premendo F9 ed inseriamo il nostro seriale fittizio (per me 1234567890), appena prima che la MsgBox compaia per dirci che il nostro seriale è sbagliato il debugger ci brekka qui:
Se guardate appena più sopra vedrete:
0040178D |. 75 29 JNZ SHORT Keygen.004017B8 ; |salto condizionato, se la Z flag è 0 salta a 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"
0040179F |. C74424 04 EF40>MOV DWORD PTR SS:[ESP+4],Keygen.004040EF ; |ASCII "Seriale Corretto"
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 ;)"
004017C8 |. C74424 04 1041>MOV DWORD PTR SS:[ESP+4],Keygen.00404110 ; |ASCII "Seriale Sbagliato"
004017D0 |. C70424 0000000>MOV DWORD PTR SS:[ESP],0 ; |
004017D7 |. E8 E4070000 CALL <JMP.&USER32.MessageBoxA> ; \MessageBoxA
Ho commentato il listato nei punti importanti, ma la cosa che ci interessa di più al momento è quel:
0040178D |. 75 29 JNZ SHORT Keygen.004017B8 ; |salto condizionato, se la Z flag è 0 salta a 004017B8
Ora apriamo una piccola parentesi:
Il Cracking
Credo che lo abbiate già intuito, ma se noi invertissimo quel salto potremmo far si che se anche un seriale fosse sbagliato il target farebbe apparire la MsgBox di conferma (volete provare? provate a settare la Zero flag su 1, vedrete che non effettuerà il salto e apparirà la giusta MsgBox) Non mi soffermo molto perchè non è il nostro scopo e passo direttamente alla soluzione:
• Primo metodo: sostituire il JNZ (Jump if Not Zero) con un JE (Jump if Equal) • Secondo metodo: sostituire il JNZ con un JMP ovvero un salto incodizionato ad 0040178F, MsgBox di conferma • Terzo metodo: noppare il tutto facendo così apparire la MsgBox
Sinceramente preferisco il primo metodo, quindi andiamo a fare un piccola patch di appena un byte avendo così:
E infatti nel box sottostante:
Ora per rendere le modifiche permanenti andate a leggervi il tutorial su come effettuare il manual patching oppure per fare prima (se già sapete come si fà manualmente) Click destro->Copy to executables->All modifications->Save.
Individuazione e studio dell' algoritmo:
Ora entriamo nella parte vera e propria del nostro tutorial: l' analisi dell algoritmo. Ci eravamo lasciati qui:
Se scrollate ancora più in su, subito sotto la GetDlgItemInt trovate questa CALL:
Uhmm...una call...sotto la funzione che legge il valore (numerico) della textbox....vicina al salto condizionato... Bhè direi che può essere interessante, quindi piazziamo un bp con F2 sulla CALL e runniamo inserendo il solito seriale, ci brekka sulla CALL, premendo F7 steppiamo dentro alla CALL e veniamo portati su una routine che, mi ci gioco le babbucce è quella della generazione del seriale:
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
Come vedete non fà altro che fare qualche semplice calcolo con il valore contenuto in EAX, che è proprio il numerino randomico mostrato dal programma nella textbox disabilitata. Infatti il valore restituito da un API è sempre contenuto in EAX (dalla GetDlgItemInt). Per un ulteriore riprova digitate d EAX nella CmdLine e vedrete che contiene proprio quel numero.
Direi che il codice è chiarissimo, però vediamolo nei tratti fondamentali:
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]
Che tradotto in pseudo-codice sarebbe:
00401829 moltiplica eax *(eax ^2) quindi eax^3
00401833 moltiplica (eax^2) * (eax^3) quindi eax^5
00401837 somma (eax^5) + eax
Quindi la formula che genera il seriale è: (Eax^5) + Eax dove Eax è il numero randomico. Però qui sorge l' unica pseudo difficoltà di questo CrackMe, il risultato del calcolo viene gestito come dword e quando Eax è maggiore di 84 (il che capita spesso) allora Eax^5 supera 0xFFFFFFFF, il che genera errori di overflow che rendono invalido agli occhi del programma il seriale anche se potenzialmente corretto.
Un keygen deve seguire precisamente i calcoli effettuati dal programma, quindi: Eax * Eax * Eax * (Eax^2) + Eax evitando così un overflow, anzi più correttamente la soluzione più adeguata sarebbe: seriale = ((random^5) and &HFFFFFFFF + random) and &HFFFFFFFF con due And logici.
Il KeyGen
Infine il nostro beneamato keygen ;) Io ve lo presento in due linguaggi: C++ e ASM.
using std::cout;
using std::cin;
int main() {
int eax;
unsigned long serial;
cout << "Inserire il numero restituito dal programma: ";
cin >> eax;
serial = eax * eax;
serial = serial * eax;
serial = serial * (eax*eax);
serial = serial + eax;
cout << "\nIl seriale corretto: " << serial << "\n";
system("pause");
return 0;
}
.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
;dichiarazione prototipi delle funzioni del programma
DlgProc PROTO :DWORD,:DWORD,:DWORD,:DWORD ;funzione finestra
MakeSerial PROTO :DWORD ;funzione genera-seriali
ErrorProc PROTO ;funzione di errore dell'immissione del code
;impostazione nomi per gli oggetti della finestra
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 ? ;variabile per capire se il codice inserito e' valido
hWindow DWORD ? ;handle window replicato per la messagebox
;dati inizializzati
.data
szerrortext db "Valore del Code non corretto o assente!",0
szerrorcaption db "ERRORE",0
.code
ErrorProc proc
;cancella sia il code che il serial
invoke SetDlgItemText,hWindow,IDC_TXTCODE,NULL
invoke SetDlgItemText,hWindow,IDC_TXTSERIAL,NULL
;messaggio di errore
invoke MessageBox,hWindow,ADDR szerrortext,ADDR szerrorcaption,MB_ICONERROR
Ret
ErrorProc EndP
start:
invoke GetModuleHandle, NULL ;ritorna l'imagebase dell'exe (handle) da passare per creare la finestra
mov hInstance, eax
invoke DialogBoxParam, hInstance, 101, 0, ADDR DlgProc, 0 ;creazione finestra
invoke ExitProcess, eax ;esce dal processo
; -----------------------------------------------------------------------
DlgProc proc hWin :DWORD,
uMsg :DWORD,
wParam :DWORD,
lParam :DWORD
.if uMsg == WM_COMMAND ;se viene premuto un tasto...
.if wParam == IDC_CMDGEN ;se viene premuto il tasto genera...
mov eax, hWin
mov hWindow, EAX
invoke GetDlgItemInt,hWin,IDC_TXTCODE,ADDR lptranslated,FALSE ;se esiste, prende l'intero dalla casella di testo code
.if lptranslated == TRUE
invoke MakeSerial, EAX ;calcola il seriale
invoke SetDlgItemInt,hWin,IDC_TXTSERIAL,EAX,FALSE ;scrive il valore nella textbox
.else
invoke ErrorProc ;chiama il messaggio d'errore
.endif
.elseif wParam == IDC_CMDEXIT ;se viene premuto il tasto exit...
invoke EndDialog,hWin,0
.endif
.elseif uMsg == WM_CLOSE ;se viene selezionata la X
invoke EndDialog,hWin,0
.endif
xor eax,eax
ret
DlgProc endp
MakeSerial Proc code:DWORD
;inizia a calcolare il seriale solo 2 moltiplicazioni ed un'addizione, non c'e' nulla da spiegare
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
Note Finali
E cosi siamo giunti alla fine del mio primo tute su Quequero :) Bè spero di essere riuscito a scrivere il tutto in modo chiaro e non troppo palloso, per qualunque dubbio o chiarimento potete contattarmi via mail, via pm sul foro della UIC, su UG, dove preferite :)
Un ringraziamento particolare va ad EvOlUtIoN per il source in ASM del Keygen (che risolse in 40 secondi netti xd) e per alcune precisazioni.
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.
