← Torna al blog

Dati residenti: minuscoli file che vivono dentro la MFT

· 6 min di lettura

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 .lnk di collegamento (la maggior parte dei LNK è abbastanza piccola; vedi forense LNK per cosa codificano).
  • Snippet di esportazione del registro e file .reg.
  • Piccoli file .bat e .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_ROOT per 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_ROOT per le directory (le voci della directory, elencate inline).
  • Alternate data stream brevi (attributi $DATA nominati 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 .lnk da 480 byte che puntava alla directory di staging dell'attaccante, eliminato tre settimane prima dell'acquisizione.
  • Un .config da 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:

  1. Acquisisci $MFT dall'host (o da uno snapshot VSS, se ne hai uno recente).
  2. Parsala. Tira fuori ogni record dove IN_USE=0 e il $DATA primario è residente.
  3. Filtra per regex $FILE_NAME se conosci il nome del file. Filtra per riferimento al padre se conosci la directory.
  4. Ispeziona i byte $DATA residenti 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.
  • istat del Sleuth Kit per ispezionare singoli record e vedere il flag residente in contesto.

Risorse esterne