| Escribiendo en código máquina | ||
Cuando estudiaba los principios de la carrera (anteriormente tambien) me preguntaba como eran los programas por dentro y como se hacian. Despues de aprender a programar sigues y sigues avanzando pero ciertos pequeños detalles son muy escurridisos para ser tomados directamente en clase. Escribir código "maquina" siempre fue medio en broma una variante del dicho "los verdaderos hombres programan con la orden cat > programa.exe", ahora que estamos de humor veamoslo un poco más en serio y por que tal ves no llevemos algunas sorpresas algunos de nosotros que no sabemos como son los opcodes por dentro. Queda entonces advertido que lo siguiente es simplemente una pequeña investigación para adentrarnos al pequeño mundo de los bits que forman a los inmensos programas de computo de hoy en dia. Tambien para los super expertos en ingenieria inversa, data recovery o hackers sabedores de las oscuras artes subterraneas les prometo aburrirlos ya que de ninguna manera soy experto en estos temas, si quieren leer algo escrito por expertos lean un libro y no esta página dedicada a mis ratos libres llenos de poesia oscura. Como dicen nuestros vecinos norteamericanos : enjoy! Cuando programamos en ensamblador se dice que programamos a bajo nivel debido a que cada existe una relacion 1-1 a cada intruccion binaria en el conjunto de instrucciones del procesador. Lo anterior no es completamente la verdad debido a que existe un "microprograma" integrado en algunos microprocsadores los cuales interpretan cada opcode apuntado por CS:EIP ( claro despues de hacer la referencia real en el caso de estar en modo protegido y paginado por el MMU), cada opcode ( el codigo de operacion binario) es traducido a un conjuto de microinstrucciones que el hardware del microprocesador si podra realizar como abrir y cerrar compuertas de operadores logicos. Para conocer mas sobre arquitectura de computadores les recomiendo el libro de "Organizacion de computadoras, un enfoque estructurado" del Maestro Andrew. S. Tanenbaum, o el libro de "Arquitectura de Computadoras" de M. Morris Mano. Usted debería tener una copia del volumen 2 del manual de la arquitectura intel IA32 para conocer la estructura del la instrucción MOV que analisaremos aquí. Supongamos que tenemos un pequeño "programa" como el siguiente:
Ensamblamos el programa con la orden nasm bin.s -o bin.bin y luego lo desensamblamos obteniendo lo siguiente:
Disculpa por lo brusco de esta presentación del código ya que no he explicado mucho. Lo realmente importante es que la orden: ndisasm -b 32 bin.bin mostro 2 instrucciones mov en lugar de la unica mov eax,ecx. El comando 'db' es utilizado en asm para definir variables o mejor dicho para definir una etiqueta y guardar un valor en la posición del archivo del programa donde se encontro. Lo que nadie dice es que 'db' puede tener o no un nombre o etiqueta de forma que bien el programa pudo ser:
Si lo hacemos el archivo de salida no cambia en lo absoluto debido a que los nombres solo son usados en el proceso de ensamble o compilacion en el caso de compiladores pero no es guardado el archivo objeto final. Finalmente los valores guardados en variable1 y variable2 ( si lo hubieramos declarado claro) son guardados en el archivo objeto exactamente al principio del archivo final tal como habian sido explicitamente escrito. Esos datos en realidad son en realidad el binario de una intruccion mov ecx,eax como lo podemos ver en lo siguiente:
Ya con la informacion anterior, sabemos los valores hexadecimales de las 2 instrucciones mov que habiamos codificado en el programa en ensamblador anterior. Veamos que nosotros podriamos introducir esos valores en un vector de "chars" en un programa en C, utilizando el siguiente codigo y redirigiendo su salida a un archivo:
La parte realmente importante fue el haber guardado los codigos hexadecimales en un vector de caracteres utilizando caracteres '\x00' donde 00 puede ser cualquier numero hexadecimal, esto nos asegura que cada valor puesto sera exactamente de 1 bytes de longitud y que los bits seran ordenados en la forma especifica que escribimos. Recuerden tambien que algunas veces (muchas en realidad) el compilador introduce codigo de "relleno" ya sea introduciendo codigos de no operacion (en ensamblador: NOP => 0x90) los cuales sirven para que otras variables se encuentren en direcciones de memoria pares o multiplos de 4 ( dependiendo del bus de datos en intel es 4 por ser el numero de bytes del bus: 32 bits) Veamos entonces que esos datos que tambien son codigo ejecutable pueden ser mandado como informacion a un programa que este escuchando a un puerto, en este caso utilizamos netcat ( nc) para enviar y recibir los datos, pero en realidad podria ser cualquiera, viendo entonces una de las bases de en las cuales se apoya la explotacion de programas en internet.
De lo anterior se pueden relacionar muchos temas que no son tan triviales como lo presentado aqui, un tema muy interesante es la escritura de compiladores, interpretes o ensambladores que generen codigo ejecutable, ya vimos un ejemplo del codigo binario necesario para formar una instruccion en codigo maquina; un compilador es un programa que automatiza la creacion de dicho codigos ( y muchas otras cosas). Solo quisiera recalcar la absoluta posibilidad de haberme equivocado en lo anteriormente expuesto, si tienes algo que agregar, comentario o equivocacion espero me puedas hacer llegar lo que piensas, ya sea a mi correo en jorge.garcia.gonzalez (en) gmail.com o posteando en mi blog. Saludos! |
Bytes y bits son manajados e interpretados para darles un "significado" que depende del contexto, esto es probablemente una de las bases más importantes para desarrollar programas. Intel IA32 Arch. Manuals |
correo electronico : jorge.garcia.gonzalez@gmail.com