Sort Reversing

Data

by "Winebag"

 

07/01/2004

UIC's Home Page

Published by Quequero

 

Grazie wine, io non c'avrei mica mai pensato a reversarmi il Sort :))

 

....

E-mail: W[email protected]

....

Difficoltà

( )NewBies (x)Intermedio ( )Avanzato ( )Master

 

Questa è una descrizione di un algoritmo di ordinamento, vista dalla prospettiva del reverser. Esso non ha NESSUNA utilità pratica, quindi non troverete come sproteggere un determinato programma, né sorgenti di particolare interesse, solo un piccolo esempio di come possa essere utilizzato il reversing per scopi un attimino diversi dal solito.


Sort Reversing
Written by Winebag
    

 

Introduzione

Questo lavoro è frutto di una mia curiosità: volevo vedere come funzionava il comando sort del DOS, per vedere come i programmatori avevano affrontato alcuni problemi. Ho pensato di scrivere un tutorial nel caso che a qualcuno interessasse capire come funzionano le cose e anche per far vedere che il reversing non è solo cracking e per ricordare che esiste anche l'assembly a 16 bit!

Tools usati

SoftIce
IDA PRO

URL o FTP del programma

C:\WINDOWS\Command\SORT.exe  :-)

Notizie sul programma

Questo comando serve ad ordinare una serie di stringhe, ottenute o da tastiera o da un file, e a visualizzarle sullo STDOUT(che può essere ad esempio rediretto ad un file). Per una spiegazione più approfondita: SORT /? 

Essay


Il primo problema che ci si presenta è che dato che ci troviamo in modalità reale a 16 bit, non possiamo usare i soliti breakpoint, quindi per brekkare nel programma dobbiamo provocarlo esternamente, quindi disassembliamo con IDA, cerchiamo l'indirizzo dell'entry-point e apriamo il file con un editor esadecimale. Ci scriviamo il valore del byte e lo andiamo a sostituire con 0CCh. Da SIce digitiamo bpint 3 e da riga di comando invochiamo sort. A questo punto compare SIce all'entry point , che andiamo a ripristinare(eb eip sostituendo il byte originale). Adesso possiamo steppare e farci un idea della struttura del programma, ed eventualmente possiamo utilizzare bpint 21, per vedere dove viene preso l'input. Ovviamente non avrebbe senso invocare bpint 21 prima di essere entrati nel programma, perché l'interrupt 21 viene invocato continuamente da altri programmi. Molto interessante è il far jump che arriva nel segmento in cui è contenuto il codice effettivo del programma, all'offset 2E8E. Qui prima viene letta la riga di comando, poi viene formattata nel classico array di stringhe(argv) dalla procedura di offset 3184; vengono compiute altre operazioni, poi a cs:2F50 viene invocata la funzione cs:0A26, con argomento il numero di parametri(argc) e l'indirizzo dell'array di stringhe generato in precedenza: abbiamo trovato il main! All'interno del main vengono chiamate due funzioni, che non analizziamo, poi si entra in un loop che processa i parametri della riga di comando, per vedere se sono state passate alcune opzioni; vediamo come si comporta:

les si, dword ptr [bp+argv]
les bx, es:[bx+si]
<--es:bx punta ad argv[i] dove i è l'indice contenuto in una variabile locale
cmp byte ptr es:[bx], 2Fh ; '/'
<--controlla se il primo carattere è /
jz loc_0_A7C

loc_0_A7A: 
jmp short loc_0_AEF
<--se no esce dal ciclo

loc_0_A7C: 
mov al, es:[bx+1]
<--in al il secondo carattere
cbw
cmp ax, 72h ; 'r'
jz loc_0_AC9
ja loc_0_A94
sub al, 2Bh ; '+'
jz loc_0_A9C
sub al, 14h ; '?'
jz loc_0_A59   
<--stampa le opzioni di sort e esce dal programma
sub al, 13h ; 'R'
jz loc_0_AC9

...
loc_0_AC9: ;
se è stato digitato /r o /R ovvero se si vuole l'ordine inverso
mov bx, [bp+var_2]
<-- questo contiene l'indice i
add bx, bx
add bx, bx
<--moltiplica bx per 4, la dimensione degli elementi dell'array argv, puntatori a stringhe(char *)
mov es, [bp+ArgvSegment]
les bx, es:[bx+si]
<--questo punta alla stringa argv[i]
cmp byte ptr es:[bx+2], 0
<--controlla che non contenga nulla oltre a /r (o /R)
jnz loc_0_A94
or byte ptr [bp+var_4], 1
<--setta un bit di questa variabile locale, inizializzata a 0
jmp loc_0_A5C
<--torna all'inizio del ciclo

