1: .code16 # usa 16 bits 2: .global main 3: 4: main: 5: mov $0x0002, %ax 6: int $0x10 #setea 80x25 modo texto 7: 8: mov $0x0700, %ax 9: mov $0x0f, %bh 10: mov $0x184f, %dx 11: xor %cx, %cx 12: int $0x10 #limpia la pantalla (fondo negro) 13: jmp print_message 14: 15: 16: print_living_clock: 17: 18: mov $0x02, %ah 19: mov $0x00, %bh 20: mov $0x012a, %dx 21: int $0x10 #resetea la posición del cursor 22: 23: # Lee el Timer 24: mov $0x02, %ah 25: int $0x1a 26: 27: # Imprime Horas 28: mov $0x0e, %ah 29: mov %ch, %al 30: int $0x10 31: 32: # Imprime '/' 33: mov $0x0e, %ah 34: mov $0x2f, %al 35: int $0x10 36: 37: # Imprime Minutos 38: mov $0x0e, %ah 39: mov %cl, %al 40: int $0x10 41: 42: # Imprime '/' 43: mov $0x0e, %ah 44: mov $0x2f, %al 45: int $0x10 46: 47: # Imprime Segundos 48: mov $0x0e, %ah 49: mov %dh, %al 50: int $0x10 51: 52: jmp print_living_clock 53: 54: print_message: 55: mov $0x02, %ah 56: mov $0x00, %bh 57: mov $0x0000, %dx 58: int $0x10 # configura la posición del cursor 59: 60: mov $msg, %si # carga la dirección del msg dentro de si 61: mov $0x0001, %cx 62: mov $0xe, %ah # carga 0xe (función number para int 0x10) dentro de ah 63: jmp print_char 64: 65: print_message_2: 66: mov $0x02, %ah 67: mov $0x00, %bh 68: mov $0x0100, %dx 69: int $0x10 # configura la posición del cursor 70: 71: mov $msg2, %si # carga la dirección del msg dentro de si 72: mov $0x0002, %cx 73: mov $0xe, %ah # carga 0xe (función number para int 0x10) dentro de ah 74: jmp print_char 75: 76: print_message_3: 77: mov $0x02, %ah 78: mov $0x00, %bh 79: mov $0x0200, %dx 80: 81: int $0x10 # configura la posición del cursor 82: mov $msg3, %si # carga la dirección del msg dentro de si 83: mov $0x0003, %cx 84: mov $0xe, %ah # carga 0xe (función number para int 0x10) dentro de ah 85: jmp print_char 86: 87: print_char: 88: mov $0x0e, %ah 89: lodsb # carga el byte de la dirección en si dentro de al e incrementa si 90: cmp $0, %al # compara el contenido de AL con zero 91: je done # if al == 0, go to "done" 92: mov $0xc0, %bl 93: int $0x10 # imprime el caracter en al a pantalla 94: jmp print_char # lo repite con el siguiente byte 95: 96: done: 97: cmp $0x0001, %cx 98: je print_message_2 99: 100: cmp $0x0002, %cx 101: je print_message_3 102: 103: cmp $0x0003, %cx 104: je print_living_clock 105: 106: end: 107: hlt # para la ejecuciónMarco Ramilli 108: msg: .asciz "====================================================" 109: msg2: .asciz " Ejemplo de programa de arranque " 110: msg3: .asciz "====================================================" 111: 112: .fill 510-(.-main), 1, 0 # añade 0s hasta 510 bytes long 113: 114: .word 0xaa55 # byte mágico para decirle a la BIOS que es bootable

1: .code16

2: .global main

112: .fill 510 - (.- init), 1, 0

114: .word 0xaa55

Nota (gracias Fare9): el código al ser 16 bits, no es muy complejo de analizar y las interrupciones son de sobra conocidas, un pdf como este creo que te vienen todas o casi todas: http://www2.ift.ulaval.ca/~marchand/ift17583/dosints.pdf

as -o boot.o boot.asm

--oformat binary

-e main

-Ttext 0x7c00





