Está en la página 1de 28

Sistemas de Computación

Laboratorio Nº 3: “Modo Protegido”

Facultad de Ciencias Exactas, Físicas y


Naturales
Universidad Nacional de Córdoba

Preparado por:

● Bossa, Celina 43132207 Ing. Electrónica


● Montero, Felipe 95011233 Ing. en Computación
● Olocco, Laureano 40522648 Ing. en Computación

Profesor:

● Jorge, Javier

Soporte Técnico - 2023


Laboratorio: Compilar y correr una aplicación sin SO 2
Parte 1 - Clonar el repositorio Git y el submódulo 2
Paso 1: Clonar el repositorio 2
Paso 2: Trabajando con submódulos 2
Parte 2: Compilar y ejecutar los ejemplos 2
Paso 1: Instalar qemu, compilar y ejecutar los ejemplos 2
Paso 2: Depurar los ejemplos 3
Real mode 3
Protected mode 11
Parte 3 - Grabar la imagen y correrla en HW real 19
Paso 1: Determinar cual es el driver asignado al dispositivo 19
Paso 2: Grabar la imágen de disco 19
BIOS/UEFI 22
Linker 24
Modo Protegido 26
Repositorio GitHub 27

1
Laboratorio: Compilar y correr una aplicación sin SO

Parte 1 - Clonar el repositorio Git y el submódulo

Paso 1: Clonar el repositorio

A continuación se muestra el proceso de clonar el repositorio con los ejemplos.

Figura N° 1. Clonado de repositorio con ejemplos.

Paso 2: Trabajando con submódulos

Figura N° 2. Inicialización del submódulo x86-bare-metal-examples.

Parte 2: Compilar y ejecutar los ejemplos

Paso 1: Instalar qemu, compilar y ejecutar los ejemplos

A continuación se muestra el procedimiento de instalar la máquina virtual qemu y


correr los ejemplos “bios_hello_world” y “protected_mode”, obtenidos en el
submódulo.

Figura N° 3. Instalación de qemu.

2
Figura N° 4. Ejecución de “bios_hello_world” en la máquina virtual.

Figura N° 5. Máquina virtual ejecutando el programa “bios_hello_world”.

Figura N° 6. Máquina virtual ejecutando el programa “protected_mode”.

Paso 2: Depurar los ejemplos

Real mode

A continuación se muestra el código de la aplicación “bios_hello_world”, ejecutada


anteriormente.

Figura N° 7. código de “bios_hello_world”.

3
Como se muestra en la Figura N° 5, el programa imprime el string “hello world” en
pantalla. A partir del código del programa, mostrado en la Figura N° 7, se observa
que en la línea N° 6 se carga el string (representado por msg, líneas N° 16 y N° 17)
en una dirección de memoria apuntada por el registro %si. En la línea N° 7 se carga
la función “0x0E” del grupo de interrupciones 0x10 (Video services). Está función
(0x0E) imprime un caracter almacenado en %al, en la posición del cursor y avanza el
cursor en una posición. Desde la línea N° 9 hasta la N° 13 se tiene un bucle que se
encarga de imprimir el string cargado anteriormente (línea N° 6). La instrucción lodsb
(línea N° 9) carga un byte desde la dirección de memoria indicada en el registro %si,
en el registro %al, e incrementa el registro %si en un byte. Si el byte cargado en %al
es cero, es decir fin del string (línea N° 10), la ejecución salta hacia la instrucción hlt
(línea N° 11) y el procesador deja de ejecutar instrucciones y entra en modo stop
(halt, línea N° 15). Si el byte cargado en %al no es nulo, se ejecuta la función de
interrupción 0x0E (línea N° 12) y se imprime el caracter en pantalla.

Para indicar la dirección de inicio del programa, el mismo es combinado con el


archivo link.ld

Figura N° 8. código de link.ld, que indica la dirección de inicio, 0x7c00.

Con fin de estudiar las diferencias entre la nomenclatura AtyT e Intel y el uso de
GAS y NASM, el programa anterior fue reescrito en formato Intel y ensamblado con
NASM. El código fue nombrado nasm_hello_world.asm.

4
Figura N° 9. código de “nasm_hello_world.asm”, formato Intel.

Figura N° 10. código de “bios_hello_world”, compilado con NASM y ejecutado en qemu.