...
loc_0_A9C:
; /+n ,con n numero intero indica che si vuole partire dalla posizione n
mov bx, [bp+var_2]
add bx, bx
add bx, bx
les si, dword ptr [bp+argv]
mov ax, es:[bx+si]
<--argv[i]
mov dx, es:[bx+si+2]
<--il segmento puntato da argv
inc ax
<--
inc ax
<--salta i primi 2 caratteri, cioè /+
push dx
push ax
call atoi
<--trasforma la stringa in un intero
add sp, 4
mov word_0_19C8, ax
<--lo salva in questa variabile
cmp ax, 1
jb err
<--se è <1 stampa un messaggio d'errore
dec word_0_19C8
<--questo perché per la gente normale si parte da 1, mentre ai programmatori serve partire da 0
or byte ptr [bp+var_4], 2
<-- setta un bit nella variabile flag
jmp short loc_0_A5C
<--torna all'inizio del ciclo

Dopo aver finito il ciclo, vengono usati gli altri parametri, per eventualmente redirigere input o output, cosa che per i nostri scopi possiamo anche tralasciare. Poco più in basso viene chiamata la funzione che effettuerà tutto il lavoro: 

mov bx, [bp+var_4]<--questa è la variabile flag in cui si tiene conto delle opzioni selezionate
add bx, bx
<--utilizzata come indice in un array di offset
mov ax, [bx+0AEh]
<--questo array contiene indirizzi di 4 procedure
mov word_0_1A00, ax
<--viene salvato quello che ci interessa in una variabile che servirà dopo
call sub_0_7A2
<--questa è la procedura che fa tutto

In questa procedura troviamo per prima cosa un ciclo che effettua l'input, chiamando dopo alcuni passaggi il servizio 3Fh dell'interrupt 21h, che legge una stringa(terminata da CR o LF) da un file (o dalla riga di comando), che verrà salvata in un array, finché non trova il carattere  1Ah (EOF, da tastiera CTRL+Z):

loc_0_7B1: ; qui comincia il ciclo
sub ax, ax
mov [bp+var_8], ax
mov [bp+var_A], ax
cmp word_0_42, 2000h
<--guarda che l'array non contenga più di 2000h stringhe
jnb loc_0_822
<--non ci preoccupiamo di questa eventualità
lea ax, [bp+var_20A]
<--buffer locale
push ss
push ax
call sub_0_2CC
<--effettua l'input,mettendo la stringa nello stack
add sp, 4
or ax, ax
<--la funzione restituisce 0 quando trova l'EOF
jnz loc_0_822
<--esci dal ciclo
mov ax, word_0_42
inc word_0_42
<--incrementa il contatore
mov [bp+var_20C], ax
<--e lo salva in una variabile locale
lea ax, [bp+var_20A]
push ss
push ax
call sub_0_5B4E
<--salva la stringa nello heap, eventualmente allocando nuova memoria
add sp, 4
mov [bp+var_A], ax
<--salva l'indirizzo della stringa in una variabile locale
mov [bp+var_8], dx
mov es, word_1458
<--segmento in cui si trova l'array di stringhe
mov bx, [bp+var_20C]
<--utilizza il contatore come offset
add bx, bx
add bx, bx
mov es:[bx+0], ax
<--e salva l'indirizzo della stringa
mov es:[bx+2], dx
or dx, ax
jnz loc_0_822
<--esci dal ciclo in caso d'errore
...

Adesso che abbiamo  sistemato il nostro input, non ci resta che ordinarlo; vediamo come viene invocata la funzione che effettuerà questo lavoro:

loc_0_822:
mov ax, [bp+var_8]
or ax, [bp+var_A]
jnz loc_0_7B1
<--se è uscito dal ciclo a causa di un errore
cmp word_0_42, 0
jz loc_0_89B
<--array vuoto
cmp word_0_42, 1
jbe loc_0_852
<--array con un solo elemento, non ha senso ordinarlo
push word_0_1A00
<--qui avevamo salvato precedentemente l'offset di una funzione, a seconda dell'opzione selezionata
mov ax, 4
push ax
<--dimensione di ogni oggetto dell'array
push word_0_42<--lunghezza
mov ax, 0
<--offset dell'array di stringhe da ordinare
mov cx, 5C7h
<--segmento
push cx
push ax
call sub_0_D88
<--questa effettuerà l'ordinamento

