This page hosts a formal specification of vfat using Kaitai Struct. This specification can be automatically translated into a variety of programming languages to get a parsing library.
meta:
id: vfat
# actually FAT12
xref:
forensicswiki: fat
justsolve: FAT
wikidata: Q190167
tags:
- dos
license: CC0-1.0
ks-version: 0.9
imports:
- /common/dos_datetime
endian: le
bit-endian: le
doc-ref: https://download.microsoft.com/download/0/8/4/084c452b-b772-4fe5-89bb-a0cbf082286a/fatgen103.doc
seq:
- id: boot_sector
type: boot_sector
instances:
fats:
pos: boot_sector.pos_fats
size: boot_sector.size_fat
repeat: expr
repeat-expr: boot_sector.bpb.num_fats
root_dir:
pos: boot_sector.pos_root_dir
size: boot_sector.size_root_dir
type: root_directory
types:
boot_sector:
seq:
- id: jmp_instruction
size: 3
- id: oem_name
type: str
encoding: ASCII
pad-right: 0x20
size: 8
- id: bpb
type: bios_param_block
doc: Basic BIOS parameter block, present in all versions of FAT
- id: ebpb_fat16
type: ext_bios_param_block_fat16
if: not is_fat32
doc: FAT12/16-specific extended BIOS parameter block
- id: ebpb_fat32
type: ext_bios_param_block_fat32
if: is_fat32
doc: FAT32-specific extended BIOS parameter block
instances:
is_fat32:
value: bpb.max_root_dir_rec == 0
doc: |
Determines if filesystem is FAT32 (true) or FAT12/16 (false)
by analyzing some preliminary conditions in BPB. Used to
determine whether we should parse post-BPB data as
`ext_bios_param_block_fat16` or `ext_bios_param_block_fat32`.
pos_fats:
value: bpb.bytes_per_ls * bpb.num_reserved_ls
doc: Offset of FATs in bytes from start of filesystem
ls_per_fat:
value: 'is_fat32 ? ebpb_fat32.ls_per_fat : bpb.ls_per_fat'
size_fat:
value: bpb.bytes_per_ls * ls_per_fat
doc: Size of one FAT in bytes
pos_root_dir:
value: bpb.bytes_per_ls * (bpb.num_reserved_ls + ls_per_fat * bpb.num_fats)
doc: Offset of root directory in bytes from start of filesystem
ls_per_root_dir:
-orig-id: RootDirSectors
value: (bpb.max_root_dir_rec * 32 + bpb.bytes_per_ls - 1) / bpb.bytes_per_ls
doc: Size of root directory in logical sectors
doc-ref: 'FAT: General Overview of On-Disk Format, section "FAT Data Structure"'
size_root_dir:
value: ls_per_root_dir * bpb.bytes_per_ls
doc: Size of root directory in bytes
bios_param_block:
seq:
# Basic BIOS Parameter block, DOS 2.0+
- id: bytes_per_ls
-orig-id: BPB_BytsPerSec
type: u2
doc: Bytes per logical sector
- id: ls_per_clus
-orig-id: BPB_SecPerClus
type: u1
doc: Logical sectors per cluster
- id: num_reserved_ls
-orig-id: BPB_RsvdSecCnt
type: u2
doc: |
Count of reserved logical sectors. The number of logical
sectors before the first FAT in the file system image.
- id: num_fats
-orig-id: BPB_NumFATs
type: u1
doc: Number of File Allocation Tables
- id: max_root_dir_rec
-orig-id: BPB_RootEntCnt
type: u2
doc: |
Maximum number of FAT12 or FAT16 root directory entries. 0
for FAT32, where the root directory is stored in ordinary
data clusters.
- id: total_ls_2
-orig-id: BPB_TotSec16
type: u2
doc: Total logical sectors (if zero, use total_ls_4)
- id: media_code
-orig-id: BPB_Media
type: u1
doc: Media descriptor
- id: ls_per_fat
-orig-id: BPB_FATSz16
type: u2
doc: |
Logical sectors per File Allocation Table for
FAT12/FAT16. FAT32 sets this to 0 and uses the 32-bit value
at offset 0x024 instead.
# FIXME: DOS 3.0 and 3.2 BPBs continuation should follow here
# and they differ from DOS 3.31+ that most modern
# implementations use. We'll skip them for now, as they seem to
# be very rare.
#
# DOS 3.31+ BPB
- id: ps_per_track
-orig-id: BPB_SecPerTrk
type: u2
doc: |
Physical sectors per track for disks with INT 13h CHS
geometry, e.g., 15 for a "1.20 MB" (1200 KB) floppy. A zero
entry indicates that this entry is reserved, but not used.
- id: num_heads
-orig-id: BPB_NumHeads
type: u2
doc: |
Number of heads for disks with INT 13h CHS geometry,[9]
e.g., 2 for a double sided floppy.
- id: num_hidden_sectors
-orig-id: BPB_HiddSec
type: u4
doc: |
Number of hidden sectors preceding the partition that
contains this FAT volume. This field should always be zero
on media that are not partitioned. This DOS 3.0 entry is
incompatible with a similar entry at offset 0x01C in BPBs
since DOS 3.31. It must not be used if the logical sectors
entry at offset 0x013 is zero.
- id: total_ls_4
-orig-id: BPB_TotSec32
type: u4
doc: |
Total logical sectors including hidden sectors. This DOS 3.2
entry is incompatible with a similar entry at offset 0x020
in BPBs since DOS 3.31. It must not be used if the logical
sectors entry at offset 0x013 is zero.
ext_bios_param_block_fat16:
doc: |
Extended BIOS Parameter Block (DOS 4.0+, OS/2 1.0+). Used only
for FAT12 and FAT16.
seq:
- id: phys_drive_num
type: u1
doc: |
Physical drive number (0x00 for (first) removable media,
0x80 for (first) fixed disk as per INT 13h).
- id: reserved1
type: u1
- id: ext_boot_sign
type: u1
doc: |
Should be 0x29 to indicate that an EBPB with the following 3
entries exists.
- id: volume_id
size: 4
doc: |
Volume ID (serial number).
Typically the serial number "xxxx-xxxx" is created by a
16-bit addition of both DX values returned by INT 21h/AH=2Ah
(get system date) and INT 21h/AH=2Ch (get system time) for
the high word and another 16-bit addition of both CX values
for the low word of the serial number. Alternatively, some
DR-DOS disk utilities provide a /# option to generate a
human-readable time stamp "mmdd-hhmm" build from BCD-encoded
8-bit values for the month, day, hour and minute instead of
a serial number.
- id: partition_volume_label
size: 11
type: str
encoding: ASCII
pad-right: 0x20
- id: fs_type_str
size: 8
type: str
encoding: ASCII
pad-right: 0x20
ext_bios_param_block_fat32:
doc: Extended BIOS Parameter Block for FAT32
seq:
- id: ls_per_fat
type: u4
doc: |
Logical sectors per file allocation table (corresponds with
the old entry `ls_per_fat` in the DOS 2.0 BPB).
- id: has_active_fat
type: b1
doc: |
If true, then there is "active" FAT, which is designated in
`active_fat` attribute. If false, all FATs are mirrored as
usual.
- id: reserved1
type: b3
- id: active_fat_id
type: b4
doc: |
Zero-based number of active FAT, if `has_active_fat`
attribute is true.
- id: reserved2
contents: [0]
- id: fat_version
type: u2
- id: root_dir_start_clus
type: u4
doc: |
Cluster number of root directory start, typically 2 if it
contains no bad sector. (Microsoft's FAT32 implementation
imposes an artificial limit of 65,535 entries per directory,
whilst many third-party implementations do not.)
- id: ls_fs_info
type: u2
doc: |
Logical sector number of FS Information Sector, typically 1,
i.e., the second of the three FAT32 boot sectors. Values
like 0 and 0xFFFF are used by some FAT32 implementations to
designate abscence of FS Information Sector.
- id: boot_sectors_copy_start_ls
type: u2
doc: |
First logical sector number of a copy of the three FAT32
boot sectors, typically 6.
- id: reserved3
size: 12
- id: phys_drive_num
type: u1
doc: |
Physical drive number (0x00 for (first) removable media,
0x80 for (first) fixed disk as per INT 13h).
- id: reserved4
type: u1
- id: ext_boot_sign
type: u1
doc: |
Should be 0x29 to indicate that an EBPB with the following 3
entries exists.
- id: volume_id
size: 4
doc: |
Volume ID (serial number).
Typically the serial number "xxxx-xxxx" is created by a
16-bit addition of both DX values returned by INT 21h/AH=2Ah
(get system date) and INT 21h/AH=2Ch (get system time) for
the high word and another 16-bit addition of both CX values
for the low word of the serial number. Alternatively, some
DR-DOS disk utilities provide a /# option to generate a
human-readable time stamp "mmdd-hhmm" build from BCD-encoded
8-bit values for the month, day, hour and minute instead of
a serial number.
- id: partition_volume_label
size: 11
type: str
encoding: ASCII
pad-right: 0x20
- id: fs_type_str
size: 8
type: str
encoding: ASCII
pad-right: 0x20
root_directory:
seq:
- id: records
type: root_directory_rec
repeat: expr
repeat-expr: _root.boot_sector.bpb.max_root_dir_rec
root_directory_rec:
seq:
- id: file_name
size: 11
- id: attrs
size: 1
type: attr_flags
- id: reserved
size: 10
- id: last_write_time
size: 4
type: dos_datetime
- id: start_clus
type: u2
- id: file_size
type: u4
types:
attr_flags:
seq:
- id: read_only
type: b1
- id: hidden
type: b1
- id: system
type: b1
- id: volume_id
type: b1
- id: is_directory
type: b1
- id: archive
type: b1
- id: reserved
type: b2
instances:
long_name:
value: |
read_only
and hidden
and system
and volume_id