Pregunta

¿Es posible escribir una función en C que haga lo siguiente?

  1. Asigna un montón de memoria en el montón
  2. escribe el código de máquina en él
  3. Ejecuta las instrucciones de esas máquinas

Por supuesto, tendría que restaurar el estado de la pila a lo que era antes de la ejecución de esas instrucciones de la máquina manualmente, pero quiero saber si esto es posible en primer lugar.

¿Fue útil?

Solución

Es ciertamente posible . Por diversas razones, hemos dedicado mucho esfuerzo en los últimos 30-40 años para intentar que sea lo más difícil posible, pero es posible. En la mayoría de los sistemas ahora, hay mecanismos de hardware y software que intentan proteger el espacio de datos para que no se ejecute.

Sin embargo, los conceptos básicos son bastante sencillos: construyes un fragmento de código y lo ensamblas, ya sea a mano o 4 a través de un compilador. Entonces necesita un fragmento de espacio de código, por lo que inserta el código en su programa

unsigned int prgm[] = { 0x0F, 0xAB, 0x9A ... };  // Random numbers, just as an example

ya que quería usar el montón que necesita para ubicar el espacio

void * myspace ;
if((myspace= malloc(sizeof(prgm))) != NULL) {
     memcpy(myspace, pgrm, sizeof(pgrm));
} else { // allocation error
}

Ahora, lo que necesita es una forma de que el contador de programas apunte a esa porción de datos que también es su porción de código. Aquí es donde necesitas un poco de astucia. Configurar el contador del programa no es gran cosa; eso es solo una instrucción JUMP para su máquina subyacente. Pero, ¿cómo hacer eso?

Una de las formas más fáciles es meterse a propósito con la pila. La pila, de nuevo conceptualmente, se ve algo así (los detalles dependen de su sistema operativo y pares de compiladores, y de su hardware):

    | subroutine return addr |
    | parameters ...         |
    | automatic variables    |

El truco básico aquí es obtener la dirección de tu código en la dirección de devolución; cuando vuelve una rutina, básicamente salta a esa respuesta de retorno. Si puede fingirlo, la PC se configurará en el lugar que desee.

Entonces, lo que necesitas es una rutina, llamémosle " 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
}

¿Funcionará? Bueno, tal vez, dependiendo del hardware y sistema operativo. La mayoría de los sistemas operativos modernos protegerán el montón (a través de la asignación de memoria o similar) de la PC que se mueve en él. Esto es algo útil por motivos de seguridad, ya que igualmente no le permitirá tomar ese tipo de control completo.

Otros consejos

Esto es muy similar a esta pregunta :)

Lea el código de llamada almacenado en el montón de vc ++ . En posix, mprotect parece ser apropiado (mire 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

Sin embargo, dice:

  

Si PROT_EXEC tiene algún efecto diferente de PROT_READ depende de la arquitectura y de la versión del kernel. En algunas arquitecturas de hardware (por ejemplo, i386), PROT_WRITE implica PROT_READ.

Así que mejor, primero compruebe si funciona en su sistema operativo.

RE: restaurando manualmente la pila

Si sigue las convenciones de llamadas utilizadas por su plataforma / compilador dentro del código de máquina que genera, entonces no debería tener que hacer ninguna restauración manual de la pila. El compilador hará eso por ti, cuando lo hagas

* pfunc (args)

debe agregar los pasos de manipulación de pila de llamadas anteriores o posteriores que sean necesarios.

Sin embargo, solo asegúrate de seguir las convenciones correctas dentro del código generado.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top