Listing the Deleted Files from Master File Table (MFT)
The following program example uses undocumented Windows types that can be found in the Internet domain and a complete version is available as an open source used by Linux/UNIX to read the Windows NTFS MFT.
Create a new Win32 console application project and give a suitable project name.
Add the source file and give a suitable name.
Add the following source code.
// Not using winioctl.h lol!
#include <windows.h>
#include <stdlib.h>
#include <stdio.h>
#include ntfs.h
// Global variables
ULONG BytesPerFileRecord;
HANDLE hVolume;
BOOT_BLOCK bootb;
PFILE_RECORD_HEADER MFT;
// Template for padding
template <class T1, class T2> inline T1* Padd(T1* p, T2 n)
{
return (T1*)((char *)p + n);
}
ULONG RunLength(PUCHAR run)
{
wprintf(LIn RunLength()...\n);
return (*run & 0xf) + ((*run >> 4) & 0xf) + 1;
}
LONGLONG RunLCN(PUCHAR run)
{
LONG i = 0;
UCHAR n1 = 0 , n2 = 0;
LONGLONG lcn = 0;
wprintf(LIn RunLCN()...\n);
n1 = *run & 0xf;
n2 = (*run >> 4) & 0xf;
lcn = n2 == 0 ? 0 : CHAR(run[n1 + n2]);
for (i = n1 + n2 - 1; i > n1; i--)
lcn = (lcn << 8) + run[i];
return lcn;
}
ULONGLONG RunCount(PUCHAR run)
{
UCHAR n = *run & 0xf;
ULONGLONG count = 0;
ULONG i;
wprintf(LIn RunCount()...\n);
for (i = n; i > 0; i--)
count = (count << 8) + run[i];
return count;
}
BOOL FindRun(PNONRESIDENT_ATTRIBUTE attr, ULONGLONG vcn, PULONGLONG lcn, PULONGLONG count)
{
PUCHAR run = NULL;
*lcn = 0;
ULONGLONG base = attr->LowVcn;
wprintf(LIn FindRun()...\n);
if (vcn < attr->LowVcn || vcn > attr->HighVcn)
return FALSE;
for(run = PUCHAR(Padd(attr, attr->RunArrayOffset)); *run != 0; run += RunLength(run))
{
*lcn += RunLCN(run);
*count = RunCount(run);
if (base <= vcn && vcn < base + *count)
{
*lcn = RunLCN(run) == 0 ? 0 : *lcn + vcn - base;
*count -= ULONG(vcn - base);
return TRUE;
}
else
base += *count;
}
return FALSE;
}
PATTRIBUTE FindAttribute(PFILE_RECORD_HEADER file,ATTRIBUTE_TYPE type, PWSTR name)
{
PATTRIBUTE attr = NULL;
wprintf(LFindAttribute() - Finding attributes...\n);
for (attr = PATTRIBUTE(Padd(file, file->AttributesOffset));
attr->AttributeType != -1;attr = Padd(attr, attr->Length))
{
if (attr->AttributeType == type)
{
if (name == 0 && attr->NameLength == 0)
return attr;
if (name != 0 && wcslen(name) == attr->NameLength && _wcsicmp(name,
PWSTR(Padd(attr, attr->NameOffset))) == 0)
return attr;
}
}
return 0;
}
VOID FixupUpdateSequenceArray(PFILE_RECORD_HEADER file)
{
ULONG i = 0;
PUSHORT usa = PUSHORT(Padd(file, file->Ntfs.UsaOffset));
PUSHORT sector = PUSHORT(file);
wprintf(LIn FixupUpdateSequenceArray()...\n);
for (i = 1; i < file->Ntfs.UsaCount; i++)
{
sector[255] = usa[i];
sector += 256;
}
}
VOID ReadSector(ULONGLONG sector, ULONG count, PVOID buffer)
{
ULARGE_INTEGER offset;
OVERLAPPED overlap = {0};
ULONG n;
wprintf(LReadSector() - Reading the sector...\n);
wprintf(LSector: %lu\n, sector);
offset.QuadPart = sector * bootb.BytesPerSector;
overlap.Offset = offset.LowPart;
overlap.OffsetHigh = offset.HighPart;
ReadFile(hVolume, buffer, count * bootb.BytesPerSector, &n, &overlap);
}
VOID ReadLCN(ULONGLONG lcn, ULONG count, PVOID buffer)
{
wprintf(L\nReadLCN() - Reading the LCN, LCN: 0X%.8X\n, lcn);
ReadSector(lcn * bootb.SectorsPerCluster,count * bootb.SectorsPerCluster, buffer);
}
// Non resident attributes
VOID ReadExternalAttribute(PNONRESIDENT_ATTRIBUTE attr,ULONGLONG vcn, ULONG count, PVOID buffer)
{
ULONGLONG lcn, runcount;
ULONG readcount, left;
PUCHAR bytes = PUCHAR(buffer);
wprintf(LReadExternalAttribute() - Reading the Non resident attributes...\n);
for(left = count; left > 0; left -= readcount)
{
FindRun(attr, vcn, &lcn, &runcount);
readcount = ULONG(min(runcount, left));
ULONG n = readcount * bootb.BytesPerSector * bootb.SectorsPerCluster;
if(lcn == 0)
memset(bytes, 0, n);
else
{
ReadLCN(lcn, readcount, bytes);
wprintf(LLCN: 0X%.8X\n, lcn);
}
vcn += readcount;
bytes += n;
}
}
ULONG AttributeLength(PATTRIBUTE attr)
{
wprintf(LIn AttributeLength()...\n);
return attr->Nonresident == FALSE ? PRESIDENT_ATTRIBUTE(attr)->ValueLength : ULONG(PNONRESIDENT_ATTRIBUTE(attr)->DataSize);
}
ULONG AttributeLengthAllocated(PATTRIBUTE attr)
{
wprintf(L\nIn AttributeLengthAllocated()...\n);
return attr->Nonresident == FALSE ? PRESIDENT_ATTRIBUTE(attr)->ValueLength : ULONG(PNONRESIDENT_ATTRIBUTE(attr)->AllocatedSize);
}
VOID ReadAttribute(PATTRIBUTE attr, PVOID buffer)
{
PRESIDENT_ATTRIBUTE rattr = NULL;
PNONRESIDENT_ATTRIBUTE nattr = NULL;
wprintf(LReadAttribute() - Reading the attributes...\n);
if (attr->Nonresident == FALSE)
{
wprintf(LResident attribute...\n);
rattr = PRESIDENT_ATTRIBUTE(attr);
memcpy(buffer, Padd(rattr, rattr->ValueOffset), rattr->ValueLength);
}
else
{
wprintf(LNon-resident attribute...\n);
nattr = PNONRESIDENT_ATTRIBUTE(attr);
ReadExternalAttribute(nattr, 0, ULONG(nattr->HighVcn) + 1, buffer);
}
}
VOID ReadVCN(PFILE_RECORD_HEADER file, ATTRIBUTE_TYPE type,ULONGLONG vcn, ULONG count, PVOID buffer)
{
PATTRIBUTE attrlist = NULL;
PNONRESIDENT_ATTRIBUTE attr = PNONRESIDENT_ATTRIBUTE(FindAttribute(file, type, 0));
wprintf(LIn ReadVCN()...\n);
if (attr == 0 || (vcn < attr->LowVcn || vcn > attr->HighVcn))
{
// Support for huge files
attrlist = FindAttribute(file, AttributeAttributeList, 0);
DebugBreak();
}
ReadExternalAttribute(attr, vcn, count, buffer);
}
VOID ReadFileRecord(ULONG index, PFILE_RECORD_HEADER file)
{
ULONG clusters = bootb.ClustersPerFileRecord;
wprintf(LReadFileRecord() - Reading the file records..\n);
if (clusters > 0x80)
clusters = 1;
PUCHAR p = new UCHAR[bootb.BytesPerSector* bootb.SectorsPerCluster * clusters];
ULONGLONG vcn = ULONGLONG(index) * BytesPerFileRecord/bootb.BytesPerSector/bootb.SectorsPerCluster;
ReadVCN(MFT, AttributeData, vcn, clusters, p);
LONG m = (bootb.SectorsPerCluster * bootb.BytesPerSector/BytesPerFileRecord) - 1;
ULONG n = m > 0 ? (index & m) : 0;
memcpy(file, p + n * BytesPerFileRecord, BytesPerFileRecord);
delete [] p;
FixupUpdateSequenceArray(file);
}
VOID LoadMFT()
{
wprintf(LIn LoadMFT() - Loading MFT...\n);
BytesPerFileRecord = bootb.ClustersPerFileRecord < 0x80
? bootb.ClustersPerFileRecord* bootb.SectorsPerCluster
* bootb.BytesPerSector: 1 << (0x100 - bootb.ClustersPerFileRecord);
wprintf(L\nBytes Per File Record = %u\n\n, BytesPerFileRecord);
wprintf(L======THESE INFO ARE NOT ACCURATE FOR DISPLAY LOL!=====\n);
wprintf(Lbootb.BootSectors = %u\n, bootb.BootSectors);
wprintf(Lbootb.BootSignature = %u\n, bootb.BootSignature);
wprintf(Lbootb.BytesPerSector = %u\n, bootb.BytesPerSector);
wprintf(Lbootb.ClustersPerFileRecord = %u\n, bootb.ClustersPerFileRecord);
wprintf(Lbootb.ClustersPerIndexBlock = %u\n, bootb.ClustersPerIndexBlock);
wprintf(Lbootb.Code = %u\n, bootb.Code);
wprintf(Lbootb.Format = %u\n, bootb.Format);
wprintf(Lbootb.Jump = %u\n, bootb.Jump);
wprintf(Lbootb.Mbz1 = %u\n, bootb.Mbz1);
wprintf(Lbootb.Mbz2 = %u\n, bootb.Mbz2);
wprintf(Lbootb.Mbz3 = %u\n, bootb.Mbz3);
wprintf(Lbootb.MediaType = 0X%X\n, bootb.MediaType);
wprintf(Lbootb.Mft2StartLcn = 0X%.8X\n, bootb.Mft2StartLcn);
wprintf(Lbootb.MftStartLcn = 0X%.8X\n, bootb.MftStartLcn);
wprintf(Lbootb.NumberOfHeads = %u\n, bootb.NumberOfHeads);
wprintf(Lbootb.PartitionOffset = %lu\n, bootb.PartitionOffset);
wprintf(Lbootb.SectorsPerCluster = %u\n, bootb.SectorsPerCluster);
wprintf(Lbootb.SectorsPerTrack = %u\n, bootb.SectorsPerTrack);
wprintf(Lbootb.TotalSectors = %lu\n, bootb.TotalSectors);
wprintf(Lbootb.VolumeSerialNumber = 0X%.8X%.8X\n\n, bootb.VolumeSerialNumber.HighPart, bootb.VolumeSerialNumber.HighPart);
MFT = PFILE_RECORD_HEADER(new UCHAR[BytesPerFileRecord]);
ReadSector((bootb.MftStartLcn)*(bootb.SectorsPerCluster), (BytesPerFileRecord)/(bootb.BytesPerSector), MFT);
FixupUpdateSequenceArray(MFT);
}
BOOL bitset(PUCHAR bitmap, ULONG i)
{
return (bitmap[i >> 3] & (1 << (i & 7))) != 0;
}
VOID FindDeleted()
{
PATTRIBUTE attr = FindAttribute(MFT, AttributeBitmap, 0);
PUCHAR bitmap = new UCHAR[AttributeLengthAllocated(attr)];
ReadAttribute(attr, bitmap);
ULONG n = AttributeLength(FindAttribute(MFT, AttributeData, 0))/BytesPerFileRecord;
wprintf(LFindDeleted() - Finding the deleted files...\n);
PFILE_RECORD_HEADER file = PFILE_RECORD_HEADER(new UCHAR[BytesPerFileRecord]);
for(ULONG i = 0; i < n; i++)
{
if (bitset(bitmap, i))
continue;
ReadFileRecord(i, file);
if (file->Ntfs.Type == 'ELIF' && (file->Flags & 1) == 0)
{
attr = FindAttribute(file, AttributeFileName, 0);
if (attr == 0)
continue;
PFILENAME_ATTRIBUTE name = PFILENAME_ATTRIBUTE(Padd(attr,PRESIDENT_ATTRIBUTE(attr)->ValueOffset));
// * means the width/precision was supplied in the argument list
// ws ~ wide character string
wprintf(L\n%10u %u %.*s\n\n, i, int(name->NameLength), int(name->NameLength), name->Name);
// To see the very long output short, uncomment the following line
// _getwch();
}
}
}
VOID DumpData(ULONG index, WCHAR* filename)
{
PATTRIBUTE attr = NULL;
HANDLE hFile = NULL;
PFILE_RECORD_HEADER file = PFILE_RECORD_HEADER(new UCHAR[BytesPerFileRecord]);
ULONG n;
ReadFileRecord(index, file);
wprintf(LDumping the data...\n);
if (file->Ntfs.Type != 'ELIF')
return;
attr = FindAttribute(file, AttributeData, 0);
if (attr == 0)
return;
PUCHAR buf = new UCHAR[AttributeLengthAllocated(attr)];
ReadAttribute(attr, buf);
hFile = CreateFile((LPCWSTR)filename, GENERIC_WRITE, 0, 0,CREATE_ALWAYS, 0, 0);
if(hFile == INVALID_HANDLE_VALUE)
{
wprintf(LCreateFile() failed, error %u\n, GetLastError());
return;
}
if(WriteFile(hFile, buf, AttributeLength(attr), &n, 0) == 0)
{
wprintf(LWriteFile() failed, error %u\n, GetLastError());
return;
}
CloseHandle(hFile);
delete [] buf;
}
int wmain(int argc, WCHAR **argv)
{
// Default primary partition
WCHAR drive[] = L\\\\.\\C:;
ULONG n;
// No argument supplied
if (argc < 2)
{
wprintf(LUsage:\n);
wprintf(LFind deleted files: %s <primary_partition>\n, argv[0]);
wprintf(LRead the file records: %s <primary_partition> <index> <file_name>\n, argv[0]);
// Just exit
exit(1);
}
// More code to stop the user from entering the non-primary partition
// Read the user input
drive[4] = *argv[1];
// Get the handle to the primary partition/volume/physical disk
hVolume = CreateFile(
drive,
GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE,
0,
OPEN_EXISTING,
0,
0);
if(hVolume == INVALID_HANDLE_VALUE)
{
wprintf(LCreateFile() failed, error %u\n, GetLastError());
exit(1);
}
// Reads data from the specified input/output (I/O) device - volume/physical disk
if(ReadFile(hVolume, &bootb, sizeof bootb, &n, 0) == 0)
{
wprintf(LReadFile() failed, error %u\n, GetLastError());
exit(1);
}
LoadMFT();
// The primary partition supplied else
// default C:\ will be used
if (argc == 2)
FindDeleted();
// Need to convert the recovered filename to long file name
// Not implemented here. It is 8.3 file name format
// The primary partition, index and file name to be recovered
// are supplied
if (argc == 4)
DumpData(wcstoul(argv[2], 0, 0), argv[3]);
CloseHandle(hVolume);
return 0;
}
Add the ntfs.h header file.
Add the following source code.
///ntfs.h
// These types are not available in MSDN documentation
// It is taken from Internet and Linux documentation
// and not the whole code...
// Copyrights and trademarks must go to the original
// authors and/or publishers
typedef struct {
ULONG Type;
USHORT UsaOffset;
USHORT UsaCount;
USN Usn;
} NTFS_RECORD_HEADER, *PNTFS_RECORD_HEADER;
typedef struct {
NTFS_RECORD_HEADER Ntfs;
USHORT SequenceNumber;
USHORT LinkCount;
USHORT AttributesOffset;
USHORT Flags; // 0x0001 = InUse, 0x0002= Directory
ULONG BytesInUse;
ULONG BytesAllocated;
ULONGLONG BaseFileRecord;
USHORT NextAttributeNumber;
} FILE_RECORD_HEADER, *PFILE_RECORD_HEADER;
typedef enum {
AttributeStandardInformation = 0x10,
AttributeAttributeList = 0x20,
AttributeFileName = 0x30,
AttributeObjectId = 0x40,
AttributeSecurityDescriptor = 0x50,
AttributeVolumeName = 0x60,
AttributeVolumeInformation = 0x70,
AttributeData = 0x80,
AttributeIndexRoot = 0x90,
AttributeIndexAllocation = 0xA0,
AttributeBitmap = 0xB0,
AttributeReparsePoint = 0xC0,
AttributeEAInformation = 0xD0,
AttributeEA = 0xE0,
AttributePropertySet = 0xF0,
AttributeLoggedUtilityStream = 0x100
} ATTRIBUTE_TYPE, *PATTRIBUTE_TYPE;
typedef struct {
ATTRIBUTE_TYPE AttributeType;
ULONG Length;
BOOLEAN Nonresident;
UCHAR NameLength;
USHORT NameOffset;
USHORT Flags; // 0x0001 = Compressed
USHORT AttributeNumber;
} ATTRIBUTE, *PATTRIBUTE;
typedef struct {
ATTRIBUTE Attribute;
ULONG ValueLength;
USHORT ValueOffset;
USHORT Flags; // 0x0001 = Indexed
} RESIDENT_ATTRIBUTE, *PRESIDENT_ATTRIBUTE;
typedef struct {
ATTRIBUTE Attribute;
ULONGLONG LowVcn;
ULONGLONG HighVcn;
USHORT RunArrayOffset;
UCHAR CompressionUnit;
UCHAR AlignmentOrReserved[5];
ULONGLONG AllocatedSize;
ULONGLONG DataSize;
ULONGLONG InitializedSize;
ULONGLONG CompressedSize; // Only when compressed
} NONRESIDENT_ATTRIBUTE, *PNONRESIDENT_ATTRIBUTE;
typedef struct {
ULONGLONG CreationTime;
ULONGLONG ChangeTime;
ULONGLONG LastWriteTime;
ULONGLONG LastAccessTime;
ULONG FileAttributes;
ULONG AlignmentOrReservedOrUnknown[3];
ULONG QuotaId; // NTFS 3.0
ULONG SecurityId; // NTFS 3.0
ULONGLONG QuotaCharge; // NTFS 3.0
USN Usn; // NTFS 3.0
} STANDARD_INFORMATION, *PSTANDARD_INFORMATION;
typedef struct {
ATTRIBUTE_TYPE AttributeType;
USHORT Length;
UCHAR NameLength;
UCHAR NameOffset;
ULONGLONG LowVcn;
ULONGLONG FileReferenceNumber;
USHORT AttributeNumber;
USHORT AlignmentOrReserved[3];
} ATTRIBUTE_LIST, *PATTRIBUTE_LIST;
typedef struct {
ULONGLONG DirectoryFileReferenceNumber; //
ULONGLONG CreationTime; // Saved when filename last changed
ULONGLONG ChangeTime; //
ULONGLONG LastWriteTime; //
ULONGLONG LastAccessTime; //
ULONGLONG AllocatedSize; //
ULONGLONG DataSize; //
ULONG FileAttributes; //
ULONG AlignmentOrReserved; //
UCHAR NameLength; //
UCHAR NameType; // 0x01 = Long, 0x02 = Short
WCHAR Name[1]; //
} FILENAME_ATTRIBUTE, *PFILENAME_ATTRIBUTE;
typedef struct {
GUID ObjectId;
union {
struct {
GUID BirthVolumeId;
GUID BirthObjectId;
GUID DomainId;
};
UCHAR ExtendedInfo[48];
};
} OBJECTID_ATTRIBUTE, *POBJECTID_ATTRIBUTE;
typedef struct {
ULONG Unknown[2];
UCHAR MajorVersion;
UCHAR MinorVersion;
USHORT Flags;
} VOLUME_INFORMATION, *PVOLUME_INFORMATION;
typedef struct {
ULONG EntriesOffset;
ULONG IndexBlockLength;
ULONG AllocatedSize;
ULONG Flags; // 0x00 = Small directory, 0x01 = Large directory
} DIRECTORY_INDEX, *PDIRECTORY_INDEX;
typedef struct {
ULONGLONG FileReferenceNumber;
USHORT Length;
USHORT AttributeLength;
ULONG Flags; // 0x01 = Has trailing VCN, 0x02 = Last entry
// FILENAME_ATTRIBUTE Name;
// ULONGLONG Vcn; // VCN in IndexAllocation of earlier entries
} DIRECTORY_ENTRY, *PDIRECTORY_ENTRY;
typedef struct {
ATTRIBUTE_TYPE Type;
ULONG CollationRule;
ULONG BytesPerIndexBlock;
ULONG ClustersPerIndexBlock;
DIRECTORY_INDEX DirectoryIndex;
} INDEX_ROOT, *PINDEX_ROOT;
typedef struct {
NTFS_RECORD_HEADER Ntfs;
ULONGLONG IndexBlockVcn;
DIRECTORY_INDEX DirectoryIndex;
} INDEX_BLOCK_HEADER, *PINDEX_BLOCK_HEADER;
typedef struct {
ULONG ReparseTag;
USHORT ReparseDataLength;
USHORT Reserved;
UCHAR ReparseData[1];
} REPARSE_POINT, *PREPARSE_POINT;
typedef struct {
ULONG EaLength;
ULONG EaQueryLength;
} EA_INFORMATION, *PEA_INFORMATION;
typedef struct {
ULONG NextEntryOffset;
UCHAR Flags;
UCHAR EaNameLength;
USHORT EaValueLength;
CHAR EaName[1];
// UCHAR EaData[];
} EA_ATTRIBUTE, *PEA_ATTRIBUTE;
typedef struct {
WCHAR AttributeName[64];
ULONG AttributeNumber;
ULONG Unknown[2];
ULONG Flags;
ULONGLONG MinimumSize;
ULONGLONG MaximumSize;
} ATTRIBUTE_DEFINITION, *PATTRIBUTE_DEFINITION;
#pragma pack(push, 1)
typedef struct {
UCHAR Jump[3];
UCHAR Format[8];
USHORT BytesPerSector;
UCHAR SectorsPerCluster;
USHORT BootSectors;
UCHAR Mbz1;
USHORT Mbz2;
USHORT Reserved1;
UCHAR MediaType;
USHORT Mbz3;
USHORT SectorsPerTrack;
USHORT NumberOfHeads;
ULONG PartitionOffset;
ULONG Reserved2[2];
ULONGLONG TotalSectors;
ULONGLONG MftStartLcn;
ULONGLONG Mft2StartLcn;
ULONG ClustersPerFileRecord;
ULONG ClustersPerIndexBlock;
LARGE_INTEGER VolumeSerialNumber;
UCHAR Code[0x1AE];
USHORT BootSignature;
} BOOT_BLOCK, *PBOOT_BLOCK;
#pragma pack(pop)
Build and run the project. The following screenshot is an output sample.
The following sample outputs run with the primary partition supplied as an argument.
To save all the deleted file names you may want to redirect the output into a text file as shown below.
Then open the text file using any unformatted text editor such as WordPad.
Next we will try to recover a file. We got the file name and index from the previous output.
The following is a sample output.
Next, the recovered file should be stored in the project’s Debug folder.