참고

https://digitalforensicmaster.tistory.com/entry/Digital-Forensic-FAT32-File-System-Detailed-Analysis

 

[Digital Forensic] FAT32 File System Detailed Analysis

FAT32FAT32(File Allocation Table 32): 파일 시스템의 한 종류로, 4byte(32bit)의 파일 할당 테이블을 사용하여 FAT32라는 이름을 가졌으며, 1996년에 마이크로소프트에 의해 도입되었다. 넓은 호환

digitalforensicmaster.tistory.com

 

FAT32(File Allocation Table 32)

  • 파일 시스템의 한 종류로, 4byte(32bit)의 파일 할당 테이블을 사용하여 FAT32라는 이름을 가졌으며, 1996년에 마이크로소프트에 의해 도입
  • 넓은 호환성으로 인해 다양한 운영체제 및 장치에서 널리 사용되었으나, 현재에는 더 큰 파일과 볼륨을 지원하는 NTFS나 exFAT같은 더 현대적인 파일 시스템에 점차 대체되고 있음

FAT32 Structure

  • FAT32는 크게 세 개의 주요 영역으로 나눌 수 있음
  1. Reversed Area : FAT32 파일 시스템의 시작 부분으로, Boot Sector(VBR)를 포함하고 있어 해당 파티션을 올바르게 인식하는 데 사용
  2. FAT Area : 파일이나 디렉토리가 해당 볼륨에 저장될 때 디스크에 할당된 클러스터의 정보를 포함하는 영역
    • FAT Area 내부에서 Entry 형태로 클러스터가 할당되며, 이 정보는 디지털 포렌식 관점에서 파일이나 디렉토리의 저장 위치 및 무결성을 확인하는데 중요
  3. Data Area : FAT32 파일 시스템의 가장 큰 영역으로, 파일이나 디렉토리의 실제 데이터가 저장되는 곳

1. Reserved Area

  • Reserved Area는 Boot Sector와 FSINFO 두 가지 영역으로 나뉨

1) Boot Sector

  • 운영 체제가 컴퓨터를 부팅할 때 필요한 정보를 담고 있고, BIOS Parameter Block, Boot Code, Signature으로 나뉨

  1. BIOS Parameter Block(BPB)
    • 파일 시스템의 기본적인 정보를 담고 있으며, 드라이브의 구조, 파일 시스템의 크기, 섹터 크기 등을 정의하고, 시스템이 올바르게 액세스 할 수 있게 하는 중요한 메타데이터를 제공
      Offset Size(Byte) Information
      0x00~02 3 Jump Boot Code
      0x03~0A 8 OME Name(ASCII)
      0x0B~0C 2 Bytes Per Sector(BPS)
      0x0D 1 Sector Per Cluster
      0x0E~0F 2 Reserved Sector Count
      0x20~23 4 Total Sector
      0x24~27 4 FAT Size
      0x43~46 4 Volume ID
  2. Boot Code
    • 부트 코드는 시스템이 부팅 될 때 실행되는 코드로, 볼륨을 읽고 부팅 프로세스를 시작하는 데 필요한 지침 포함
  3. Signature
    • Boot Sector의 끝에 위치하며, ‘0x55 AA’의 2Byte 값으로 구성되어 파일 시스템의 무결성을 검증하고 올바른 볼륨임을 확인하는 데 사용

2) FSINFO(File System Information)

  • Boot Sector 다음 섹터에 위치하며, 디스크의 남은 공간과 할당되지 않은 클러스터 등의 정보를 저장

Offset Size(Byte) Information
0x00~03 4 Signature(RRaA)
0x04~483 480 Unused
0x484~487 4 Signature(rrAa)
0x488~491 4 Num of Free Cluster
0x492~495 4 Next Free Cluster
0x496~508 14 Unused
0x508~511 2 Signature(0x55 AA)

2. FAT Area

  • 파일 혹은 디렉토리의 데이터를 저장할 때, 디스크 상에 할당되는 클러스터의 정보를 엔트리 형태로 저장하는 영역
  • FAT #1과 FAT #2 라는 두 개의 영역으로 구성되며 FAT #2는 FAT #1의 백업 본으로써 내용은 동일하며 FAT #1 의 다음 섹터부터 시작

  • FAT Area는 각각 4byte 크기의 엔트리들로 구성되어 있음
  • 0번째 엔트리(Media Type)와 1 번째 엔트리(Partition Status)는시스템에 의해 사용되는 특별한 엔트리이고, 2 번째 엔트리부터는 파일이나 디렉토리가 디스크에 저장될 때 할당받는 디스크의 클러스터 정보 나타냄
    • Root Directory의 데이터 저장 시작 클러스터가 2 번째 클러스터 엔트리이기 때문!!

Cluster Entry

  • 파일 혹은 디렉토리의 데이터를 저장할 때, 디스크상에 할당되는 클러스터 체인의 다음 클러스터 정보를 저장하여 다음 클러스터로의 연결 상태를 나타내는 데 사용되는 Entry
  • 만약 n번째 클러스터부터 데이터 저장이 시작된 파일의 시작 Cluster Entry는 n+1로 다음 클러스터 번호를 가리킴
  • 데이터의 마지막을 의미하는 값은 0xFF FF FF 0F 로 파일에 할당된 마지막 클러스터를 의미

FAT #1은 VBR의 BPB 영역에서 “Reserved Sector Count” 정보를 참고하여 “VBR 섹터 + Reserved Sector Count”를 계산하면 접근할 수 있고, 추가적으로 FAT #2는 BPB 영역에서 “FAT Size” 정보를 참고하여 “FAT #1 섹터 + FAT Size”를 계산하면 접근 가능

