부팅의 시작 – MBR 이해

MBR(Master Boot Record)는 운영체제가 부팅 할 때POST(Power on Self-Test)과정을 마친후 저장매체의 첫 번째 섹터를 호출을 시도하는데, 이 때 MBR이 존재한다면, MBR의 부트 코드가 수행되게 된다.

 

[그림]MBR이 존재하지 않으면, 부팅 에러가 발생한다

 

부트 코드의 역할은 파티션 테이블에서 부팅 가능한 파티션을 찾아, 해당 파티션의 부트 섹터를 호출해 주는 역할을 한다. 부팅 가능한 파티션이 없을 경우 미리 정의된 메시지를 호출하게 되는데, 윈도우 구조 이해에 MBR이 필요한 이유는 이 영역을 조작하는 실제 악성코드가 존재 가능성과, 해당 영역의 문제로 부팅이 되지 않는 경우가 발생할 수 있어, 이는 복구하기 위해 실무에서도 확인이 필요한 영역이다.

이 MBR의 영역의 정보를 이해한다면, 차후 부팅 관련 문제점 분석 및 해결에 큰 도움이 될 것이라 판단된다.

자 그럼 계속해서 부팅 단계는 아래 그림과 같으 MBR을 통해 부팅 가능 파티션에서 운영체제의 부트 섹터를 실행하여 커널을 실행하며 윈도우가 실행되게 된다.

이 흐름을 간단히 그림으로 표현하면 아래와 같다.

 

[그림] MBR과 운영체제의 연결 관계

여기서는 MBR영역과 NTFS이 부트섹터(Boot Sector)를 먼저 분석하도록 하겠다. MBR은 각 물리디스크의 섹터 0에 저장되며, 각 영역은 아래와 같이 구분된다.

 

[그림]MBR 구조

 

위 그림과 같이 MBR영역은 부트코드(Boot code)와 파티션테이블(Partition Table), 시그니쳐(Signature)로 구성되어 있다.

먼저 부트코드는 파티션테이블에서 부팅 가능한 파티션을 찾아 해당 파티션의 부트 섹터를 호출해 주는 역할을 하게 된다. 부팅이 불가능할 경우 부트코드에 저장된 에러 메시지를 출력하게 된다.

그리고 파티션 테이블에는 파티션 관련 정보 정보들이 16byte씩 4개로 구성된다. (디스크 관리자에서 기본 볼륨을 구성시, 최대 4개의 볼륨까지만 구성이 가능한 이유이기도 하다.)

 

 

위 0x12c부터 0x17a까지 부팅 에러 메시지가 출력되는 부분이며 그 위 부분들이 부트 코드(16비트 어셈블리)이다.

그리고 아래 0x1be부터 0x1cd까지 첫번째 파티션 영역으로, 이부분을 조금더 자세히 확인해 보자.

0x0~0x0: 부팅 지시자로 부팅 가능 여부를 확인할수 있다. 00인경우 부팅불가, 0x80인 경우 부팅이 가능한 파티션이다.

0x1~0x3: CHS 시작 주소

0x4~0x4: 파티션 타입으로, 0x07이면 NTFS, 0x05이면 DOS형을 의미 한다.

0x5~0x7: CHS 마지막 주소

0x8~0x11: LBA 시작 주소

0xC~0xF: 전체 섹터수

이렇게 제공된 MBR정보를 토대로 부팅가능한파티션 정보를 확인후 해당 볼륨의 첫 섹터로 넘어가게 된다.

 

그럼 MBR 영역을 bin 파일로 추출하여, 이를 IDA에 연결후 분석을 진행해 보자.

MBR영역 및 부트섹터 영역의 어셈블리 코드를 확인하기 위해서는 먼저 DskProbe 프로그램을 이용하여, MBR영역을 선택후 디스크에 저장한다.

이는 DiskProbe에서 물리디스크를 핸들을 선택후 활성화 하여 섹터0을 읽기가 가능하다.

DiskProbe는 윈도우 시디의 Support 디렉토리에서 받아서 설치하거나, http://www.microsoft.com/download/en/details.aspx?id=18546 에서 다운로드 할 수 있다.

(설치시 Complete, 즉 전체 설치로 진행하여야 한다.)

DiskProbe를 실행 이후, Drive à Open Physical Drive 메뉴를 선택하고, MBR 영역이 존재하는 디스크를 마우스 더블 클릭으로 선택한 이후 “Set Acitve” 메뉴를 눌려 디스크를 활성화 한다.

 