In realtà questa procedura si limiterà ad aggiustare i parametri per passarli alla funzione di offset B9Ch, che ho rinominato sort: 

push bp
mov bp, sp
cmp [bp+arg_4], 1
jbe loc_0_DAD
push [bp+arg_8]
<--offset della funzione selezionata
push [bp+arg_6]
<--dimensione di ogni oggetto
mov ax, [bp+arg_4]
<--lunghezza
dec ax
mul [bp+arg_6]
add ax, [bp+arg_0]
<--offset array[lunghezza-1], ovvero dell'ultimo elemento
mov dx, [bp+arg_2]
<--segmento
push dx
push ax
push dx
push [bp+arg_0]
<--offset del primo elemento
call sort
; cs:0B9Ch
...

Prima di analizzare a fondo l'algoritmo, diamo uno sguardo alle funzioni di supporto. La funzione sort si serve di una procedura per confrontare due oggetti (variabile a seconda delle opzioni) una che scambia due oggetti, e una che verifica se l'array è ordinato, restituendo TRUE o FALSE (1 o 0).
Dal momento che questo programma è a 16 bit, un puntatore è definito da due variabili: segmento e offset, quindi nel corso delle spiegazioni mi riferirò sempre al concetto astratto di puntatore,  perciò dirò ad esempio che una funzione riceve 2 puntatori, mentre in realtà riceve 4 valori.
Detto questo possiamo passare al codice: la prima funzione che vediamo è quella di offset 9Ch, che prende in input due stringhe e restituisce 0 se sono uguali, un valore negativo se la prima precede la seconda, viceversa negativo analogamente a strcmp del C, ma non nello stesso modo: 

loc_0_A4: 
les bx, [bp+arg_4]
<--puntatore alla seconda stringa
inc word ptr [bp+arg_4]
mov bl, es:[bx]
<--i-esimo carattere
sub bh, bh
les si, dword_0_19CA
<--??
les si, [bp+arg_0]
<--??
les di, dword_0_19CA
<--array di 256 char
mov al, es:[bx+di]
<--elemento dell'array usando l'i-esimo carattere come indice
mov cx, es
les bx, [bp+arg_0]
<--prima stringa
mov bl, es:[bx]
<--i-esimo carattere della prima stringa 
sub bh, bh
mov es, cx
mov dl, es:[bx+di]
<--utilizzato come indice
sub dh, dh
sub ah, ah
sub dx, ax
<--primo meno secondo
jnz loc_0_DF
<--se diversi esci dal ciclo
les bx, [bp+arg_0]
inc [bp+arg_0]
cmp es:[bx], ah
jnz loc_0_A4
<--continua fino alla fine della prima stringa

loc_0_DF:
mov ax, dx
<--restituisce la differenza
...

Direi che è tutto abbastanza chiaro, l'array ausiliario è costruito in modo da equiparare maiuscole e minuscole: ad esempio agli indici corrispondenti ai valori ASCII di e,E,è,é si trova sempre la E. Questa funzione non viene invocata direttamente da sort, ma da una di quelle 4 funzioni di cui abbiamo parlato in precedenza che andiamo ad analizzare partendo dal caso più semplice, ovvero quando non ci sono opzioni selezionate:

sub_0_91A:
push bp
mov bp, sp
les bx, [bp+arg_4]
<--secondo parametro, di tipo char ** cioè  l'indirizzo dell'array di puntatori
push word ptr es:[bx+2]<--viene pushato il primo puntatore puntato 
push word ptr es:[bx] 
<--cioè una stringa
les bx, [bp+arg_0]
push word ptr es:[bx+2]
<--lo stesso per il primo parametro
push word ptr es:[bx]
call cmp
<--la funzione vista sopra a cui vengono passati i parametri nello stesso ordine
mov sp, bp
pop bp
retn

