File System - MBR Parser

2022. 1. 5. 14:59보안 연구/Forensics

반응형

본 티스토리 블로그는 PC에 최적화되어 있습니다.

 

안녕하세요! ICMP입니다!

포렌식을 공부한다면 빠질 수 없는 주제인 File System!!!

파일 시스템을 공부하면서 구조체를 헥스 에디터로 따라가면서 공부하는 것도 좋지만 직접 바이너리 값을 파싱 하는 프로그램을 직접 구현해 보는 것도 이해하는데 상당히 도움 되었습니다.

 

이번 포스팅에서는 제가 파이썬으로 간단하게 구현한 MBR Parser 구조를 간단하게 설명드리도록 하겠습니다.

 

코드를 보기 전, 연속된 바이너리 값을 읽어서 조건문으로 출력만 할 뿐, 해당 파일의 손상 여부에 따른 카빙(복구 기능)은 코드에 적용하지 않았으니 참고 바랍니다.

 

기능을 구현하는 데 있어서 필요 이상으로 코드를 분리해서 지저분한 느낌이 들기도 하지만... MBR 구조를 파싱 하는 구역 단위로 함수 기능을 분리하여 코드를 작성하였습니다.

 

1. Main.py

- 파싱 할 파일의 경로를 입력받는 부분으로 MBR_parser 함수를 호출하는 부분입니다.

import MBR

file_dir = str(input("파일 경로를 입력해 주세요. : "))
MBR(file_dir)
 

 

2. MBR_parser.py

MBR structure
 

파일 경로를 입력받아 바이너리 모드로 open 한 다음, 바이너리를 읽고, MBR 파일의 구조 순서에 맞추어 각각의 구조를 파싱 하는 함수들을 순차적으로 호출합니다.

import Boot_code
import Partition
import Signature

def parser(dir_addr):
    
    #MBR_Structure
    #1. Boot_Code (0 ~ 445 byte)
    #2. Partition (446 ~ 510 byte)
    #3. Signature (511 ~ 512 byte)

    with open(dir_addr, "rb") as f:
        Boot_code.parsing(f)
        
        for i in range(4):
            Partition.parsing(f, i+1)
            print('\n')
        Signature.parsing(f)
        
    f.close()
 

 

2-1. Boot_code

MBR 파일 구조 문서를 보면, 처음 446Byte는 Boot 코드가 저장되어 있습니다.

이들은 운영체제가 POST 과정을 마치고 저장 장치의 맨 처음 섹터가 호출되는데, 이때 실행됩니다.

 

해당 부분의 주 기능은 파티션 테이블 정보를 확인하여 부팅 가능한 파티션이 어디 있는지를 찾아 알려주는 기능을 하는데, 제가 구현한 파서에서는 특별한 기능 없이 그냥 부트 코드를 바이트로 보여주는 기능만 탑재하였습니다.

def parsing(f):
    print("Boot Code : ",end='')
    print(str(f.read(456))+'\n\n')
 

 

특별한 기능은 없으나, 추후에 추가될 기능을 생각해서 그냥 파일을 따로 분리하였으며, 아래는 재연 화면입니다.

 

지금은 파일 확장자와 시그니처 정보를 대조하여 파티션 정보를 가지고 있는 파일이 맞는지 검증하는 로직이 없어서 그냥 아무 파일을 이용해 열리는지만 확인하였습니다.

 

 

2-2. Partition.py

파티션 테이블은 총 4개의 파티션의 정보를 저장하고 있기에 반복문을 이용하여 해당 바이너리 값만 읽어드리면 간단하게 처리가 가능합니다.

아래는 파티션 테이블이 가지는 정보를 시각화한 것이며, 해당 부분을 해석하여 보여주는 코드도 같이 작성하였습니다.

 

1) Check_bootFlag

- 파티션 테이블의 첫 번째 구조체가 가지는 값으로, 해당 파티션이 부팅 가능 여부를 저장하고 있습니다.

- 부팅이 가능할 경우 0x80, 부팅이 불가능할 경우 0x00 값을 저장하고 있습니다.

