programacion - ¿Cómo escribir código auto modificador en C?
lenguaje c pdf (9)
Quiero escribir un fragmento de código que se modifique continuamente, incluso si el cambio es insignificante.
Por ejemplo, tal vez algo como
for i in 1 to 100, do begin x := 200 for j in 200 downto 1, do begin do something end end
Supongamos que quiero que mi código cambie después de la primera iteración la línea x := 200
a alguna otra línea x := 199
y luego, después de la siguiente iteración, la cambie a x := 198
y así sucesivamente.
¿Es posible escribir un código así? ¿Necesitaría usar el ensamblaje en línea para eso?
EDITAR: He aquí por qué quiero hacerlo en C:
Este programa se ejecutará en un sistema operativo experimental y no puedo / no sé cómo usar programas compilados desde otros idiomas. La verdadera razón por la que necesito este código es porque este código se ejecuta en un sistema operativo invitado en una máquina virtual. El hipervisor es un traductor binario que está traduciendo trozos de código. El traductor hace algunas optimizaciones. Solo se traducen los trozos de código una vez. La próxima vez que se use el mismo fragmento en el invitado, el traductor usará el resultado traducido previamente. Ahora, si el código se modifica sobre la marcha, el traductor se da cuenta de eso y marca su traducción anterior como obsoleta. Forzando así una re-traducción del mismo código. Esto es lo que quiero lograr, forzar al traductor a hacer muchas traducciones. Normalmente, estos fragmentos son instrucciones entre las instrucciones de bifurcación (como las instrucciones de salto). Simplemente creo que el código de auto-modificación sería una manera fantástica de lograr esto.
Dependiendo de la cantidad de libertad que necesite, puede lograr lo que desea mediante el uso de punteros de función. Usando su pseudocódigo como un punto de inicio, considere el caso en el que queremos modificar esa variable x
de diferentes maneras a medida que cambia el índice de bucle i
. Podríamos hacer algo como esto:
#include <stdio.h>
void multiply_x (int * x, int multiplier)
{
*x *= multiplier;
}
void add_to_x (int * x, int increment)
{
*x += increment;
}
int main (void)
{
int x = 0;
int i;
void (*fp)(int *, int);
for (i = 1; i < 6; ++i) {
fp = (i % 2) ? add_to_x : multiply_x;
fp(&x, i);
printf("%d/n", x);
}
return 0;
}
La salida, cuando compilamos y ejecutamos el programa, es:
1
2
5
20
25
Obviamente, esto solo funcionará si tienes un número finito de cosas que quieres hacer con x
en cada ejecución. Para hacer que los cambios sean persistentes (lo cual es parte de lo que quiere de "auto-modificación"), desearía que la variable de puntero de función sea global o estática. No estoy seguro de poder recomendar este enfoque, porque a menudo hay formas más simples y claras de lograr este tipo de cosas.
En el estándar C11 (lectura n1570 ), no puede escribir código de modificación automática (al menos sin un comportamiento indefinido ). Conceptualmente, al menos, el segmento de código es de solo lectura.
Podría considerar ampliar el código de su programa con plugins usen su enlazador dinámico . Esto requiere funciones específicas del sistema operativo. En POSIX, use dlopen (y probablemente dlsym para obtener punteros de función recién cargados). A continuación, puede sobrescribir los punteros de función con la dirección de los nuevos.
Tal vez podría utilizar alguna biblioteca de JIT-compiling (como libgccjit o asmjit ) para lograr sus objetivos. Obtendrá nuevas direcciones de función y las pondrá en sus punteros de función.
Recuerde que un compilador de C puede generar código de varios tamaños para una llamada de función determinada o un salto, por lo que incluso sobrescribir que de una manera específica de la máquina es frágil.
Es posible que desee considerar escribir una máquina virtual en C, donde puede crear su propio código de auto-modificación.
Si desea escribir ejecutables auto-modificables, mucho depende del sistema operativo al que esté apuntando. Puede acercarse a la solución deseada modificando la imagen del programa en memoria. Para hacerlo, obtendría la dirección en memoria de los bytes del código de su programa. Luego, podría manipular la protección del sistema operativo en este rango de memoria, permitiéndole modificar los bytes sin encontrar una infracción de acceso o '''' ''SIG_SEGV'' ''''. Finalmente, usaría punteros (tal vez punteros '''' ''unsigned char *'' '''', posiblemente '''' ''unsigned long *'' '''' como en máquinas RISC) para modificar los códigos de operación del programa compilado.
Un punto clave es que estará modificando el código de máquina de la arquitectura de destino. No hay un formato canónico para el código C mientras se está ejecutando; C es una especificación de un archivo de entrada de texto para un compilador.
Es posible, pero lo más probable es que no sea portiblemente posible y que tenga que lidiar con segmentos de memoria de solo lectura para el código en ejecución y otros obstáculos puestos en su lugar por su sistema operativo.
Este sería un buen comienzo. Esencialmente la funcionalidad de Lisp en C:
http://nakkaya.com/2010/08/24/a-micro-manual-for-lisp-implemented-in-c/
La sugerencia sobre la implementación de LISP en C y su uso es sólida, debido a problemas de portabilidad. Pero si realmente quisiera, esto también podría implementarse en la otra dirección en muchos sistemas, cargando el bytecode de su programa en la memoria y luego volviendo a él.
Hay un par de maneras en que podrías intentar hacer eso. Una forma es a través de un exploit de desbordamiento de buffer Otra sería usar mprotect () para hacer que la sección del código pueda escribirse, y luego modificar las funciones creadas por el compilador.
Las técnicas como esta son divertidas para los desafíos de programación y las competiciones confusas, pero dada la ilegibilidad de la combinación de su código con el hecho de que está explotando lo que C considera un comportamiento indefinido, es mejor evitarlos en los entornos de producción.
Lo siento, estoy respondiendo un poco tarde, pero creo que encontré exactamente lo que está buscando: https://shanetully.com/2013/12/writing-a-self-mutating-x86_64-c-program/
En este artículo, cambian el valor de una constante inyectando el ensamblaje en la pila. Luego ejecutan un código shell modificando la memoria de una función en la pila.
A continuación se muestra el primer código:
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/mman.h>
void foo(void);
int change_page_permissions_of_address(void *addr);
int main(void) {
void *foo_addr = (void*)foo;
// Change the permissions of the page that contains foo() to read, write, and execute
// This assumes that foo() is fully contained by a single page
if(change_page_permissions_of_address(foo_addr) == -1) {
fprintf(stderr, "Error while changing page permissions of foo(): %s/n", strerror(errno));
return 1;
}
// Call the unmodified foo()
puts("Calling foo...");
foo();
// Change the immediate value in the addl instruction in foo() to 42
unsigned char *instruction = (unsigned char*)foo_addr + 18;
*instruction = 0x2A;
// Call the modified foo()
puts("Calling foo...");
foo();
return 0;
}
void foo(void) {
int i=0;
i++;
printf("i: %d/n", i);
}
int change_page_permissions_of_address(void *addr) {
// Move the pointer to the page boundary
int page_size = getpagesize();
addr -= (unsigned long)addr % page_size;
if(mprotect(addr, page_size, PROT_READ | PROT_WRITE | PROT_EXEC) == -1) {
return -1;
}
return 0;
}
Mi amigo y yo tuvimos este problema mientras trabajábamos en un juego que se auto modifica su código. Permitimos que el usuario reescriba fragmentos de código en un ensamblaje x86.
Esto solo requiere aprovechar dos bibliotecas: un ensamblador y un desensamblador:
FASM assembler https://github.com/ZenLulz/Fasm.NET
Desensamblador UDIS86: https://github.com/vmt/udis86
Leemos las instrucciones usando el desensamblador, permitimos que el usuario las edite, convierta las nuevas instrucciones a bytes con el ensamblador y las escriba de nuevo en la memoria. La VirtualProtect
requiere el uso de VirtualProtect
en Windows para cambiar los permisos de la página y permitir la edición del código. En Unix tienes que usar mprotect
lugar.
Publiqué un artículo aquí sobre cómo lo hicimos:
https://medium.com/squallygame/how-we-wrote-a-self-hacking-game-in-c-d8b9f97bfa99
Así como el código de muestra aquí:
https://github.com/Squalr/SelfHackingApp
Estos ejemplos están en Windows usando C ++, pero debería ser muy fácil hacer solo multiplataforma y C.
Un lenguaje de autointerpretación (no compilado y enlazado como C) podría ser mejor para eso. Perl, javascript, PHP tienen la función eval()
malvada que podría ser adecuada para su propósito. Por ello, podría tener una cadena de código que modifica constantemente y luego ejecutar a través de eval()
.