Cada ficheiro NTFS tem um atributo $DATA (tipo 0x80) com o seu conteúdo. Para a maioria dos ficheiros, o $DATA é não residente: o cabeçalho do atributo carrega uma runlist a apontar para cluster runs noutro sítio do volume. Para ficheiros pequenos, os bytes vivem inline no próprio registo MFT. Esse segundo caso é um dos artefactos mais úteis em forense NTFS, e é aquele que continuo a ver analistas a esquecer.
Quão pequeno é suficientemente pequeno
Um registo MFT tem 1.024 bytes. Daí subtrai o cabeçalho de 56 bytes, o fixup array, o $STANDARD_INFORMATION (cerca de 72 bytes), pelo menos um $FILE_NAME (variável, cerca de 90 bytes para um nome típico), padding, e o sentinela de fim de atributos. O que resta é o orçamento utilizável para $DATA residente e quaisquer outros atributos residentes.
Na prática, o limiar anda à volta de uns 700 bytes de dados. Algumas referências dizem até cerca de 750 bytes para ficheiros com nomes muito curtos e sem outros atributos residentes; outras descem até cerca de 580 bytes para ficheiros com nomes mais longos ou atributos adicionais. O limiar não é uma constante; depende do que mais está no registo.
Ficheiros que cabem, e que vejo constantemente:
- Ficheiros de texto pequenos: notas, listas de tarefas, rascunhos.
- Ficheiros de configuração pequenos:
.ini,.cfg,.jsonpequenos,.xmlpequenos. - One-liners e scripts curtos de PowerShell.
- Ficheiros de atalho
.lnkpequenos (a maioria dos LNK é suficientemente pequena; veja LNK forensics para o que codificam). - Snippets de exports do registo e ficheiros
.reg. - Ficheiros
.bate.cmdpequenos. - Ficheiros de certificado em formato PEM que sejam curtos.
- Ficheiros vazios e placeholders de zero bytes. Os "dados" são zero bytes, e
$DATAé trivialmente residente. - Muitos atributos
$INDEX_ROOTpara directórios pequenos.
Porque isto importa
Um atributo $DATA residente pode ser recuperado sem ler o resto do disco. Se tiver uma cópia do $MFT, já tem:
- O conteúdo completo de ficheiros de texto pequenos.
- Muita data de
$INDEX_ROOTpara directórios (as entradas do directório, listadas inline). - Alternate data streams curtos (atributos
$DATAcom nome e conteúdo pequeno). - Reparse points e alvos de symlink (o caminho-alvo vive no
$REPARSE_POINT, que também é tipicamente residente).
É uma quantidade surpreendente de evidência dentro de uma única extracção de MFT de 200 MB a 2 GB. Para ficheiros pequenos eliminados, os bytes residentes sobrevivem mesmo depois de a área de dados ter sido sobrescrita, porque a área de dados nunca foi tocada em primeiro lugar; os bytes viveram no MFT.
Já recuperei:
- Um loader PowerShell de 320 bytes de um registo eliminado cujos clusters de dados nunca existiram.
- Um
.lnkde 480 bytes a apontar para o directório de staging do atacante, eliminado três semanas antes da aquisição. - Um
.configde 700 bytes de um serviço que tinha sido desinstalado. O directório desaparecera; o registo MFT estava no slot 412.000, intocado.
O MFT era o único sítio onde esses ficheiros ainda existiam.
Residente não é estável
Quando um ficheiro residente cresce para além do espaço livre do registo, o NTFS converte-o para não residente. Os dados saem para clusters e o $DATA torna-se uma runlist. A conversão é registada no $LogFile à medida que a mudança acontece.
O inverso pode acontecer, tecnicamente (um ficheiro que encolha abaixo do limiar poderia voltar a ser residente), mas o Windows raramente o faz voluntariamente. Uma vez não residente, o $DATA tende a ficar assim. Se encontrar um registo com $DATA residente para um ficheiro que sabe ter sido maior, isso é invulgar e vale a pena investigar; sugere ou manipulação deliberada ou um caminho de código pouco comum que encolheu o ficheiro no lugar.
Detectar dados residentes durante o parsing
Cada parser de MFT expõe uma flag de "residente" no cabeçalho do atributo $DATA. A saída CSV do MFTECmd tem-na como coluna booleana. O JSON do mft_dump tem header.is_resident. O libmft expõe-a no objecto de atributo. O istat do Sleuth Kit mostra-a como parte da listagem de atributos.
O parser de browser deste site filtra para IN_USE=0 com $DATA residente em dois cliques. Esse filtro é o primeiro que corro em qualquer MFT extraído onde recuperação de ficheiros pequenos importa.
Como são os bytes
Para um $DATA residente, o cabeçalho do atributo é seguido por um cabeçalho residente de 16 bytes (tamanho do conteúdo, offset do conteúdo, flag indexada, padding) e depois os bytes brutos. Os bytes são exactamente o que estava em disco no ficheiro: texto na codificação que o ficheiro usou, conteúdo binário na sua forma nativa. Sem transformação.
Para um pequeno ficheiro de texto em UTF-8, pode copiar os bytes e ler o ficheiro em qualquer editor. Para conteúdo binário (um PE pequeno, um .pyc compilado, um objecto serializado), os bytes são igualmente utilizáveis; trate-os como um ficheiro extraído do volume.
Os limites
A maioria dos ficheiros não é residente. Numa instalação Windows normal, bem menos de 5% dos ficheiros de utilizador são residentes. O resto é tudo o que é Office, todos os ficheiros de cache do browser, todos os programas em \Program Files\, todos os downloads. A recuperação de residentes é um nicho de valor alto, não o modo por omissão.
O limiar não é exacto. Não assuma que "ficheiros abaixo de 700 bytes são recuperáveis". O limiar real depende dos outros atributos do registo. Teste no MFT específico que está a analisar.
Residente não sobrevive a compressão ou encriptação para um ficheiro suficientemente grande. Ficheiros encriptados com EFS usam $LOGGED_UTILITY_STREAM para os metadados de encriptação e o $DATA é o texto cifrado. Se o cifrado for pequeno o suficiente para ser residente, tem os bytes encriptados; continua a precisar da chave EFS para os ler.
Um fluxo que usa isto
Da próxima vez que tiver um caso de recuperação de ficheiro eliminado e o ficheiro que lhe importa for pequeno:
- Adquira o
$MFTdo host (ou de um snapshot VSS, se tiver um recente). - Analise-o. Puxe todos os registos onde
IN_USE=0e o$DATAprimário é residente. - Filtre por regex no
$FILE_NAMEse souber o nome do ficheiro. Filtre pela referência ao pai se souber o directório. - Inspeccione os bytes do
$DATAresidente inline. O ficheiro está mesmo ali.
Cinco minutos de trabalho para uma recuperação que o carving por assinatura demoraria horas (e provavelmente falharia para pequenos ficheiros fragmentados).
Leituras adicionais
- Notas sobre atributos residentes do projecto linux-ntfs. Explicação clara da disposição do cabeçalho residente.
- Microsoft, NTFS allocation algorithm. Referência oficial para como é feita a decisão residente/não residente.
istatdo Sleuth Kit para inspeccionar registos individuais e ver a flag residente em contexto.