Il codice è abbastanza semplice, anche se non ho spiegato molto bene la faccenda dei puntatori; aggiungo una nota per chi ha poca dimestichezza col c: quando parleremo dei puntatori utilizzati da sort, ci riferiremo al tipo char**, dato che sort ordina un array di stringhe, cioè un array di indirizzi di array di caratteri, ogni variabile puntatore rappresenta l'indirizzo di uno specifico elemento di questo array; sub_0_91A deve però passare a cmp una stringa, ovvero un indirizzo di un array di char, ovvero un elemento dell'array su cui lavora sort, quindi spiegato perché non si limita a passare quello che riceve. Adesso vediamo come si comporterebbe se avessimo selezionato l'opzione /r ovvero ordinamento inverso:

les bx, [bp+arg_0]
push word ptr es:[bx+2]
push word ptr es:[bx]
les bx, [bp+arg_4]
push word ptr es:[bx+2]
push word ptr es:[bx]
call cmp
<--uguale a sopra ma con i parametri invertiti 

Questo se avessimo scelto l'opzione /+n, ovvero ordina a partire dall'n-esimo carattere

sub_0_956:
push bp
mov bp, sp
sub sp, 0Ah
les bx, [bp+arg_0]
<--prende il primo parametro
mov ax, es:[bx]
<--ottiene il puntatore al primo carattere della stringa
mov dx, es:[bx+2]
mov [bp+var_6], ax
mov [bp+var_4], dx
push dx
push ax
call strlen
<-- calcola la lunghezza
add sp, 4
mov [bp+var_2], ax
<--e la mette in una variabile locale 
mov ax, word_0_19C8
<--questo è il valore di n salvato in precedenza
cmp [bp+var_2], ax
jbe loc_0_982
<--salta se strlen<=n
mov [bp+var_2], ax
<--altrimenti sovrascrive la variabile locale con n

loc_0_982:
mov ax, [bp+var_2]
<--prende il contenuto di questa variabile, ovvero il minore tra strlen e n
add [bp+var_6], ax<--e lo usa per spostare il puntatore(se la stringa è più corta di n, punterà a una stringa vuota)
les bx, [bp+arg_4]
<--lo stesso per la seconda stringa
mov ax, es:[bx]
mov dx, es:[bx+2]
mov [bp+var_A], ax
mov [bp+var_8], dx
push dx
push ax
call strlen
add sp, 4
mov [bp+var_2], ax
mov ax, word_0_19C8
cmp [bp+var_2], ax
jbe loc_0_9AE
mov [bp+var_2], ax

loc_0_9AE:
mov ax, [bp+var_2]
add [bp+var_A], ax
push [bp+var_8]
<--questo è il puntatore all'n-esimo char della seconda
push [bp+var_A]
<--(o a una stringa vuota)
push [bp+var_4]
<--e questo si riferisce alla prima
push [bp+var_6]
call cmp
<--questa effettuerà il confronto

Se avessimo scelto contemporaneamente le due opzioni, la funzione di confronto sarebbe stata questa:

sub_0_9C8:
push bp
mov bp, sp
push [bp+arg_6]
push [bp+arg_4]
push [bp+arg_2]
push [bp+arg_0]
call sub_0_956
<--pusha i parametri nello stesso ordine alla funzione vista sopra
neg ax
<--ma inverte il risultato
mov sp, bp
pop bp
retn

Adesso diamo un occhiata alla funzione che scambia due elementi dell'array di stringhe, all'offset DB2:

mov cx, [bp+arg_8]<--questa è la dimensione in byte degli elementi, in questo caso 4 trattandosi di puntatori
jcxz loc_0_DDD
<--esci se è nulla
lds si, [bp+arg_0]
les bx, [bp+arg_4]
shr cx, 1
jnb loc_0_DD1
<--salta se è pari
lodsb
<--altrimenti scambia il primo byte
xchg al, es:[bx]
<--utilizzando al come variabile temporanea
inc bx
mov [si-1], al
jcxz loc_0_DDD

loc_0_DD1:
lodsw
<--scambia coppie di word
xchg ax, es:[bx]
add bx, 2
mov [si-2], ax
loop loc_0_DD1
<--fino alla fine

Adesso vediamo la funzione che verifica se l'array è ordinato, essa riceve in input gli indirizzi del primo e dell'ultimo elemento del tratto di array da controllare, la dimensione in byte degli elementi, e l'offset della procedura di confronto:


