Indietro

ⓘ Shellcode




                                     

ⓘ Shellcode

In informatica uno shellcode è un programma in linguaggio assembly che tradizionalmente esegue una shell, come la shell Unix /bin/sh oppure la shell command.com sui sistemi operativi DOS e Microsoft Windows. Uno shellcode può essere utilizzato per sfruttare un bug mediante un exploit, consentendo ad un hacker o un cracker di acquisire laccesso alla riga di comando di un computer, o più in generale di eseguire codice arbitrario.

                                     

1.1. Descrizione Tipi di shellcode

Esistono due diversi tipi di shellcode, locale e remoto. La distinzione dipende dal tipo di controllo che lesecuzione dello shellcode fornisce sulla macchina bersaglio, che può essere locale o remoto se avviene attraverso la rete.

                                     

1.2. Descrizione Locale

Uno shellcode locale è utilizzato da un attaccante che possiede un accesso limitato alla macchina, ma che sfruttando una vulnerabilità di un processo con privilegi più elevati, per esempio un buffer overflow, può ottenerne gli stessi privilegi se lesecuzione della shellcode va a buon fine.

                                     

1.3. Descrizione Remoto

Lo shellcode remoto è invece utilizzato quando un attaccante vuole sfruttare una vulnerabilità di un processo di unaltra macchina sulla rete locale o su una rete intranet. Se lo Shellcode è eseguito correttamente, questo ritorna il controllo della macchina bersaglio attraverso la rete. Gli shellcode remoti utilizzano normalmente lo standard socket TCP/IP per consentire laccesso alla shell della macchina bersaglio. Si possono classificare ulteriori distinzioni in base al metodo con cui la connessione viene stabilita. Se è lo shellcode stesso che può stabilire la connessione, questo viene chiamato "reverse shell" o connect-back shellcode, perché lo shellcode in esecuzione sulla macchina remota si connette alla macchina dellattaccante. Se invece lattaccante ha bisogno di creare la connessione, lo shellcode viene chiamato bindshell, in quanto lo shellcode esegue il bind su una determinata porta, che verrà utilizzata dallattaccante per connettersi e controllare la macchina bersaglio. Un terzo tipo di shellcode, meno comune, è il socket-reuse shellcode. Questo tipo di shellcode è di solito utilizzato quando un exploit stabilisce una connessione al processo vulnerabile che non viene chiusa prima che lo shellcode venga eseguito. Lo shellcode può riutilizzare questa connessione per comunicare con lattaccante. Il Socket re-use shellcode è di più complessa realizzazione, perché lo shellcode deve identificare quale connessione può utilizzare fra le possibili aperte sulla macchina. Si può utilizzare un firewall per identificare le connessioni in uscita effettuate da una connect-back shellcode e il tentativo di connessione in ingresso da parte di una bindshell. Il firewall può fornire una protezione aggiuntiva contro un attaccante, anche se il sistema è vulnerabile, impedendo in maniera preventiva di ottenere laccesso alla shell creata dallesecuzione dello shellcode. Questa è una delle ragioni del perché a volte viene utilizzata una socket re-using shellcode, perché non creando nuove connessioni risulta più difficile da identificare e bloccare.



                                     

1.4. Descrizione Download and execute

Download and execute è un tipo di shellcode remoto che effettua un download ed esegue una qualche forma di malware sul sistema bersaglio. Questo tipo di shellcode non crea una shell, ma istruisce la macchina di scaricare un certo file eseguibile dalla rete, salvarlo su disco e poi eseguirlo. Al giorno doggi, è comunemente utilizzato negli attacchi drive-by download, quando una vittima visita un sito malevolo che cerca di avviare un download e di eseguire una shellcode per installare software sulla macchina vittima. Una variazione di questo tipo di shellcode è" download and loads a library”. I vantaggi di questa tecnica è che il codice dello shellcode può essere più piccolo, non richiede la creazione di un nuovo processo sulla macchina bersaglio e che lo shellcode non ha bisogno di implementare il codice per la pulizia del processo bersagliato, ma questo può essere effettuato da una libreria caricata allinterno del processo.

                                     

