이전 포스팅에서 MBR(Master Boot Record)의 구조에 대해서 살펴보았다. 이번에는 MBR 영역의 부트 코드(Boot Code)에 대해 분석해보자. MBR이나 VBR(Volume Boot Record) 또는 BS(Boot Sector)에는 컴퓨터가 부팅될 때 처리되는 코드가 들어간다. 부팅 절차는 기본적으로 BIOS에 의해 POST(Power On Self-Test) 과정이 수행되고, 이후에 추가적인 BIOS(SCSI, Video등)가 로드된다. 그리고 POST 과정에 의해 처리된 데이터들도 로드된다.
이러한 하드웨어적인 테스트를 모두 마치게 되면, 주(Primary) 하드디스크 드라이브의 MBR의 부트 코드를 호출한다. 간단히 말해, MBR의 부트 코드는 파티션 테이블에서 부팅 가능한 파티션을 찾아 해당 파티션의 VBR의 부트 코드를 호출하는 역할을 한다. VBR의 부트 코드는 운영체제를 로드하게 된다(윈도우는 NT Loader). 이를 간략히 그림으로 나타내면 다음과 같다. 좀 더 자세한 부팅 절차를 살펴보고 싶다면 이전 글을 참조하기 바란다.
MBR의 부트 코드 영역은 앞서 살펴본 바와 같이 446 바이트 크기이다. 이후에 파티션 테이블과 시그니처가 따라온다. 다음은 필자의 주 드라이브의 MBR 영역을 덤프한 것이다.
해당 부트 코드 영역은 BIOS에서 해석할 수 있는 16비트 기계어 코드들로 이루어져 있기 때문에 디스어셈블러를 이용하면 부트 코드에 대한 어셈블리 코드를 얻을 수 있다. 다음은 IDA Pro 를 통해 해당 영역의 코드를 살펴본 것이다.
MBR 부트 코드 분석
다음은 필자의 MBR 부트 코드 영역을 분석한 내용이다.
먼저, POST 과정을 마치면 부트로더에 의해 LBA 0(MBR)의 512 바이트가 메모리에 로드된다.
로드되는 위치는 항상 0000:7C00 이다.
MBR이 메모리 0:7C00 위치에 로드된 후 7C1B부터 485 바이트를 061B 위치로 복사한다.현재 코드에서 27 바이트를 사용하므로, 나머지 485 바이트만 복사한다.RELOCATION : 0:7C1B to 0:061B as 1E5h (except for 0:7C00 - 7C1A)0000:7C00 xor ax, ax ;set zero accumulator reg0000:7C02 mov ss, ax ;set zero stack seg reg0000:7C04 mov sp, 7C00 ;set stack ptr to 0000:7C000000:7C07 sti ;enable interrupts0000:7C08 push ax ;AX=0 at this point0000:7C09 pop es ;ES(Extra Segment) now 00000:7C0A push ax ;AX=0 at this point0000:7C0B pop ds ;DS(Data Segment) now 00000:7C0C cld ;clear direction flag0000:7C0D mov si, 7C1B ;SI(Src Index) now 7C1Bh0000:7C10 mov di, 061B ;DI(Des Index) now 061Bh0000:7C13 push ax ;set up AX and DI0000:7C14 push di ; form jump to 0000:061B0000:7C15 mov cx, 1E5 ;move as 1E5(512-27=485) bytes0000:7C18 repz movsb ;move a portion of MBR from 7C1B to 061B0000:7C1A retf ;jump to 0000:061B---------------------------------------------------------------------------부팅 가능한 파티션 테이블 엔트리를 찾는다. 파티션 테이블 첫 바이트가 0x80 값을 가지면부팅 가능한 파티션이다. 0x80을 부팅 가능한 값으로 사용한 이유는 일반적으로 첫 번째하드디스크를 부팅 가능한 드라이브로 사용하는데, 첫 번째 하드디스크의 드라이브 인덱스 값이0x80 이기 때문이다. 부팅가능한 엔트리를 찾았다면 0:062E로 점프한다.4개의 엔트리 모두부팅가능하지 않다면 "PRESS A KEY TO REBOOT"를 출력한다.SECARH FOR AN BOOTABLE ENTRY :0000:061B mov bp, 07BE ;point to first partition table entry0000:061E mov cl, 04 ;maximum table entries are 40000:0620 cmp [bp+00], ch ;(ch=0)is this a non-bootable entry?0000:0623 jl 062E ;no, this is bootable entry0000:0625 jnz 063A ;jump to error message navigator0000:0627 add bp, 0010 ;next to partition table entry0000:062A loop 0620 ;jump to 0620h unless cl=00000:062C int 18 ;None of them were bootable ;GO TO ROM BASIC (only available IBM-PC) ;just many BIOS display ;"PRESS A KEY TO REBOOT"---------------------------------------------------------------------------부팅 가능한 엔트리를 찾은 경우, 나머지 엔트리가 부팅 가능하지 않은지 검사한다.기본적으로 MBR에 의해 부팅되는 경우, 부팅 가능한 엔트리는 하나만 존재해야 한다.부팅 가능한 엔트리가 하나 이상인 경우, 0:063A 로 점프한다.부팅 가능한 엔트리가 하나인 경우, 0:064F 로 점프한다.FOUND THE BOOTABLE ENTRY, MAKE SURE ONLY ONE BOOTABLE ENTRY :0000:062E mov si, bp ;SI now 07BE---------------------------------------------------------------------------0000:0630 add si, 0010 ;SI now 10(16 bytes)0000:0633 dec cx ;iF CX=0, done here0000:0634 jz 064F ;jump to 064F0000:0636 cmp [si], ch ;(ch=0)is this a non-bootable entry?0000:0638 jz 0630 ;if not zero, fall into the following ; error message routine---------------------------------------------------------------------------부팅 가능한 엔트리를 하나 이상 찾은 경우 "Invalid partition table"을 출력한다.FOUND MORE THAN ONE BOOTABLE ENTRY :0000:063A mov al, [07B5] ;al now [07B5]=2C ;(072C) in the src index reg;following ;display "Invalid partition table"---------------------------------------------------------------------------오류에 맞는 에러 메시지를 출력한다.DISPLAY ERROR MESSAGE :0000:063D mov ah, 07 ;ah now 070000:063F mov si, ax ;ax(07xx) in the src index reg ;xx = 2C,44,63 (if english ver)0000:0641 lodsb ;Load character into AL from [SI]0000:0642 cmp al, 00 ;is this end of message marker?0000:0644 je 0642 ;infinite loop0000:0646 mov bx, 0007 ;0000:0649 mov ah, 0E ;0000:064B int 10 ;VIDEO - WRITE CHARACTER AND ADVANCE CURSOR ;(TTY WRITE) ;AL = character, BH = display page (alpha modes) ;BL = foreground color (graphics modes)0000:064D jmp 0641 ;go back for another message---------------------------------------------------------------------------0000:064F mov [bp+10], cl ;0000:0652 call 069B ;jump to 069B0000:0055 jnb 0681 ;jump to 0681, if CF=0---------------------------------------------------------------------------0000:0657 inc byte ptr [bp+10] ;0000:065A cmp byte ptr [bp+04], 0B ;0000:065E je 066B ;0000:0660 cmp byte prt [bp+04], 0C ;0000:0664 je 066B ;0000:0666 mov al, [07B6] ;al now [07B6] = 44 -> (0744)0000:0669 jne 063D ;display "Error loading operating system"---------------------------------------------------------------------------0000:066B add byte ptr [bp+02], 06 ;0000:066F add word ptr [bp+08], 0006 ;0000:0673 adc word ptr [bp+0A], 0000 ;0000:0677 call 069B ;0000:067A jnb 0681 ;jump to 0681, if CF=00000:067C mov al, [07B6] ;al now [07B6] = 44 -> (0744)0000:067F jmp 063D ;display "Error loading operating system"---------------------------------------------------------------------------시그니처 검사후 시그니처가 올바르지 않다면 에러 메시지를 출력한다.CHECK FOR SIGNATURE : 0000:0681 cmp word ptr [7DFE], AA55 ;check for signature0000:0687 je 0694 ;ok, jump to 06940000:0689 cmp byte ptr [bp+10], 00 ;0000:068D je 0657 ;0000:068F mov al, [07B7] ;al now [07B7] = 63 -> (0763)0000:0692 jmp 063D ;display "Missing operating system"---------------------------------------------------------------------------0000:0694 mov di, sp ;sp is still 7C000000:0696 push ds ;ds now 00000000:0697 push di ;di now 7C000000:0698 mov si, bp ;bp is still 07BE0000:069A retf ;jump to 0000:7C00---------------------------------------------------------------------------부팅 가능한 파티션 테이블 엔트리를 읽은 후, 해당 파티션의 시작 위치를 찾는다.CHS 주소를 사용할 경우 0:06CA로 점프하고, LBA 주소를 사용할 경우 0:06E6로 점프한다.GET DRIVE PARAMETERS :0000:069B mov di, 0005 ;des index now 00050000:069E mov dl, [bp+00] ;0000:06A1 mov ah, 08 ;AH = 08, Get Drive Parameters0000:06A3 int 13 ;DISK - GET CURRENT DRIVE PARAMETERS ;DL = drive number ;Return: CF set on error, AH = status code, ;BL = drive type ;DL = number of consecutive drives ;DH = maximum value for head number, ;ES:DI -> drive parameter0000:06A5 jb 06CA ;jump to 06CA, if CF=1(not successful)0000:06A7 mov al, cl ;0000:06A9 and al, 3F ;0000:06AB cbw ;convert byte to word0000:06AC mov bl, dh ;0000:06AE mov bh, ah ;0000:06B0 inc bx ;0000:06B1 mul bx ;0000:06B3 mov dx, cx ;0000:06B5 xchg dh, dl ;0000:06B7 mov cl, 06 ;0000:06B9 shr dh, cl ;0000:06BB inc dx ;0000:06BC mul dx ;0000:06BE cmp [bp+0A], dx ;0000:06C1 ja 06E6 ;check for extended INT 130000:06C3 jb 06CA ;read sectors from drive0000:06C5 cmp [bp+08], ax ;0000:06C8 jnb 06E6 ;check for extended INT 13---------------------------------------------------------------------------CHS 주소를 사용할 경우 주소 값을 읽는다.부팅 가능한 파티션의 첫 섹터(512 bytes)를 읽어서 메모리 0:7C00에 덮어쓴다.READ DISK SECTORS USING CHS FORMAT : 0000:06CA mov ax, 0201 ;INT 13 AH=020000:06CD mov bx, 7C00 ;bx = memory buffer0000:06D0 mov cx, [bp+02] ;0000:06D3 mov dx, [bp] ;0000:06D6 int 13 ;Read Sectors From Drive ;AL = number of sectors to read, CH = track, ;CL = sector ;DH = head, DL = drive, ES:BX -> buffer to fill ;Return: CF set on error, AH = status, ;AL = number of sectors read0000:06D8 jnb 072B ;return0000:06DA dec di0000:06DB je 072B ;return0000:06DD xor ah, ah ;INT 13 AH=00h0000:06DF mov dl, [bp+00]0000:06E2 int 13 ;Reset Disk Drives0000:06E4 jmp 06CA ;go back for read sectors from drive---------------------------------------------------------------------------확장된 INT 13 명령(LBA를 사용하기 위한)을 지원하는지 검사한다.IBM/MS INT 13 EXTENSIONS - INSTALLATION CHECK : 0000:06E6 mov dl, [bp+00] ;dl = drive number0000:06E9 pusha ;push all registers onto stack0000:06EA mov bx, 55AA ;bx now 55AA0000:06ED mov ah, 41 ;INT 13 AH=410000:06EF int 13 ;Check Extensions Present0000:06F1 jb 0729 ;if not, return with CF set0000:06F3 cmp bx, AA55 ;bx now AA550000:06F7 jne 0729 ;0000:06F9 test cl, 01 ;0000:06FC je 0729 ;0000:06FE popa ;pop alll registers from stack---------------------------------------------------------------------------확장된 INT 13 명령을 지원한 경우 LBA 주소 값을 읽는다.부팅 가능한 파티션의 첫 섹터(512 bytes)를 읽어서 메모리 0:7C00에 덮어쓴다.EXTENDED READ - READS DISK SECTORS USING LBA FORMAT : 0000:06FF pusha ;push all registers onto stack0000:0700 push 00000000:0702 push 00000000:0704 push word ptr [bp+0A]0000:0707 push word ptr [bp+08]0000:070A push 00000000:070C push 7C000000:070F push 00010000:0711 push 00100000:0713 mov ah, 42 ;INT 13 AH=420000:0715 mov si, sp ; using LBA0000:0717 int 13 ;Extended Read Sectors From Drive0000:0719 popa ;0000:071A popa ;0000:071B jnb 072B ;return0000:071D dec di ;0000:071E je 072B ;return0000:0720 xor ah, ah ;INT 13 AH=000000:0722 mov dl, [bp+00] ;0000:0725 int 13 ;Reset Disk Drives0000:0727 jmp 06FF ;go back---------------------------------------------------------------------------0000:0729 popa0000:072A stc ;set carry flag---------------------------------------------------------------------------0000:072B ret
ERROR MESSAGES:0000:0720 32E48A56 00CD13EB D661F9C3 496E7661 *2..V.....a..Inva*0000:0730 6C696420 70617274 6974696F 6E207461 *lid partition ta*0000:0740 626C6500 4572726F 72206C6F 6164696E *ble.Error loadin*0000:0750 67206F70 65726174 696E6720 73797374 *g operating syst*0000:0760 656D004D 69737369 6E67206F 70657261 *em.Missing opera*0000:0770 74696E67 20737973 74656D00 00000000 *ting system.....*
예상한 대로 MBR 영역의 부트 코드는 파티션 테이블 엔트리에서 부팅 가능한 엔트리를 찾은 후, 해당 엔트리 외에 부팅 가능한 엔트리가 추가적으로 있는지 검사한다. 부팅 가능한 엔트리가 하나라면 해당 엔트리를 해석하여 CHS 주소, LBA 주소 사용에 따라 각각 점프하여 해당 주소 값을 읽는다. 최근에는 CHS 주소를 사용하지 않으므로 항상 LBA 루틴으로 점프한다.
이후 주소를 읽어 부팅 가능한 파티션의 첫 번째 섹터(부트 섹터)를 메모리 0:7C00 번지에 로드한다. 부트 섹터에 대한 부트 코드는 추후에 알아보도록 하자.
덧글