def Check_BootFlag(f):
    bootFlag = f.read(1)
    print(" - Boot Flag : ",end='')
    print(bootFlag,end='')
    if(bootFlag == b'\x80'):
        print(", 부팅 가능합니다.")
    elif(bootFlag == b'\x00'):
        print(", 부팅 불가능합니다.")
    else:
        print("값이 손상되었습니다.")
 

 

2) Start_CHS

- CHS는 Cylinder-Head-Sector의 약자로 실린더, 헤드, 섹터 기반의 물리적 주소 지정 방식으로 배정?된 파티션의 시작 주소를 저장하고 있습니다.

- 검색해 본 결과, 해당 부분은 저장 매체의 용량 증가로 인해 현재는 거의 사용되고 있지 않다고 합니다.

- 또한, 해당 주소는 리틀엔디언 방식으로 저장되어 있기 때문에 실제 주소를 보기 위해서는 빅 엔디언으로 변환한 후, 문자열로 이어붙여주는 수작업을 진행하였습니다...

(필자의 코드가 엉망이기에 여러분들이 수정 부탁드립니다...)

def Start_CHS(f):
    size_info = '0x' + struct.pack('<l',int(f.read(3).hex(),16)).hex()[:-2]
    print(" - start address of CHS : " + size_info)
 
 

3) Type_partition

- 말 그대로 파티션의 파일시스템 종류를 보여주는 부분입니다.

- 값의 종류가 무진장 많기 때문에 추후 특정 값에 해당하는 파일시스템 종류를 보여주도록 수정할 필요가 있다.

def Type_partition(f):
    type_partition = f.read(1)
    print(" - Type of Partition : ",end='')
    print(type_partition,end='')
    
    if(type_partition == b'\x05'):
        print(", DOS 3.3+ 확장 파티션 입니다.")
    elif(type_partition == b'\x0f'):
        print(", 확장 LBA 파티션 입니다.")
    else:
        print(", 값이 손상되었습니다.")
 

 

4) End_CHS

def End_CHS(f):
    addr_info = '0x' + struct.pack('<l',int(f.read(3).hex(),16)).hex()[:-2]
    print(" - end address of CHS : " + addr_info)
 

- CHS 방식의 주소일 경우 파티션이 끝나는 지점의 주소를 저장하고 있는 값을 파싱 합니다.

 

5) Start_LBA

def Start_LBA(f):
    addr_info = ''
    #addr_info = '0x' + struct.pack('<l',int(f.read(4).hex(),16)).hex()[:-2]
    print(" - start address of LBA : " + str(f.read(4)))
 

- 주소 지정 방식이 LBA인 경우 파티션의 시작 주소를 저장하고 있는 부분이며 위와 동일하게 파싱 하는 코드도 거의 같습니다.

 

 

6) Size_sector

def Size_Sector(f):
    size_info = ''
    size_info = '0x' + struct.pack('<l',int(f.read(4).hex(),16)).hex()[:-2]
    print(" - Size of Sector : " + str(int(size_info,16)*512) + "Kb")
 

- 섹터의 사이즈를 파싱 하는 부분이며, 파티션 섹터를 구하는 법은 파싱 된 값에 512를 곱하면 됩니다.

 

2-3. Signature.py

 

def parsing(f):
    sign = f.read(2)
    
    print(" - MBR Signature : ",end='')
    if(sign == b'\x55\xAA'):
        print(sign)
    else:
        print("시그니처 값이 훼손되었습니다.")
 

- 여기는 특별한 부분은 없이 시그니처만 비교하는 코드이며, 비교적 간단합니다.

 

 

아래는 해당 코드 실행 화면입니다.

 

파싱 테스트를 진행하는 파일이 비정상적인 파일이어서 대부분 파일이 손상되었다고 표시되는군요...

코드 전반적으로 기능 부분이나 로직 관점에서도 부족한 부분이 많기 때문에 참고용으로만 사용하는 것이 좋을 듯합니다.

 

 

아래에 해당 파이썬 코드를 업로드해 드릴 테니 참고 바랍니다.

 

이상! ICMP였습니다!

긴 글 읽어주셔서 감사합니다!

 

반응형