Tutti i linguaggi del computer sono essenzialmente istruzioni per una (o più di una) CPU, sotto forma di "fai questo. Ora fai così". Quindi sempre "scendere all'assemblaggio" prima o poi. I linguaggi di alto livello sono framework (scaffold?) Per evitare lunghe ripetizioni del codice assembly e consentire la programmazione in un modo più intuitivo; questo guadagno in termini di comprensibilità, flessibilità e manutenibilità ha un prezzo - in termini di dimensioni, prestazioni e requisiti.
Quando riesci a ottenere il controllo del processo di applicazione, qualunque sia il mezzo (overflow del buffer, ecc.), in pratica hai solo un'area di memoria in cui puoi inserire ciò che vuoi, e questo è per lo più indipendente su come hai ottenuto a questo punto.
Ora, per uno, l'assembly ha la più alta "densità" di istruzioni sulla dimensione del codice, almeno per le piccole dimensioni del codice, quindi è naturalmente la tua prima scelta se vuoi stipare il massimo nell'area che hai appena "conquistato" .
In secondo luogo, e probabilmente più importante, quando irrompi in un'area di memoria, conosci poco o nulla del "contesto" - non hai hai una configurazione del contesto . Ad esempio, in linguaggio C, la tua funzione main()
è in effetti una funzione - ottiene chiamato dal runtime C, che ha già preparato un ambiente per l'esecuzione del programma in, una sorta di sistema di supporto vitale per il tuo codice. Con uno shellcode, non hai questo tipo di supporto e devi essere in grado di fare a meno (cioè assemblare) o crearne uno nuovo (di nuovo in assembly, dal momento che il codice di costruzione del supporto vitale non può aver bisogno di un supporto vitale stesso) .
Quindi in teoria, potresti preparare uno "stub runtime di shellcode" inizializzando un ambiente e "collegando" il tuo codice C al sistema. Una volta ottenuto ciò, è possibile semplicemente allegare un codice shell C al runtime ed essere in grado di riferire le funzioni del sistema operativo per nome. Diversi shellcode hanno uno stub per farlo, ma di solito è scritto in Assembly perché la ragione numero uno continua a essere valida: lo spazio è prezioso.
Gli exploit che consentono l'esecuzione di un binario indipendente (nella forma più elementare e banale, un allegato eseguibile a un'e-mail su cui la vittima farà clic) hanno restrizioni di dimensioni ridotte e (solitamente) nessun requisito di runtime, quindi hanno bisogno di no, e per scopi di manutenzione non lo sono quasi mai, scritti in linguaggi di basso livello.