2012-01-25 11 views
6

Supponiamo Ho una funzione:C: sovrascrivere un'altra funzione byte per byte

int f1(int x){ 
// some more or less complicated operations on x 
return x; 
} 

e che ho un'altra funzione

int f2(int x){ 
// we simply return x 
return x; 
} 

Vorrei essere in grado di fare qualcosa di simile alla seguente :

char* _f1 = (char*)f1; 
char* _f2 = (char*)f2; 
int i; 
for (i=0; i<FUN_LENGTH; ++i){ 
f1[i] = f2[i]; 
} 

Ie Vorrei interpretare f1 e f2 come matrici di byte grezzi e "sovrascrivere f1 byte per byte" e, quindi, sostituirlo con f2.

So che in genere il codice callable è protetto da scrittura, tuttavia, nella mia situazione particolare, è possibile semplicemente sovrascrivere la posizione di memoria in cui si trova f1. Cioè, posso copiare i byte su f1, ma in seguito, se chiamo f1, il tutto si blocca.

Quindi, il mio approccio è possibile in linea di principio? O ci sono alcune questioni relative alla macchina/all'implementazione/che devo prendere in considerazione?

+0

Credo che "il codice chiamabile sia protetto da scrittura" è la risposta al perché non funziona. Dubito che sia il primo a dirlo, ma il codice auto-modificante di solito è una pessima idea o un sintomo di un bug. – DwB

+0

@DwB Come ho accennato nella mia domanda, ho scoperto che * posso * scrivere nella sezione in cui sono memorizzate le funzioni. Solo * chiamando * la variante sovrascritta provoca un arresto anomalo. – phimuemue

+0

, inoltre, considerare che f1 è probabilmente più lungo di f2 ... cioè è composto da più byte –

risposta

8

Sarebbe più facile sostituire i primi pochi byte di f1 con l'istruzione della macchina jump all'inizio di f2. In questo modo, non dovrai affrontare eventuali problemi di rilocalizzazione del codice.

Inoltre, le informazioni sul numero di byte occupati da una funzione (FUN_LENGTH nella domanda) non sono normalmente disponibili in fase di esecuzione. L'utilizzo di un jump eviterebbe anche questo problema.

Per x86, l'opcode di istruzione di salto relativo necessario è E9 (in base a here). Questo è un salto relativo a 32 bit, il che significa che è necessario calcolare l'offset relativo tra f2 e f1. Questo codice potrebbe farlo:

int offset = (int)f2 - ((int)f1 + 5); // 5 bytes for size of instruction 
char *pf1 = (char *)f1; 
pf1[0] = 0xe9; 
pf1[1] = offset & 0xff; 
pf1[2] = (offset >> 8) & 0xff; 
pf1[3] = (offset >> 16) & 0xff; 
pf1[4] = (offset >> 24) & 0xff; 

L'offset viene preso dal fine dell'istruzione JMP, è per questo che v'è 5 aggiunto l'indirizzo del f1 nel calcolo offset.

È consigliabile passare attraverso il risultato con un debugger a livello di assembly per assicurarsi di eseguire i byte corretti. Naturalmente, non tutti gli standard sono conformi, quindi se si rompe si ottiene di mantenere entrambi i pezzi.

+0

Questa sarebbe certamente un'opzione. Sai come farlo all'interno di un programma C? – phimuemue

+0

Certo, prendi l'indirizzo di 'f1' e gettalo su' char * '. Quindi, sovrascrivi i primi pochi byte con un'istruzione macchina appropriata per la tua architettura che provoca un salto a 'f2'. Potrebbe essere possibile utilizzare un indirizzo assoluto o potrebbe essere necessario calcolare un offset di salto relativo e utilizzarlo. Non posso dire con più dettagli senza sapere quale architettura stai usando. –

+1

Di che tipo di CPU stiamo parlando? –

0

La maggior parte delle architetture di memoria impedirà di scrivere sul codice funzione. Crollerà .... Ma alcuni dispositivi incorporati, puoi fare questo genere di cose, ma è pericoloso a meno che tu non sappia che c'è abbastanza spazio, che la chiamata sarà ok, che lo stack sarà ok, ecc. Ecc. ..

Molto probabilmente c'è un modo migliore per risolvere il problema.

1

L'approccio è un comportamento non definito per lo standard C.

E su molti sistemi operativi (ad es.Linux), il tuo esempio si bloccherà: il codice della funzione è all'interno del segmento di sola lettura .text (e sezione) dell'eseguibile ELF, e tale segmento è (sort-of) mmap -ed di sola lettura da execve (o da dlopen o da il linker dinamico), quindi non puoi scrivere al suo interno.

+1

Sì, e non solo perché tenta di modificare il codice. La conversione di un puntatore a funzione in 'char *' ha un comportamento indefinito. –

+0

Tuttavia, il puntatore della funzione di fusione di 'void *' è consentito nell'ultimo standard Posix (ma non nello standard C, e alcune architetture bizzarre hanno dimensioni diverse per il puntatore alla funzione e il puntatore ai dati). –

1

Invece di cercare di sovrascrivere la funzione (che hai già trovato è fragile nel migliore dei casi), mi piacerebbe prendere in considerazione utilizzando un puntatore ad una funzione:

int complex_implementation(int x) { 
    // do complex stuff with x 
    return x; 
} 

int simple_implementation(int x) { 
    return x; 
} 

int (*f1)(int) = complex_implementation; 

usereste questo qualcosa di simile a:

for (int i=0; i<limit; i++) { 
    a = f1(a); 
    if (whatever_condition) 
     f1 = simple_implementation; 
} 

... e dopo l'assegnazione, chiamando f1 sarebbe solo restituire il valore di ingresso.

Chiamare una funzione tramite un puntatore impone un sovraccarico, ma (grazie a ciò è comune nei linguaggi OO) la maggior parte dei compilatori e delle CPU fanno un buon lavoro di minimizzare il sovraccarico.