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 ****** ********************************************** **********************************************