Soluzione lezione tre UIC |
|
|
28/09/1999 |
by "syscalo" |
|
|
Published by Quequero |
|
| Those who do not understand Unix are condemned to reinvent it | Bravo sys, anche stavolta hai fatto un bel lavoro ma mancano spiegazioni a funzioni importanti come le istruzioni di divisione e conversione di doubleword |
UIC: the most fun university in the world. |
| UIC's form | E-mail: x4sys@iname.com | UIC's form |
Difficoltà |
( )NewBies (X)Intermedio ( )Avanzato ( )Master |
Il programma serve a dimostrare come funziona il codice automodificante. Noi dovremo riuscire a trovare il seriale corrispondente al nostro nome ed infine dobbiamo crackare il programma in modo che accetti la registrazione con qualsiasi nome e seriale.
Introduzione |
Questa lezione tratta il crack ed il reverse di programmi che contengono codice automodificante (per capire in cosa consiste leggere la spiegazione di Quequero)
Tools usati |
Only SoftIce.
URL o FTP del programma |
Notizie sul programma |
Il programma richiede nome e codice seriale. Se i dati inseriti non sono corretti non viene visualizzato alcun messaggio, questo impedisce di impostare breakpoint su MessageBox o similari, ma a noi non importa :). Inoltre presenta un'altra piccola particolarità; la generazione del seriale dal nome avviene con un algoritmo per le prime quattro lettere e con un altro per le successive quattro. In totale l'algoritmo lavora al max sulle prime 8 lettere ma si possono inserire anche nomi più lunghi che genereranno codici diversi. Questo è dovuto al fatto che nell'algoritmo che lavora sulle lettere dalla quinta all'ottava c'è l'istruzione "xor al,[0040210F]" dove la locazione 0040210F contiene la lunghezza del nome inserito. Da qui si capisce chiaramente che se noi inseriamo "Quequero" otteniamo il seriale "4648;577" mentre se inseriamo "QuequeroCheCiTiraMatti" otteniamo il seriale "4648=75?". Per un'ulteriore dimostrazione che la differenza dei codici dopo l'ottava lettera dipende solo dalla lunghezza del nome (non dalle lettere) vediamo che per "Quequero?" viene generato il seriale "4648:688" che coincide con il seriale generato per "Quequero¿".
Essay |
Ok iniziamo a prendere confidenza con il programma: apriamo softice (ctrl+D) ed impostiamo un breakpoint sull'acquisizione del nome e del seriale (bpx GetWindowTextA), usciamo da softice (F5) inseriamo nome e seriale nel programma e premiamo il tasto register. Eccoci in softice! Premiamo F12 per tornare dalla CALL, disabilitiamo il breakpoint con "bd 0". Ora ci troviamo davanti al codice del programma appena dopo la chiamata a GetWindowTextA. (un consiglio, procedete nel programma con F8, tranne nelle chiamate alle GetWindowTextA e GetWindowTextLenghtA (qui usate F10), o vi perderete il bello del SMC :)
La funzione chiamata durante il programma con "call 0040160C" la trovate alla fine del listato.
Per tenere sott'occhio l'acquisizione del nome e del seriale, e soprattutto il seriale corretto, procedete nel seguente modo: in softice se non è visibile la finestra dei dati premete alt+F2 (se non sapete qual'è la finestra dei dati premete più volte alt+F2 e vedrete una finestra aprirsi e chiudersi, bhe quella deve essere visibile :) poi date il comando "d 0040209B". In questo modo avrete visibile il range di memoria per leggere tutte le informazioni utili. Per avere il giusto seriale dovete leggere dalla locazione 004020C7 (ovviamente il valore sarà visibile solo dopo aver eseguito tutto l'algoritmo spiegato sotto forzando i salti come verrà indicato :); i valori che vedete a questa locazione andranno così "decodificati":
-per i primi 4 valori prendeteli e xorateli ognuno con 81 e poi con 40.
-per i successivi 4 valori prendeteli e xorateli ognuno con 82, fate due shift a sx e due rotazioni a dx (per eseguire queste operazioni convertite il valore in binario e poi per lo shift aggiungete due zeri a dx ed eliminate due valori a sx, per la rotazione cancellate due valori a dx e riscriveteli a sx; poi riconvertite in esadecimale)
es. : se dalla conversione in binario avete 10110100 con lo shift diventa 11010000 e con la rotazione diventa 00110100.
I valori così ottenuti rappresentano il codice ASCII in esadecimale dei chr del vostro seriale...ora non vi resta che trovare i chr corrispondenti :)
Ovviamente non è detto che avrete per forza 8 valori, possono essere meno, dipende dal nome che avete inserito.
Ci siamo, partiamo ad analizzare il programma:
| push | 14 | | | |
| push | 0040209B | |locazione dove viene salvato il nome | |
| push | dword ptr [0040204C] | | | |
| call | GetWindowTextA | |chiamata alla funzione per acquisire il nome | |
| mov | edx,eax | salva in edx il # di chr letti (ritornati in eax dall GetWindowTextA) | |
| push | 28 | | | |
| push | 004020AF | |locazione dove viene salvato il nome | |
| push | dword ptr [0040204C] | | | |
| call | GetWindowTextA | |chiamata alla funzione per acquisire il nome (con questa chiamata rilegge il nome per la seconda volta ma lo salva in una locazione diversa) | |
| mov | ebx,[004020AF] | carica in ebx il 1° chr del nome | |
| mov | esi,004015C1 | |questa istruzione e la prossima servono a predisporre i dati per l'esecuzione di movsb (A) | |
| mov | edi,00401265 | | | |
| mov | ecx,1 | ecx deve contenere il # di volte che deve essere eseguita la repz | |
| repz | movsb | Con questa istruzione si va a modificare il codice del programma nel seguente modo: movsb copia il byte indirizzato da ds:esi nel byte indirizzato da es:edi e il tutto viene ripetuto (tramite la repz) fino a quando ecx è uguale a zero. | |
| nop | |||
| nop | |||
| push | 14 | | | |
| push | 0040209B | |locazione dove viene salvato il nome inserito | |
| push | dword ptr [0040204C] | | | |
| call | GetWindowTextA | |chiamata alla funzione per acquisire il nome (rilegge il nome per la terza volta e lo sovrascrive a quello della prima lettura) | |
| push | dword ptr [0040204C] | | | |
| call | GetWindowTextLenghtA | |chiamata alla funzione per determinare la lunghezza del nome | |
| inc | eax | in eax c'è il valore tornato dalla GetWindowTextLenghtA e qui viene incrementato di 1 (B) | |
| 00401266: | mov | esi,0040157C | |per le seguenti quattro istruzioni vedere (A) |
| mov | edi,00401277 | | | |
| mov | ecx,38 | |questa volta la repz viene ripetuta 56 volte (38 esedecimale = 56 decimale) | |
| repz | movsb | | |
Arrivati a questo punto dobbiamo fermarci un attimo. Qui (in softice) vediamo delle istruzioni che sono le stesse che vedremmo disassemblando il programma. Ora, supponendo di essere arrivati all'istruzione "repz movsb" procedendo con F8 vedremo magicamente cambiare sotto i nostri occhi le istruzioni successive! Le istruzioni riportate di seguito sono quelle già modificate (le altre non ci interessano per niente).
| 00401277: | mov | [0040210F],eax | salva in 0040210F il # di chr del nome+1 (vedere (B)) |
| xor | ecx,ecx | azzera ecx | |
| 0040127E: | mov | al,[ecx+0040209B] | carica in al i chr del nome dal primo (la locazione da cui prende il chr corrisponde alla terza acquisizione fatta) |
| mov | edi,0B | |da qui inizia una serie di istruzioni per la crittazione dei chr del nome... | |
| cdq | | | ||
| idiv | edi | | | |
| add | eax,edx | | | |
| shl | eax,1 | | | |
| mov | dl,al | | | |
| call | 0040160C | |qui viene chiamata una procedura che fa in modo di modificare il valore per mantenerlo in un certo range di valori (questa procedura lavora sul registro dl) | |
| xor | eax,eax | | | |
| mov | al,dl | | | |
| xor | al,81 | | | |
| xor | al,40 | | | |
| mov | [ecx+004020C7],al | |...e qui terminano, con il salvataggio del valore crittato a partire dalla locazione 004020C7 | |
| inc | ecx | incrementa conteggio chr | |
| cmp | ecx,4 | | | |
| jnz | 0040217E | |esegue le istruzioni precedenti sui primi 4 chr poi prosegue | |
| nop | |||
| nop | |||
| nop | |||
| nop | |||
| 004012AF: | mov | esi,00401516 | |per le seguenti quattro istruzioni vedere (A) |
| mov | edi,004012C0 | | | |
| mov | ecx,5C | |questa volta la repz viene ripetuta 92 volte (5C esedecimale = 92 decimale) | |
| repz | movsb | | |
A questo punto ci ritroviamo nella stessa situazione descritta sopra. Valgono le stesse considerazioni. Ricordo che il codice seguente è quello già modificato.
| 004012C0: | mov | ecx,4 | carica 4 in ecx per far partire il seguente algoritmo dal 5° chr del nome |
| 004012C5: | xor | eax,eax | azzera eax |
| mov | al,[ecx+0040209B] | carica in al i chr del nome dal 5° | |
| cmp | ecx,[0040210F] | confronta ecx con il # di chr del nome+1 (vedere (B)) | |
| jz | 0040131C | esegue le istruzioni seguenti fino alla fine del nome (dopo salta a 0040131C) | |
| mov | edi,8 | |da qui inizia una serie di istruzioni per la crittazione dei chr del nome... | |
| cdq | | | ||
| idiv | edi | | | |
| add | al,dl | | | |
| shl | al,3 | | | |
| ror | al,1 | | | |
| add | al,6 | | | |
| shl | al,1 | | | |
| xor | al,[0040210F] | |Questa è l'istruzione che xora il chr con la lunghezza del nome (è quella che causa la differenza di seriale tra due nomi di lunghezza diversa e maggiore di 8) | |
| mov | edi,3 | | | |
| cdq | | | ||
| idiv | edi | | | |
| add | al,dl | | | |
| mov | dl,al | | | |
| call | 0040160C | |qui viene chiamata una procedura che fa in modo di modificare il valore per mantenerlo in un certo range di valori (questa procedura lavora sul registro dl) | |
| mov | al,dl | | | |
| xor | al,82 | | | |
| shl | al,2 | | | |
| ror | al,2 | | | |
| mov | [ecx+004020C7],al | |...e qui terminano, con il salvataggio del valore a partire dalla locazione 004020CA (004020C7+4) | |
| xor | eax,eax | azzera eax | |
| inc | ecx | incrementa il conteggio dei chr | |
| cmp | ecx,8 | | | |
| jnz | 004012C5 | |esegue le istruzioni precedenti per max 8 chr (i successivi non vengono considerati) | |
| nop | |||
| nop | |||
| nop | |||
| nop | |||
| nop | |||
| 0040131C: | mov | esi,004014E5 | |per le seguenti quattro istruzioni vedere (A) |
| mov | edi,0040133F | | | |
| mov | ecx,22 | |questa volta la repz viene ripetuta 34 volte (22 esedecimale = 34 decimale) | |
| repz | movsb | | |
Anche qui stessa cosa di sopra (e dai che questa è la penultima volta :)
| 0040132D: | push | 18 | |con le seguenti tre istruzioni predispone la lettura del seriale |
| push | 004020AF | |confrontando gli indirizzi si vede che sovrascrive il seriale alla seconda acquisizione del nome | |
| push | dword ptr [00402050] | | | |
| call | GetWindowTextA | |legge il seriale | |
| xor | ecx,ecx | azzera ecx | |
| 00401341: | xor | eax,eax | azzera eax |
| mov | al,[ecx+004020AF] | carica in al i chr del seriale dal primo | |
| xor | al,81 | |queste due istruzioni crittano il chr del seriale | |
| xor | al,40 | | | |
| mov | bl,[ecx+004020C7] | carica in bl dal primo i chr del nome crittati | |
| cmp | al,bl | confronta il chr crittato del nome con il chr crittato del seriale | |
| jnz | 00401170 | se sono diversi salta a 00401170 ( SERIALE SBAGLIATO) Questo jnz va forzato a non saltare; per fare questo date il comando "r fl z" ovviamente prima di eseguire l'istruzione e solo se softice segnala che è previsto il salto, magari avete indovinato il valore :) | |
| inc | ecx | incrementa conteggio chr | |
| cmp | ecx,4 | | | |
| jnz | 00401341 | |esegue le istruzioni precedenti per i primi 4 chr | |
| nop | |||
| nop | |||
| 00401363: | mov | esi,004015C3 | |per le seguenti quattro istruzioni vedere (A) |
| mov | edi,00401374 | | | |
| mov | ecx,49 | |questa volta la repz viene ripetuta 73 volte (49 esedecimale = 73 decimale) | |
| repz | movsb | | |
Ok questa è l'ultima volta...
| 00401374: | mov | ecx,4 | carica 4 in ecx per far partire il seguente algoritmo dal 5° chr del nome |
| 00401379: | xor | eax,eax | azzera eax |
| mov | al,[ecx+004020AF] | carica in al i chr del seriale dal quinto | |
| cmp | ecx,[0040210F] | confronta ecx con il # di chr del nome+1 (vedere (B)) | |
| jz | 004013A5 | ripete le istruzioni seguenti fino alla fine del nome+1 (poi salta a 004013A5) | |
| xor | al,82 | |con le prossime tre istruzioni critta il chr del seriale | |
| shl | al,2 | | | |
| ror | al,2 | | | |
| mov | bl,[ecx+004020C7] | carica in bl i chr del nome crittati dal quinto | |
| cmp | al,bl | confronta il chr crittato del nome con il chr crittato del seriale | |
| jnz | 00401170 | se sono diversi salta a 00401170 (SERIALE SBAGLIATO) Questo jnz va forzato a non saltare; per fare questo date il comando "r fl z" ovviamente prima di eseguire l'istruzione e solo se softice segnala che è previsto il salto, magari avete indovinato il valore :) | |
| inc | ecx | incrementa conteggio chr | |
| cmp | ecx,8 | | | |
| jnz | 00401379 | |esegue le istruzioni precedenti per al max 8 chr | |
| 004013A5: | push | 20 | |con le prossime 4 istruzioni prepara la chiamata alla MessageBoxA |
| push | 00402061 | | | |
| push | 00402113 | | | |
| push | 00 | | | |
| call | MessageBoxA | |visualizza il messaggio SERIALE ESATTO |
Questa è la procedura che viene chiamata ad ogni istruzione "call 0040160C":
| 0040160C: | cmp | dl,7A | confronta dl (valore passato prima della chiamata) con 7A |
| jg | 00401617 | se è maggiore salta a 00401617 | |
| cmp | dl,30 | confronta dl con 30 | |
| jl | 00401621 | se è minore salta a 00401621 | |
| 00401616: | ret | ritorna dalla chiamata | |
| 00401617: | sub | dl,0A | sottrae 0A a dl |
| cmp | dl,7A | | | |
| jg | 00401617 | |continua fino a quando dl è maggiore di 7A | |
| jmp | 00401616 | salta a 00401616 (ritorno dalla chiamata) | |
| 00401621: | add | dl,0A | somma 0A a dl |
| cmp | dl,30 | | | |
| jl | 00401621 | |continua fino a quando dl è minore di 30 | |
| jmp | 00401616 | salta a 00401616 (ritorno dalla chiamata) |
Siamo giunti alla fine del programma. Ma non è ancora finita: ora abbiamo il nostro seriale esatto ma siccome non vogliamo trovarne uno per ogni nostro amico crackiamo il programma in modo che accetti qualsiasi nome e qualsiasi seriale :)
Dobbiamo tenere conto che stiamo lavorando su SMC quindi il codice che viene eseguito non è uguale al codice disassemblato e quindi nemmeno all'eseguibile che noi andremo a modificare. Per fregare questo tipo di protezione dobbiamo annotare gli opcode delle istruzioni da modificare dopo che il codice è stato modificato (e quindi abbiamo il programma descritto sopra). Inoltre, in questo caso, è utile annotare gli opcode dall'istruzione interessata in avanti (normalmente si prendono un po' di byte prima e un po' dopo). Io ho pensato di modificare le istruzioni immediatamente successive alle "repz movsb" (cioè dopo che il codice è stato modificato) in jmp alla successiva istruzioni "repz movsb" (ovviamente qualche istruzione prima, cioè quelle necessarie all'esecuzione della repz). Per trovare l'opcode delle istruzioni da sostituire dovete procedere nel seguente modo: in softice date il comando "a indirizzo_istruzione" (es. "a 00401277"), scrivete "jmp indirizzo_a_cui_saltare" (es. "jmp 004012AF") e premete invio. Ora annotate l'opcode della nuova istruzione.
Ok schematizziamo il tutto:
-consideriamo il repz prima dell'istruzione all'indirizzo 00401277; vogliamo modificare la "mov [0040210F],eax" in "jmp 004012AF" (004012AF è l'indirizzo della successiva repz). Per fare ciò annotiamo gli opcode partendo proprio dalla mov: A30F|214000 va sostituito con EB36|214000 (la sostituzine andrà fatta con l'editor esedecimale)
Proseguiremo allo stesso modo per i seguenti casi:
-sostituiamo l'istruzione "mov ecx,4" all'indirizzo 004012C0 con una "jmp 0040131C": B904|00000033C08A819B con EB5A|000...
-sostituiamo l'istruzione "push 18" all'indirizzo 0040132D con una "jmp 00401363": 6A18|68AF204000FF3550 con EB34|68AF... (qui è importante considerare anche il 50 sottolineato perchè se non includiamo questo con l'editor esadecimale troveremo tre volte lo stesso valore e non sapremo quale modificare)
-sostituiamo l'istruzione "mov ecx,4" all'indirizzo 00401374 con una "jmp 004013A5": B904|00000033C08A81AF con EB2F|000...
Ok, fatte queste modifiche arriveremo direttamente alla chiamata al messaggio di congratulazioni qualsiasi dato inseriremo :)
Per le modifiche con l'editor esadecimale, se non accetta nel campo ricerca l'intera lunghezza dei valori indicati non c'è problema, inserite fino dove arriva e date l'invio; quando avrà trovato il valore leggete i byte successivi e confronataeli con quelli indicati. Se sono uguali siete nel punto giusto.
Ok ho capito, non avete voglia di fare tutto questo lavoro? Va bhe, vi allego il generatore del seriale :)) ma come dice Que siete dei lavativi :)...almeno leggete la spiegazione del codice...
unit Unit1;
interface
uses
Forms, Classes, Controls, StdCtrls;
type
TForm1 = class(TForm)
Edit1: TEdit;
Button1: TButton;
Edit2: TEdit;
Label1: TLabel;
procedure Button1Click(Sender: TObject);
private
procedure
codifica(chr:integer);
procedure
codifica2(chr:integer);
public
end;
{Il codice che interessa a voi parte da qui}
var
Form1: TForm1;
nome: string; {variabile che contiene il nome inserito}
mezzo: byte=0; {variabile per
il passaggio del chr crittato}
lungh: integer;
{variabile che contiene la lunghezza del nome}
implementation
procedure TForm1.Button1Click(Sender: TObject); {questa procedura
viene chiamata quando premete il tasto Seriale}
var
i: integer; {variabile per il conteggio dei chr (fa la funzione di ecx)}
cr: integer; {variabile per il passaggio del codice ASCII dei chr}
begin
i:=0;
Edit2.Text:='';
if Edit1.Text<>'' then {esegue le istruzioni seguenti solo se si è inserito un nome}
begin
nome:=Edit1.Text; {assegna il nome inserito alla
variabile nome}
lungh:=Edit1.GetTextLen; {carica in lungh il # di
chr del nome}
inc(lungh); {incrementa
di 1 lungh (corrisponde all'istruzione "inc eax" dopo la GetWindowTextLenghtA)}
while((nome[i+1]<>'') and (i<4)) do {esegue
le istruzioni seguenti solo per i primi 4 chr o fino alla fine del nome se sono meno di 4}
begin
cr:=Ord(nome[i+1]); {carica in cr il codice ASCII
dei chr del nome a partire dal primo}
codifica(cr); {chiama la
procedura di codifica dei primi 4 chr}
Edit2.Text:=Edit2.Text+Chr(mezzo); {visualizza il
seriale (lo fa chr per chr ma noi lo vediamo tutto insieme)}
inc(i); {incrementa il
conteggio dei chr (corrisponde alla "inc ecx")}
end;
if((i<4)and(nome[i+1]='')) then {se il nome è
più corto di quattro lettere aggiunge il chr 2 al seriale}
Edit2.Text:=Edit2.Text+'2' {il chr 2 corrisponde
alla codifica del codice ASCII 0}
else {se invece i chr
sono almeno 4 esegue le istruzioni seguenti}
begin
while((nome[i+1]<>'')and(i<8)) do {esegue le istruzioni
seguenti fino alla fine del nome+1 e al max per 8 chr}
begin
cr:=Ord(nome[i+1]); {carica in cr il codice ASCII
dei chr del nome a partire dal quinto}
codifica2(cr); {chiama la procedura di codifica
dei chr dal quinto all'ottavo}
Edit2.Text:=Edit2.Text+Chr(mezzo); {visualizza il
seriale (lo fa chr per chr ma noi lo vediamo tutto insieme)}
inc(i); {incrementa il conteggio dei chr
(corrisponde alla "inc ecx")}
end;
if((i<8)and(nome[i+1]='')) then {se il nome è
meno di 8 chr aggiunge la codifica del codice ASCII 0}
begin
codifica2(0); {qui non possiamo mettere il valore
fisso come per il 2 prima perchè cambia in base alla lunghezza del nome}
Edit2.Text:=Edit2.Text+Chr(mezzo); {visualizza il
seriale (lo fa chr per chr ma noi lo vediamo tutto insieme}
end;
end;
end;
end;
{procedura per la codifica dei primi 4 chr-le istruzioni sono uguali
a quelle del programma; sono state omesse la istruzioni "xor al,81h" e "xor
al,40h" perchè avremmo dovuto rieseguirle per ottenere il codice ASCII del seriale}
procedure TForm1.codifica(chr:integer);
asm
mov eax,chr
xor edx,edx
mov edi,0Bh
cdq
idiv edi
add eax,edx
shl eax,1
cmp al,7Ah {le seguenti 12 istruzioni
sostituiscono la chiamata "call 0040160C"}
jg @sottrai
cmp al,30h
jl @somma
jmp @continua
@sottrai: sub al,0Ah
cmp al,7Ah
jg @sottrai
jmp @continua
@somma: add al,0Ah
cmp al,30h
jl @somma
@continua: mov mezzo,al {salva il chr
elaborato nella variabile mezzo per la visualizzazione successiva}
end;
{procedura per la codifica dei chr dal quinto all'ottavo-le
istruzioni sono uguali a quelle del programma; sono state omesse le istruzioni "xor
al,82h", "shl al,02h" e "ror al,02h" perchè avremmo dovuto
rieseguirle per ottenere il codice ASCII del seriale}
procedure TForm1.codifica2(chr:integer);
asm
mov eax,chr
xor edx,edx
mov edi,08h
cdq
idiv edi
add al,dl
shl al,03h
ror al,01h
add al,06h
shl al,01h
xor al,byte ptr lungh {corrisponde all'istruzione
"xor al,[0040210F]"}
mov edi,03h
cdq
idiv edi
add al,dl
cmp al,7Ah {le seguenti 12 istruzioni
sostituiscono la chiamata "call 0040160C"}
jg @sottrai
cmp al,30h
jl @somma
jmp @continua
@sottrai: sub al,0Ah
cmp al,7Ah
jg @sottrai
jmp @continua
@somma: add al,0Ah
cmp al,30h
jl @somma
@continua: mov mezzo,al {salva
il chr elaborato nella variabile mezzo per la visualizzazione successiva}
end;
end.
|
Un ringraziamento va a Quequero per questo programma perchè quando ho visto modificarsi il codice in softice ho iniziato a ridere come un deficiente (forse che sono un po' matto...bhe ci vuole anche questo nella vita :-)
Un saluto va ovviamente a tutti gli iscritti alla UIC e al newsgroup ahccc (e soprattutto ad un certo Parsifal ;-)
Disclaimer |