[그림]DiskProbe를 이용하여, MBR 영역이 존재하는 디스크를 활성화

 

이후 Sector à Read 메뉴를 통해서 첫번째 섹터를 읽어들인후 해당 섹터를 디스크에 저장하도록 하자.

 

[그림] 첫번째 섹터를 읽어들이자

 

이 방법 이외에도 이후 역분석에서 진행할 예정인 디스크 데이터 수집툴인 dd.exe를 이용해서도 쉽게 MBR영역을 가져올 수 있다.(dd툴 사용법은 3장 역분석에서 다룬다.)

이렇게 추출한 바이너리 파일을 IDA와 연결해 분석을 진행하면 된다.

 

[그림] IDA에서 16비트 코드로 MBR.bin을 로드

 

그럼 위 그림과 같이 알수 없는 데이터 구조로 나오는데, 이를 코드 타입 시작위치에서 “C”키를 누르거나 메뉴 “Edit”에서 Code를 선택해주면 코드형식으로 화면을 재구성하여, 정상적으로 어셈블리 할 수 있다.

그럼 이제 각 코드 내용을 분석해보자.

 

메모리 0x7C00 위치에 로드된 후 0x 7C1B부터 485 바이트를 0x061B 위치로 복사한다.

(MBR의 메모리 로드 위치는 0x7C00으로 일정하다.)

 

seg000:0000 seg000 segment byte public ‘CODE’ use16

seg000:0000 assume cs:seg000

seg000:0000 assume es:nothing, ss:nothing, ds:nothing, fs:nothing, gs:nothing

seg000:0000 xor ax, ax

seg000:0002 mov ss, ax

seg000:0004 mov sp, 7C00h

seg000:0007 sti

seg000:0008 push ax

seg000:0009 pop es

seg000:000A push ax

seg000:000B pop ds

seg000:000C cld

seg000:000D mov si, 7C1Bh

seg000:0010 mov di, 61Bh ;7C1Bh부터 61Bh로 복사

seg000:0013 push ax

seg000:0014 push di

seg000:0015 mov cx, 1E5h ;485바이트가 될때까지 반복

seg000:0018 rep movsb

seg000:001A retf

[내용]MBR 부트 코드를 메모리에 로드

 

즉 위 내용은 부팅작업을 진행하기 위해 메모리에 부팅 코드를 로드하는 코드이다. 그리고 이후 현재 디스크에서 부팅 가능한 파티션 테이블 엔트리를 찾는 과정이 진행되는데, 파티션 테이블의 첫번째 값이 0x80을 기지면 부팅이 가능한 파티션으로 판단하게 된다.

 

seg000:001B mov bp, 7BEh

seg000:001E mov cl, 4

seg000:0020

seg000:0020 loc_20: ;

seg000:0020 cmp [bp+0], ch

seg000:0023 jl short loc_2E ;부팅 가능시 loc_2E로 이동

seg000:0025 jnz short loc_3A ;부팅이 불가능한 경우 loc_3A로 이동한다.

seg000:0027 add bp, 10h

seg000:002A loop loc_20

seg000:002C int 18h ; TRANSFER TO ROM BASIC

seg000:002C ; causes transfer to ROM-based BASIC (IBM-PC)

seg000:002C ; often reboots a compatible; often has no effect at all

[내용] 파티션 테이블 엔트리가 있는지 확인

 

이후 부팅이 불가능할 경우 각 상황에 맞는 메시지를 위한 조건과 부팅이 가능한경우 진행하는 코드들이 아래와 같이 존재한다. loc_4F를 통해 부팅 가능한 경우 sub_9B을 호출하게 된다.

 

seg000:0030 loc_30: ;

seg000:0030 add si, 10h

seg000:0033 dec cx

seg000:0034 jz short loc_4F ; 부팅 가능한 엔트리가 1개인 경우 loc_4F로 이동

seg000:0036 cmp [si], ch

seg000:0038 jz short loc_30

seg000:003A

seg000:003A loc_3A: ; 부팅이 불가능한 경우 실행되는 코드

seg000:003A mov al, ds:7B5h ; ds:7B5h에는 “Invalid partition table”값이 위치한다.

seg000:003D

seg000:003D loc_3D: ; 오류에 맞는 메시지를 찾는 코드

seg000:003D ;

seg000:003D mov ah, 7