Figura N° 11. Programa ejecutado en qemu, modo monitor.

El funcionamiento del programa se analizó con gdb, utilizando la extensión gdb


dashboard. El procedimiento consistió en revisar el contenido de los registro %si y
%al a lo largo de la ejecución y analizar el proceso de impresión del string.

5
Figura N° 12. Inicio de depuración con gdb.

Para depurar el programa sin iniciarlo desde gdb, se utilizó el comando target
remote localhost:1234. Esto inicializó la depuración del programa ejecutado
anteriormente. Luego se colocó un breakpoint en la dirección de arranque y se inició
la ejecución del programa para frenar en esa dirección.

A continuación se muestra el desensamblado del programa al frenar el breakpoint


colocado en la dirección de arranque y el estado del programa, frenado en ese
momento.

Figura N° 13. Secuencia de instrucciones seguidas a la dirección de arranque.

Figura N° 14. Estado del programa al frenar en la dirección de arranque.

6
Figura N° 15. Contenido del registro %si al inicio del programa.

En la Figura N° 13 se puede observar que la instrucción lodsb se encuentra en la


dirección 0x7C05 y la llamada a la función de interrupción se encuentra en la
dirección 0x7C0A. Por lo tanto, se procedió a establecer breakpoints en ambas
direcciones.

Figura N° 16. Breakpoints en lodsb y llamada a interrupción.

Se continuó la ejecución hasta el siguiente breakpoint (0x7C05) y se analizó el


contenido del registro %si.

Figura N° 17. Programa frenado por primera vez en 0x7C05.

Figura N° 18. Contenido de %si al frenar en el primer breakpoint.

En la Figura N° 8 se ve que %si contiene la dirección 0x7C0F. Se analizó el


contenido en dicha dirección.

Figura N° 19. Contenido de en la dirección apuntada por %si.

7
Se puede observar que efectivamente el registro %si apunta a la dirección donde
está almacenado el string a imprimir. El primer byte (“H”) es almacenado en %al,
luego de ejecutar la instrucción.

Figura N° 20. Contenido %al luego de ejecutar la instrucción lods.

Se puede observar que el contenido de %al (los primero 8 bits de %eax) es el byte
0x48, que representa el caracter “H” en codificación ASCII.

Luego de ejecutar la instrucción de interrupción se puede observar que se imprime


el caracter “H” en pantalla.

Figura N° 21. Freno de ejecución en llamada a interrupción.

Figura N° 22. Impresión del caracter “H” luego de ejecutar la función de interrupción.

8
Como se mencionó anteriormente, al ejecutarse la instrucción lodsb, el contenido
del registro %si es incrementado en un byte. En la Figura N° 22 se observa el
contenido de %si, luego de imprimir el caracter “H” y antes de volver a ejecutar lodsb.

Figura N° 23. Contenido en %si luego de imprimir el caracter “H”.

Figura N° 24. Contenido en memoria a partir de 0x7C10.

Se continuó la depuración del programa analizando el estado del mismo y de los


registros en cada breakpoint.

Figura N° 25. Impresión del mensaje a lo largo de la depuración.

9
Figura N° 26. Contenido en %al luego de imprimir el último caracter.

En la figura N° 25 se observa que el contenido en %al es de 0x00, condición que


permite salir del bucle de impresión y frenar en la instrucción hlt.

Figura N° 27. Programa detenido en instrucción hlt, al finalizar impresión.

10
Figura N° 28. Revisión del contenido en memoria, desde 0x7C0F hasta 0x7C1B.

Protected mode

A continuación se depura con gdb el programa “protected_mode”, ejecutado


anteriormente (Parte 2, Paso 1). El código assembler del programa se muestra a
continuación.

Figura N° 29. Código del programa “protected_mode”.

En la Figura N° 29 se puede observar que el programa ejecuta una serie de


macros que, en primer lugar, entran en modo protegido, y luego imprime el mensaje
“hello world”.

Al igual que en el caso anterior, se inició gdb, y se colocó un breakpoint en la


dirección de inicio del programa, 0x7C00. Luego se inspeccionó el código del
programa.

Figura N° 30. Inicialización de programa “protected_mode” en qemu, modo monitor.

