Advanced Zip Password Recovery v2.2

(Patching in memory)


22-12-1999

by "Quequero"

 

 

UIC's Home Page

Published by Quequero

Una luce splendente, elegante come il sol levante, apparve all' orizzonte. Arrivò silenzioso un fautore del buon umore.

Vi presento qualcosa di nuovo, un tutorial sul patching in memory di un programma packato col NeoLite, non è semplicissimo ma vi sarà comunque molto utile

Col suo mantello color cammello scese dal ciel un uomo gaio, si posò su un albero e disse: "Miiiii sono Quequero".
UIC's form
Home page se presente: http://quequero.cjb.net
E-mail: [email protected]
In IRCNet: #crack-it #UIC
UIC's form

Difficoltà

( )NewBies (X)Intermedio (X)Avanzato ( )Master

 

Ecco come si fa a crackare un programma senza averi bisogno di unpackarlo :).


Advanced Zip Password Recovery v2.2
(Patching in memory)
Written by Quequero

Scaricate l'allegato

Introduzione

Advanced Zip Password Recovery è un tool compresso con il caro neolite, non è difficile da unpackare ma noi creeremo un programma che ci permetterà di crackarlo in memoria senza per questo doverlo unpackare.

Tools usati

SoftIce qualunque versione
Turbo Assembler 5.0
Spy++
W32.inc (nell'allegato assieme ai sorgenti)

URL o FTP del programma

http://www.eclomsoft.com/azpr.html

Notizie sul programma 

Advanced Zip Password Recovery (AZPR d'ora in poi) è un utile programmino che ci permette di bruteforzare i file zip per poi trovarne le password   

Essay

AZPR è abbastanza utile come programma ma se non lo registriamo allora non ci permetterà di bruteforzare password più lunghe di 5 caratteri grgrgrgrgr, la cosa in effetti non ci piace ma noi comunque non siamo interessati a questo, infatti un giorno potrebbe capitarci di incontrare un programma unpackabile (moooooolto improbabile) o che non riusciamo ad unpackare (già più probabile :) allora cosa facciamo, lasciamo la vittoria ai programmatori? Mai! Quindi alleniamoci per prospettive future :). Per prima cosa dobbiamo crackare il programma, non mi dilungherò più di tanto su questo dal momento che il nostro target è un altro, quindi avviamo il programma e come ovvio ci mostra la sua scrittina "Unregistered", bene, procedendo per ipotesi iniziamo a brekkare sulle API che gestiscono il registro di Windows perchè forse il serial è letto da lì....quindi fire up your winice (ctrl+d) e settate un bpx su RegQueryValueExA....O se preferite brekkate direttamente così: bpx RegQueryValueExA if ecx = = 70FCFC, premete F11 e sarete approdati al posto giusto, disabilitate i breakpoint con "bd *" e steppate con F10 fino a:
xxxx:0040C733  E800FEFFFF  call 0040C538
questa è la chiamata che decide se il programma è registrato o meno, se riporta 1 lo è altrimenti no...Steppate ancora con F10 finchè non arriverete qui:
xxxx:0040258C  7479  jz 00402607
se il programma arrivato a questo punto salta, allora non è bene :) se non salta invece apparirà come registrato, ottimo, ciò che dobbiamo fare è semplicemente noppare questi due byte....Bhè facciamolo...ma facciamolo in memoria.
Il crack è stato abbastanza rapido :) questa parte invece non so come verrà fuori dal momento che evito di cercare tute su argomenti che conosco poco onde evitare di essere influenzato, (anche se mi sono rimaste delle reminescenze di qualche tute su fravia) quindi farò secondo cioè che  mi è venuto in mente e cercherò di esporvi tutto con chiarezza :)
 
PATCHING IN MEMORY
 
Bene, iniziamo col pianificare i nostri scopi malevoli  :), per prima cosa abbiamo bisogno di dire a WinPorco in quale processo andare a mettere le mani e per farlo abbiamo bisogno di trovarlo questo processo....Come? Bhè, io ho pensato di usare un bel FindWindow che ha anche una semplice sintassi:
 
HWND FindWindow(
LPCTSTR lpClassName, // puntatore al nome della classe
LPCTSTR lpWindowName // puntatore al nome della finestra
);
Se il valore riportato è 0 allora c'è stato un errore altrimeni in eax ritroveremo l'handle della finestra.
Andiamo quindi a scrivere il nostro programma, prima di tutto dobbiamo sapere come si chiama la classe della finestra, per far questo avviate AZPR, aprite spy++, clickate su refresh e cercate la finestra:
 
FindWindow.jpg (7151 bytes)
 
quanto so bravo, c'ho messo pure l'immaginina :)..cmq C30h nel mio caso è l'handle della finestra (quello che ci troveremo con FindWindow, "AZPR version 2.0 - unregistered" è la caption della finestra e TForm1 è la classe, cioè quello che ci interessa, allora iniziamo ad abbozzare qualcosa nel nostro programmino:
 
.386P
LOCALS
JUMPS
.MODEL FLAT, STDCALL
UNICODE=0
INCLUDE W32.inc ; W32.inc lo trovate nell'allegato assieme ai sorgenti

.data

class     db "TForm1",0   ; ecco il nome della classe che ci interessa

.code
start:
xor eax, eax  ; meglio metterlo onde evitare che qualcosa vada storto...in fondo sono solo 2 byte :)
call FindWindow, offset class, NULL  ; la nostra cara chiamatina
int 3              ; ci servirà per il debug

end start
 
bene, compilate il tutto con:
tasm32 -ml -m5 /t -q patchinmen
tlink32 -Tpe -aa -x -c patchinmen ,,, import32
 
mettete in sice un bpint 3, avviate il prog e non dovete far altro che vedere se in eax, c'è zero o qualcos'altro, se c'è qualcos'altro allora è andato a buon fine....proviamo......avvio.....ottimo, eax contiene proprio C30h (attenti che a voi l'handle sarà diverso). Adesso dall'handle dobbiamo risalire all'ID del processo, e possiamo usare GetWindowThreadProcessId, ecco la sintassi:

DWORD GetWindowThreadProcessId(

HWND hWnd, // handle della finestra
LPDWORD lpdwProcessId // indirizzo di una variabile per l'ID del processo
);
quindi andiamo ad aggiungere questa funzione al nostro programma che così diventa:
 
.386P
LOCALS
JUMPS
.MODEL FLAT, STDCALL
UNICODE=0
INCLUDE W32.inc

extrn GetWindowThreadProcessId:Proc ; Questa volta l'API la dobbiamo definire

.data

class         db "TForm1",0
handle     dd 1 dup (0)        ; qua ci mettiamo l'handle
ProcessID    dd 1 dup (0)   ; e qua il PID

.code
start:
xor eax, eax
call FindWindow, offset class, NULL
mov handle, eax
call GetWindowThreadProcessId, handle, offset ProcessID
int 3    
end start
 
compiliamo.....testiamo...oki la chiamata riporta il un valore che evidentemente sarà il PID :)
a questo punto siamo in grado di aprire il processo con OpenProcess....

HANDLE OpenProcess(

DWORD dwDesiredAccess, // flag di accesso
BOOL bInheritHandle, // ereditazione dell'handle
DWORD dwProcessId // process identifier
);
qua l'unica cosa da spiegare sono i flag, ne esistono molteplici che potrete vedere su win32.hlp, ma io vi elenco l'unico che ci serve cioè:
PROCESS_ALL_ACCESS  che corrisponde al valore di FFFh, ma che è inserito cmq nel file W32.inc, e quindi non dobbiamo preoccuparci di definirlo, andiamo ancora una volta a modificare il nostro programma aggiungedoci la funzione OpenProcess con i parametri che ci interessano quindi PROCESS_ALL_ACCESS come primo parametro, il secondo lo settiamo a FALSE ed il terzo valore lo prendiamo dall'ultima call, ma facciamo un'ultima modifca, riapriamo Spy++, clickiamo sulla nostra finestra e ci appare il menu di prima, andiamo su "Windows" e vediamo qual'è la finestra "principale" che troviamo all'ultima riga:
 
