Código Muerto ASM x86

En un grupo de Facebook el cual frecuento, un miembro solicitó ayuda de cómo traducir el siguiente código ensamblador:

Obteniendo información

Podemos observar que es un ELF (Linux) y que al parecer el código fue copiado desde el depurador GDB con el comando:

disas main

Otra forma de obtener el código ensamblador de un archivo ELF es por medio del comando:

objdump -d ./archivo

Se puede destacar es que las direcciones no poseen una base (base address), la explicación a esto es que  probablemente el ejecutable fue compilado con la protección PIE.

Sin más preámbulos comentemos las instrucciones:

Como es posible observar lo que hace este programa es multiplicar cada carácter de la string 3jd9cjfk98hnd por el tamaño de esta misma e ir sumando el resultado.

Programación en MASM

Pongamos lo teórico a lo práctico, para ello realizamos el mismo programa pero en MASM, aun cuando el sistema operativo será distinto, la arquitectura, set de instrucciones y librerías serán similares.

El código queda de la siguiente forma:
Ensamblamos y enlazamos (link), para luego depurar el ejecutable y ver el valor que retorna printf():

Dando como resultado el valor: 15015

Programación en C

Una forma de corroborar el resultado obtenido es que, una vez entendida la lógica del programa podemos intentar replicarlo en lenguaje C y ejecutar este dentro de un sistema GNU/Linux:

Compilamos el código y lo ejecutamos:

El código generado es el mismo.

A continuación se lista el contenido de la función main en lenguaje ensamblador:

El resultado es similar al código publicado por el miembro del grupo, aunque no hay protección PIE (utilicé una versión de gcc más antigua), la cantidad de instrucciones difieren y se utilizan algunas instrucciones distintas como repnz scas y not.

Otra cosa que podemos observar, es la ineficiencia dentro del BUCLE, pues cada vez que pasa por él, obtiene el tamaño de la string:

Por lo general, como buena práctica de programación, se recomienda usar variables en vez de constantes pues facilita futuras modificaciones de código, irónicamente a nivel de lenguaje ensamblador, esta buena práctica (en este caso) hizo al programa menos eficiente.

Se recomienda escribir el FOR de esta manera:

En vez de:

Lo más probable es que el compilador al momento de leer el código de fuente encontró que la variable code no era una constante y por ello la necesidad de insertar una instrucción que verificara en cada ciclo del bucle su tamaño por si esta fuese alterada.

Conclusiones

Como conclusión fue posible entender el comportamiento de un binario por medio de la lectura de su código muerto, recreamos el programa tanto en lenguaje ensamblador como en C, logramos visualizar el proceso de transcripción de un lenguaje de alto nivel (conocido también como lenguaje intermedio) y cómo este podría degradar el rendimiento en ciertas circunstancias. Cualquier persona que se está iniciando en programación pensaría que lo mejor sería programar todo en lenguaje ensamblador, pero se debe tener en cuenta que el tiempo necesario para programar es mayor que en los lenguajes de alto nivel, es por ello que se tiende a utilizar este magnifico lenguaje sólo en circunstancias específicas, como por ejemplo: en proyectos en donde lo primordial es la eficiencia y calidad de la programación (micro-controladores, drivers, sistemas embebidos) por sobre el tiempo que esto requiera.

 

Compartir

Agregar un comentario

Pwned Chile