11
Figura N° 31. Código de programa, a partir de la dirección de inicio.

En la Figura N° 31 se observa que la primera instrucción ejecutada es cli, la cual


deshabilita las interrupciones. Al ingresar en modo protegido, se debe asegurar que
la única instancia de ejecución sea el programa entrando en modo protegido. Para
que el programa no pueda ser interrumpido, deshabilita las interrupciones.
Inspeccionando el código assembler en el archivo de cabecera “common.h” se puede
ver que estas instrucciones están declaradas en el macro BEGIN.

Figura N° 32. Macro BEGIN, en common.h

12
Figura N° 33. Código de programa, seguido a deshabilitar interrupciones.

En la Figura N° 33 se observa que el programa limpia el contenido de los


cementos %ds, %es, %fs y %gs (Direcciones 0x7C08 a 0x7C0E). Esto lo hace a
través del registro %ax, configurándolo inicialmente en cero con la instrucción xor
(Dirección 0x7C06).

Figura N° 34. Segmentos luego de ejecutar instrucción en 0x7C0E.

En la Figura N° 29 se observa que, luego de ejecutar el macro BEGIN, se ejecuta


el macro CLEAR. El mismo se muestra en la Figura N° 35.

13
Figura N° 35. Macros CURSOR_POSITION y CLEAR.

Se puede observar que los macros CLEAR y CURSOR_POSITION invocan los


macros PUSH_ADX y POP_DAX. Estos se muestran a continuación.

Figura N° 36. Macros PUSH_ADX y POP_DAX.

A continuación se muestra la ejecución del programa, ejecutando las instrucciones


correspondientes al macro PUSH_ADX, dentro de CLEAR, luego de haber ejecutado
las instrucciones correspondientes a BEGIN.

14
Figura N° 37. Ejecución de instrucciones definidas en CLEAR y PUSH_ADX.

Figura N° 38. Estado de programa luego de ejecutar instrucciones declaradas en CLEAR.

Como se muestra en la figura N° 35, dentro del macro CLEAR se invoca el macro
CURSOS_POSITION.

Figura N° 39. Ejecución de instrucciones declaradas en CURSOR_POSITION.

En la Figura N° 29, se observa que luego de ejecutar las instrucciones declaradas


en el macro CLEAR, se ejecuta el macro PROTECTED_MODE, que contiene las
instrucciones para entrar en modo protegido. A continuación se muestra el código del
macro PROTECTED_MODE.

15
Figura N° 40. Macro PROTECTED_MODE.

Figura N° 41. Macro PROTECTED_MODE (continuación).

Figura N° 42. Macro PROTECTED_MODE (continuación).

16
A continuación se muestra la ejecución de las instrucciones declaradas en el
macro PROTECTED_MODE.

Figura N° 43. Ejecución de instrucciones para entrar en modo protegido.

En la Figura N° 43 se puede observar cómo, en primer lugar, se carga la GDT


(Global Descriptor Table) con la instrucción lgdtl. El operando que recibe esta
instrucción define la dirección base y el límite de la GDT. En la Figura N° 41 se puede
ver el armado de la GDT. En las líneas N° 191 a N° 193 se define el operando
recibido por la instrucción lgdtl. En las instrucciones ubicadas en las direcciones
0x7C4C y 0x7C50 (Figura N° 43) se puede ver como se setea el bit 0 del registro
CR0 para efectivamente entrar en modo protegido.

Figura N° 44. Registro CR0 antes de entrar en modo protegido.

Figura N° 45. Registro CR0 después de entrar en modo protegido.

Finalmente, se limpia el pipeline de instrucciones. Las instrucciones que realizan


dicho procedimiento se declaran al final del macro PROTECTED_MODE (Figura N°
42).

Figura N° 46. Ejecución de instrucciones finales definidas en macro PROTECTED_MODE.

17
Finalmente, en la Figura N° 29 se observa que, luego de entrar en modo
protegido, se imprime el mensaje “hello world” ejecutando el macro
VGA_PRINT_SCREEN, el cual se muestra a continuación.

Figura N° 47. Macro VGA_PRINT_SCREEN.

El macro VGA_PRINT_SCREEN ejecuta instrucciones que permiten imprimir un