OwnerWindow.jpg (1306 bytes)
 
adesso sappiamo che l'handle della finestra principale è 57C, quindi nella lista di spy++ cerchiamo questa finestra (che in realtà sta una riga sotto la finestra di prima :), troviamone la classe che è TApplication e sostituiamola nel codice a TForm, quindi alla fine avremo ciò:
 
class                      db "TApplication",0   ; Usiamo questa perchè è meglio operare sulla finestra principale
handle                   dd 1 dup (0)
ProcessID             dd 1 dup (0)
ProcessHandle     dd 1 dup (0)

.code
start:

xor eax, eax
call FindWindow, offset class, NULL
mov handle, eax
call GetWindowThreadProcessId, handle, offset ProcessID
call OpenProcess, PROCESS_ALL_ACCESS, FALSE, dword ptr[ProcessID] ; Ecco la nostra nuova chiamata
mov ProcessHandle, eax ; Spostiamo l'handle in un buffer per usarlo poi in futuro
int 3
 
Andiamo a debuggare il tutto, se la chiamata a OpenProcess vi riporta 0 allora è fallita, altrimenti è andata a buon fine, di consegueza se tutto è a posto (e vi ricordo che dovete debuggare il tutto con AZPR in esecuzion) possiamo procedere a trovare la locazione di memoria che ci interessa patchare. Come precedentemente detto nel tute il jump che ci interessa noppare sta al RVA 0040258C, dovete sapere che ogni applicazione ha un suo range di memoria da poter usare e ciò che abbiamo fatto con OpenProcess è stato solo di aprire il processo di AZPR per poter andar a leggere nella memoria del programma in esecuzione, ora però prima di continuare nel patch dobbiamo controllare se effettivamente la versione i byte che ci interessano sono presenti, per far ciò useremo WriteProcessMemory, una funzione estremamente semplice:

BOOL ReadProcessMemory(

HANDLE hProcess, // handle del processo nel quale dobbiamo andare a leggere
LPCVOID lpBaseAddress, // indirizzo nel quale iniziare la lettura
LPVOID lpBuffer, // indirizzo di un buffer nel quale piazzare i dati letti
DWORD nSize, // numero di byte da leggere
LPDWORD lpNumberOfBytesRead // indirizzo del numero dei byte letti
);
come vedete la funzione non presenta nessuna difficoltà, ci serve solo conoscere l'indirizzo nel quale andare a leggere i dati, e la guida di winfrocio ci dice anche che l'ultimo parametro può essere NULL, quindi ora che sappiamo quale deve essere l'RVA andiamo a scrivere un'altra volta il nostro programma:
 
class                     db "TApplication",0
handle                  dd 1 dup (0)
ProcessID           dd 1 dup (0)
ProcessHandle    dd 1 dup (0)
ReadBuffer          dd 1 dup (0)
OrigBytes           dw 7974h ; Bytes originali invertiti dal momento che gli intel li invertono
CaptionDiff       db 'Error!',0  ; MessageBox di avviso nel caso i byte non vengano trovati
MessageDiff      db 'Program version is probably different,',0dh,0ah
                          db 'crack not applied!',0

.code
start:

