File system · Lezione 3 di 8
NTFS: la MFT, i suoi record e attributi
12 min di lettura
NTFS è il file system presente su quasi tutti i volumi Windows che si esamineranno, e la sua Master File Table (MFT) descrive ogni file presente: nome, cartella padre, dimensione, timestamp e posizione del contenuto. Questa lezione illustra come individuare la MFT, leggere un record byte per byte, decodificare la posizione dei dati di un file e stabilire che cosa un record dimostra e che cosa non dimostra.
Il settore di avvio: individuare la MFT
Il primo settore di un volume NTFS è il settore di avvio; una copia di backup è conservata nell'ultimo settore del volume. Campi principali:
| Offset | Dimensione | Campo | Valore tipico |
|---|---|---|---|
| 0x03 | 8 | ID OEM | NTFS (NTFS seguito da quattro spazi) |
| 0x0B | 2 | Byte per settore | 512 |
| 0x0D | 1 | Settori per cluster | 8 (un cluster da 4.096 byte) |
| 0x28 | 8 | Settori totali nel volume | variabile |
| 0x30 | 8 | Cluster iniziale di $MFT | variabile |
| 0x38 | 8 | Cluster iniziale di $MFTMirr | variabile |
| 0x40 | 1 | Dimensione del record MFT | 0xF6 (negativo, quindi 2^10 = 1.024 byte) |
| 0x48 | 8 | Numero di serie del volume | variabile |
| 0x1FE | 2 | Firma di avvio | 0x55 0xAA |
I valori sono in formato little-endian; i numeri di cluster (LCN) sono conteggiati dall'inizio del volume. Il byte della dimensione del record è con segno: un valore negativo n indica 2^|n| byte, quindi 0xF6 (−10) indica 1.024. Esempio: con cluster da 4.096 byte e $MFT al cluster 786.432, la MFT inizia a 786.432 × 4.096 = 3.221.225.472 byte dall'inizio del volume.
Tutto è un file
In NTFS, le strutture del file system stesso sono file con record MFT, come i documenti degli utenti; la MFT descrive se stessa nel record 0. I primi 16 record (da 0 a 15) sono riservati ai metadati e i record da 0 a 11 contengono questi file:
| Record | Nome | Funzione |
|---|---|---|
| 0 | $MFT | La MFT stessa; il suo $DATA contiene tutti i record |
| 1 | $MFTMirr | Backup almeno dei primi quattro record MFT |
| 2 | $LogFile | Journal delle transazioni per le modifiche ai metadati |
| 3 | $Volume | Nome del volume, versione NTFS, flag dirty |
| 4 | $AttrDef | Definizioni dei tipi di attributo |
| 5 | . (directory radice) | La cartella di livello più alto del volume |
| 6 | $Bitmap | Stato di allocazione di ogni cluster |
| 7 | $Boot | Il settore di avvio e il codice di avvio |
| 8 | $BadClus | Cluster contrassegnati come danneggiati |
| 9 | $Secure | Descrittori di sicurezza condivisi |
| 10 | $UpCase | Tabella delle maiuscole per il confronto dei nomi |
| 11 | $Extend | Cartella per i file di metadati introdotti successivamente |
$Extend contiene $UsnJrnl (il journal delle modifiche), $ObjId (ID degli oggetti), $Quota (dati delle quote) e $Reparse (punti di analisi). A differenza dei record da 0 a 11, questi file non hanno numeri di record fissi. I record da 12 a 15 sono riservati e non vengono utilizzati per i file degli utenti. La lezione 1.4 tratta $LogFile e $UsnJrnl.
Anatomia di un record MFT
Ogni record ha normalmente una dimensione di 1.024 byte e inizia con la firma ASCII FILE. Una firma BAAD contrassegna un record risultato danneggiato, in genere dopo il fallimento di un controllo fixup (vedere sotto). Campi dell'intestazione:
| Offset | Dimensione | Campo |
|---|---|---|
| 0x00 | 4 | Firma FILE |
| 0x04 | 2 | Offset dell'array della sequenza di aggiornamento |
| 0x06 | 2 | Numero di voci in tale array |
| 0x08 | 8 | Numero di sequenza di $LogFile (LSN) |
| 0x10 | 2 | Numero di sequenza |
| 0x12 | 2 | Numero di collegamenti fisici |
| 0x14 | 2 | Offset del primo attributo |
| 0x16 | 2 | Flag: 0x01 in uso, 0x02 directory |
| 0x18 | 4 | Dimensione utilizzata del record |
| 0x1C | 4 | Dimensione allocata del record (1.024) |
| 0x20 | 8 | Riferimento al file del record di base (0 se questo è il record di base) |
| 0x2C | 4 | Numero di questo record (NTFS 3.1, Windows XP e successivi) |
Valori comuni dei flag: 0x0000 file eliminato, 0x0001 file in uso, 0x0002 directory eliminata, 0x0003 directory in uso. Il record di un file eliminato conserva il proprio contenuto fino al riutilizzo, perciò nomi e timestamp spesso sopravvivono (lezione 1.8).
Fixup
L'array della sequenza di aggiornamento (array fixup) rileva i record scritti solo parzialmente. Prima della scrittura, NTFS salva nell'array gli ultimi due byte di ciascun segmento da 512 byte e li sovrascrive con un numero di sequenza di aggiornamento di due byte. In un record da 1.024 byte vengono sostituiti i byte a 0x1FE e 0x3FE, quindi l'array contiene tre valori: il numero e due coppie originali. Durante la lettura, entrambe le posizioni devono corrispondere al numero e i valori originali vengono ripristinati. Una vista esadecimale grezza mostra i valori sostituiti, quindi occorre ripristinare i fixup prima di interpretare dati che attraversano tali posizioni; gli strumenti forensi lo fanno automaticamente.
Riferimenti ai file e numero di sequenza
NTFS fa riferimento a un record mediante un riferimento al file di 8 byte: i 48 bit meno significativi sono il numero del record e i 16 bit più significativi sono il numero di sequenza. Windows incrementa il numero di sequenza quando libera un record, quindi un record eliminato, e qualsiasi suo successivo riutilizzo, presenta un valore superiore a quello dei riferimenti creati quando esisteva il file originale.
Esempio: i byte 2B 1A 00 00 00 00 03 00 letti in formato little-endian corrispondono a 0x0003000000001A2B. Il numero del record è 0x1A2B = 6.699 e il numero di sequenza è 3.
I riferimenti compaiono in $FILE_NAME (la cartella padre), nelle voci degli indici delle directory, nei record di $UsnJrnl e negli artefatti Windows come i file LNK. Se un riferimento indica il record 6.699 con sequenza 3, ma il record 6.699 è ora in uso con sequenza 5, il record è stato riutilizzato per un file diverso. Il confronto di entrambi i numeri evita di attribuire il nome, il percorso o i timestamp di un nuovo file a un vecchio riferimento.
Attributi
Dopo l'intestazione segue un elenco di attributi. Ciascuno inizia con un codice di tipo di 4 byte e una lunghezza totale di 4 byte, seguiti da un flag di non residenza, dalla lunghezza e dall'offset di un nome facoltativo, da flag e da un ID dell'attributo. L'elenco termina con il marcatore 0xFFFFFFFF; i byte successivi possono ancora contenere dati di un utilizzo precedente del record.
| Tipo | Nome | Contenuto |
|---|---|---|
| 0x10 | $STANDARD_INFORMATION | Quattro timestamp, flag degli attributi DOS, ID di sicurezza, USN |
| 0x20 | $ATTRIBUTE_LIST | Posizione degli attributi quando non rientrano in un solo record |
| 0x30 | $FILE_NAME | Nome, riferimento alla cartella padre, quattro timestamp, dimensioni, spazio dei nomi |
| 0x40 | $OBJECT_ID | GUID dell'oggetto di 16 byte (utilizzato per il tracciamento dei collegamenti) |
| 0x50 | $SECURITY_DESCRIPTOR | Diritti di accesso; su NTFS 3.x solitamente conservati centralmente in $Secure |
| 0x80 | $DATA | Contenuto del file; senza nome è il flusso principale, con nome è un flusso di dati alternativo |
| 0x90 | $INDEX_ROOT | Radice di un indice di directory (sempre residente) |
| 0xA0 | $INDEX_ALLOCATION | Ulteriori record di indice memorizzati nei cluster |
| 0xB0 | $BITMAP | Quali record di indice sono in uso |
Quando gli attributi non rientrano in un solo record, NTFS utilizza record di estensione: $ATTRIBUTE_LIST del record di base indica dove si trova ciascun attributo e ogni record di estensione contiene il riferimento al record di base a 0x20.
Due serie di timestamp
$STANDARD_INFORMATION ($SI) e $FILE_NAME ($FN) memorizzano ciascuno quattro valori FILETIME: conteggi a 64 bit di intervalli di 100 nanosecondi a partire da 1601-01-01 00:00:00 UTC. Nell'ordine: creazione, modifica (del contenuto), modifica della voce MFT e accesso, spesso abbreviati in MACB (modified, accessed, changed, born: modifica del contenuto, accesso, modifica dei metadati, creazione). Devono essere riportati in UTC, ad esempio 2026-03-14T09:26:53.1234567Z. Le due serie vengono aggiornate da operazioni diverse e $SI è più facile da alterare, quindi confrontarle è utile; il modulo 3 tratta il modo in cui ciascuna cambia.
Spazi dei nomi di $FILE_NAME
Ogni $FN ha un byte dello spazio dei nomi: 0 POSIX (distinzione tra maiuscole e minuscole, qualsiasi carattere Unicode tranne / e il carattere nullo), 1 Win32 (nome lungo), 2 DOS (nome breve 8.3), 3 Win32 e DOS (il nome lungo è già un nome 8.3 valido). Se la creazione dei nomi 8.3 è abilitata, Quarterly Report.docx ha due attributi $FN: Win32 e DOS (QUARTE~1.DOC). Un collegamento fisico aggiunge un altro $FN con la propria cartella padre. I campi delle dimensioni di $FN non vengono mantenuti aggiornati, quindi la dimensione va ricavata da $DATA.
Dati residenti e non residenti
Il contenuto di piccole dimensioni viene memorizzato all'interno del record: l'attributo $DATA è residente. In un record da 1.024 byte può occupare fino a circa 700 byte, a seconda della lunghezza del nome e degli altri attributi. Un file residente non utilizza cluster, quindi il suo contenuto si trova nella MFT, non nell'area dati. Il contenuto più grande rende $DATA non residente: l'attributo conserva le dimensioni e una lista di run, mentre il contenuto viene collocato nei cluster. Non viene riportato all'interno del record se il file in seguito si riduce.
L'intestazione di un attributo non residente registra la dimensione allocata, la dimensione reale (logica) e la dimensione inizializzata. La differenza tra la dimensione allocata e quella reale è lo spazio residuo del file (lezione 1.2).
Decodificare un run di dati
Una lista di run è una serie di run, ciascuno costituito da un tratto contiguo di cluster, che termina con un byte 0x00. Ogni run inizia con un byte di intestazione: il nibble meno significativo indica il numero di byte che contengono la lunghezza del run, quello più significativo il numero di byte che contengono l'offset iniziale. L'offset è con segno e relativo all'inizio del run precedente (quello del primo run è relativo al cluster 0).
Esempio svolto: la lista di run 31 04 A0 3F 01 00.
- Intestazione 0x31: il nibble meno significativo 1 indica una lunghezza di 1 byte; il nibble più significativo 3 indica un offset di 3 byte.
- Lunghezza:
04= 4 cluster. - Offset:
A0 3F 01letto in formato little-endian è 0x013FA0 = 1 × 65.536 + 0x3F × 256 + 0xA0 = 65.536 + 16.128 + 160 = 81.824. Il run inizia all'LCN 81.824 e copre gli LCN da 81.824 a 81.827. - Byte successivo
00: fine della lista di run.
Con cluster da 4.096 byte, il contenuto inizia a 81.824 × 4.096 = 335.151.104 byte dall'inizio del volume e sono allocati 4 × 4.096 = 16.384 byte. Se la dimensione reale è di 13.000 byte, lo spazio residuo del file è 16.384 − 13.000 = 3.384 byte.
Un file frammentato ha più run. Un run successivo 21 08 00 F0 ha un offset di 2 byte pari a 0xF000, che come valore con segno è −4.096, quindi inizia all'LCN 81.824 − 4.096 = 77.728. Un run con dimensione dell'offset pari a 0 è sparso: non vengono allocati cluster e la sua lettura restituisce zeri.
Directory e indice $I30
Una directory ha il flag directory e un indice denominato $I30, ordinato per nome. Le directory piccole rientrano in un $INDEX_ROOT residente; quelle più grandi aggiungono $INDEX_ALLOCATION, che punta a record di indice (firma INDX, normalmente 4.096 byte) nei cluster, con $BITMAP che contrassegna quelli in uso. Ogni voce contiene il riferimento al file figlio e una copia del suo $FN, inclusi i timestamp. Le voci di file eliminati o rinominati possono sopravvivere nello spazio inutilizzato dei record INDX.
Analizzare la MFT con gli strumenti
Le raccolte di triage includono solitamente il file $MFT. Un comando tipico è MFTECmd.exe -f "E:\Triage\C\$MFT" --csv E:\Out --csvf mft.csv. Il CSV include EntryNumber, SequenceNumber, InUse, ParentPath, FileName e colonne separate per i timestamp $SI e $FN, come Created0x10 e Created0x30.
Con un'immagine, The Sleuth Kit opera direttamente sui record (-o indica il settore iniziale della partizione): fsstat -o 2048 image.raw riporta la dimensione dei cluster e la posizione della MFT; istat -o 2048 image.raw 6699 mostra il record 6.699 con il suo numero di sequenza, entrambe le serie di timestamp, gli attributi e i cluster di $DATA; icat -o 2048 image.raw 6699 restituisce il suo contenuto; ffind -o 2048 image.raw 6699 restituisce il suo percorso. Per i risultati principali, occorre verificare l'output dello strumento confrontandolo con il record grezzo.
Che cosa dimostra un record MFT e che cosa non dimostra
Un record mostra che sul volume era descritto un file con quel nome, quella cartella padre, quella dimensione e quella posizione dei dati, se il record è allocato e gli ultimi timestamp scritti. Un riferimento corrispondente (numero del record e numero di sequenza) collega un altro artefatto a quello specifico file.
Non mostra chi ha creato o aperto il file e non dimostra che i timestamp siano veritieri: l'orologio potrebbe essere stato errato, il software può modificare i valori $SI e gli aggiornamenti dell'ultimo accesso possono essere disabilitati o ritardati. I cluster di un record eliminato potrebbero essere stati riallocati, quindi occorre controllare $Bitmap e il contenuto prima di attribuire dati al file eliminato. È necessario cercare riscontri in $UsnJrnl, $LogFile, nelle voci $I30 e negli artefatti Windows.
Focus sull'esame: È necessario saper convertire un numero di record e un numero di sequenza in un riferimento al file di 8 byte e viceversa, decodificare un run di dati ricavandone LCN iniziale, lunghezza e offset in byte e leggere i flag di utilizzo e di directory. Le attività pratiche richiedono l'ora di creazione $SI o $FN di un file in UTC, il percorso completo o la dimensione: occorre quindi sapere quale attributo contiene ciascuna informazione e confermare che il record non sia stato riutilizzato.
Punti chiave
- Il settore di avvio NTFS (ID OEM
NTFS) fornisce la dimensione dei settori, la dimensione dei cluster, la dimensione dei record MFT e i cluster iniziali di $MFT e $MFTMirr. - I record da 0 a 11 sono file di metadati fissi ($MFT, $MFTMirr, $LogFile, $Volume, $AttrDef, directory radice, $Bitmap, $Boot, $BadClus, $Secure, $UpCase, $Extend).
- Ogni record inizia con
FILE, ha normalmente una dimensione di 1.024 byte, utilizza fixup alla fine di ciascun segmento da 512 byte ed elenca attributi che terminano con 0xFFFFFFFF. - Un riferimento al file è composto da un numero di record di 48 bit e da un numero di sequenza di 16 bit; un numero di sequenza diverso significa che il record è stato riutilizzato.
- I file piccoli sono residenti all'interno del record; quelli più grandi utilizzano liste di run con lunghezza e offset relativo con segno.
- $SI e $FN contengono ciascuno quattro timestamp FILETIME in UTC; cambiano in modi diversi, trattati nel modulo 3.