string en pantalla, sin hacer uso de llamadas a interrupción del BIOS, sino que
escribiendo directamente en la memoria de video. A continuación se muestra la
ejecución del bucle definido en el macro que imprime los caracteres en pantalla
(Figura N° 47, líneas N° 594 a N° 601).

Figura N° 48. Ejecución del bucle que imprime caracteres en pantalla.

18
Figura N° 49. Programa luego de la primera iteración del bucle de impresión.

Figura N° 50. Fin del programa.

Parte 3 - Grabar la imagen y correrla en HW real

Paso 1: Determinar cual es el driver asignado al dispositivo

Se utilizó un Pendrive USB Kingston Datatraveler de 60 GB. Para ubicarlo en el


sistema se utilizaron los comandos lsblk y fdisk.

Figura N° 51. Detección de unidad de almacenamiento con lsblk.

Figura N° 52. Detección de unidad de almacenamiento con fdisk.

Paso 2: Grabar la imágen de disco

No fue posible correr en HW real los códigos aportados en el repositorio (Parte 1).
Por lo tanto, se modificó el código del programa “nasm_hello_world” (Parte 2, Paso
2, Figura N° 9) de acuerdo a:

19
https://stackoverflow.com/questions/47277702/custom-bootloader-booted-via-usb-driv
e-produces-incorrect-output-on-some-compute

El código modificado se guardó bajo el nombre “custom_bootloader.asm”. A


continuación se muestra el código modificado.

Figura N° 53. Código assembler de “custom_bootloader.asm”.

Figura N° 54. Código assembler de “custom_bootloader.asm” (continuación).

20
El código fue ensamblado con NASM y grabado en la unidad con el comando dd.

Figura N° 55. Generación y grabado de imágen.

La imágen fue ejecutada en una computadora Acer Aspire 3 A315-55. En primer


lugar se configuró el modo de booteo en “Legacy” y luego se seleccionó la unidad
USB con la imagen como unidad de arranque.

Figura N° 56. Configuración de arranque en modo Legacy.

Figura N° 57. Programa ejecutado en HW real.

21
BIOS/UEFI

1. ¿Qué es UEFI? ¿Cómo puedo usarlo? Mencionar además una función a


la que podría llamar usando esa dinámica.

UEFI es un firmware, una porción de código que está almacenada en una


memoria aparte situada en la placa base del ordenador. Es el firmware
sucesor, escrito en C, del BIOS y se encarga de iniciar, configurar y
comprobar que se encuentre en buen estado el hardware del ordenador,
incluyendo la memoria RAM, los discos duros, la placa base o la tarjeta
gráfica. Cuando termina selecciona el dispositivo de arranque (disco duro,
CD, USB etcétera) y procede a iniciar el sistema operativo, y le cede a él el
control de tu ordenador. La UEFI tiene funciones adicionales a la BIOS y
mejoras sustanciales, como una interfaz gráfica mucho más moderna, un
sistema de inicio seguro, una mayor velocidad de arranque o el soporte para
discos duros de más de 2 TB.
Al encender la computadora, se mostrará la interfaz de firmware UEFI en
lugar de la pantalla de inicio de la BIOS tradicional. Desde aquí, puede
configurar opciones de inicio, como el dispositivo de arranque y la prioridad
de arranque.
Una función que podría utilizar con UEFI es la configuración del Secure Boot.
Secure Boot es una función de seguridad que ayuda a prevenir la carga de
malware y otros software maliciosos durante el proceso de inicio. Con Secure
Boot habilitado, el firmware UEFI solo cargará el software que tiene una firma
digital válida de un fabricante confiable.

2. Menciona casos de bugs de UEFI que puedan ser explorados

Buffer overflow: Una vulnerabilidad en la que un atacante puede desbordar un


área de memoria y ejecutar código malicioso en el firmware UEFI.
Uso incorrecto de memoria: Los errores en el manejo de la memoria pueden
permitir que un atacante escriba o lea datos de áreas de memoria no
autorizadas.
Inyección de comandos: Un atacante puede explotar una vulnerabilidad de
inyección de comandos en el firmware UEFI para ejecutar comandos
maliciosos.
Ataques de canal lateral: Los ataques de canal lateral se refieren a la
explotación de debilidades en la implementación del hardware para robar
datos sensibles. En el caso de UEFI, esto puede involucrar la explotación de
debilidades en el firmware de la tarjeta madre o en los chips de memoria.