xor eax, eax
call FindWindow, offset class, NULL
mov handle, eax
call GetWindowThreadProcessId, handle, offset ProcessID
call OpenProcess, PROCESS_ALL_ACCESS, FALSE, dword ptr[ProcessID]
mov ProcessHandle, eax
mov eax, 0040258Ch
call ReadProcessMemory, [ProcessHandle], eax, offset ReadBuffer, 2, NULL ; Qui andiamo a leggere la memoria
mov esi, offset OrigBytes  ; E facciamo un compare tra i
mov edi, offset ReadBuffer  ; byte letti
mov ecx, 2
repz cmpsb ; Corrispondono?
jnz Different ; Se la risposta è NO, allora manda la messagebox
int 3 ; Questo è il solito int3 per fare il debug

Different:
push MB_ICONSTOP or MB_OK
push offset CaptionDiff
push offset MessageDiff
push NULL
call MessageBoxA;, MB_ICONSTOP or MB_OK, offset CaptionDiff, offset MessageDiff, NULL

end start
 
bene, se anche il check dei byte è superato possiamo andare a cambiarli con due NOP. Ma c'è un piccolo problema, cioè che il programma fa quel check una sola volta all'avvio, noi invece abbiamo operato ad applicazione già avviata e quindi anche se modificassimo i byte non otterremmo nulla visto che il programma è già in UNREGISTERED status :), per ovviare a questo problema potremmo avviare l'applicazione con WinExec solo che questa API ci ritorna il controllo solo dopo che l'applicazione è completamente avviata......e di conseguenza anche dopo il check, quindi useremo l'API CreateProcess, mi era stato suggerito di sospendere il thread con CREATE_SUSPENDED e poi di riesumarlo, ma ho trovato un metodo un po' più semplice, cioè, avvieremo lo stesso il programma con CreateProcess e con un loop cercheremo la locazione e quindi la patcheremo, lasciate prima che vi spieghi come funziona CreateProcess che è una della API più antipatiche :):
 
BOOL CreateProcess(
LPCTSTR lpApplicationName, // pointer to name of executable module
LPTSTR lpCommandLine, // pointer to command line string
LPSECURITY_ATTRIBUTES lpProcessAttributes, // pointer to process security attributes
LPSECURITY_ATTRIBUTES lpThreadAttributes, // pointer to thread security attributes
BOOL bInheritHandles, // handle inheritance flag
DWORD dwCreationFlags, // creation flags
LPVOID lpEnvironment, // pointer to new environment block
LPCTSTR lpCurrentDirectory, // pointer to current directory name
LPSTARTUPINFO lpStartupInfo, // pointer to STARTUPINFO
LPPROCESS_INFORMATION lpProcessInformation // pointer to PROCESS_INFORMATION
);
...Come prima parametro settiamo il nome dell'exe da eseguire (azpr.exe) il secondo parametro non ci interessa e lo settiamo a NULL, il terzo parametro anche, il 4° lo possiamo tranquillamente mettere a FALSE, questo comando serve a specificare se l'handle ritornato potrà essere ereditabile dai processi figli, il 5° lo settiamo a DEBUG_PROCESS, serve a specificare che il processo deve essere trattato come un debugger, non è necessario ma mettiamocelo :), il 6° lo settiamo a NORMAL_PRIORITY_CLASS visto che non ci interessa avere una priorità di esecuzione più alta o più bassa, il 7° e 8° lo settiamo a NULL mentre gli utlimi due necessitano di puntare rispettivamente ad una struttura di Startup e ad una struttura di PROCESS_INFORMATION, non preoccupatevi non è assolutamente difficile. Dopo ciò possiamo andare a scrivere la routine che patcherà il programma in memoria, per far ciò aggiungeremo dopo ReadProcessMemory il nostro bel WriteProcessMemory che presenta più o meno la stessa sintassi dell'API affine:

