Table des matières

.

, ,

Que trouve-t-on comme programme dans le mbr

Si vous êtes arrivés sur cette page par erreur, faites demi tour sans tarder car ici se trouve l'antre de la bête :-( - où il ne faut pas connaître la peur car vous allez découvrir les entrailles de votre machine. 8-o
Le mbr est le premier secteur du disque chargé en mémoire lors du démarrage d'un ordinateur. Il a pour fonction principale de rechercher une partition où se trouve un système d'exploitation à lancer.
Avant tout il faudra vérifier que la table des partitions est conforme - à défaut un message d'erreur sera affiché.

Pré-requis

Que se passe-t-il au démarrage d'un PC ?

Lors de la mise sous tension, différentes routines se mettent en place ; la première consiste à attendre que la tension de l'alimentation soit stabilisée puis des tests processeur, carte mère, barrettes de mémoire sont effectués. Ceci correspond à la séquence POST.
Vient ensuite la recherche de périphériques de démarrage selon l'ordre de boot.
Le bios charge alors le premier secteur du disque (512 octets) en mémoire vive à l'adresse 0000:7C00
Ce type d'adressage correspond au mode réel de fonctionnement d'un PC, c'est un fonctionnement en mode 16 bits.
Une fois le chargement terminé et sans erreur, l'exécution se poursuit à l'adresse 0000:7C00, et donc suit les instructions qui figurent dans le premier secteur du mbr

Lors d'un démarrage en mode UEFI avec un disque GPT, le mbr (protector) ne semble quasiment pas utilisé et seul l'emplacement de l'en-tête GPT est nécessaire (unique entrée de la table des partitions du mbr protector)

Comment accéder à ces informations

Nous avons vu comment afficher le contenu du mbr ; ici il faudra effectuer deux opérations :