push bp
mov bp, sp
jmp short loc_0_B8E
<--salta all'interno del ciclo
; --------------------------------------

loc_0_B6F:
; inizio del ciclo
mov dx, [bp+arg_2]
add ax, [bp+size]
push dx
push ax
push dx
push [bp+arg_0]
call [bp+compare]
<--confronta un elemento con il suo successivo
mov sp, bp
mov sp, bp
or ax, ax
jle loc_0_B88
<--se è minore o uguale continua
xor ax, ax
<--altrimenti non è ordinato e restituisce 0
pop bp
retn
; -----------------------------------

loc_0_B88:
mov ax, [bp+size]
add [bp+arg_0], ax

loc_0_B8E:
mov ax, [bp+arg_0]
<--offset dell'elemento da cui deve partire, incrementato ad ogni ciclo
cmp [bp+arg_4], ax
<--offset dell'ultimo elemento
ja loc_0_B6F
<--se non è arrivato alla fine ripete il ciclo
mov ax, 1
<--se ha finito e non è uscito prima, allora è ordinato
pop bp
retn

Ora che abbiamo visto le procedure di supporto possiamo passare ad analizzare a fondo l'algoritmo. La funzione sort prende come argomenti: l'indirizzo del primo e dell'ultimo elemento dell'array da ordinare(F e L),la dimensione (4), e l'indirizzo della funzione di confronto(scelto tra le 4 illustrate sopra) e utilizza come variabili locali 3 puntatori: uno che percorre l'array dall'inizio, uno dalla fine (chiamati da me con molta fantasia I e J :-) e uno che identifica un elemento in mezzo(M). Per semplicità userò il simbolo <<, per indicare che una stringa precede un'altra; A<<B vuol dire compare(A,B)<0, quindi che la stringa *A precede in ordine alfabetico *B, in alcuni casi potrei utilizzare tale scrittura direttamente con le stringhe: A<<B vorrà dire compare(&A,&B)<0, ma sarà chiaro dal contesto a quale delle 2 mi starò riferendo. 

push bp
mov bp, sp
sub sp, 0Eh
push di
push si
mov [bp+xchg], offset xchg
<--abbiamo visto sopra come funziona
cmp [bp+size], 2
jz loc_0_BB2
<--??
jmp loc_0_D74
<--salta all'ingresso del ciclo

loc_0_BB2:
mov [bp+xchg], offset xchg
jmp loc_0_D74

...
loc_0_D74:
mov ax, [bp+L]
mov cx, [bp+F]
cmp ax, cx
jbe RET
jmp loop
<--continua il ciclo se F<=L      while(F<=L) 
 
...

loop:
mov bx, [bp+segF]
mov [bp+I], cx 
;I=F
mov [bp+segI], bx
mov si, ax
mov di, [bp+segL]
mov [bp+J], ax 
;J=L
mov [bp+segJ], di
sub ax, cx
<-distanza tra I e J
sub dx, dx
div [bp+size]
<--numero di elementi tra I e J
shr ax, 1
<--diviso 2
mul [bp+size]
<--ritorna ad essere una distanza in byte
add ax, cx
<--e viene aggiunta all'offset del primo
mov [bp+M], ax  
<--quindi M punterà all'elemento fisicamente a metà
mov [bp+segM], bx<--(in caso di numero di elementi pari, al primo dei 2 medi)
push bx
push ax
push di
push si
mov si, ax
mov di, bx
call [bp+compare]
; compare(J,M)
add sp, 8
or ax, ax
jge loc_0_C05
<--se J>>M va tutto bene
push [bp+size]
<--altrimenti scambia *J e *M (L e J a questo punto sono equivalenti) 
push di
push si
push [bp+segL]
push [bp+L]
call [bp+xchg]
; xchg (L,M)
add sp, 0Ah

loc_0_C05: 
push [bp+segI]
push [bp+I]
push [bp+segJ]
push [bp+J]
call [bp+compare]
; compare(J,I)
add sp, 8
or ax, ax
jge loc_0_C30
<--se J>>I OK 
push [bp+size]
<--se no bisogna scambiarli
push [bp+segI]
push [bp+I]
push [bp+segJ]
push [bp+J]
call [bp+xchg]
; xchg(J,I)
add sp, 0Ah

