Copy Link
Add to Bookmark
Report

Mammon --- Traslation by Little-John --- Issue 1 (Oct/Nov 98)

DrWatson's profile picture
Published in 
guide hacker
 · 3 Jun 2018

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.

 
; smc1.asm ===============================
.286
.model small
.stack 200h
.DATA
;buffer for Keyboard Input, formatted for easy reference:
MaxKbLength db 05h
KbLength db 00h
KbBuffer dd 00h

;strings: nota che la password non è criptata, ma potrebbe esserlo
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 ; uguale alla direttiva "assume"
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
; EOF =======================================================================

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 byte3 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
;cambiamento da qui in poi
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 precedente. 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
;compare password
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'encriptazione 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 ******
**********************************************
**********************************************

← previous
next →
loading
sending ...
New to Neperos ? Sign Up for free
download Neperos App from Google Play
install Neperos as PWA

Let's discover also

Recent Articles

Recent Comments

Neperos cookies
This website uses cookies to store your preferences and improve the service. Cookies authorization will allow me and / or my partners to process personal data such as browsing behaviour.

By pressing OK you agree to the Terms of Service and acknowledge the Privacy Policy

By pressing REJECT you will be able to continue to use Neperos (like read articles or write comments) but some important cookies will not be set. This may affect certain features and functions of the platform.
OK
REJECT