- effectuer une copie de la partie exécutable du mbr (les 440 premiers octets) sous la forme d'un fichier
- utiliser un désassembleur pour afficher les instructions sous une forme lisibles par un humain (c'est tout relatif - à moins que je ne sois pas humain) au lieu de valeurs hexadécimales (opcodes)

Pour la première opération, on rentrera dans un terminal

sudo dd if=/dev/sda of=~/mbr_exec.bs bs=1 count=440


La deuxième opération transforme le fichier d'instruction hexadécimales en adresses, opcodes, instructions

ndisasm -b16 ~/mbr_exec.bs > ~/mbr_exec.asm


ndisasm est le désassembleur du paquet nasm, -b16 indique que le code doit être considéré comme de l'assembleur 16 bits. Le résultat est écrit dans le fichier ~/mbr_exec.asm
Le résultat brut pose problème car des chaines de texte ont été considérées comme des instructions et sont donc mal interprétées. Il faut alors procéder par tâtonnement pour les repérer (par exemple avec un éditeur hexa) et les exclure du traitement de désassemblage.
Une autre difficulté est que certaines adresses peuvent contenir, soit du code à un moment donné, soit des données (zone tampon) lorsque le code n'est plus utilisé.

Exemple de zone exécutable d'un mbr

L'exemple qui suit concerne un mbr obtenu lors de l'installation de Lucid. Lucid a été installé en mettant grub-pc dans le mbr.
J'ai effectué une analyse (partielle) du fonctionnement des différents morceaux mais des parties restent encore nébuleuses.
Le programme utilise des fonctions du bios (seules fonctions disponibles à ce stade du démarrage du PC) et je me suis servi de la liste des interruptions établies par Ralph Brown.

Voilà donc à quoi ressemble la chose.

; Programme 16 bits chargé en 0000:7C00
00000000  EB63              jmp short 0x65
00000002  90                nop
00000003  D0
00000004		resb 1	; mode : 0x00 pour le mode CHS, 0x01 pour le mode LBA

; Buffer pour chargement en mode CHS
00000005		resd 1	; nombre de secteurs
00000009		resd 1	; nombre de têtes
0000000D		resd 1	; nombre de cylindres

; Buffer pour chargement en mode LBA
00000005  	        resw 1	; 0x0010 - taille paquet
00000007		resw 1	; 0x0001 - nb blocs à transférer
00000009		resw 1	; 0x0000
0000000B		resw 1	; 0x7000 - buffer en 0x70000000
0000000D		resd 1	; 0x00000001 - emplacement LBA du bloc à charger
00000011		resd 1	; 0x00000000 - démarrer au bloc 0x0000000000000001


0000005A  0080              dw 0x8000           ; Emplacement de l'offset où le contenu du tampon sera copié
0000005C  01000000          dd 0x00000001	; Adresse LBA (valeur basse) où se trouve le secteur suivant de chargement (core.img)
00000060  00000000          dd 0x00000000	; Valeur haute du qword de l'adresse LBA
00000064  FF                db 0xff             ; 0xff pour le chargement de core.img sur le disque de boot, si 0x80 premier disque dur, si 0x81 deuxième...

00000065  FA                cli
00000066  90                nop
00000067  90                nop
00000068  F6C280            test dl,0x80	; teste le bit 7 de dl (si activé=disque dur)
0000006B  7502              jnz 0x6f		; si disque dur
0000006D  B280              mov dl,0x80		; marque le périphérique comme disque dur
0000006F  EA747C0000        jmp word 0x0:0x7c74	; saute à la ligne suivante (car mbr chargé en 0000:7c00)
00000074  31C0              xor ax,ax
00000076  8ED8              mov ds,ax
00000078  8ED0              mov ss,ax
0000007A  BC0020            mov sp,0x2000	; la pile pointe sur 0000:2000
0000007D  FB                sti
0000007E  A0647C            mov al,[0x7c64]	; examine le contenu de l'adresse 7C64 (ici 0xff)
00000081  3CFF              cmp al,0xff
00000083  7402              jz 0x87
00000085  88C2              mov dl,al		; si non égal à 0xff, sauvegardé dans dl - si core.img est sur un autre disque
00000087  52                push dx
00000088  BB1704            mov bx,0x417        ; adresse clavier
0000008B  802703            and byte [bx],0x3   ; teste si une touche shift est activée
0000008E  7406              jz 0x96
00000090  BE887D            mov si,0x7d88	; pointe sur la chaine "GRUB"
00000093  E81C01            call word 0x1b2

00000096  BE057C            mov si,0x7c05	; définit une zone tampon pour les paramètres de chargement
00000099  F6C280            test dl,0x80	; teste si disque dur
0000009C  7448              jz 0xe6		; pas disque dur
0000009E  B441              mov ah,0x41
000000A0  BBAA55            mov bx,0x55aa
000000A3  CD13              int 0x13		; installation check
000000A5  5A                pop dx
000000A6  52                push dx
000000A7  723D              jc 0xe6		; extensions non supportées
000000A9  81FB55AA          cmp bx,0xaa55
000000AD  7537              jnz 0xe6		; extension non installée

000000AF  83E101            and cx,byte +0x1
000000B2  7432              jz 0xe6		; fonctions 42h-44h, 47h et 48h non supportées

; traitement extension lba_mode
000000B4  31C0              xor ax,ax
000000B6  894404            mov [si+0x4],ax	; met zéro en 7c09 et 7c0a
000000B9  40                inc ax
000000BA  8844FF            mov [si-0x1],al	; met 1 en 7c04 (1=lba_mode)
000000BD  894402            mov [si+0x2],ax	; met 0x0001 en 7c07
000000C0  C7041000          mov word [si],0x10	; met 0x0010 en 7c05
000000C4  668B1E5C7C        mov ebx,[0x7c5c]    ; récupère l'emplacement (partie basse du qword) de core.img
000000C9  66895C08          mov [si+0x8],ebx    ; sauvegarde cet emplacement pour le charger
000000CD  668B1E607C        mov ebx,[0x7c60]    ; récupère la partie haute du qword de core.img
000000D2  66895C0C          mov [si+0xc],ebx    ; sauvegarde
000000D6  C744060070        mov word [si+0x6],0x7000  ; adresse de segment du tampon
000000DB  B442              mov ah,0x42
000000DD  CD13              int 0x13		; fonction extended read
000000DF  7205              jc 0xe6		; si erreur
000000E1  BB0070            mov bx,0x7000
000000E4  EB76              jmp short 0x15c

; traitement sans extension chs_mode
000000E6  B408              mov ah,0x8
000000E8  CD13              int 0x13		; récupère les paramètres du disque (numéro maxi tête, numéro maxi secteur, numéro maxi cylindre)
000000EA  730D              jnc 0xf9		; si pas d'erreur
000000EC  F6C280            test dl,0x80
000000EF  0F84D000          jz word 0x1c3
000000F3  BE937D            mov si,0x7d93	; pointe sur la chaine "Hard Disk" 
000000F6  E98200            jmp word 0x17b

000000F9  660FB6C6          movzx eax,dh	; numéro maxi de têtes
000000FD  8864FF            mov [si-0x1],ah	; met zéro en 7c04
00000100  40                inc ax		; nombre de têtes
00000101  66894404          mov [si+0x4],eax	; sauvegardé en 7c09
00000105  0FB6D1            movzx dx,cl		; 2bits pour cylindre+nombre max de secteurs
00000108  C1E202            shl dx,0x2		; décale de 2 bits vers la gauche
0000010B  88E8              mov al,ch		; 8 bits de poids faible n° maxi cylindre
0000010D  88F4              mov ah,dh		; 2 bits de poids fort cylindre
0000010F  40                inc ax		; nb de cylindres
00000110  894408            mov [si+0x8],ax	; sauvegardé en 7c0d
00000113  0FB6C2            movzx ax,dl		; copie le numéro maxi de secteurs (x4)
00000116  C0E802            shr al,0x2		; numéro maxi de secteurs=nb secteurs
00000119  668904            mov [si],eax	; sauvegardé
0000011C  66A1607C          mov eax,[0x7c60]
00000120  6609C0            or eax,eax		; teste si zéro
00000123  754E              jnz 0x173           ; erreur de géométrie

; Convertit adresse linéaire en tête, secteur et cylindre
00000125  66A15C7C          mov eax,[0x7c5c]    ; charge l'adresse LBA de l'emplacement de core.img (valeur basse qword)
00000129  6631D2            xor edx,edx		; met à zéro
0000012C  66F734            div dword [si]	; divise l'adresse LBA par le nombre de secteurs par piste
0000012F  88D1              mov cl,dl		; reste de la division = numéro secteur (commencement à 0)
00000131  31D2              xor dx,dx
00000133  66F77404          div dword [si+0x4]	; divise par le nombre de têtes - donne un nb de cylindres 
00000137  3B4408            cmp ax,[si+0x8]  ; compare au nombre de cylindres du disque
0000013A  7D37              jnl 0x173		; supérieur au nombre de cylindres du disque - erreur
0000013C  FEC1              inc cl		; rajoute 1 car les n°secteurs commencent à 1
0000013E  88C5              mov ch,al		; 8 bits de poids faible n°cylindre
00000140  30C0              xor al,al
00000142  C1E802            shr ax,0x2		; décale de 2 bits à droite (les bits 6 et 7 pour poids fort cylindre)
00000145  08C1              or cl,al		; modifie les bits 7 et 6 de cl en conséquence (poids fort cylindre)
00000147  88D0              mov al,dl		; numéro de tête = reste division
00000149  5A                pop dx
0000014A  88C6              mov dh,al		; numéro de tête
0000014C  BB0070            mov bx,0x7000
0000014F  8EC3              mov es,bx
00000151  31DB              xor bx,bx		; tampon en 7000:0000
00000153  B80102            mov ax,0x201	; fonction ah=02h = lecture secteur, al=01 = 1 secteur
00000156  CD13              int 0x13		; lit un secteur
00000158  721E              jc 0x178		; erreur de lecture
0000015A  8CC3              mov bx,es

; Copie des données chargées (partie commune mode CHS ou LBA)
0000015C  60                pushaw
0000015D  1E                push ds
0000015E  B90001            mov cx,0x100
00000161  8EDB              mov ds,bx		; charge le segment du buffer
00000163  31F6              xor si,si		; ds:si pointe sur 7000:0000
00000165  BF0080            mov di,0x8000
00000168  8EC6              mov es,si		; es:di pointe sur 0000:8000
0000016A  FC                cld
0000016B  F3A5              rep movsw		; copie le contenu du buffer de 7000:0000-7000:01FF en 0000:8000-0000:81FF
0000016D  1F                pop ds
0000016E  61                popaw
0000016F  FF265A7C          jmp word near [0x7c5a]; saute en 0000:8000 (car 7c5a contient 8000) - début de core.img


00000173  BE8E7D            mov si,0x7d8e	; pointe sur la chaine "Geom"
00000176  EB03              jmp short 0x17b
00000178  BE9D7D            mov si,0x7d9d	; pointe sur la chaine "Read"
0000017B  E83400            call word 0x1b2
0000017E  BEA27D            mov si,0x7da2	; pointe sur la chaine "Error"
00000181  E82E00            call word 0x1b2
00000184  CD18              int 0x18
00000186  EBFE              jmp short 0x186	; boucle sans fin

; Chaînes messages d'erreur
00000188                db "GRUB ",0
0000018E  		db "Geom",0
00000193  		db "Hard Disk",0
0000019D  		db "Read",0
000001A2		db " Error",13,10,0

; Affiche caractère
000001AB  BB0100            mov bx,0x1
000001AE  B40E              mov ah,0xe
000001B0  CD10              int 0x10		; affiche un caractère

; Affiche une chaine
000001B2  AC                lodsb
000001B3  3C00              cmp al,0x0
000001B5  75F4              jnz 0x1ab		; teste la fin de la chaine
000001B7  C3                ret


L'emplacement de la LBA où se trouve core.img est indiquée en 5c (8 octets).
Ici nous avons la LBA qui vaut 1, soit le secteur qui suit le mbr. Dans le cas d'un partitionnement GPT cet emplacement est occupé par l'en-tête GPT et n'est plus disponible pour core.img. Il faudra donc mettre l'adresse LBA de la partition bios_boot pour le boot en mode bios sur un disque GPT


Le programme chargé en mémoire à l'adresse 0000:7c00 commence par l'instruction jmp short 0x65 qui saute directement à l'adresse 065

Différentes parties sont visibles :

de 065 à 095 - Routine vérifie que le boot s'effectue à partir du disque dur
de 096 à 0b3 - Définit l'emplacement des paramètres de chargement du prochain secteur et le mode CHS ou LBA selon les capacités du bios
de 0b4 à 0e5 - Charge le premier secteur de core.img en mode LBA (interruption bios int13/ah=42h)
de 0e6 à 0f8 - Récupère les paramètres du disque et gère les erreurs
de 0f9 à 124 - Sauvegarde ces paramètres dans la zone des paramètres de chargement
de 125 à 15b - Convertit l'adresse LBA de core.img dans le mode CHS et charge ce secteur dans le tampon (interruption bios int13/ah=02h)
de 15c à 172 - Recopie le secteur chargé à son nouvel emplacement et saute à cet emplacement (première partie de core.img, soit diskboot.img)
de 173 à 187 - Affichage des messages d'erreur
de 1ab à 1b0 - Affichage de caractères
de 1b2 à 1b7 - Affichage des chaînes des messages d'erreurs

Voir aussi



Contributeurs principaux : Nasman.