참고
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는 크게 세 개의 주요 영역으로 나눌 수 있음
- Reversed Area : FAT32 파일 시스템의 시작 부분으로, Boot Sector(VBR)를 포함하고 있어 해당 파티션을 올바르게 인식하는 데 사용
- FAT Area : 파일이나 디렉토리가 해당 볼륨에 저장될 때 디스크에 할당된 클러스터의 정보를 포함하는 영역
- FAT Area 내부에서 Entry 형태로 클러스터가 할당되며, 이 정보는 디지털 포렌식 관점에서 파일이나 디렉토리의 저장 위치 및 무결성을 확인하는데 중요
- Data Area : FAT32 파일 시스템의 가장 큰 영역으로, 파일이나 디렉토리의 실제 데이터가 저장되는 곳
1. Reserved Area
- Reserved Area는 Boot Sector와 FSINFO 두 가지 영역으로 나뉨
1) Boot Sector
- 운영 체제가 컴퓨터를 부팅할 때 필요한 정보를 담고 있고, BIOS Parameter Block, Boot Code, Signature으로 나뉨
- 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
- 파일 시스템의 기본적인 정보를 담고 있으며, 드라이브의 구조, 파일 시스템의 크기, 섹터 크기 등을 정의하고, 시스템이 올바르게 액세스 할 수 있게 하는 중요한 메타데이터를 제공
- Boot Code
- 부트 코드는 시스템이 부팅 될 때 실행되는 코드로, 볼륨을 읽고 부팅 프로세스를 시작하는 데 필요한 지침 포함
- 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")