c argument-passing deobfuscation

Entendiendo un argumento poco común a main



argument-passing deobfuscation (2)

¿Qué hace este programa?

main(_){write(read(0,&_,1)&&main());}

Antes de que lo analicemos, vamos a pretenderlo:

main(_) { write ( read(0, &_, 1) && main() ); }

Primero, debes saber que _ es un nombre de variable válido, aunque sea uno feo. Vamos a cambiarlo:

main(argc) { write( read(0, &argc, 1) && main() ); }

A continuación, observe que el tipo de retorno de una función y el tipo de un parámetro son opcionales en C (pero no en C ++):

int main(int argc) { write( read(0, &argc, 1) && main() ); }

A continuación, comprenda cómo funcionan los valores de retorno. Para ciertos tipos de CPU, el valor de retorno siempre se almacena en los mismos registros (EAX en x86, por ejemplo). Por lo tanto, si omite una declaración de retorno, es probable que el valor de retorno sea la que devuelva la función más reciente.

int main(int argc) { int result = write( read(0, &argc, 1) && main() ); return result; }

La llamada a read es más o menos evidente: se lee desde el estándar en (descriptor de archivo 0), en la memoria ubicada en &argc , para 1 byte. Devuelve 1 si la lectura fue exitosa, y 0 en caso contrario.

&& es el operador lógico "y". Evalúa su lado derecho si y solo si su lado izquierdo es "verdadero" (técnicamente, cualquier valor que no sea cero). El resultado de la expresión && es un int que siempre es 1 (para "true") o 0 (para false).

En este caso, el lado derecho invoca main sin argumentos. Llamar a main sin argumentos después de declararlo con 1 argumento es un comportamiento indefinido. Sin embargo, a menudo funciona, siempre y cuando no te importe el valor inicial del parámetro argc .

El resultado de && se pasa a write() . Entonces, nuestro código ahora se ve como:

int main(int argc) { int read_result = read(0, &argc, 1) && main(); int result = write(read_result); return result; }

Hmm Un vistazo rápido a las páginas del manual revela que write toma tres argumentos, no uno. Otro caso de comportamiento indefinido. Al igual que al llamar a main con muy pocos argumentos, no podemos predecir qué write recibirá para sus argumentos 2 y 3. En las computadoras típicas, obtendrán algo , pero no podemos saber con seguridad qué. (En computadoras atípicas, pueden suceder cosas extrañas). El autor confía en la write lo que se almacenó previamente en la pila de memoria. Y, él está confiando en que es el segundo y tercer argumentos para leer.

int main(int argc) { int read_result = read(0, &argc, 1) && main(); int result = write(read_result, &argc, 1); return result; }

Corrigiendo la llamada no válida a main , agregando encabezados y expandiendo el && tenemos:

#include <unistd.h> int main(int argc, int argv) { int result; result = read(0, &argc, 1); if(result) result = main(argc, argv); result = write(result, &argc, 1); return result; } Conclusiones

Este programa no funcionará como se espera en muchas computadoras. Incluso si usa la misma computadora que el autor original, es posible que no funcione en un sistema operativo diferente. Incluso si usa la misma computadora y el mismo sistema operativo, no funcionará en muchos compiladores. Incluso si utiliza el mismo compilador de la computadora y el mismo sistema operativo, es posible que no funcione si cambia los indicadores de la línea de comando del compilador.

Como dije en los comentarios, la pregunta no tiene una respuesta válida. Si encuentra un organizador de la competencia o un juez de la competencia que diga lo contrario, no los invite a su próxima competencia.

La siguiente pregunta fue dada en un concurso de programación de la universidad. Nos pidieron que adivináramos la salida y / o explicáramos su funcionamiento. No hace falta decir que ninguno de nosotros tuvo éxito.

main(_){write(read(0,&_,1)&&main());}

Un poco de Google me llevó a esta pregunta exacta, formulada en codegolf.stackexchange.com :

https://codegolf.stackexchange.com/a/1336/4085

Allí, se explica lo que hace: Reverse stdin and place on stdout , pero no cómo .

También encontré ayuda en esta pregunta: tres argumentos para main y otros trucos ofuscantes, pero aún así no explica cómo funciona main(_) , &_ &&main() .

Mi pregunta es, ¿cómo funcionan estas sintaxis? ¿Son algo que debería saber, como sigue siendo relevante?

Agradecería cualquier sugerencia (a enlaces de recursos, etc.), si no es una respuesta directa.


Ok, _ es solo una variable declarada en la sintaxis de K&R C con un tipo predeterminado de int. Funciona como almacenamiento temporal.

El programa intentará leer un byte de la entrada estándar. Si hay una entrada, llamará principal de forma recursiva para leer un byte.

Al final de la entrada, read(2) devolverá 0, la expresión devolverá 0, la llamada al sistema write(2) se ejecutará y la cadena de llamadas probablemente se desenrollará.

Digo "probablemente" aquí porque desde este punto los resultados dependen en gran medida de la implementación. Faltan los otros parámetros para write(2) , pero algo estará en los registros y en la pila, por lo que algo se pasará al núcleo. El mismo comportamiento indefinido se aplica al valor de retorno de las diversas activaciones recursivas de main .

En mi x86_64 Mac, el programa lee la entrada estándar hasta EOF y luego sale, sin escribir nada.