FAT #1은 Reserved Area 다음 섹터부터 존재하고, FAT #2는 FAT #1의 다음 섹터부터 존재하기 때문에, FAT32의 구조를 도식화 해보면 이해 하기 쉬움

Media Type Partition Status Cluster 2 Cluster 3
Cluster 4 Cluster 5 Cluster 6 Cluster 7
Cluster 8 Cluster 9 Cluster 10 Cluster 11
Cluster 12 Cluster 13 Cluster 14 Cluster 15

 

Entry Value Information
0x00 00 00 00 Unassigned(available)
0x(Entry Number) Allocated(in use)
0xFF FF FF 0F End of Marker
0xFF FF FF F7 Bad Cluster

3. Data Area

  • 파일이나 디렉토리 데이터가 실제로 저장되는 곳
  • 해당 영역의 시작 부분에는 Root Directory의 데이터가 저장되며, 이후 디렉토리와 파일의 데이터는 ‘Directory Entry’ 형태로 순차적으로 저장

  • Directory Entry는 파일이나 디렉토리의 이름 길이에 따라 두 가지 유형 존재
  • 7byte 이하인 경우는 ‘Regular Directory Entry’로, 8byte 이상인 경우는 ‘Long name Directory Entry’로 저장

1. Short Name Directory Entry

  • 파일 혹은 디렉토리 이름이 7Byte 이하인 경우 생성되는 Entry로, 32Byte의 크기를 가짐
  • 파일 혹은 디렉토리 이름, 확장자, 속성 플래그 값, 메타데이터, 클러스터 주소, 크기 등의 정보를 포함

2. Long Name Directory Entry

  • 파일 혹은 디렉토리 이름이 8 Byte 이상인 경우 2개의 Long Name Directory Entry와 하나의 Short Name Directory Entry가 생성되어 총 3개의 Entry가 파일의 이름을 저장함

FAT32 파일 시스템 구조 파싱 프로그램 작성

import struct

def parsing(file_path):
    with open(file_path, 'rb') as f:
        
        print("# MBR\\n")
        print(f"@ Partition 0")
        f.read(446)  # MBR의 첫 446바이트를 읽어옴
            
        # Boot Flag
        bootFlag = f.read(1)
        print("Boot Flag: 0x" + bootFlag.hex())
            
        # CHS Addr (Starting)
        chs_addr = f.read(3)
        print("Starting CHS Addr: 0x" + chs_addr.hex())
        
        # Part Type
        part_type = f.read(1)
        print("Part Type: 0x" + part_type.hex())
        
        # CHS Addr (Ending)
        end_chs_addr = f.read(3)
        print("Ending CHS Addr: 0x" + end_chs_addr.hex())
        
        # Starting LBA Addr
        starting_lba_addr = f.read(4)
        starting_lba_value = struct.unpack("<I", starting_lba_addr)[0]
        print(f"Starting LBA Addr: {starting_lba_value} sector")
        
        # Size in Sectors
        size_in_sectors = struct.unpack("<I", f.read(4))[0]
        print(f"Size in Sector: {size_in_sectors}")
        
        print("\\n-----MBR Check Complete-----\\n")
        
        # Reserved Area and Boot Sector Info
        f.seek(starting_lba_value * 512)  # LBA 주소를 바이트로 변환해 이동
        print(f"@ Partition: 0")
        print("# Reserved Area")
        print(f"sector: {starting_lba_value}")
        
        # Jump Boot Code
        jump_boot_code = f.read(3)
        print("Jump Boot Code: " + ' '.join(f"{byte:02x}" for byte in jump_boot_code))
        
        # OEM ID
        oem_id = f.read(8)
        print(f"OEM ID: {oem_id.decode('ascii')} / {' '.join(f'{byte:02x}' for byte in oem_id)}")
        
        # Bytes Per Sector
        bytes_per_sector = struct.unpack("<H", f.read(2))[0]
        print(f"Bytes Per Sector: {bytes_per_sector}")
        
        # Sectors Per Cluster
        sectors_per_cluster = struct.unpack("<B", f.read(1))[0]
        print(f"Sectors Per Cluster: {sectors_per_cluster}")
        
        # Reserved Sector Count
        reserved_sector_count = struct.unpack("<H", f.read(2))[0]
        print(f"Reserved Sector Count: {reserved_sector_count}")
        
        # FAT32 Size
        f.seek(11, 1)  
        fat_size = struct.unpack("<I", f.read(4))[0]
        print(f"FAT32 Size: {fat_size} sectors")
        
        # FAT Area Calculation
        fat_area_start = starting_lba_value + reserved_sector_count
        print(f"\\n# FAT Area")
        print(f"sector (FAT Area #1): {fat_area_start}")
        
        # Backup FAT Area Calculation
        backup_fat_area_start = fat_area_start + fat_size
        print(f"Backup FAT Area (FAT Area #2): {backup_fat_area_start} sector")
        
        # Media Type
        f.seek(fat_area_start * 512)
        media_type = f.read(4)
        print("Media Type: " + ' '.join(f"{byte:02x}" for byte in media_type))
        
        #Partition status
        PST = f.read(4)
        print("Partition status: " + ' '.join(f"{byte:02x}" for byte in PST))
        
        # Data Area Calculation
        data_area_start = backup_fat_area_start + fat_size
        print(f"\\n# Data Area")
        print(f"sector: {data_area_start}")
      
parsing("./etst.001")