3. ¿Qué es Converged Security and Management Engine (CSME), the Intel


Management Engine BIOS Extension (Intel MEB.x)?

Converged Security and Management Engine (CSME) y Intel Management


Engine BIOS Extension (Intel MEBx) son dos tecnologías de seguridad y
administración integradas en los procesadores Intel modernos.

22
La CSME es una plataforma de seguridad que se ejecuta en una CPU
separada dentro del procesador Intel. Proporciona funciones de seguridad y
administración para la plataforma, incluyendo el cifrado y la autenticación de
datos, la gestión de identidades y la protección contra amenazas de
seguridad.
El Intel MEBx, por otro lado, es una extensión de firmware de la BIOS que se
ejecuta en la plataforma del sistema. Proporciona una interfaz para la
configuración y la administración de las funciones de seguridad y
administración de la plataforma, incluyendo la gestión de contraseñas, la
configuración de la red y la solución de problemas.

4. ¿Qué es Coreboot? ¿Qué productos lo incorporan? ¿Cuáles son las


ventajas de su utilización?

Coreboot es un firmware libre y de código abierto que reemplaza a la BIOS


tradicional en las computadoras. Se ejecuta en la etapa de inicio de una
computadora y es responsable de la inicialización del hardware y de preparar
el sistema para que cargue el sistema operativo. Está escrito principalmente
en lenguaje C y Assembler, y se puede compilar para ejecutarse en una
amplia variedad de sistemas y arquitecturas de procesadores.
Productos que incorporan Coreboot:
● Chromebooks
● Servidores
● Sistemas embebidos
Su principales ventajas son:
● Mayor velocidad de inicio: tiempo de inicio más rápido que la BIOS
tradicional.
● Mayor seguridad: Al ser de código abierto permite que los
desarrolladores y la comunidad de seguridad puedan auditar el firmware en
busca de vulnerabilidades y corregirlas de manera oportuna. Además tiene
una arquitectura de seguridad que hace que sea difícil para los atacantes
modificar el firmware.
● Personalización: es modular y fácil de personalizar, lo que permite a
los usuarios agregar o quitar características según sus necesidades.
● Independencia del proveedor: los usuarios pueden utilizar cualquier
sistema operativo que deseen, sin estar limitados por las restricciones
impuestas por el fabricante de la BIOS.

23
Linker
1. ¿Qué es un linker? ¿qué hace ?

El linker es el programa encargado de tomar todos los archivos objeto (.o)


generados por el compilador (o ya presentes en una biblioteca), combinarlos y crear
un ejecutable. Para hacer esto, el linker busca las referencias a funciones y variables
en los diferentes archivos objeto y resuelve estas referencias, asegurándose de que
todas las referencias a las funciones y variables se encuentren en el archivo
ejecutable o en la biblioteca compartida. También realiza otras tareas, como la
asignación de direcciones de memoria a los diferentes segmentos del programa, la
eliminación de código no utilizado, la gestión de la tabla de símbolos y la creación de
archivos de depuración.

2. ¿Qué es la dirección que aparece en el script del linker?¿Por qué es


necesaria?

La dirección que aparece en el script del linker se refiere a la dirección de memoria


en la que se cargará un segmento de código o de datos en el archivo ejecutable
resultante. La dirección es necesaria para garantizar que el archivo ejecutable tenga
la estructura adecuada y pueda ser cargado correctamente en memoria cuando se
ejecute el programa.

3. Compare la salida de objdump con hd, verifique donde fue colocado el


programa dentro de la imagen.

A continuación se muestra el procedimiento de usar NASM para producir un


archivo del tipo object file y el uso de objdump para ver información del mismo. El
código es generado a partir de “nasm_hello_world.asm” (Figura N° 9).

Figura N° 58. Salida de objdump.

24
En la Figura N° 9 se muestra el uso de hexdump para analizar la imágen generada
a partir del mismo código.

Figura N° 59. Salida de hexdump de la imágen generada.

En ambos casos (Figura N° 58 y Figura N° 59) se puede observar que el programa