1.5. Descrizione Staged

Quando la quantità di dati che un attaccante può iniettare in un processo bersaglio è troppo limitata per una corretta esecuzione dello shellcode, è possibile eseguirlo in più fasi. Prima, un piccolo pezzo di shellcode fase 1 viene eseguita. Questo codice scarica una fetta più grande dello shellcode fase 2 nella memoria del processo e lo manda in esecuzione.

                                     

1.6. Descrizione Egg-hunt

Egg-hunt è un altro tipo di shellcode a fasi. Viene utilizzato quando un attaccante ha la possibilità di inserire uno shellcode grande in un processo, ma non può determinare in che posizione della memoria verrà inserito. Quindi viene inserito nel processo un piccolo egg-hunt in una locazione predicibile e poi eseguito. Il codice cerca lo spazio di memoria per un shellcode più grande the egg e lo esegue.

                                     

1.7. Descrizione Omelette

Questo tipo di shellcode è simile al egg-hunt, ma effettua una ricerca di più piccoli blocchi eggs ricombinandoli in uno più grande omelette che viene eseguito successivamente. Tale tecnica viene utilizzata quando un attaccante è limitato, per qualche ragione, allinserimento di piccoli blocchi di dati allinterno del processo.

                                     

1.8. Descrizione Strategia di esecuzione di uno Shellcode

Un exploit comunemente inserisce uno shellcode allinterno del processo bersaglio prima o nello stesso istante in cui si verifica un exploit di una vulnerabilità, per ottenere il controllo sul program counter. Il program counter viene indirizzato per puntare allo shellcode da mandare in esecuzione. Linserimento dello shellcode è spesso fatta immagazzinando il codice nei dati inviati sulla rete al processo vulnerabile, rendendolo disponibile in un file che viene letto dal processo o attraverso la riga di comando o variabili di ambiente nel caso si tratti di exploit locali.

                                     

1.9. Descrizione Shellcode encoding

Dato che molti processi filtrano o limitano i dati che possono essere inseriti, spesso lo shellcode deve essere scritto per superare queste restrizioni, rendendo il codice piccolo, privo di null o caratteri alfanumerici. Sono state trovate diverse soluzioni per aggirare queste restrizioni:

  • Codice automodificante che modifica il numero di bytes del suo codice prima di eseguirli, in modo da ricreare bytes che non è possibile inserire normalmente allinterno del processo.
  • Design e ottimizzazioni di implementazione per ridurre la dimensione dello shellcode.
  • Modifiche di implementazione per aggirare limitazioni nel range dei bytes utilizzati nello shellcode.

Da quando gli strumenti di intrusion detection possono identificare la firma di semplici shellcode inviati attraverso la rete, questi vengono codificati e resi self-decrypting o polimorfici per evitare di essere riconosciuti.



                                     

1.10. Descrizione Percent encoding

Gli exploit che hanno come obiettivo i browser, codificano comunemente lo shellcode in una stringa JavaScript usando la notazione di percent-encoding o URL-encoding, tramite caratteri di escape" \uXXXX” o mediante entity. Alcuni exploit fanno unulteriore offuscamento dello shellcode codificato tramite stringhe per evitare di essere rilevati da strumenti di IDS. Per esempio, sul una architettura IA-32, due istruzioni di NOP no-operation prima di essere codificate hanno questa forma.

90 NOP 90 NOP

Vengono codificate in una stringa con il percent-encoding. utilizzando il metodo unescape per la decodifica

unescape"%u9090";

Successivamente viene codificata nella notazione" \uXXXX”:

"\u9090";

E infine nella codifica entity.

"邐"

o

"邐"
                                     

1.11. Descrizione Shellcode Null-Free