seg000:003F mov si, ax ;

seg000:0041

seg000:0041 loc_41: ;

seg000:0041 lodsb

seg000:0042

seg000:0042 loc_42: ;

seg000:0042 cmp al, 0

seg000:0044 jz short loc_42

seg000:0046 mov bx, 7

seg000:0049 mov ah, 0Eh

seg000:004B int 10h ; DATA XREF: sub_9B+8r

seg000:004B ;

seg000:004B ; – VIDEO – WRITE CHARACTER AND ADVANCE CURSOR (TTY WRITE)

seg000:004B ; AL = character, BH = Display page (alpha modes)

seg000:004B ; BL = foreground color (graphics modes)

seg000:004D jmp short loc_41

seg000:004F

seg000:004F loc_4F: ;

seg000:004F mov [bp+10h], cl

seg000:0052 call sub_9B ; 해당 파티션의 시작 위치를 찾는 코드를 실행한다.

seg000:0055 jnb short loc_81 ; cf가 0인지 확인

seg000:0057

seg000:0057 loc_57: ;

seg000:0057 inc byte ptr [bp+10h]

seg000:005A cmp byte ptr [bp+4], 0Bh

seg000:005E jz short loc_6B

seg000:0060

seg000:0060 loc_60: ;

seg000:0060 cmp byte ptr [bp+4], 0Ch

seg000:0064 jz short loc_6B

seg000:0066 mov al, ds:7B6h

seg000:0069 jnz short loc_3D ;

seg000:006B

seg000:006B loc_6B: ;

seg000:006B ;

seg000:006B add byte ptr [bp+2], 6

seg000:006F add word ptr [bp+8], 6

seg000:0073 adc word ptr [bp+0Ah], 0

seg000:0077 call sub_9B

seg000:007A jnb short loc_81 ; cf가 0인지 확인

seg000:007C mov al, ds:7B6h

seg000:007F jmp short loc_3D

[내용]부팅 가능 파티션이 있는지 확인

 

sub_9B은 부팅 의해 실행되는 코드로 부팅 가능한 파티션 테이블 엔트리를 읽은 후, 해당 파티션의 시작 위치를 찾는다. 그리고 각 파티션 타입을 확인하여 CHS 주소를 사용할 경우 loc_CA를 실행하고, LBA 주소를 사용할 경우 loc_E6를 실행하게된다.

이를 통해 부팅 가능한 파티션의 첫 섹터(512 bytes)를 읽어서 메모리 0:7C00에 덮어쓰게 된다.

 

seg000:009B sub_9B proc near ;

seg000:009B ;

seg000:009B mov di, 5

seg000:009E mov dl, [bp+0]

seg000:00A1 mov ah, 8

seg000:00A3 int 13h ; DISK – DISK – GET CURRENT DRIVE PARAMETERS (XT,AT,XT286,CONV,PS)

seg000:00A3 ; DL = drive number

seg000:00A3 ; Return: CF set on error, AH = status code, BL = drive type

seg000:00A3 ; DL = number of consecutive drives

seg000:00A3 ; DH = maximum value for head number, ES:DI -> drive parameter

seg000:00A5 jb short loc_CA

seg000:00A7 mov al, cl

seg000:00A9 and al, 3Fh

seg000:00AB cbw

seg000:00AC mov bl, dh

seg000:00AE mov bh, ah

seg000:00B0 inc bx

seg000:00B1 mul bx

seg000:00B3 mov dx, cx

seg000:00B5 xchg dl, dh

seg000:00B7 mov cl, 6

seg000:00B9 shr dh, cl

seg000:00BB inc dx

seg000:00BC mul dx

seg000:00BE cmp [bp+0Ah], dx

seg000:00C1 ja short loc_E6

seg000:00C3 jb short loc_CA

seg000:00C5 cmp [bp+8], ax

seg000:00C8 jnb short loc_E6

seg000:00CA

seg000:00CA loc_CA: ; CHS 주소를 사용할 경우 주소 값을 읽음

seg000:00CA ;

seg000:00CA mov ax, 201h

seg000:00CD mov bx, 7C00h

seg000:00D0 mov cx, [bp+2]

seg000:00D3 mov dx, [bp+0]

seg000:00D6 int 13h ; DISK – READ SECTORS INTO MEMORY

seg000:00D6 ; AL = number of sectors to read, CH = track, CL = sector

seg000:00D6 ; DH = head, DL = drive, ES:BX -> buffer to fill

