2012-01-25 18 views
6

Supongamos que tengo una función:C: sobrescribir otro byte función a byte

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

y que tengo otra función

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

me gustaría ser capaz de hacer algo como lo siguiente :

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

Ie Me gustaría interpretar f1 y f2 como matrices de bytes sin formato y "sobrescribir f1 byte por byte" y así reemplazarlo por f2.

Sé que el código generalmente invocable está protegido contra escritura, sin embargo, en mi situación particular, puede simplemente sobrescribir la ubicación de la memoria donde se encuentra f1. Es decir, puedo copiar los bytes en f1, pero luego, si llamo a f1, todo falla.

Entonces, ¿mi enfoque es posible en principio? ¿O hay algunas cuestiones dependientes de la máquina/implementación/que debo tener en cuenta?

+0

Creo que "el código invocable está protegido contra escritura" es la respuesta a por qué está fallando. Dudo que sea el primero en decir esto, pero el código de auto modificación suele ser una idea terrible o un síntoma de un error. – DwB

+0

@DwB Como mencioné en mi pregunta, descubrí que * puedo * escribir en la sección donde se almacenan las funciones. Simplemente * llamando * la variante sobrescrita produce un bloqueo. – phimuemue

+0

también, tenga en cuenta que f1 es probablemente más largo que f2 ... es decir, está compuesto por más bytes –

Respuesta

8

Sería más fácil reemplazar los primeros bytes de f1 con una instrucción de máquina jump al comienzo de f2. De esta forma, no tendrá que lidiar con ningún posible problema de reubicación del código.

Además, la información sobre cuántos bytes ocupa una función (FUN_LENGTH en su pregunta) normalmente no está disponible en tiempo de ejecución. Usar un jump evitaría ese problema también.

Para x86, el código de operación de instrucción de salto relativo que necesita es E9 (según here). Este es un salto relativo de 32 bits, lo que significa que debe calcular el desplazamiento relativo entre f2 y f1. Este código podría hacerlo:

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; 

El desplazamiento se toma de la final de la instrucción JMP, así que por eso hay 5 añadieron a la dirección de f1 en el cálculo de compensación.

Es una buena idea revisar el resultado con un depurador de nivel de ensamblaje para asegurarse de que está pulsando los bytes correctos. Por supuesto, esto no es compatible con los estándares, así que si se rompe puede mantener ambas piezas.

+0

Eso sin duda sería una opción. ¿Sabes cómo hacer esto dentro de un programa C? – phimuemue

+0

Claro, tome la dirección de 'f1' y echela a' char * '. A continuación, sobrescriba los primeros bytes con una instrucción de máquina adecuada para su arquitectura que provoque un salto a 'f2'. Puede utilizar una dirección absoluta o puede necesitar calcular una compensación de salto relativa y usarla. No puedo decir con más detalle sin saber qué arquitectura estás usando. –

+1

¿De qué tipo de CPU estamos hablando? –

0

La mayoría de las arquitecturas de memoria le impedirán escribir sobre el código de función. Se bloqueará ... Pero algunos dispositivos integrados, puedes hacer este tipo de cosas, pero es peligroso a menos que sepas que hay suficiente espacio, que la llamada va a estar bien, que la pila va a estar bien, etc., etc. ..

Es muy probable que exista una forma mejor de resolver el problema.

1

Su enfoque es el comportamiento indefinido para el estándar C.

Y en muchos sistemas operativos (p.Linux), su ejemplo se bloqueará: el código de la función está dentro del segmento de solo lectura .text (y sección) del ejecutable ELF, y ese segmento es (tipo de) mmap -ed de solo lectura por execve (o por o por el enlazador dinámico), por lo que no puede escribir dentro de él.

+1

Sí, y no solo porque intenta modificar el código. La conversión de un puntero a 'char *' tiene un comportamiento indefinido. –

+0

Sin embargo, el puntero de función de conversión a 'void *' está permitido en el último estándar de Posix (pero no en el estándar C, y algunas arquitecturas raras tienen diferentes tamaños para el puntero a la función y el puntero a los datos). –

1

lugar de tratar de sobrescribir la función (que ya has encontrado es frágil en el mejor), me gustaría considerar el uso de un puntero a una función:

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

int simple_implementation(int x) { 
    return x; 
} 

int (*f1)(int) = complex_implementation; 

que tendría que utilizar algo como esto:

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

... y después de la asignación, llamando f1 simplemente devolvería el valor de entrada.

Llamar a una función a través de un puntero impone cierta sobrecarga, pero (gracias a que es común en los lenguajes OO) la mayoría de los compiladores y CPU hacen un buen trabajo al minimizar esa sobrecarga.