proyectos programacion programa hacer guardar ejemplos ejecutar ejecutable dev detener crear compilar como c metaprogramming self-modifying

programacion - ¿Puede un programa en C modificar su archivo ejecutable?



proyectos c++ ejemplos (9)

Tenía demasiado tiempo en mis manos y comencé a preguntarme si podría escribir un programa de auto-modificación. Para ello, escribí un "Hello World" en C, luego usé un editor hexadecimal para encontrar la ubicación de la cadena "Hello World" en el ejecutable compilado. ¿Es posible modificar este programa para abrirse y sobrescribir la cadena "Hello World"?

char* str = "Hello World/n"; int main(int argc, char* argv) { printf(str); FILE * file = fopen(argv, "r+"); fseek(file, 0x1000, SEEK_SET); fputs("Goodbyewrld/n", file); fclose(file); return 0; }

Esto no funciona, supongo que hay algo que impide que se abra por sí mismo, ya que puedo dividirlo en dos programas separados (A "Hello World" y algo para modificarlo) y funciona bien.

EDITAR: Entiendo que cuando se ejecuta el programa, se carga completamente en el ram. Entonces, el ejecutable en el disco duro es, para todos los propósitos y propósitos, una copia. ¿Por qué sería un problema para que se modifique?

¿Hay una solución?

Gracias


El código de auto-modificación se usa para modificaciones en la memoria, no en el archivo (como los desempaquetadores en tiempo de ejecución como lo hace UPX). Además, la representación del archivo de un programa es más difícil de operar debido a las direcciones virtuales relativas, las posibles reubicaciones y las modificaciones a los encabezados necesarios para la mayoría de las actualizaciones (por ejemplo, cambiando el Hello world! al de longer Hello World , necesitará extender el segmento de datos en el archivo).

Te sugiero que primero aprendas a hacerlo en la memoria. Para las actualizaciones de archivos, el enfoque más simple y más genérico sería ejecutar una copia del programa para que modifique el original.

EDITAR: Y no se olvide de las razones principales por las que se utiliza el código de auto-modificación:

1) Ofuscación, de modo que el código que se ejecuta realmente no es el código que verá con un simple análisis estadístico del archivo.

2) Rendimiento, algo así como JIT.

Ninguno de ellos se beneficia de modificar el ejecutable.


En Windows, cuando se ejecuta un programa, el archivo *.exe completo se asigna a la memoria mediante las funciones de archivo de memoria asignada en Windows . Esto significa que el archivo no necesariamente está todo cargado a la vez, sino que las páginas del archivo se cargan a pedido a medida que se accede.

Cuando el archivo se asigna de esta manera, otra aplicación (que se incluye a sí misma) no puede escribir en el mismo archivo para cambiarlo mientras se está ejecutando. (Además, en Windows tampoco se puede cambiar el nombre del ejecutable en ejecución, pero sí en Linux y otros sistemas Unix con sistemas de archivos basados ​​en inodos).

Es posible cambiar los bits asignados a la memoria, pero si lo hace, el sistema operativo lo hace utilizando la semántica de "copia en escritura", lo que significa que el archivo subyacente no se modifica en el disco, sino una copia de la página ( s) en la memoria se realiza con sus modificaciones. Sin embargo, antes de que se le permita hacer esto, normalmente debe jugar con los bits de protección en la memoria en cuestión (por ejemplo, VirtualProtect ).

En un momento, solía ser común que los programas de ensamblaje de bajo nivel que estaban en entornos de memoria muy restringidos usaran un código de auto-modificación. Sin embargo, ya nadie hace esto porque no estamos ejecutando en los mismos entornos restringidos, y los procesadores modernos tienen tuberías largas que se molestan mucho si comienzas a cambiar el código desde abajo.


En las versiones más recientes de Windows CE (al menos 5.x más reciente) donde las aplicaciones se ejecutan en el espacio del usuario (en comparación con las versiones anteriores donde todas las aplicaciones se ejecutan en modo supervisor), las aplicaciones ni siquiera pueden leer su propio archivo ejecutable.


Es muy dependiente del sistema operativo. Algunos sistemas operativos bloquean el archivo, por lo que podría intentar hacer trampa al hacer una nueva copia en algún lugar, pero simplemente está ejecutando otro compy del programa.

Otros sistemas operativos realizan comprobaciones de seguridad en el archivo, por ejemplo, iPhone, por lo que escribirlo supondrá una gran cantidad de trabajo, además de que reside en un archivo de solo lectura.

Con otros sistemas es posible que ni siquiera sepa dónde está el archivo.


