Backup Magic 1.0.0
Backup Magic, un programma che promette miracoli nell'esecuzione di copie di riserva di ogni tipo di dati.
'Please enter your name and license number [..] ' Se si potessero trovare in un luogo comune tutti questi programmatori non riuscirebbero a comunicare tra loro: o parlerebbero nello stesso istante (dicendo le stesse cose) o non parlerebbero affatto. Veramente. Dire che si assomigliano tutti è dir poco...
Ok, a parte l'introduzione inseriamo un bel nome e un numero fasullo, e premiamo 'OK'. Una MessageBox (strano...) ci avverte che abbiamo inserito un codice non valido per quel nome, bla, bla, bla... Entriamo in SoftIce e impostiamo un breakpoint (bpx) per MessageBoxA. Ritorniamo al nostro programmino e inseriamo di nuovo i dati per cliccare 'OK'. SoftIce riappare e noi premiamo F11. Clicchiamo su 'OK' e ritorneremo in SoftIce. Annotiamoci l'indirizzo ma notiamo anche ciò che c'è poche righe più sotto... un 'ret'. Bè, perchè non farsi un giretto? Premiamo F10 fino a ritornare da questa chiamata e annotiamo anche questo indirizzo, potrà servirci. Per adesso possiamo uscire da SoftIce e aprire il nostro disassemblatore preferito, nel quale caricheremo il file e ci recheremo al primo indirizzo che ci siamo annotati. (45FF18) Effettivamente c'è la chiamata alla MessageBoxA ma se risaliamo di un pò il codice osserviamo che invece di un salto condizionale troviamo solo l'inizio della chiamata.
Dunque abbiamo fatto bene ad annotare anche l'altro indirizzo! Rechiamoci a quest'ultimo (46D7E8) e arriveremo giusto dopo la chiamata che visualizza la MessageBox di errore. Valutiamo un attimo la situazione: la chiamata contenente il riferimento a MessageBox aveva alcune sub-chiamate prima. Vale la pena di analizzarle? Per rispondere immedesimiamoci nel programmatore, avido di soldi che probabilmente non arriveranno:
Quesito: - Il programmatore ha pensato a ottimizzare il codice? (il che equivale a utilizzare un solo riferimento a MessageBox per tutte le MessageBox possibili)
La risposta in molti casi è: NO, il programmatore pensa solamente a un modo sbrigativo e non ottimizza QUASI MAI il suo codice.
Adesso che sappiamo questo proviamo a risalire un poco il codice e scopriamo un riferimento condizionale da 46D6BA. Andiamo a vedere cos'è e scopriamo che è del tipo je xxxxxxxx. Chissa che... BINGO! Due righe più sopra c'è una chiamata il cui valore in uscita (tipicamente posizionato in eax) viene verificato (con test al, al). Ricordiamoci solo il salto condizionale: je -> jump if zero ovvero salta all'indirizzo specificato se l'istruzione test al, al verifica che al è uguale a zero. Noi quindi abbiamo bisogno di avere in ritorno un valore diverso da zero (tipicamente 1). Ok, andiamo al riferimento della chiamata e guardiamo inanzitutto quanto è lunga. Poco. Molto bene. Cerchiamo, partendo dalla fine, se c'è qualche istruzione che azzera eax. C'è. E giusto sopra c'è un riferimento incondizionale... Andiamo a vedere e vediamo strane istruzioni tipo
mov dword ptr fs:[eax], esp
Il più delle volte sono da ignorare in quanto NON sono volute dal programmatore stesso ma dal suo compilatore, quindi cerchiamo altrove il nostro codice.
Avanziamo e osserviamo due chiamate la seconda delle quali è seguita dall'istruzione sete al, che non centra nulla con la sete di acqua. Quest'istruzione verifica il flag Zero e assegna 1 ad al se il valore del suddetto flag è 1. Il valore di eax viene poi salvato spostando tutto in ebx (molto importante). La chiamata precedente a sete al può essere di due tipi: può essere una routine di confronto oppure può contenere al suo interno sia la routine per il calcolo del codice che per il suo controllo.
Non ci resta che verificare... E' solo una routine di controllo... Quindi la chiamata prima di questa è quella che calcola il codice... Bene, andiamo a vedere e scopriamo questo loop:
:0044AFB9 33C9 xor ecx, ecx
:0044AFBB 8A4C06FF mov cl, byte ptr [esi+eax-01]
:0044AFBF 03C8 add ecx, eax
:0044AFC1 0FB77DFE movzx edi, word ptr [ebp-02]
:0044AFC5 0FAFCF imul ecx, edi
:0044AFC8 69C9B2000000 imul ecx, 000000B2
:0044AFCE 03D9 add ebx, ecx
:0044AFD0 40 inc eax
:0044AFD1 4A dec edx
:0044AFD2 75E5 jne 0044AFB9
Anche qui dovrebbero trovarsi nello stesso luogo tutti i programmatori che non capiscono che certe cose i compilatori le traducono in ASM allo stesso modo... Comunque noi siamo alla ricerca di un codice, non del programmatore perciò mettiamoci al lavoro e cerchiamo di capire cosa avviene.
In poche parole il loop provvede a:
- azzerare ecx
- porre in cl una lettera del nome
- aggiungere a questa lettera il valore della sua posizione
- moltiplicare il nuovo valore per una costante (661)
- moltiplicare il risultato per 178
- aggiungere il risultato ad un checksum (azzerato inizialmente)
- ripetere per ogni lettera
Alla fine del loop non si deve far altro che verificare il contenuto di ebx e annotarsi il valore: è il corrispondente esadecimale del codice corretto.
Ora, mi sembra una protezione abbastanza semplice e perciò possiamo provare a riprodurla in un nostro programma: (Scritto per essere assemblato con TASM 5.0)
----Tagliare qui----------------------------------------------------
Ideal
Model Tiny
p386
CodeSeg
Org 100h
Inizio:
mov ah, 9
lea dx, [Intro]
int 21h ; Visualizza l'introduzione
inc ah
lea dx, [L_Max]
int 21h ; Riceve il nome utente
mov bl, [L_Eff]
mov [byte ptr Buff+bx], 0 ; Rimuove l'ultimo carattere (0Dh)
lea esi, [Buff] ; Offset del nome in esi
xor ebx, ebx ; Prepara gli altri registri
xor eax, eax
inc eax
xor edx, edx
mov dl, [L_Eff]
; Routine per il calcolo del codice
Loop1:
xor ecx, ecx
mov cl, [byte ptr esi+eax-01]
add ecx, eax
movzx edi, [word ptr Const1]
imul ecx, edi
imul ecx, 0B2h
add ebx, ecx
inc eax
dec edx
jne Loop1
mov eax, ebx ; in ebx c'è il codice calcolato
xor ebx, ebx
mov bl, 8 ; Ripetiamo 8 volte
xor ecx, ecx
mov cl, 10 ; Fattore di divisione
; Routine per la conversione hex->dec
Dump:
xor edx, edx
cdq
idiv ecx
add dl, 30h
mov [Codice+bx-1], dl
dec bx
jne Dump
mov ah, 9
lea dx, [Cod] ; Visualizza il codice calcolato
int 21h
int 20h
Intro db 'T H E _ D U X',13,10,'Key Generator per Backup Magic',13,10
db '26 Giugno 1999.',13,10, 'Inserire il nome : $'
L_Max db 16
L_Eff db 0
Buff db 16 dup (0) ; Buffer per memorizzare il nome
Const1 dw 661
Cod db 13,10,'Codice : '
Codice db '00000000$' ; Qui verrà memorizzato il codice calcolato
End Inizio
--e qui--------------------------------------------------------------