← Voltar ao blog

Dados residentes: ficheiros minúsculos que vivem dentro do MFT

· 6 min de leitura

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, .json pequenos, .xml pequenos.
  • One-liners e scripts curtos de PowerShell.
  • Ficheiros de atalho .lnk pequenos (a maioria dos LNK é suficientemente pequena; veja LNK forensics para o que codificam).
  • Snippets de exports do registo e ficheiros .reg.
  • Ficheiros .bat e .cmd pequenos.
  • 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_ROOT para 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_ROOT para directórios (as entradas do directório, listadas inline).
  • Alternate data streams curtos (atributos $DATA com 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 .lnk de 480 bytes a apontar para o directório de staging do atacante, eliminado três semanas antes da aquisição.
  • Um .config de 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:

  1. Adquira o $MFT do host (ou de um snapshot VSS, se tiver um recente).
  2. Analise-o. Puxe todos os registos onde IN_USE=0 e o $DATA primário é residente.
  3. Filtre por regex no $FILE_NAME se souber o nome do ficheiro. Filtre pela referência ao pai se souber o directório.
  4. Inspeccione os bytes do $DATA residente 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.
  • istat do Sleuth Kit para inspeccionar registos individuais e ver a flag residente em contexto.

Recursos externos