Ogni file NTFS ha un attributo $DATA (tipo 0x80) che contiene il suo contenuto. Per la maggior parte dei file, $DATA è non residente: l'header dell'attributo porta una runlist che punta a cluster run altrove sul volume. Per i file piccoli, i byte vivono inline nel record MFT stesso. Quel secondo caso è uno degli artefatti più utili nella forense NTFS, ed è quello che continuo a vedere dimenticato dagli analisti.
Quanto piccolo è abbastanza piccolo
Un record MFT è di 1.024 byte. Da quello sottrai l'header da 56 byte, l'array di fixup, $STANDARD_INFORMATION (circa 72 byte), almeno un $FILE_NAME (variabile, circa 90 byte per un nome tipico), il padding e la sentinella di fine attributi. Ciò che resta è il budget utilizzabile per $DATA residente e per ogni altro attributo residente.
In pratica la soglia è di circa 700 byte di dati. Alcuni riferimenti dicono fino a ~750 byte per file con nomi molto corti e senza altri attributi residenti; altri scendono (~580 byte) per file con nomi più lunghi o attributi aggiuntivi. La soglia non è una costante; dipende da cos'altro c'è nel record.
File che ci stanno, e che vedo costantemente:
- Piccoli file di testo: note, liste di cose da fare, bozze.
- Piccoli file di configurazione:
.ini,.cfg, piccoli.json, piccoli.xml. - One-liner PowerShell e script brevi.
- Piccoli file
.lnkdi collegamento (la maggior parte dei LNK è abbastanza piccola; vedi forense LNK per cosa codificano). - Snippet di esportazione del registro e file
.reg. - Piccoli file
.bate.cmd. - File di certificato in formato PEM che sono brevi.
- File vuoti e placeholder a zero byte. I "dati" sono zero byte, e
$DATAè banalmente residente. - Molti attributi
$INDEX_ROOTper piccole directory.
Perché conta
Un attributo $DATA residente può essere recuperato senza leggere il resto del disco. Se hai una copia di $MFT, hai già:
- Il contenuto completo dei piccoli file di testo.
- Molti dati
$INDEX_ROOTper le directory (le voci della directory, elencate inline). - Alternate data stream brevi (attributi
$DATAnominati con contenuto piccolo). - Reparse point e destinazioni di symlink (il percorso di destinazione vive in
$REPARSE_POINT, anch'esso tipicamente residente).
Quella è una quantità sorprendente di prove dentro un singolo estratto MFT da 200 MB a 2 GB. Per piccoli file eliminati, i byte residenti sopravvivono anche dopo che l'area dati è stata sovrascritta, perché l'area dati non è mai stata toccata in primo luogo; i byte vivevano nella MFT.
Ho recuperato:
- Un loader PowerShell da 320 byte da un record eliminato i cui cluster di dati non sono mai esistiti.
- Un
.lnkda 480 byte che puntava alla directory di staging dell'attaccante, eliminato tre settimane prima dell'acquisizione. - Un
.configda 700 byte di un servizio che era stato disinstallato. La directory era sparita; il record MFT stava nello slot 412.000 intatto.
La MFT era l'unico posto dove quei file esistevano ancora.
Residente non è stabile
Quando un file residente cresce oltre lo spazio libero del record, NTFS lo converte a non residente. I dati si spostano sui cluster e $DATA diventa una runlist. La conversione viene registrata in $LogFile mentre il cambiamento accade.
Il contrario può accadere tecnicamente (un file ridotto sotto la soglia potrebbe ridiventare residente), ma Windows raramente lo fa di propria iniziativa. Una volta che $DATA è non residente, tende a restarlo. Se trovi un record con $DATA residente per un file che sai essere stato più grande, è insolito e vale la pena indagare; suggerisce manipolazione deliberata o un percorso di codice non comune che ha ridotto il file sul posto.
Rilevare i dati residenti durante il parsing
Ogni parser MFT espone un flag "residente" nell'header dell'attributo $DATA. L'output CSV di MFTECmd lo ha come colonna booleana. Il JSON di mft_dump ha header.is_resident. libmft lo espone sull'oggetto attributo. istat del Sleuth Kit lo mostra come parte del listato di attributi.
Il parser nel browser di questo sito filtra a IN_USE=0 con $DATA residente in due clic. Quel filtro è il primo che eseguo su qualsiasi MFT estratta in cui il recupero di file piccoli conta.
Come appaiono i byte
Per un $DATA residente, l'header dell'attributo è seguito da un header residente di 16 byte (dimensione del contenuto, offset del contenuto, flag indicizzato, padding) e poi dai byte grezzi. I byte sono esattamente ciò che c'era sul disco nel file: testo nella codifica usata dal file, contenuto binario nella sua forma nativa. Nessuna trasformazione.
Per un piccolo file di testo in UTF-8, puoi copiare i byte fuori e leggere il file in qualunque editor. Per contenuti binari (una piccola PE, un .pyc compilato, un oggetto serializzato), i byte sono ugualmente usabili; trattali come un file estratto dal volume.
I limiti
La maggior parte dei file non sono residenti. Su un'installazione Windows normale, ben meno del 5% dei file utente sono residenti. Il resto è tutto Office, ogni file di cache del browser, ogni programma in \Program Files\, ogni download. Il recupero residente è una nicchia ad alto valore, non la modalità di default.
La soglia non è esatta. Non assumere "i file sotto i 700 byte sono recuperabili". La soglia effettiva dipende dagli altri attributi del record. Verifica sulla specifica MFT che stai analizzando.
Residente non sopravvive a compressione o cifratura verso un file sufficientemente grande. I file cifrati con EFS usano $LOGGED_UTILITY_STREAM per i metadati di cifratura e $DATA è il testo cifrato. Se il testo cifrato è abbastanza piccolo da essere residente, hai i byte cifrati; ti serve comunque la chiave EFS per leggerli.
Un workflow che usa questo
La prossima volta che hai un caso di recupero di file eliminato e il file che ti interessa è piccolo:
- Acquisisci
$MFTdall'host (o da uno snapshot VSS, se ne hai uno recente). - Parsala. Tira fuori ogni record dove
IN_USE=0e il$DATAprimario è residente. - Filtra per regex
$FILE_NAMEse conosci il nome del file. Filtra per riferimento al padre se conosci la directory. - Ispeziona i byte
$DATAresidenti inline. Il file è proprio lì.
Cinque minuti di lavoro per un recupero che il carving per firma avrebbe richiesto ore (e probabilmente fallito comunque per piccoli file frammentati).
Letture aggiuntive
- Le note sugli attributi residenti del progetto linux-ntfs. Spiegazione chiara del layout dell'header residente.
- Microsoft, Algoritmo di allocazione NTFS. Riferimento ufficiale per come viene presa la decisione residente/non residente.
istatdel Sleuth Kit per ispezionare singoli record e vedere il flag residente in contesto.