ld -o boot.bin --oformat binary -e main -Ttext 0x7c00 -o boot.bin boot.o

qemu-system-x86_64 boot.bin

o bueno de qemu es que te permite especificar un flag de depuración por tcp, al que puedes conectarte con gdb en remoto (o IDA a través de gdb), poner el breakpoint en 0x7C00, y en cuanto la bios haya cargado el MBR, empezar a depurar: https://en.wikibooks.org/wiki/QEMU/Debugging_with_QEMU





Algunos tipos de malware se guardan así mismos en el Master Boot Record (en adelante MBR) como método de persistencia arrancándose durante el proceso de inicio del sistema. Recientemente, Marco Ramilli (basándose en los trabajos de Prabir Shrestha y Martin Splitt) explicaba brevemente como funcionaba el MBR y cómo escribir un programa bootloader, skill básica que nos ayudará a analizar artefactos de malware que implementen esta característica.En realidad, el proceso de arranque es súper fácil. Cuando presionamos el botón de encendido, proporcionamos la potencia necesaria para la electrónica del PC. Una vez que se enciende la BIOS, comienza ejecutando su propio código almacenado y cuando termina de ejecutar sus rutinas de inicialización, busca dispositivos de arranque.Un dispositivo de arranque es un dispositivo conectado físicamente que tiene 521 bytes de código al principio y que contiene el número mágico de arranque: 0x55AA como últimos 2 bytes. Si la BIOS encuentra 510 bytes seguidos de 0x55AA, toma los 510 bytes anteriores los mueve a la RAM (a la dirección 0x7c00) y asume que son bytes ejecutables. Este código es el llamado gestor de arranque.El siguiente código en ensamblador con la sintaxis AT&T se ejecuta en el arranque mostrando 3 strings y una especie de progresión a modo reloj. Como la BIOS está "cerca" de la memoria, podemos usar un conjunto completo de instrucciones de BIOS e interrupciones como se muestran a continuación:1. Int_10,02 para configurar el tamaño de la pantalla2. int_10,07 para limpiar la pantalla de las salidas de la BIOS3. int_12a, 02 para configurar las posiciones del cursor4. int_1a, 02 para leer el estado del reloj5. int_10,0e para escribir caracteres en la pantallaLas dos primeras líneas:indican que el código se escribirá en modo de 16 bits y la función etiquetada externa (expuesta) es la etiquetada como "main" (el linker lo necesita para configurar el punto de entrada original en el espacio de direcciones adecuado).Las dos últimas líneas:indican que el código es. En la línea 112 tenemos el comando de llenado que el compilador interpretará escribiendo de nops (hasta 510 bytes) para mantener la estructura del MBR. La línea 113 tiene el código mágico enTodo el código usa el registro %cx para el estado actual. Por ejemplo, %cx podría ser: 0x0000 si se imprime msg, 0x0001 si se imprime msg2, 0x0002 si se imprime msg3 y 0x0003 si queremos iniciar el ciclo de impresión del reloj. Se usa un comando lodsb para iterar sobre los caracteres de cadena a fin de imprimirlos hasta el byte nulo (\0).En el ejemplo se utilizará GNU Assembler (compilador y linker) que implementa la sintaxis de AT&T, que es bastante diferente a la de Intel pero funciona bien para el código sencillo que vamos a usar.Lo primero que usaremos es el compilador GNU (as), que tomará como entrada un archivo en ensamblador y devolverá su representación binaria:Luego usaremos el enlazador o linker GNU (ld) para obtener un archivo binario sencillo sin librerias ni símbolos vinculados:También tenemos que decirle al linker dónde comienza el código () y agregaríamos el parámetroen caso de que el código que vamos a escribir no se ajuste a un espacio de direcciones de 16 bits, por lo que forzaremos a nuestro linker a mapear la función main en dicha dirección, que sabemos que es la dirección donde la BIOS ejecuta el cargador de arranque o bootloader.En definitiva, suponiendo que nuestro código se llana boot.asm y nuestro entry point original es 'main', podríamos usar el siguiente comando:Y para ejecutar el código compilado, usaremos qemu de la siguiente manera: