Tipo di programma automodificante in C [duplicato]
-
03-07-2019 - |
Domanda
È possibile scrivere una funzione C che procede come segue?
- Alloca un mucchio di memoria nell'heap
- Scrive il codice macchina in esso
- Esegue le istruzioni di tali macchine
Naturalmente, dovrei ripristinare lo stato dello stack su quello che era prima dell'esecuzione manuale di quelle istruzioni della macchina, ma voglio sapere se questo è fattibile in primo luogo.
Soluzione
È certamente possibile . Per vari motivi, negli ultimi 30-40 anni abbiamo lavorato molto per renderlo il più difficile possibile, ma è possibile. Nella maggior parte dei sistemi ora esistono meccanismi hardware e software che tentano di proteggere l'esecuzione dello spazio dati.
Le basi, tuttavia, sono abbastanza semplici: costruisci un pezzo di codice e lo assembli, a mano o 4 tramite un compilatore. È quindi necessario un frammento di spazio del codice, quindi inserire il codice nel programma
unsigned int prgm[] = { 0x0F, 0xAB, 0x9A ... }; // Random numbers, just as an example
poiché volevi usare heap devi mallocare lo spazio
void * myspace ;
if((myspace= malloc(sizeof(prgm))) != NULL) {
memcpy(myspace, pgrm, sizeof(pgrm));
} else { // allocation error
}
Ora, ciò di cui hai bisogno è un modo per fare in modo che il contatore programmi punti a quel blocco di dati che è anche il tuo blocco di codice. Ecco dove hai bisogno di un po 'di astuzia. L'impostazione del contatore del programma non è un grosso problema; questa è solo un'istruzione JUMP per la tua macchina sottostante. Ma come farlo?
Uno dei modi più semplici è fare scherzi intenzionali con lo stack. Lo stack, ancora una volta concettualmente, assomiglia a questo (i dettagli dipendono sia dal sistema operativo che dalle coppie del compilatore e dall'hardware):
| subroutine return addr | | parameters ... | | automatic variables |
Il trucco di base qui è ottenere di nascosto l'indirizzo del tuo codice nell'indirizzo di ritorno; quando una routine ritorna, sostanzialmente passa a quel ritorno addrfess. Se riesci a simularlo, il PC verrà impostato dove preferisci.
Quindi, ciò di cui hai bisogno è una routine, chiamiamola " goThere () "
void goThere(void * addr){
int a ; // observe above; this is the first space
// on the stack following the parameters
int * pa; // so we use it's address
pa = (&a - (sizeof(int)+(2*sizeof(void*))) ; // so use the address
// but back up by the size of an int, the pointer on the
// stack, and the return address
// Now 'pa' points to the routine's return add on the stack.
*pa = addr; // sneak the address of the new code into return addr
return ; // and return, tricking it into "returning"
// to the address of your special code block
}
Funzionerà? Bene, forse, a seconda dell'hardware e del sistema operativo. La maggior parte dei sistemi operativi moderni proteggerà l'heap (tramite mappatura della memoria o simili) dal PC che si sposta al suo interno. Questa è una cosa utile per motivi di sicurezza, perché non ti lascerebbe prendere quel tipo di controllo completo.
Altri suggerimenti
Questo è molto simile a questa domanda :)
Leggi il codice di chiamata memorizzato nell'heap da vc ++ . Su posix, mprotect
sembra appropriato (guarda in man mprotect
):
char *mem = malloc(sizeof(code));
mprotect(mem, sizeof(code), PROT_READ|PROT_WRITE|PROT_EXEC);
memcpy(mem, code, sizeof(code));
// now arrange some code to jump to mem. But read the notes here on casting
// from void* to a function pointer:
// http://www.opengroup.org/onlinepubs/009695399/functions/dlsym.html
Tuttavia, dice:
Il fatto che PROT_EXEC abbia effetti diversi da PROT_READ dipende dall'architettura e dalla versione del kernel. Su alcune architetture hardware (ad es. I386), PROT_WRITE implica PROT_READ.
Quindi, per prima cosa, controlla se sul tuo sistema operativo funziona.
RE: ripristino manuale dello stack
Se segui le convenzioni di chiamata utilizzate dalla tua piattaforma / compilatore all'interno del codice macchina che generi, non dovresti fare alcun ripristino manuale dello stack. Il compilatore lo farà per te, quando lo fai
* pfunc (args)
dovrebbe aggiungere tutti gli opportuni passaggi di manipolazione dello stack pre o post chiamata necessari.
Assicurati solo di seguire le giuste convenzioni all'interno del codice generato, tuttavia.