seg000:00D6 ; Return: CF set on error, AH = status, AL = number of sectors read

seg000:00D8 jnb short locret_12B

seg000:00DA dec di

seg000:00DB jz short locret_12B

seg000:00DD xor ah, ah

seg000:00DF mov dl, [bp+0]

seg000:00E2 int 13h ; DISK – RESET DISK SYSTEM

seg000:00E2 ; DL = drive (if bit 7 is set both hard disks and floppy disks reset)

seg000:00E4 jmp short loc_CA

seg000:00E6

seg000:00E6 loc_E6: ; 확장된 INT 13 명령을 지원여부를 검사하는 코드

seg000:00E6 ; sub_9B+2Dj

seg000:00E6 mov dl, [bp+0]

seg000:00E9 pusha

seg000:00EA mov bx, 55AAh

seg000:00ED mov ah, 41h ; ‘A’

seg000:00EF int 13h ; DISK –

seg000:00F1 jb short loc_129

seg000:00F3 cmp bx, 0AA55h

seg000:00F7 jnz short loc_129

seg000:00F9 test cl, 1

seg000:00FC jz short loc_129

seg000:00FE popa

seg000:00FF

seg000:00FF loc_FF: ; 확장된 INT 13 명령을 지원한 경우 실행, LBA 주소값을 읽음

seg000:00FF pusha

seg000:0100 push 0

seg000:0102 push 0

seg000:0104 push word ptr [bp+0Ah]

seg000:0107 push word ptr [bp+8]

seg000:010A push 0

seg000:010C push 7C00h

seg000:010F push 1

seg000:0111 push 10h

seg000:0113 mov ah, 42h ; ‘B’

seg000:0115 mov si, sp

seg000:0117 int 13h ; DISK –

seg000:0119 popa

seg000:011A popa

seg000:011B jnb short locret_12B

seg000:011D dec di

seg000:011E jz short locret_12B

seg000:0120 xor ah, ah

seg000:0122 mov dl, [bp+0]

seg000:0125 int 13h ; DISK – RESET DISK SYSTEM

seg000:0125 ; DL = drive (if bit 7 is set both hard disks and floppy disks reset)

seg000:0127 jmp short loc_FF

seg000:0129

seg000:0129 loc_129: ;

seg000:0129 ;

seg000:0129 popa

seg000:012A stc

seg000:012B

seg000:012B locret_12B: ;

seg000:012B ;

seg000:012B retn

seg000:012B sub_9B endp

[내용]시작위치 확인과 첫 섹터를 메모리에 덮어 씀

 

call sub_9B를 통해 정상적으로 부팅 가능한 파티션의 첫 섹터(512 bytes)를 읽어서 메모리 0:7C00에 덥어쓰게 되고, 앞서본 MBR 구조에서 시그니처와 비교후 정상이라면 loc_94를 실행해 메모리에 로드된 부트 섹터를 통해 부팅을 계속 진행하게 된다.

 

seg000:0081 loc_81: ;

seg000:0081 ;

seg000:0081 cmp word ptr ds:7DFEh, 0AA55h ; 시그니쳐를 체크한다.

seg000:0087 jz short loc_94 ;정상인 경우 loc_94를 호출

seg000:0089 cmp byte ptr [bp+10h], 0

seg000:008D jz short loc_57

seg000:008F mov al, ds:7B7h

seg000:0092 jmp short loc_3D

seg000:0094

seg000:0094 loc_94: ; 부트섹터를 통해 부팅 작업을 계속한다.

seg000:0094 mov di, sp

seg000:0096 push ds

seg000:0097 push di

seg000:0098 mov si, bp

seg000:009A retf

[내용]정상적으로 부트섹터를 호출하는 코드

 

지금까지 코드로 확인한 MBR 영역을 이제 IDA와 보쉬(Bochs)를 이용하여 MBR 실시간 디버깅을 진행하는 방법에 대해 알아보도록 하자.

MBR영역을 실시간으로 디버깅하기 위해서는 운영체제 부팅 전단계에서부터 디버거가 디버기를 제어할 수있어야 한다. Windbg는 부팅로더에서 디버거를 연결할 수 있기 때문에 이미 MBR영역에 대한 처리가 완료된 상태로 Windbg를 이용한 커널 디버깅 방법으로는 MBR영역을 분석할 수 없다는 점을 유의하자.

Facebook Comments

Leave A Reply

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다.