loc_0_C30: 
push [bp+segI]
push [bp+I]
push [bp+segM]
push [bp+M]
call [bp+compare]
; compare(M,I)
add sp, 8
or ax, ax
jge loc_0_C5B
<--lo stesso per M ed I
push [bp+size]
push [bp+segI]
push [bp+I]
push [bp+segM]
push [bp+M]
call [bp+xchg]
; xchg(M,I)
add sp, 0Ah

loc_0_C5B: ; CODE XREF:
mov ax, [bp+J]
sub ax, [bp+I]
mov cx, [bp+size]
add cx, cx
cmp ax, cx
jbe Ret_
; j-i <= 2*size <--esci se ci sono 3 o meno di 3 elementi
push [bp+compare]
push [bp+size]
push [bp+segJ]
push [bp+J]
push [bp+segI]
push [bp+I]
call Sorted
<--guarda se per qualche ragione è già ordinato
add sp, 0Ch
or ax, ax
jz loc_0_C89
<-- se no ti tocca ordinarlo!!

Ret_: 
jmp RET

Il codice qui sopra si riferisce al ciclo più esterno, che continua finché F<=L, ovvero finché non è stato ordinato tutto; all'inizio di ogni ciclo inizializza I e J rispettivamente a F e L, poi assegna a M l'indirizzo dell'elemento a metà nel modo visto sopra; quindi opera tre confronti in modo da ordinare *I,*M e *J. 

I                       M                       J

Dove le celle bianche <<=M, mentre quelle nere >>M.

Se ci fossero solo questi 3 elementi o meno, oppure se l'array fosse ordinato la funzione ritornerebbe, altrimenti continuerebbe ad eseguire il codice che stiamo per andare a vedere. Anche se può non sembrare troppo utile questa serie di controlli, è quello che assicura il corretto funzionamento di un algoritmo ricorsivo(come vedremo in seguito): questa funzione è capace di ordinare array sufficientemente piccoli. 

loc_0_C89:
mov ax, [bp+size] 
add [bp+I], ax
; i++  <--incrementa il puntatore i della grandezza di un elemento
mov ax, [bp+I]
cmp [bp+J], ax
ja loc_0_CCA
; if i<j <--se l'array non è stato ancora percorso tutto

...
loc_0_CCA:
push [bp+segM]
push [bp+M]
push [bp+segI]
push ax
call [bp+compare]
; compare(I,M)
add sp, 8
or ax, ax
jle loc_0_C89
;if(I<<=M)

Questo ciclo continua finché non trova un elemento >>M, nel disegno finché non trova una casella nera (a dire il vero c'è anche l'eventualità che esca perché I ha raggiunto J, ma di questo ce ne preoccupiamo dopo). Faccio notare che M essendo <<=M punta in realtà ad una casella bianca.

          I             M                       J

loc_0_CDE: 
mov ax, [bp+I]
cmp [bp+J], ax
jbe loc_0_C8F
; if i>=j <--esci dal ciclo se i ha raggiunto j
push [bp+segM]
push [bp+M]
push [bp+segJ]
push [bp+J]
call [bp+compare]
; compare(J,M)
add sp, 8
or ax, ax
jg loc_0_D33
<--continua se J>>M

...
loc_0_D33: ; CODE XRE
mov ax, [bp+size]
sub [bp+J], ax
; j-- <--fa puntare J all'elemento precedente
jmp short loc_0_CDE
<--torna all'inizio del ciclo

Questo ciclo lavora dall'altra parte finché non trova un "bianco":

          I             M               J        

A questo punto, se non ha finito l'array, si troverà con un bianco dove serve un nero, e viceversa e quindi occorrerà scambiarli: 

push [bp+size]
push [bp+segJ]
push [bp+J]
push [bp+segI]
push [bp+I]
call [bp+xchg]
; xchg(I,J)
add sp, 0Ah
mov ax, [bp+J]
mov dx, [bp+segJ]
cmp [bp+M], ax          
jnz loc_0_D21
cmp [bp+segM], dx
jz loc_0_D24
; if(J==M)

loc_0_D21:
jmp loc_0_C8F
<--ricomincia a percorrere l'array con I
; --------------------------------------------------------
loc_0_D24:
mov ax, [bp+I]
<--Abbiamo detto che *M è bianco, quindi se in questo scambio l'avessimo spostato
mov dx, [bp+segI]
mov [bp+M], ax
mov [bp+segM], dx
; M=I<--Dovremmo sistemare il puntatore
jmp loc_0_C8F

Quindi queste operazioni continueranno ciclicamente, e quando I avrà raggiunto J l'array sarà diviso in due porzioni: 

I J

mov ax, [bp+I]
cmp [bp+J], ax
ja loc_0_CCA
; if i<j
mov ax, [bp+L]
<--qui I==J ed essi puntano al primo elemento della parte nera
mov cx, [bp+I]
sub ax, cx    
; L-I
sub cx, [bp+F]
; I-F
cmp ax, cx 
jnb loc_0_CA9
<--se I si trova nella prima metà
jmp loc_0_D3B
<--se I si trova nella seconda metà

loc_0_CA9:
;la parte bianca è più corta 
mov ax, [bp+F] 
mov dx, [bp+segF]
mov [bp+I], ax       
mov [bp+segI], dx
; i=f  <--sposta I all'inizio 
mov ax, [bp+J]
mov dx, [bp+segJ]
mov [bp+F], ax
; f=j <--Sposta il proprio limite sinistro a J (inizio parte nera)
mov [bp+segF], dx
mov ax, [bp+size]
sub [bp+J], ax
;J-- <--fa puntare J all'ultimo elemento della parte bianca
jmp loc_0_D56

...
loc_0_D3B:
;la parte nera è più corta
mov ax, [bp+L]
mov dx, [bp+segL]
mov [bp+J], ax 
mov [bp+segJ], dx
; j=l <--Sposta J all'ultimo posto
mov ax, [bp+I]
mov dx, [bp+segI]
sub ax, [bp+size]
mov [bp+L], ax
mov [bp+segL], dx
;l=i-1 <--Sposta il proprio limite destro a I-1(ultimo bianco)

loc_0_D56: 
mov ax, [bp+I]
cmp [bp+J], ax
jbe loc_0_D74
<--se I==J evita di ordinarlo(tanto ha 1 solo elemento)
push [bp+compare]
; if i<j
push [bp+size]
push [bp+segJ]
push [bp+J]
push [bp+segI]
push ax
call sort
<--Ordina la parte definita sopra
add sp, 0Ch

loc_0_D74:
<--continua a lavorare sull'altra parte
mov ax, [bp+L]
mov cx, [bp+F]
cmp ax, cx
jbe RET
<-- non credo che serva a molto a basso livello
jmp loop
<--ciclo più esterno

A questo punto, avendo separato le due parti in questo modo, è possibile ordinarle separatamente; la procedura sort valuta la lunghezza delle due parti e tiene la più lunga per sè, mentre fa ordinare la più corta da un'altra procedura sort, mediante una chiamata ricorsiva; quindi a forza di dividere in due, si arriverà ad avere parti così piccole da essere ordinate con un solo passaggio, per quanto visto sopra; in questo modo si può dimostrare che si possono ordinare array di lunghezza arbitraria.
Così abbiamo finito l'analisi di quest'algoritmo che se non mi sbaglio si chiama quicksort, ed è uno tra i più usati; adesso vi lascio con i sorgenti di un programmino che ordina la sua riga di comando: funziona esattamente come sort, a parte il fatto che non fa input né da file né da console, perché giustamente non avevo voglia di sbattermi ulteriormente per ottenere un array di stringhe quando c'è argv che è molto comodo; supporta anche le stesse opzioni (/R /+n /?) a patto che le inseriate prima degli argomenti da ordinare, spero di non aver fatto grossi errori come algoritmo,se poi c'è qualche piccolo bug, non è certo un problema :-).

//----------------------------------------Sort.cpp---------------------------------------------

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int n;
typedef int(*COMP)(char **,char**);
char tab[256]={0x0,0x1,0x2,0x3,0x4,0x5,0x6,0x7,0x8,0x9,0x0,0xB,0xC,0xD,0xE,0xF,
0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F,
0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29,0x2A,0x2B,0x2C,0x2D,0x2E,0x2F,
0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3A,0x3B,0x3C,0x3D,0x3E,0x3F,
0x40,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4A,0x4B,0x4C,0x4D,0x4E,0x4F,
0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5A,0x5B,0x5C,0x5D,0x5E,0x5F,
0x40,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4A,0x4B,0x4C,0x4D,0x4E,0x4F,
0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5A,0x7B,0x7C,0x7D,0x7E,0x7F,
0x43,0x55,0x45,0x41,0x41,0x41,0x41,0x43,0x45,0x45,0x45,0x49,0x49,0x49,0x41,0x41,
0x45,0x41,0x41,0x4F,0x4F,0x4F,0x55,0x55,0x59,0x4F,0x55,0x4F,0x24,0x4F,0x9E,0x24,
0x41,0x49,0x4F,0x55,0x4E,0x4E,0xA6,0xA7,0x3F,0xA9,0xAA,0xAB,0xAC,0x21,0x22,0x22,
0xB0,0xB1,0xB2,0xB3,0xB4,0x41,0x41,0x41,0xB8,0xB9,0xBA,0xBB,0xBC,0x24,0x24,0xBF,
0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0x41,0x41,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0x24,
0x44,0x44,0x45,0x45,0x45,0xD5,0x49,0x49,0x49,0xD9,0xDA,0xDB,0xDC,0xDD,0x49,0xDF,
0x4F,0x53,0x4F,0x4F,0x4F,0x4F,0xE6,0xE7,0xE8,0x55,0x55,0x55,0x59,0x59,0xEE,0xEF,
0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF};

int cmp(char *str1,char *str2){
int i=0;
char c1,c2;
do{
c1=tab[str1[i]];
c2=tab[str2[i]];
c1-=c2;
}
while(!c1 && str1[i++]);
return c1;
}


int compare(char **str1,char **str2){
return cmp(*str1,*str2);
}

int comparer(char **str1,char **str2){
return cmp(*str2,*str1);
}

int comparen(char **str1,char **str2){
char *s1,*s2;
int l=strlen(*str1);
s1=&(*str1)[(l<=n)?l:n];
l=strlen(*str2);
s2=&(*str2)[(l<=n)?l:n];
return cmp(s1,s2);
}

int comparenr(char **str1,char **str2){
return comparen(str2,str1);
}

bool sorted(char **a,char **b,COMP compare){
while(a<b && compare(a,a+1)<=0)a++;
return a>=b;
}

void xchg(char **a,char **b){
char *temp=*a;
*a=*b;
*b=temp;
}

void sort(char **f,char **l,COMP compare){
char **m,**i,**j;
while(f<=l){
i=f;
j=l;
m=i+((j-i)>>1);
if(compare(j,m)<0)xchg(j,m);
if(compare(j,i)<0)xchg(j,i);
if(compare(m,i)<0)xchg(m,i);
if((j-i)<=2 || sorted(i,j,compare)) return;
i++;
while(i<j){
while(i<j && compare(i,m)<=0)i++;
while(i<j && compare(j,m)>0)j--;
if(i<j){
xchg(i,j);
if(j==m)m=i;
}
}
if(l-i>=i-f){
i=f;
f=j--;
}
else{
j=l;
l=i-1;
}
if(i<j)sort(i,j,compare);
}
}

void _sort(char **ptr,int l,COMP cmp){
sort(ptr,&ptr[l-1],cmp);
}

main(int argc, char **argv)
{
argv++;
argc--;
if(!argc)return -1;
int flag=0;
COMP c[4]={&compare,&comparer,&comparen,&comparenr};
while(**argv=='/' && --argc){
switch((*argv)[1]){
case '?':
printf("Ordina la sua riga di comando e invia il risultato allo schermo\n\n");
printf("SORT [/R] [/+n] dati da ordinare\n\n");
printf(" /R\t\t\tUsa il tipo di ordinamento inverso:\n");
printf("\t\t\tordina i dati da Z a A e da 9 a 0\n");
printf(" /+n\t\t\tOrdina il file in base al carattere\n");
printf("\t\t\tspecificato nella colonna n\n");
return 0;
case 'r':flag|=1;break;
case 'R':flag|=1;break;
case '+':flag|=2;
n=atoi(*argv+2);
n--;
if(n>=0)break;
default:printf("SORT: Opzione non valida");
return -1;
}
argv++;
}
_sort(argv,argc,c[flag]);
for (int i=0;i<argc;i++)printf("%s\n",argv[i]);
}
//-------------------------------------------------------------------------------------------------

 

Ciao a tutti

                                                                        Winebag

Note finali

Spero che il tutorial vi sia piaciuto e che possiate trarre spunto per spingere la vostra curiosità oltre al semplice cracking, magari con qualcosa di un po' più utile di questo.

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.