Menu

Mammon, traslation by Little John, Issue 1 (Oct/Nov 98)

Mammon --- Traslation by Little-John --- Issue 1 (Oct/Nov 98)
"SMC Techniques: The Basics"...................................mammon_
"CAM Tecniche: Le basi"........................................mammon_
Uno dei vantaggi della programmazione in assembler è che hai piene facoltà di controllo sull'applicazione:
la 'ginnastica binaria' del codice di un virus dimostra ciò più di ogni altra cosa. Uno dei "trucchi" utilizzati
dai virus, che ha avuto poi seguito negli schemi di protezione dei programmi, è il codice automodificante
(SMC = self-modifyng code).
In quest'articolo non tratterò di virus polimorfici o di motori di mutazione (mutation engines); non analizzerò
nessuno schema di protezione in particolare, non considererò trucchi anti-debugger/anti-disassembler, e non
affronterò l'argomento della PIQ (Prefetch Instruction Queue, ndt). Questo articolo è solamente una prima
trattazione riguardante il codice automodificante, per coloro a cui il concetto risulta nuovo e da
implementare.
Episodio 1: Cambiamento di opcode (opcode alteration)
-----------------------------------------------------
Una delle forme più pure del codice automodificante è il cambiare il valore di una istruzione  prima che sia
eseguita... a volte come il risultato di una comparazione, e a volte per  nascondere il codice da occhi curiosi.
Questa tecnica segue essenzialmente questo schema:
mov reg1, codice-da-sostituire
mov [indir-su-cui-scrivere], reg1
in cui 'reg1' può essere qualsiasi registro, e '[indir-su-cui-scrivere]' dovrebbe essere un   puntatore
all'indirizzo da cambiare. Nota che il 'codice-da-sostituire' dovrebbe essere una  istruzione in formato
esadecimale, ma posizionando il codice da qualche altra parte nel  programma -- in una subroutine non
chiamata, o in un segmento diverso -- è possibile  semplicemente trasferire il codice compilato da una
locazione ad un'altra attraverso  l'indirizzamento indiretto, come segue:
call changer
mov dx, offset [string]     ;questo sarà eseguito ma ignorato
label:    mov ah, 09                  ;questo non sarà mai eseguito
int 21h                     ;questo chiuderà il programma
....
changer:  mov di, offset to_write     ;carica l'indirizzo del codice da scrivere in DI
mov byte ptr [label], [di]  ;scrivi il codice nella locazione 'label'
ret                         ;ritorna dalla chiamata
to_write: mov ah, 4Ch                 ;codice di fine programma (int 21, ah=4c ndt)
questa piccola routine farà chiudere il programma, anche se in un disassembler essa all'inizio sembra
essere una semplice routine di scrittura di stringa. Nota che combinando l'indirizzamento con dei loops,
intere subroutine -- anche programmi -- possono essere sovrascritti, e il codice da scrivere -- che può essere
presente nel programma come dati -- può essere criptato con un semplice XOR per farlo sfuggire da un
disassembler.
Il seguente è un programma in assembler per dimostrare il cambiamento dal "vivo" del codice; esso chiede
all'utente una password, poi cambia la stringa da scrivere a seconda che la password sia corretta o meno.
.286
.model small
.stack 200h
.DATA
MaxKbLength  db 05h
KbLength     db 00h
KbBuffer     dd 00h
szGuessIt        db     'Care to guess the super-secret password?',0Dh,0Ah,'$'
szString1        db     'Congratulations! You solved it!',0Dh,0Ah, '$'
szString2        db     'Ah, damn, too bad eh?',0Dh,0Ah,'$'
secret_word      db     "this"
.CODE
start:
mov     ax,@data                ; setta il registro di segmento
mov     ds, ax
1000
mov     es, ax
call Query                      ; chiede all'utente la password
mov     ah, 0Ah                 ; funzione DOS 'Ricevi input dall'utente'
mov     dx, offset MaxKbLength  ; inizio del buffer
int     21h
call Compare                    ; confronta la password e cambia il codice
exit:
mov ah,4ch                      ; funzione 'Chiudi al DOS'
int 21h
Query            proc
mov  dx, offset szGuessIt       ; stringa di richiesta password
mov  ah, 09h                    ; funzione 'visualizza stringa'
int  21h
ret
Query            endp
Reply            proc
PatchSpot:
mov  dx, offset szString2       ; stringa 'hai fallito'
mov  ah, 09h                    ; funzione 'visualizza stringa'
int  21h
ret
Reply            endp
Compare            proc
mov     cx, 4                   ; num. di bytes nella password
mov     si, offset KbBuffer     ; inizio del buffer di input della password
mov     di, offset secret_word  ; indirizzo della password corretta
rep cmpsb                       ; confronto password
or cx, cx                       ; sono uguali?
jnz     bad_guess               ; no, non applicare il patch
mov word ptr cs:PatchSpot[1], offset szString1  ;cambia in GoodString
bad_guess:
call Reply                      ; output della stringa per visualizzare il risultato
ret
Compare            endp
end     start
Episodio 2: Encryption (la traduzione di questo termine è proprio brutta, encriptazione, quindi ho preferito
lasciare l'originale inglese)
-------------------------------------------------
Senza dubbio l'encryption è la forma più comune di codice automodificante utilizzato oggigiorno. E' utilizzata
dai packers e dagli exe-encriptors o per comprimere o per nascondere codice, dai virus per rendere oscuri i
propri contenuti, dagli schemi di protezione per nascondere dati. La forma base dell'encription può essere:
mov reg1, indir-da-sovrascrivere
mov reg2, [reg1]
manipola reg2
mov [reg1], reg2
dove 'reg1' sarebbe il registro contenente l'indirizzo (l'offset) della locazione da sovrascrivere, e 'reg2' un
registro temporaneo che carica i contenuti del primo e poi li modifica attraverso operazioni matematiche
(ROL) oppure logiche (XOR). L'indirizzo da cambiare è caricato in reg1, il suo contenuto è poi modificato
all'interno di reg2, ed infine riscritto nella locazione originale ancora contenuta in reg1.
Il programma della sezione precedente può essere modificato in modo tale che esso decripti la password
sovrascrivendola (in tal modo questa rimane decriptata finchè il programma non è terminato) prima
cambiando la 'parola segreta' come segue:
secret_word      db     06Ch, 04Dh, 082h, 0D0h
e poi cambiando la routine di confronto, Compare, per cambiare la locazione della 'secret_word' nel
segmento dati:
magic_key        db     18h, 25h, 0EBh, 0A3h ;non molto sicura!
Compare            proc           ;Passo 1: decripta la password
mov     al, [magic_key]              ; metti il byte1 della maschera XOR in al
mov     bl, [secret_word]            ; metti il byte1 della password in bl
xor     al, bl
mov     byte ptr secret_word, al     ; cambia il byte1 della password
mov     al, [magic_key+1]            ; metti il byte2 della maschera XOR in al
mov     bl, [secret_word+1]          ; metti il byte2 della password in bl
xor     al, bl
mov     byte ptr secret_word[1], al  ; cambia il byte2 della password
mov     al, [magic_key+2]            ; metti il byt
1000
e3 della maschera XOR in al
mov     bl, [secret_word+2]          ; metti il byte3 della password in bl
xor     al, bl
mov     byte ptr secret_word[2], al  ; cambia il byte3 della password
mov     al, [magic_key+3]            ; metti il byte4 della maschera XOR in al
mov     bl, [secret_word+3]          ; metti il byte4 della password in bl
xor     al, bl
mov     byte ptr secret_word[3], al  ; cambia il byte4 della password
mov     cx, 4          ;Passo 2: Comfonta le passwords...nessun
mov     si,offset KbBuffer
mov     di, offset secret_word
rep     cmpsb
or      cx, cx
jnz     bad_guess
mov     word ptr cs:PatchSpot[1], offset szString1
bad_guess:
call Reply
ret
Compare            endp
Nota l'aggiunta della locazione della 'magic_key' (chiave magica) che contiene la maschera XOR per la
password. Tutto ciò potrebbe essere stato eseguito in maniera più sofisticata con un loop, ma con soli 4 byte
il codice sopra velocizza il tempo di debugging (e, inoltre, il tempo di scrittura della articolo ( e della
traduzione, ndt !)). Nota come la password è caricata, XORata, e riscritta un byte la volta; utilizzando codice a
32-bit, l'intera password (dword) può esser scritta, XORata e riscritta in un sol colpo.
Episodio 3: Giocherellando con lo stack
---------------------------------------
Questo è un trucco che ho imparato mentre decompilavo del codice di SunTzu. Ciò che accade qui è
abbastanza interessante: lo stack è spostato nel segmento codice del programma, cosicchè il top dello stack
punta al primo indirizzo da essere modificato (che, inoltre, dovrebbe essere uno vicinissimo alla fine del
programma per il modo in cui lo stack funziona); il byte a questo indirizzo è POPato in un registro,
manipolato, e PUSHato indietro nella sua locazione originale. Lo stack pointer (SP) è poi decrementato in
modo che l'indirizzo successivo da essere modificato (1 byte più basso in memoria) è ora nel top dello stack.
In più, i byte sono XORati con una porzione del codice stesso del programma, anche per camuffare il valore
della maschera XOR. Nel codice seguente, ho scelto di utilizzare i byte dallo Start: (200h quando è compilato)
fino a -- ma non includendolo -- Exit: (214h quando è compilato; Exit-1=213h).
Comunque, come nel codice originale di SunTzu ho mantenuto la sequenza "inversa" della maschera di XOR
sicchè il byte 213h è il primo byte della maschera di XOR, e il byte 200h ne è l'ultimo. Dopo alcuni
esperimenti ho capito che questo era il modo più facile per sincronizzare una patch -- o un editor
esadecimale -- al codice che manipola lo stack; dal momento che lo stack si muove all'indietro (uno stack
che si sposta in avanti è più un problema che una soluzione), utilizzando una maschera XOR "inversa" che
permetta a tutti e due i puntatori di file in un patcher di essere INCati o DECati in sincronia.
Come mai questa è una issue? A differenza dei due esempi precedenti, la seguente non contiene una
versione criptata del codice da modificare. Questa contiene giusto il code di origine che, quando compilato,
risulta essere nei byte non criptati che sono elaborati attraverso la routine di XOR, criptati, ed infine eseguiti
(che, se hai seguito il discorso, si dimostrerà subito di non esser buono... in ogni caso è un ottimo metodo
per far crashare la VM di DOS (Virtual Machine, ndt)).
Una volta che il programma è compilato bisogna o cambiare i byte da decriptare a mano, o scrivere un
patcher che faccia ciò per te. La prima è più conveniente, l'ultima è più sicura e diventa una necessità se hai
intenzione di conservare il codice. Nell'esempio seguente ho incluso 2 CCh (int 3) nel codice prima e dopo la
fine dei byte da decriptare; un patcher deve solo ricercare questi due valori, contare i byte nel mezzo, ed
infine eseguire lo XOR con i byte tra 200h e 213h.
Ancora una volta, questo esempio è la continuazione di quello prece
1000
dente. In questo ho scritto una routine
per decriptare per intero la  routine 'Compare' della sezione precedente XORandola con i byte compresi tra
'Start' e 'Exit'. Ciò è eseguito settando il segmento di stack come segmento di codice, poi settando il
puntatore di stack uguale all'ultimo indirizzo di codice (il più alto) da modificare. Un byte è POPato dallo
stack (es. la sua locazione originale), XORato, e PUSHato indietro nella sua posizione originale. Il byte
successivo è caricato decrementando il puntatore di stack. Quando tutto il codice è decriptato, il controllo è
restituito alla appena decriptata routine 'Compare' e l'esecuzione continua normalmente.
magic_key        db     18h, 25h, 0EBh, 0A3h
Compare            proc
mov cx, offset EndPatch[1]    ;inizio dell'indiriz-da-sovrascriv + 1
sub cx, offset patch_pwd      ;fine dell'indiriz-da-sovrascriv
mov ax, cs
mov dx, ss                    ;salva lo stack segment -- importante!
mov ss, ax                    ;setta lo stack segment come code segment
mov bx, sp                    ;salva lo stack pointer
mov sp, offset EndPatch       ;inizio dell'indiriz-da-sovrascriv
mov si, offset Exit-1         ;inizio dell'indiriz della maschera XOR
XorLoop:
pop ax                        ;prendi il byte-da-patchare in AL
xor al, [si]                  ;XORa al con la XorMask
push ax                       ;scrivi il byte-to-patch in memoria
dec sp                        ;carica il successivo byte-da-patchare
dec si                        ;carica il successivo byte della maschera XOR
cmp si, offset Start          ;fine della maschera XOR?
jae GoLoop                    ;Se No, continua
mov si, offset Exit-1         ;reinizializza la maschera XOR
GoLoop:
loop XorLoop                  ;XORa il byte successivo
mov sp, bx                    ;ripristina lo stack pointer
mov ss, dx                    ;ripristina lo stack segment
jmp    patch_pwd
db     0CCh,0CCh              ;Identifcation mark: START
patch_pwd:                             ;Nessun cambiamento da qui
mov     al, [magic_key]
mov     bl, [secret_word]
xor     al, bl
mov     byte ptr secret_word, al
mov     al, [magic_key+1]
mov     bl, [secret_word+1]
xor     al, bl
mov     byte ptr secret_word[1], al
mov     al, [magic_key+2]
mov     bl, [secret_word+2]
xor     al, bl
mov     byte ptr secret_word[2], al
mov     al, [magic_key+3]
mov     bl, [secret_word+3]
xor     al, bl
mov     byte ptr secret_word[3], al
mov     cx, 4
mov     si, offset KbBuffer
mov     di, offset secret_word
rep cmpsb
or cx, cx
jnz     bad_guess
mov word ptr cs:PatchSpot[1], offset szString1
bad_guess:
call Reply
ret
Compare            endp
EndPatch:
db 0CCh, 0CCh                  ;Identification Mark: END
Questo genere di programmi è davvero difficile da debuggare. Per testarlo, ho sostituito 'xor al, [si]' prima
con 'xor al,00', che non encripta ed è utile per cercercare eventuali errori nel codice, e poi con 'xor al, EBh',
che mi ha permesso di verificare che venivano criptati i byte corretti (non fa mai male dare una controllatina,
dopotutto).
Episodio 4: Conclusione
------------------------
Tutto ciò dovrebbe dare le basi sul codice automodificante. Ogni programma che utilizza tali tecniche sarà
pieno di insidie.
La cosa più importante è avere il programma completamente funzionante prima di iniziare a sovrascrivere
parti del suo codice. Inoltre, crea sempre una applicazione che esegua l'inverso di ogni routine di
decriptazione/criptazione -- non solo per velocizzare la compilazione e il test automatizzando l'encrip
b2c
tazione
di codice che sarà decriptato durante l'esecuzione, ma anche per disporre di un buono strumento per
controlli utilizzando un disassemblatore (es. encripta il codice, dissasemblalo, decripta il codice,
disassemblalo, confrontalo). Infatti, è una buona idea mantenere la porzione di codice automodificante del
tuo programma in un eseguibile diverso e testarlo sulla versione definitiva, finchè tutti i bug sono eliminati
dalla routine di decriptazione, e solo allora aggiungi la routine di decriptazione al codice finale. I segni di
riconoscimento CCh (codemarks?) sono anch'essi estremamente utili.
Infine, esegui il debug con il debug.com per applicazioni DOS -- il debugger è veloce, piccolo, e se crasha
hai solo perso una finestra di DOS.
Esempi più complessi di codice automodificante può esser trovato nel codice di Dark Angel, il motore
Rhince, o in qualsiasi motore di mutazione utilizzato nei virus polimorfici. Si ringrazia Sun-Tzu per la tecnica
dello stack usata nella su applicazione ghf-crackme.
----------------------------------------------------------------------------------------------
----------------------------------------------------------------------------------------------
**********************************************
****** Little-John Little-Addiction ******
******     Start  ******
**********************************************
**********************************************
Le tecniche per scrivere codice automodificante sono molto utili, specialmente quando sono poco leggibili o
interpretabili con un disassemblatore. Una maniera non contemplata da +mammon in questo suo articolo
riguarda l'uso dell'istruzione STOSB/W. Guardate queste 13 righe di codice:
1 mov di, cs:[offset_locazione_da_sovrascrivere]
2 push ds  ; salviamo DS nello stack
3 shl ds,4 ; operiamo su DS in modo tale da farlo diventare un op-code valido
4 push cx  ; questo valore sarà recuperato nell'istruzione #11
5 mov ds, cs:[offset_locazione_da_cui_attingere]
6 cld
7 mov ax,ds ; ax = istruzione da sovrascrivere, di = locazione da sovrascrivere
8 STOSW  ; scrive ax in [di] ed incrementa di
9 mov ax,bx+ds ; sposta in ax il valore bx+ds
10 STOSW  ; scrive ax in [di] ed incrementa di
11 pop ax  ; ax=cx
12 STOSW  ; scrive ax in [di] ed incrementa di
13 pop ds  ; recuperiamo ds
...
La 'locazione_da_sovrascrivere' conterrà quindi le nuove istruzioni passate volta per volta da ax. Ho ripetuto
per 3 volte l'istruzione STOSW per far notare come il valore in ax può esser caricato in diversi modi a proprio
piacimento.
**********************************************
****** Little-John Little-Addiction ******
******     End   ******
**********************************************
**********************************************

Top: Sicurezza su Internet - Back
Previous: Reversing BO Server - Up: Sicurezza su Internet - Next: The Italian CRaCKiNG Encyclopedia vol. 2

This page last updated: Friday 08 November 2002 at 3:59pm

Please send any comments on this page to Astalalista.

Labelled with ICRA

This page is powered by Copyright Button(TM).
Click here to read how this page is protected by copyright laws.

Use this button to
NOT browser button


begin banner Exchange zone

Member of BannerPower Rotation System

Home Main Portal Translate Tell A Friend Create Page Create Site Search Site Search WWW E-mail Login Insert A Link Free for All Free Dating Forum disabled-come soon Guestbook Links Vote For Us disabled-come soon Discount Window Shades | Jewelry Directory | Shades | Beaded Necklace | Roman Shades