Hay formas no portátiles de hacer esto en muchas plataformas. En Windows puede hacer esto con WriteProcessMemory() , por ejemplo. Sin embargo, en 2010 suele ser una muy mala idea hacer esto. Estos no son los días de DOS donde se codifica en ensamblaje y se hace esto para ahorrar espacio. Es muy difícil hacerlo bien, y básicamente estás pidiendo problemas de estabilidad y seguridad. A menos que esté haciendo algo de muy bajo nivel, como un depurador, yo diría que no se moleste con esto, los problemas que presentará no valdrán la ganancia que pueda tener.


Si está operando en Windows, creo que bloquea el archivo para evitar que se modifique mientras se ejecuta. Es por eso que a menudo necesita salir de un programa para instalar una actualización. Lo mismo no es cierto en un sistema Linux.


Si está utilizando Windows, puede hacer lo siguiente:

Ejemplo paso a paso:

  1. Llame a VirtualProtect() en las páginas de códigos que desea modificar, con la protección PAGE_WRITECOPY .
  2. Modificar las páginas de códigos.
  3. Llame a VirtualProtect() en las páginas de códigos modificados, con la protección PAGE_EXECUTE .
  4. Llame a FlushInstructionCache() .

Para obtener más información, consulte Cómo modificar el código ejecutable en la memoria (Archivado: agosto de 2010)


Si estamos hablando de hacer esto en un entorno x86, no debería ser imposible. Sin embargo, debe usarse con precaución porque las instrucciones x86 son de longitud variable. Una instrucción larga puede sobrescribir la (s) siguiente (s) instrucción (es) y una más corta dejará los datos residuales de la instrucción sobrescrita que se debe anular (instrucción NOP).

Cuando el x86 se protegió por primera vez, los manuales de referencia de Intel recomendaron el siguiente método para depurar el acceso a las áreas XO (solo ejecución):

  1. crear un nuevo selector vacío (parte "alta" de punteros lejanos)
  2. establece sus atributos a los del área XO
  3. las propiedades de acceso del nuevo selector deben configurarse RO DATA si solo desea ver lo que hay dentro
  4. Si desea modificar los datos, las propiedades de acceso deben establecerse en RW DATA.

Así que la respuesta al problema está en el último paso. El RW es necesario si desea poder insertar la instrucción de punto de interrupción, que es lo que hacen los depuradores. Los procesadores más modernos que el 80286 tienen registros de depuración internos para habilitar la funcionalidad de monitoreo no intrusivo que podría resultar en la emisión de un punto de interrupción.

Windows puso a disposición los componentes básicos para hacer esto comenzando con Win16. Probablemente todavía están en su lugar. Creo que Microsoft llama "thunk" a esta clase de manipulación de punteros.

Una vez escribí un motor de base de datos de 16 bits muy rápido en PL / M-86 para DOS. Cuando llegó Windows 3.1 (ejecutándose en 80386s), lo porté al entorno Win16. Quería hacer uso de la memoria de 32 bits disponible pero no había PL / M-32 disponible (o Win32).

Para resolver el problema, mi programa usó thunk de la siguiente manera.

  1. definió punteros lejanos de 32 bits (sel_16: offs_32) utilizando estructuras
  2. asignó áreas de datos de 32 bits (<=>> tamaño de 64 KB) usando memoria global y las recibió en formato de puntero lejano de 16 bits (sel_16: offs_16)
  3. rellene los datos de las estructuras copiando el selector, luego calcule el desplazamiento utilizando la multiplicación de 16 bits con resultados de 32 bits.
  4. cargado el puntero / estructura en es: ebx usando el prefijo de reemplazo de tamaño de instrucción
  5. accedió a los datos utilizando una combinación del tamaño de instrucción y prefijos de tamaño de operando

Una vez que el mecanismo estuvo libre de errores, funcionó sin problemas. Las áreas de memoria más grandes que mi programa usó fueron 2304 * 2304 de doble precisión que llegan a unos 40MB. Incluso hoy, llamaría a esto un "gran" bloque de memoria. En 1995 era el 30% de un stick SDRAM típico (128 MB PC100).


Todas las respuestas actuales giran más o menos en torno al hecho de que hoy en día ya no se puede modificar fácilmente el código de la máquina. Estoy de acuerdo en que eso es básicamente cierto para las PC de hoy.

Sin embargo, si realmente desea ver su propio código de auto-modificación en acción, tiene algunas posibilidades disponibles:

  • Pruebe los microcontroladores, los más simples no tienen una canalización avanzada. La opción más barata y rápida que encontré es una memoria USB MSP430

  • Si una emulación está bien para usted, puede ejecutar un emulador para una plataforma anterior sin canalizaciones.

  • Si querías un código de auto-modificación solo por diversión, puedes divertirte aún más con el código de autodestrucción (más exactamente, destruir enemigos) en Corewars .

  • Si está dispuesto a pasar de C para decir un dialecto Lisp, el código que escribe el código es muy natural allí. Sugeriría un Scheme que intencionalmente se mantiene pequeño.