inicia en la dirección virtual 0x0000 y termina en la dirección 0x01Ff. Esto confirma el
tamaño de 512 bytes de la imágen. En la Figura N° 59 se puede observar que los
últimos bytes son efectivamente 0x55 y 0xAA.

Al analizar con gdb el código de programa ejecutado en la dirección de arranque


(0x7C00) se puede observar que es el mismo código que el inicio de programa
mostrado por hexdump.

Figura N° 60. Instrucción ejecutada en 0x7C00.

En la Figura N° 60 se observa que el código ejecutado en la dirección 0x7C00 es el


mismo que el inicio de programa mostrado por la salida de hexdump de la imágen
generada. El código ejecutado corresponde a los bytes 0xB8, 0xC0, 0x07, 0x8E,
0xD8, mostrados en gdb como 0xD88E07C0,eax.

4. Grabar la imagen en un pendrive y probarla en una pc y subir una foto

Esta actividad fue realizada en “Laboratorio: Compilar y correr una aplicación sin
SO”, Parte 3.

5. ¿Para qué se utiliza la opción --oformat binary en el linker?

Se utiliza para indicar que se desea generar un archivo binario como salida
en lugar del formato de archivo ejecutable convencional. El linker generará un
archivo binario que contiene el código y los datos del programa en su forma más
cruda y sin ningún formato específico para el sistema operativo o el procesador. Este
archivo binario puede ser cargado directamente en la memoria y ejecutado por el
procesador sin necesidad de ningún sistema operativo o capa de abstracción de
hardware adicional.

25
Modo Protegido
1. Crear un código assembler que pueda pasar a modo protegido (sin
macros).

Se creó el código “nasm_protected_mode.asm”, en sintaxis intel, que entra en


modo protegido e imprime el mensaje “Hello Protected Mode!”. A continuación se
muestra la generación de la imagen y la ejecución en la máquina virtual qemu.

Figura N° 61. Generación y ejecución de imágen, modo protegido.

Figura N° 62. Ejecución en qemu de imágen, modo protegido.

Se depuró el programa con gdb para confirmar el ingreso a modo protegido.

Figura N° 63. Depuración con gdb al inicio del programa.

Figura N° 64. Carga de la GDT.

Figura N° 65. Registro CR0 antes de entrar en modo protegido.

Figura N° 66. Ejecución al momento de entrar en modo protegido.

Figura N° 67. Registro CR0 después de entrar en modo protegido.

El código del programa se encuentra en el repositorio del proyecto.

2. ¿Cómo sería un programa que tenga dos descriptores de memoria


diferentes, uno para cada segmento (código y datos) en espacios de
memoria diferenciados?

26
Para tener dos descriptores de memoria diferentes, uno para cada segmento
de código y datos en espacios de memoria diferenciados, necesitarás definir y cargar
dos tablas de descriptores diferentes, una para el segmento de código y otra para el
segmento de datos.

3. Cambiar los bits de acceso del segmento de datos para que sea de solo
lectura, intentar escribir, ¿Qué sucede? ¿Qué debería suceder a
continuación? (Revisar el teórico) Verificarlo con gdb.

Al intentar escribir sin tener permisos , se reinicia el programa porque se produce


un fallo de memoria. Al querer verificarlo con el gdb se rompe porque solo un
descriptor de segmento con escritura habilitada puede ser cargado en en el registro
de segmento de la pila (ss) y al revisar el programa este se rompe en la siguiente
línea de código:
mov %ax, %ss
Siendo que ‘ax’ está cargado con 10b (2) haciendo referencia al descriptor que
apunta al segmento de datos cuyo atributo de escritura está en 0 por lo que es solo
de lectura.

4. En modo protegido, ¿Con qué valor se cargan los registros de


segmento ? ¿Por qué?

Los registros de segmentos se cargan con un dato de 16 bits donde los dos bits
menos significativos representan RPL (Requestor Privilege Level) cuya funcionalidad
es permitir que un programa pueda solicitar acceso a un segmento con un nivel de
privilegio diferente al del segmento actualmente activo. Los 14 bits más significativos
conforman el Selector el cuál apunta a una dirección de memoria de la tabla de
descriptores donde estará contenida la información del segmento.

Repositorio GitHub

Los códigos desarrollados en el presente proyecto se encuentran disponibles en:

https://github.com/felipx/syscom-tp3

27

También podría gustarte