컴퓨터에 전원이 들어오면, 바이오스가 BootSector(0x00..)에서 부트로더를 메모리에 적재한다. 원래는 운영체제가 존재했으나, 현대의 운영체제는 512바이트 안에서 해결될 수 없을만큼 거대하고 커다란 용량을 자랑하기에 메모리에 적재된 부트로더가 다시 커널을 메모리에 적재시키는 역활을 한다.
흔히 윈도우를 사용하다보면 , C드라이브 및 D드라이브등으로 하드 디스크가 파티션 되어있다는 것을 볼 수 있다. 각 파티션은 , 독립적으로 쓰이는 개별적 파일 시스템으로 생각해볼 수 있는데 지금부터 파일시스템 디스크 레이아웃과 파일시스템 구현에 대해 알아보자
Master Boot Record , 이하 MBR은 하드디스크 0byte에서 512byte에 존재한다. 한 개의 sector는 512byte이므로 0 번째 sector에 존재한다고 생각해도 무방하다.
첫 512바이트 부트섹터에서 , offset 446부터 64바이트에 PTE가 들어있다.
그럼 한가지 의문이 발생하는데 , 'MBR에서 PTE가 16바이트라는데 그럼 디스크 레이아웃을 4개밖에 생성 못하나?' 라는 의문이 발생할 수 있다. MBR은 이 문제를 Extended Boot Record로 해결하였다. 이는 조금 나중에 알아보도록 하고 , 먼저 PTE의 구조에 대해 살펴보자 .
위의 PTE를 보면 LBA Address of Start를 통해서 파티션의 시작 위치와 , Num of sectors로 해당 파티션의 볼륨을 알아낼 수 있다.
MBR이 4개의 파티션을 나눌 수 밖에 없다는 단점이 존재하므로 , 이를 해결하기 위한 방안이다. 기본적으로 중첩 구조를 사용해서 , 추가적인 Partion Table Entry를 표현할 수 있게 만들었다. 3번째 인덱스의 PTE offset으로 이동할 경우 EBR이 나오게 되고 , MBR과 유사한 형식을 따르고 있다.
계속 디스크 레이아웃이 확장될경우, EBR의 PTE가 다음 EBR을 가르키도록 링크드 리스트구조를 따르고 있다. 첫번째 EBR을 base로 하여, 두번째 EBR부터의 offset을 계산하게된다.
import sys
import struct
_PTE_STRUCT = "<1s3s1s3sII"
_EBR_STRUCT = "<446s16s16s16s16s2s"
_PARTITION_START = 446
_SECTOR_SIZE = 512
def usage():
print("Usage : python mbr_parser.py <image name> ")
types = {
b'\x07' : "NTFS",
b'\x05' : "Extend",
b'\0B' : "FAT32(CHS)",
b'\0C' : "FAT32(LBA)"
}
if len(sys.argv)!=2:
usage()
exit(-1)
image_name = sys.argv[1]
try:
image_file = open("./"+image_name,'rb')
except:
print("can not open file")
exit(-1)
partionTableEntries = []
#Parse Table Entries
for i in range(4):
image_file.seek(_PARTITION_START+ 16*i)
TableEntry = image_file.read(16)
partionTableEntries.append(TableEntry)
partitions = []
#MBR 엔트리 분석
for idx,entry in enumerate(partionTableEntries):
partitionTableEntry = struct.unpack(_PTE_STRUCT,entry)
isActive = partitionTableEntry[0]
CHS_Address = partitionTableEntry[1]
partionType = partitionTableEntry[2]
CHS_Address = partitionTableEntry[3]
LBA_Address = partitionTableEntry[4]
Num_of_Sector = partitionTableEntry[5]
startAddress = LBA_Address*_SECTOR_SIZE
size = Num_of_Sector*_SECTOR_SIZE
if types[partionType]=="Extend":
pivotAddress = startAddress
offset = 0
while True:
nowAddress = pivotAddress+offset
#EBR로 이동 및 Read
image_file.seek(nowAddress) # EBR 섹터 시작 주소
EBR = image_file.read(_SECTOR_SIZE) # 512 byte read
EBR = struct.unpack(_EBR_STRUCT,EBR)
partition = EBR[1]
nextEBR = EBR[2]
# 여기서부터는 상대 주소를 사용해서 file seek
unpacked_partition = struct.unpack(_PTE_STRUCT,partition)
isActive = unpacked_partition[0]
CHS_Address = unpacked_partition[1]
partionType = unpacked_partition[2]
CHS_Address = unpacked_partition[3]
LBA_Address = unpacked_partition[4]
Num_of_Sector = unpacked_partition[5]
startAddress = (nowAddress+LBA_Address)*_SECTOR_SIZE
size = Num_of_Sector*_SECTOR_SIZE
partitions.append([
types[partionType],
str(startAddress),
str(size)
])
if(int.from_bytes(nextEBR,byteorder='big')==0):
break
unpacked_nextEBR = struct.unpack(_PTE_STRUCT,nextEBR)
isActive = unpacked_nextEBR[0]
CHS_Address = unpacked_nextEBR[1]
partionType = unpacked_nextEBR[2]
CHS_Address = unpacked_nextEBR[3]
start_Address = unpacked_nextEBR[4]
Num_of_Sector = unpacked_nextEBR[5]
offset = start_Address*_SECTOR_SIZE
else:
partitions.append([
types[partionType],
str(startAddress),
str(size)
])
for i,partition in enumerate(partitions):
print("index : "+str(i+1))
print(" FileSystem : "+partition[0])
print(" StartAddress : "+partition[1])
print(" Size : "+partition[2])
조금 길기는 하지만 , MBR 이미지 파일을 통해 각 파티션의 정보를 파싱하고 출력해볼 수 있는 코드이다. 구조체와 바이트 스트림을 python으로 다룰려니 상당히 애를 먹었지만 , struct 모듈 사용법을 알게되어서 굉장히 재미있었다.