BOOL WriteProcessMemory(

HANDLE hProcess, // handle del processo nel quale dobbiamo andare a scrivere
LPVOID lpBaseAddress, // indirizzo nel quale iniziare la scrittura
LPVOID lpBuffer, // puntatore a un buffer nel quale piazzare i dati da scriverei
DWORD nSize, // numero di byte da scrivere
LPDWORD lpNumberOfBytesWritten // numero attuale di byte scritti
);
per l'ultima volta vi ripresento tutto il codice anche con quest'ultime aggiunte ed anche ottimizzato con alcuni messagebox ed un loop che vi spiegherò a fine listato:
 
.386P
LOCALS
JUMPS
.MODEL FLAT, STDCALL
UNICODE=0
INCLUDE W32.inc

extrn GetWindowThreadProcessId:Proc

.data

class                      db "TApplication",0
handle                   dd 1 dup (0)
ProcessID            dd 1 dup (0)
ProcessHandle     dd 1 dup (0)
ReadBuffer          dd 1 dup (0)
OrigBytes            dw 7974h ; Bytes originali invertiti
PatchBytes          dw 9090h  ; I byte con i quali cambieremo l'istruzione
CaptionDiff        db 'Error!',0
MessageDiff       db 'Program version is probably different,',0dh,0ah
                           db 'crack not applied!',0
CaptionSucc      db 'Success!',0
MessageSucc   db ' Crack successfully applied! ',0dh,0ah
                         db '                                              ',0dh,0ah
                       db ' Created By Quequero          ',0dh,0ah
                        db '      FOR U.I.C.                       ',0dh,0ah
                       db '      http://quequero.cjb.net      ',0
ExeName        db 'azpr.exe',0
CaptionProb   db 'Problem!!!',0
MessageProb db 'There is a problem, crack cannot be applied! ',0dh,0ah
                      db 'Here''s some possible solution: ',0dh,0ah
                      db '1) Name of .exe is different from azpr.exe (if yes rename it) ',0dh,0ah
                      db '2) This crack is not in the same directory of the program. ',0dh,0ah
                      db 'Try to move in the right directory, generally: c:\program\AZPR',0

SecAttr        STARTUPINFO <?> ; Questi sono i semplicissimi puntatori alle strutture che vi indicato durante la
ProcInf        PROCESS_INFORMATION <?> ; spiegazione del CreateProcess


.code
start:
    call CreateProcess, offset ExeName, NULL, NULL, FALSE, DEBUG_PROCESS, NORMAL_PRIORITY_CLASS,    NULL, NULL, offset SecAttr, offset ProcInf  ; Ecco a voi il CreateProcess, se vi riporta 1 allora è tutto okkei
test eax, eax ; Il CreateProcess riporta 0?
jz Problem ; Se si vuol dire che ci sono dei problemi, mostra la messagebox con le soluzioni
loop: call FindWindow, offset class, NULL ; Ecco il loop che vi spiegherò dopo
    test eax, eax
    jz loop
    mov handle, eax
    call GetWindowThreadProcessId, handle, offset ProcessID
    call OpenProcess, PROCESS_ALL_ACCESS, FALSE, dword ptr[ProcessID]
    mov ProcessHandle, eax
    mov eax, 0040258Ch ; Muovi in eax l'RVA della locazione alla quale andremo a leggere
    call ReadProcessMemory, [ProcessHandle], eax, offset ReadBuffer, 2, NULL ; Vai a leggere
    mov esi, offset OrigBytes
    mov edi, offset ReadBuffer
    mov ecx, 2
    repz cmpsb
    jnz Different  ; Confronta i byte e se sono uguali procedi, altrimenti salta
    mov eax, 0040258Ch
    call WriteProcessMemory, [ProcessHandle], eax, offset PatchBytes, 2, NULL ; Ottimo, sono uguali, allora cambiali
    push MB_ICONINFORMATION or MB_OK
    push offset CaptionSucc
    push offset MessageSucc
    push NULL
    call MessageBoxA ; Mostra il box che ci dice che tutto è andato bene
    call ExitProcess ; Ed esci