Molti shellcode vengono scritti senza utilizzare il bytes null, perché sono progettati per essere inseriti nel processo bersaglio attraverso un stringa null-terminata. Quando una stringa null-terminata viene copiata, la copia includerà il primo carattere null, ma i bytes successivi a questo non verranno processati. Quando lo shellcode che contiene il null viene inserito in questa maniera, verrà inserita solo una parte dello shellcode non rendendolo in grado di eseguire successivamente. Per produrre uno shellcode libero da null partendo da uno che contiene dei bytes null, possono essere sostituite le istruzioni macchina che contengono gli zeri con istruzioni che producono lo stesso effetto ma che sono prive di bytes null. Per esempio su una architettura IA-32 si potrebbe eseguire questa sostituzione:

B8 01000000 MOV EAX,1 // Imposta il registro EAX a 0x000000001

questa istruzione contiene zeri come parte del literal 1 viene espanso come 0x000000001 con queste istruzioni:

33C0 XOR EAX,EAX // Imposta il registro EAX a 0x000000000 40 INC EAX // Incrementa il valore di EAX a 0x00000001

che hanno lo stesso effetto ma richiedono meno bytes per la codifica e sono prive di bytes a null.



                                     

1.12. Descrizione Shellcode alfanumerici e stampabili

In certe circostanze, un processo bersaglio potrebbe filtrare tutti i bytes provenienti dallo shellcode inserito che non sono stampabili o alfanumerici. In queste condizioni, il range di istruzioni che possono essere utilizzate per scrivere uno shellcode diventano molto limitate. Una soluzione a questo problema è stata pubblicata da Rix in Phrack 57 dove viene mostrato come è possibile convertire ogni tipo di codice in uno alfanumerico. Una tecnica molto utilizzata è quella di creare codice automodificante, perché questo permette al codice di modificare i propri bytes per includerne altri che non rientrano fra quelli ammissibili e di espandere il range di istruzioni utilizzabili. Con questo tipo di trucco, un decoder auto-modificante può essere creato inizialmente utilizzando solamente bytes compresi nellintervallo ammissibile. Quando lo shellcode in uscita va in esecuzione, il decoder può modificare il proprio codice per essere in grado di utilizzare ogni istruzione richiesta per consentirne il corretto funzionamento e contemporaneamente continuare a decodificare lo shellcode originale. Dopo avere fatto la decodifica, il decoder trasferisce il controllo allo shellcode, in modo tale che possa essere eseguito normalmente. È stato mostrato come è possibile creare shellcode di arbitraria complessità che somigliano a normale testo inglese.

                                     

1.13. Descrizione Shellcode a prova di caratteri Unicode

Molti programmi moderni utilizzano la codifica di stringhe in formato Unicode per permettere linternalizzazione del testo. Spesso questi programmi convertono le stringhe ASCII in ingresso prima di processarle. Le stringhe Unicode codificate in UTF-16 utilizzano due byte per decodificare ogni carattere o quattro bytes per alcuni caratteri speciali. Quando una stringa ASCII viene convertita in UTF-16, un byte a zero viene inserito dopo ogni bytes della stringa originale. Obscu ha mostrato in Phrack 61 che è possibile scrivere shellcode che possono eseguire correttamente anche dopo questa trasformazione. Esistono programmi in grado di modificare automaticamente ogni shellcode in uno codificato mediante UTF-16 e sono basati sullo stesso principio di un decoder automodificante che decodifica lo shellcode originale.

                                     

1.14. Descrizione Piattaforme

Molti shellcode sono scritti in codice macchina a causa del basso livello a cui la vulnerabilità diventa sfruttabile. Lo shellcode è spesso creato per attaccare una specifica combinazione di processore, sistema operativo e service pack, che vengono chiamati comunemente piattaforma. Per alcuni exploit, a causa dei vincoli imposti dal processo bersaglio, è necessario creare uno shellcode specifico. In ogni caso non è sempre possibile per uno shellocde lavorare per exploit multipli, service pack, sistemi operativi e eventualmente processori. Una versatilità può essere data dalla creazioni di differenti versioni dello shellcode, sulla base delle varie piattaforme da attaccare e creando un header che identifica la versione corretta per la piattaforma in uso. Quando viene eseguito, il codice si comporta differentemente in base alla piattaforma ed è in grado di eseguire la versione corretta dello shellcode.