¿Cómo determinar la longitud de una función?

Considere el siguiente código que toma la función f (), copia la función en su totalidad a un búfer, modifica su código y ejecuta la función alterada. En la práctica, la función original que devuelve el número 22 se clona y modifica para devolver el número 42.

#include  #include  #include  #define ENOUGH 1000 #define MAGICNUMBER 22 #define OTHERMAGICNUMBER 42 int f(void) { return MAGICNUMBER; } int main(void) { int i,k; char buffer[ENOUGH]; /* Pointer to original function f */ int (*srcfptr)(void) = f; /* Pointer to hold the manipulated function */ int (*dstfptr)(void) = (void*)buffer; char* byte; memcpy(dstfptr, srcfptr, ENOUGH); /* Replace magic number inside the function with another */ for (i=0; i < ENOUGH; i++) { byte = ((char*)dstfptr)+i; if (*byte == MAGICNUMBER) { *byte = OTHERMAGICNUMBER; } } k = dstfptr(); /* Prints the other magic number */ printf("Hello %d!\n", k); return 0; } 

El código ahora se basa en simplemente adivinar que la función se ajustará al búfer de 1000 bytes. También viola las reglas al copiar demasiado en el búfer, ya que la función f () probablemente será mucho más corta que 1000 bytes.

Esto nos lleva a la pregunta : ¿Existe un método para averiguar el tamaño de cualquier función dada en C? Algunos métodos incluyen buscar en la salida del enlazador intermedio y adivinar según las instrucciones de la función, pero eso no es suficiente. ¿Hay alguna manera de estar seguro?


Tenga en cuenta: comstack y funciona en mi sistema, pero no cumple con los estándares porque las conversiones entre los punteros de función y el vacío * no están permitidas exactamente:

 $ gcc -Wall -ansi -pedantic fptr.c -o fptr fptr.c: In function 'main': fptr.c:21: warning: ISO C forbids initialization between function pointer and 'void *' fptr.c:23: warning: ISO C forbids passing argument 1 of 'memcpy' between function pointer and 'void *' /usr/include/string.h:44: note: expected 'void * __restrict__' but argument is of type 'int (*)(void)' fptr.c:23: warning: ISO C forbids passing argument 2 of 'memcpy' between function pointer and 'void *' /usr/include/string.h:44: note: expected 'const void * __restrict__' but argument is of type 'int (*)(void)' fptr.c:26: warning: ISO C forbids conversion of function pointer to object pointer type $ ./fptr Hello 42! $ 

Tenga en cuenta que en algunos sistemas no es posible ejecutar desde la memoria de escritura y este código se bloqueará. Se ha probado con gcc 4.4.4 en Linux que se ejecuta en la architecture x86_64.

No puede hacer esto en C. Incluso si conociera la longitud, la dirección de una función es importante, porque las llamadas a funciones y los accesos a ciertos tipos de datos utilizarán el direccionamiento relativo al progtwig-contador . Por lo tanto, una copia de la función ubicada en una dirección diferente no hará lo mismo que la original. Por supuesto, hay muchos otros problemas también.

En el estándar C, no hay noción de introspección o reflexión, por lo tanto, tendría que idear un método usted mismo, como lo ha hecho, sin embargo, existen otros métodos más seguros.

Hay dos maneras:

  1. Desmonte la función (en tiempo de ejecución ) hasta que llegue al RETN / JMP / etc final , mientras contabiliza las tablas de conmutación / salto. Por supuesto, esto requiere un análisis exhaustivo de la función que desmonta (usando un motor como beaEngine ), este es el más confiable, pero es lento y pesado.
  2. Abusar de las unidades de comstackción, esto es muy arriesgado y no infalible, pero si sabe que el comstackdor genera funciones secuencialmente en su unidad de comstackción, puede hacer algo en este sentido:

     void MyFunc() { //... } void MyFuncSentinel() { } //somewhere in code size_t z = (uintptr_t)MyFuncSentinel - (uintptr_t)MyFunc; uint8_t* buf = (uint8_t*)malloc(z); memcpy(buf,(char*)MyFunc,z); 

    esto tendrá un poco de relleno adicional, pero será mínimo (e inalcanzable). Aunque es altamente arriesgado, es mucho más rápido que el método de desassembly.

Nota: ambos métodos requerirán que el código de destino tenga permisos de lectura.


@R .. plantea un muy buen punto, su código no se podrá reubicar a menos que sea PIC o lo vuelva a armar para ajustar las direcciones, etc.

Aquí hay una manera de cumplir con los estándares para lograr el resultado que desea:

 int f(int magicNumber) { return magicNumber; } int main(void) { k = f(OTHERMAGICNUMBER); /* Prints the other magic number */ printf("Hello %d!\n", k); return 0; } 

Ahora, puede que tenga muchos usos de f() todo el lugar sin argumentos y no quiera revisar su código cambiando cada uno, así que puede hacer esto en su lugar

 int f() { return newf(MAGICNUMBER); } int newf(int magicNumber) { return magicNumber; } int main(void) { k = newf(OTHERMAGICNUMBER); /* Prints the other magic number */ printf("Hello %d!\n", k); return 0; } 

No estoy sugiriendo que esta sea una respuesta directa a su problema, pero que lo que está haciendo es tan horrible que necesita repensar su diseño.

Bueno, puede obtener la longitud de una función en tiempo de ejecución utilizando tags:

 int f() { int length; start: length = &&end - &&start + 11; // 11 is the length of function prologue // and epilogue, got with gdb printf("Magic number: %d\n", MagicNumber); end: return length; } 

Después de ejecutar esta función, conocemos su longitud, por lo que podemos realizar el malloc para la longitud correcta, copiar y editar el código y luego ejecutarlo.

 int main() { int (*pointerToF)(), (*newFunc)(), length, i; char *buffer, *byte; length = f(); buffer = malloc(length); if(!buffer) { printf("can't malloc\n"); return 0; } pointerToF = f; newFunc = (void*)buffer; memcpy(newFunc, pointerToF, length); for (i=0; i < length; i++) { byte = ((char*)newFunc)+i; if (*byte == MagicNumber) { *byte = CrackedNumber; } } newFunc(); } 

Sin embargo, ahora hay otro problema mayor, el de @R. mencionado El uso de esta función una vez modificada (correctamente) produce un error de segmentación cuando se llama a printf porque la instrucción de call tiene que especificar un desplazamiento que será incorrecto. Puedes ver esto con gdb , usando disassemble f para ver el código original y el x/15i buffer para ver el editado.
Por cierto, tanto mi código como el tuyo se comstackn sin advertencias pero se bloquean en mi máquina ( gcc 4.4.3 ) al llamar a la función editada.