1 The boot sector
The first sector of the volume. You recognise it by the OEM ID NTFS at offset 3.
NTFS counts in clusters (LCN = logical cluster number), so the two values to read first are the cluster size
and the cluster where the $MFT starts.
What ntfsinfo -m and ntfsls say about this volume
2 MFT records
Every file and folder has a record of 1024 bytes in the MFT, numbered from 0. Records 0–15 are reserved for the
system files: 0 is $MFT itself, 1 $MFTMirr, 2 $LogFile, 5 the root
directory, 6 $Bitmap (cluster allocation), and so on. User files start at record 64 on this volume.
A record starts with the signature FILE and a header, followed by a list of attributes, and
ends with FF FF FF FF. Record 0 describes the MFT itself, including where all of its clusters are:
Fixups
Before a record is written, NTFS replaces the last two bytes of each 512-byte sector with the update sequence number and saves the real bytes in the update sequence array. If a record's sectors don't all carry the same number, the write was torn. When reading a record by hand, put the real bytes back first. The decoder does that, so check the fixup rows above to see which bytes it replaced.
3 Attributes
Each attribute has a type, a length (to find the next one), and either resident content (inside the record) or non-resident content (in clusters, described by data runs). The most important ones:
0x10 $STANDARD_INFORMATION: four timestamps and the DOS attributes. Explorer shows these.0x30 $FILE_NAME: name, parent directory and four more timestamps, set by the kernel.0x80 $DATA: the content. A file can have several: unnamed is the main stream, named ones are alternate data streams.
Resident data
A file this small (57 bytes) is stored inside its MFT record. It has no clusters at all:
4 Data runs and alternate data streams
For larger files the $DATA attribute is non-resident. Its content is a list of data runs. Each run starts
with a header byte: the low nibble gives the size of the length field, the high nibble the size of the offset field.
The offset is signed and relative to the previous run's LCN; a 00 byte ends the list.
This record also has a second, named $DATA attribute: Zone.Identifier. Windows adds this
alternate data stream to downloaded files and records where they came from. It is invisible in Explorer and
lost when the file is copied to FAT.
5 Try it yourself
Answer from the hex views above. Numbers can be typed in decimal or hex (0x…).
At which byte offset of the volume does MFT record 65 (report.bin) start?
$MFT offset + N × record size. Both come from the boot sector (0x30 and 0x40).
$MFT LCN 04 00 00 00 00 00 00 00 = 4, cluster size 512 × 8 = 4,096, so $MFT is at 4 × 4,096 = 0x4000. Clusters per record F6 = −10 → 210 = 1,024 bytes.
0x4000 + 65 × 1,024 = 0x4000 + 0x10400 = 0x14400. The record header confirms it: record number 41 00 00 00 = 65.
How many bytes long is the main (unnamed) data stream of report.bin?
Look in the non-resident $DATA attribute (type 80 00 00 00 at record +0x158). The real size is 8 bytes at +0x30 of the attribute.
Attribute start +0x158 + 0x30 = +0x188: 20 4E 00 00 00 00 00 00 = 0x4E20 = 20,000 bytes.
The allocated size is 5 clusters = 20,480 bytes, so the last cluster has 480 bytes of slack. The $FILE_NAME copy of the size is 0 here: it is often not updated, so read the size from $DATA.
A data run has a 2-byte length field and a 3-byte offset field. What is its header byte?
Low nibble = size of the length field, high nibble = size of the offset field.
High nibble 3 (offset), low nibble 2 (length): 0x32. The whole run is then 1 + 2 + 3 = 6 bytes long.
Compare report.bin's run 21 05 69 01: 1-byte length (05), 2-byte offset (69 01).
Where are the 57 bytes of readme.txt (record 64) stored?
Check the non-resident flag at +0x08 of its $DATA attribute.
The $DATA attribute at record +0x158 has non-resident flag 00, content size 39 00 00 00 = 57 and content offset 18 00.
The text starts at +0x158 + 0x18 = +0x170 (53 6D 61 6C 6C = "Small"). The file has no clusters at all.
These are the data runs of report.bin (21 05 69 01, then the 00 end marker). Edit them and watch the $DATA attribute below:
- Change the length
05to0A. The run now claims 10 clusters, more than the allocated size. - Change the offset
69 01to6A 01. By how many bytes does the data move? - Change the header
21to22. The same bytes are now split differently:05 69becomes the length and01 00the offset. One wrong nibble, and every later number is wrong.