Different:
    push MB_ICONSTOP or MB_OK
    push offset CaptionDiff
    push offset MessageDiff
    push NULL
    call MessageBoxA
call ExitProcess

Problem:
    push MB_ICONSTOP or MB_OK
    push offset CaptionProb
    push offset MessageProb
    push NULL
    call MessageBoxA
call ExitProcess

end start
 
Ora state attenti che vi riassumo tutto quanto: per prima cosa il file viene avviato con CreateProcess visto che ci rida il controllo all'istante, se la funzione riporta 1 è andato tutto bene, se riporta 0 avete sbagliato qualcosa, quindi viene eseguito un loop di FindWindow, perchè? Perchè in questo modo facciamo un check continuo fino all'apparizione della nostra finestra e così avremo il tempo di patchare i byte prima che il programma decida di non essere registrato, appena FindWindow riporta un valore diverso da 0 chiamamo GetWindowThreadId per ottenere l'ID del thread della finestra, se anche questa va a buon fine otteniamo un ID che possiamo usare con OpenProcess per aprire il processo. Aperto il processo andiamo a fare un confronto tra i byte che "dovremmo" trovare e quelli che effettivamente troviamo, se sono uguali procediamo col cambiarli e con l'avvisare l'utente che tutto è andato bene, altrimenti facciamo comparire il box che ci informa di una diversità tra l'exe che abbiamo e quello che dovremmo avere. I check per vedere se tutto procede bene sono necessari solamente dopo il CreateProcess ed al check tra i byte, non serve inserirne altri visto che le possibilità di fallimento del WriteProcessMemory sono alquanto remote. Nell'allegato troverete tutto il necessario alla compilazione del programma, ovviamente non troverete il tasm che potrete scaricare al link che trovate all'inizio. Forse la prossima volta creeremo un trainer, vi garantisco comunque che fare un trainer è molto più semplice di patchare un programma come questo in quanto non avete la necessità di cambiare i byte prima di determinati eventi, prendete come esempio il numero di vite di un gioco, lo potete cambiare quando volete, mia dopo certi eventi giusto? Bhè ci siamo divertiti abbastanza, abbiamo imparato come patchare in memoria un programma......non è mica una cosa da poco :))))
                                                  
                                                               Quequero
Note finali
I ringraziamenti sono d'obbligo stavolta, ma prima saluto tutti quelli della UIC...Ciauzzzzzz....ed ora quelli che si meritano un sentito ringraziamento:
Kill3xx: Grazie tantissimo che hai una soluzione per ogni problema, se non ci fossi stato tu starei ancora provando chissà cosa per patchare il programma prima del check :)
Xoanon: Mia musa ispiratrice che mi ha fatto venire un paio di ideuzze non male per il programma
Neural_Noise: Grazie anche al mio cyberfratellino che è sempre gentile e che mi voleva far creare thread su thread per patchare sto programma :)))
Ciauz a tuttiiiiiiiiiiiiiiiiiiiiiiii

Disclaimer

Vorrei ricordare che il software va comprato e  non rubato, dovete registrare il vostro prodotto dopo il periodo di valutazione. Non mi ritengo responsabile per eventuali danni causati al vostro computer determinati dall'uso improprio di questo tutorial. Questo documento è stato scritto per invogliare il consumatore a registrare legalmente i propri programmi, e non a fargli fare uso dei tantissimi file crack presenti in rete, infatti tale documento aiuta a comprendere lo sforzo immane che ogni singolo programmatore ha dovuto portare avanti per fornire ai rispettivi consumatori i migliori prodotti possibili.
Noi reversiamo al solo scopo informativo e di miglioramento del linguaggio Assembly.
Capitoooooooo????? Bhè credo di si ;)))) 

 
UIC's page of reverse engineering, scegli dove andare:

Home   Anonimato   Assembly    ContactMe  CrackMe   Links   
NewBies   News   Forum   Lezioni  
Tools   Tutorial  Search

UIC