|
|
// fastfind.c
#include <stdio.h>
#include <string.h>
#include <nt.h>
#include <ntrtl.h>
#include <nturtl.h>
#include <windows.h>
#include <ntioapi.h>
#include <myntfs.h>
#define VOLUME_PATH L"\\\\.\\H:"
#define VOLUME_DRIVE_LETTER_INDEX 4
#define FULL_PATH L"\\??\\H:\\1234567890123456"
#define FULL_DRIVE_LETTER_INDEX 4
#define DEVICE_PREFIX_LEN 14
typedef struct _EXTENT {
LONGLONG Vcn; LONGLONG Lcn; LONGLONG Length;
} EXTENT, *PEXTENT;
#define MAX_EXTENTS 64
//
// Some globals.
//
LARGE_INTEGER MftStart; ULONG ClusterSize; ULONG FrsSize; EXTENT Extents[MAX_EXTENTS]; ULONG DebugLevel; UCHAR CacheBuffer[0x10000]; // max cluster size
LONGLONG CachedOffset = -1; char mybuffer[32768];
LONGLONG ComputeFileRecordLbo ( IN ULONG MftIndex ) { LONGLONG vcn; LONGLONG lcn = 0; ULONG extentIndex; ULONG offsetWithinCluster;
vcn = (MftIndex * FrsSize) / ClusterSize;
for (extentIndex = 0; extentIndex < MAX_EXTENTS; extentIndex += 1) {
if ((vcn >= Extents[extentIndex].Vcn) && (vcn < Extents[extentIndex].Vcn + Extents[extentIndex].Length)) {
lcn = Extents[extentIndex].Lcn + (vcn - Extents[extentIndex].Vcn); } }
if (ClusterSize >= FrsSize ) {
offsetWithinCluster = (MftIndex % (ClusterSize / FrsSize)) * FrsSize; return (lcn * ClusterSize + offsetWithinCluster);
} else {
//
// BUGBUG keithka 4/28/00 Handle old fashioned big frs and/or big
// clusters someday.
//
ASSERT( FALSE ); return 0; } }
VOID FindAttributeInFileRecord ( IN PFILE_RECORD_SEGMENT_HEADER FileRecord, IN ATTRIBUTE_TYPE_CODE TypeCode, IN PATTRIBUTE_RECORD_HEADER PreviousAttribute OPTIONAL, OUT PATTRIBUTE_RECORD_HEADER *Attribute ) // Attribute set to NULL if not found.
{ PATTRIBUTE_RECORD_HEADER attr;
*Attribute = NULL; if (FileRecord->Pad0[0] != 'F' || FileRecord->Pad0[1] != 'I' || FileRecord->Pad0[2] != 'L' || FileRecord->Pad0[3] != 'E') {
if (DebugLevel >= 1) { printf( "\nBad MFT record %c%c%c%c", FileRecord->Pad0[0], FileRecord->Pad0[1], FileRecord->Pad0[2], FileRecord->Pad0[3] ); }
//
// This isn't a good file record, but that doesn't make this a corrupt volume.
// It's possible that this file record has never been used. Since we don't look
// at the MFT bitmap, we don't know if this was expected to be a valid filerecord.
// The output Attribute is set to NULL already, so we can exit now.
//
return; }
if (0 == (FileRecord->Flags & FILE_RECORD_SEGMENT_IN_USE)) {
//
// File record not in use, skip it.
//
return; }
if (NULL == PreviousAttribute) {
attr = (PATTRIBUTE_RECORD_HEADER) ((PUCHAR)FileRecord + FileRecord->FirstAttributeOffset);
} else {
attr = (PATTRIBUTE_RECORD_HEADER) ((PUCHAR) PreviousAttribute + PreviousAttribute->RecordLength);
if (((PUCHAR)attr - (PUCHAR)FileRecord) > (LONG) FrsSize) {
ASSERT (FALSE); return; } }
while (attr->TypeCode < TypeCode && attr->TypeCode != $END) {
ASSERT( attr->RecordLength < FrsSize );
attr = (PATTRIBUTE_RECORD_HEADER) ((PUCHAR) attr + attr->RecordLength);
//
// BUGBUG keitha 4/20/00 need to handle attribute list case someday...
// It's relativley rare that an MFT gets so fragmented it needs an
// attribute list. Certainly rare enough to skip it for now in a
// piece of test code.
//
}
if (attr->TypeCode == TypeCode) {
*Attribute = attr; }
return; }
BOOLEAN FindNameInFileRecord ( IN PFILE_RECORD_SEGMENT_HEADER FileRecord, IN PWCHAR FileName, IN ULONG FileNameLength ) { PATTRIBUTE_RECORD_HEADER attr; PFILE_NAME fileNameAttr; ULONG cmpResult;
FindAttributeInFileRecord( FileRecord, $FILE_NAME, NULL, &attr );
while (NULL != attr) {
if (((PUCHAR)attr - (PUCHAR)FileRecord) > (LONG) FrsSize) {
ASSERT( FALSE ); return FALSE; }
//
// Names shouldn't go nonresident.
//
if (attr->FormCode != RESIDENT_FORM) {
ASSERT( FALSE ); return FALSE; }
fileNameAttr = (PFILE_NAME) ((PUCHAR)attr + attr->Form.Resident.ValueOffset);
if (fileNameAttr->FileNameLength == FileNameLength) { cmpResult = wcsncmp( FileName, (PWCHAR) fileNameAttr->FileName, fileNameAttr->FileNameLength );
if (0 == cmpResult) {
return TRUE; }
} else if (DebugLevel >= 3) {
printf( "\nNot a match %S,%S", FileName, fileNameAttr->FileName ); }
//
// Find the next filename, if any.
//
FindAttributeInFileRecord( FileRecord, $FILE_NAME, attr, &attr ); }
return FALSE; }
int FsTestOpenById ( IN UCHAR *ObjectId, IN HANDLE VolumeHandle ) { HANDLE File; IO_STATUS_BLOCK IoStatusBlock; NTSTATUS Status; NTSTATUS GetNameStatus; NTSTATUS CloseStatus; OBJECT_ATTRIBUTES ObjectAttributes; UNICODE_STRING str; WCHAR nameBuffer[MAX_PATH]; PFILE_NAME_INFORMATION FileName; WCHAR Full[] = FULL_PATH; // Arrays of WCHAR's aren't constants
RtlInitUnicodeString( &str, Full );
str.Length = 8; RtlCopyMemory( &str.Buffer[0], // no device prefix for relative open.
ObjectId, 8 );
InitializeObjectAttributes( &ObjectAttributes, &str, OBJ_CASE_INSENSITIVE, VolumeHandle, NULL );
Status = NtCreateFile( &File, GENERIC_READ, &ObjectAttributes, &IoStatusBlock, NULL, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ | FILE_SHARE_WRITE, FILE_OPEN, FILE_OPEN_BY_FILE_ID, NULL, 0 );
if (NT_SUCCESS( Status )) {
RtlZeroMemory( nameBuffer, sizeof(nameBuffer) ); FileName = (PFILE_NAME_INFORMATION) &nameBuffer[0]; FileName->FileNameLength = sizeof(nameBuffer) - sizeof(ULONG);
GetNameStatus = NtQueryInformationFile( File, &IoStatusBlock, FileName, sizeof(nameBuffer), FileNameInformation );
printf( "%S\n", FileName->FileName );
CloseStatus = NtClose( File );
if (!NT_SUCCESS( CloseStatus )) {
printf( "\nCloseStatus %x", CloseStatus ); } }
return Status; }
NTSTATUS ReadFileRecord ( IN HANDLE VolumeHandle, IN ULONG RecordIndex, IN OUT PVOID Buffer ) { NTSTATUS status; LARGE_INTEGER byteOffset; IO_STATUS_BLOCK ioStatusBlock; ULONG offsetWithinBuffer;
byteOffset.QuadPart = ComputeFileRecordLbo( RecordIndex );
if (FrsSize >= ClusterSize) {
status = NtReadFile( VolumeHandle, NULL, // Event
NULL, // ApcRoutine
NULL, // ApcContext
&ioStatusBlock, Buffer, FrsSize, &byteOffset, // ByteOffset
NULL ); // Key
} else {
//
// Clusters bigger than filerecords, do cluster
// size reads and dice up the returns.
//
if ((-1 == CachedOffset) || (byteOffset.QuadPart < CachedOffset) || ((byteOffset.QuadPart + FrsSize) > (CachedOffset + ClusterSize))) {
if (DebugLevel >= 1) {
printf( "\nCache miss at %I64x", byteOffset.QuadPart ); }
status = NtReadFile( VolumeHandle, NULL, // Event
NULL, // ApcRoutine
NULL, // ApcContext
&ioStatusBlock, CacheBuffer, ClusterSize, &byteOffset, // ByteOffset
NULL ); // Key
if (STATUS_SUCCESS != status) {
//
// The cache buffer may be junk now, reread it next time.
//
CachedOffset = -1; return status; }
CachedOffset = byteOffset.QuadPart; offsetWithinBuffer = 0;
} else {
if (DebugLevel >= 1) {
printf( "\nCache hit at %I64x", byteOffset.QuadPart ); } offsetWithinBuffer = (ULONG) (byteOffset.QuadPart % CachedOffset); status = STATUS_SUCCESS; }
RtlCopyMemory( Buffer, CacheBuffer + offsetWithinBuffer, FrsSize ); }
return status; }
int FastFind ( IN PWCHAR FileName, IN PWCHAR DriveLetter ) { IO_STATUS_BLOCK IoStatusBlock; UNICODE_STRING str; NTSTATUS Status; NTSTATUS ReadStatus; NTSTATUS CloseStatus; LARGE_INTEGER byteOffset; LONGLONG mftBytesRead; HANDLE volumeHandle; DWORD WStatus; WCHAR Full[] = FULL_PATH; // Arrays of WCHAR's aren't constants
WCHAR Volume[] = VOLUME_PATH; BIOS_PARAMETER_BLOCK bpb; PPACKED_BOOT_SECTOR bootSector; PFILE_RECORD_SEGMENT_HEADER fileRecord; PATTRIBUTE_RECORD_HEADER attr; VCN nextVcn; VCN currentVcn; VCN vcnDelta; LCN currentLcn; LCN lcnDelta; PUCHAR bsPtr; UCHAR v; UCHAR l; UCHAR i; ULONG extentCount; ULONG recordIndex; ULONG mftRecords; ULONG fileNameLength; MFT_SEGMENT_REFERENCE segRef;
RtlInitUnicodeString( &str, Full );
RtlCopyMemory( &str.Buffer[FULL_DRIVE_LETTER_INDEX], DriveLetter, sizeof(WCHAR) ); str.Length = 0x1E;
//
// Open the volume for relative opens.
//
RtlCopyMemory( &Volume[VOLUME_DRIVE_LETTER_INDEX], DriveLetter, sizeof(WCHAR) ); printf( "\nOpening volume handle, this may take a while..." ); volumeHandle = CreateFileW( (PUSHORT) &Volume, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL );
if (volumeHandle == INVALID_HANDLE_VALUE) {
WStatus = GetLastError(); printf( "Unable to open %ws volume\n", &Volume ); printf( "Error from CreateFile", WStatus ); return WStatus; }
printf( "\nVolume handle opened, starting MFT scan" ); byteOffset.QuadPart = 0;
ReadStatus = NtReadFile( volumeHandle, NULL, // Event
NULL, // ApcRoutine
NULL, // ApcContext
&IoStatusBlock, mybuffer, 0x200, &byteOffset, // ByteOffset
NULL ); // Key
if (STATUS_SUCCESS != ReadStatus) {
printf( "\nBoot sector read failed with status %x", ReadStatus ); goto exit; }
bootSector = (PPACKED_BOOT_SECTOR) mybuffer;
if (bootSector->Oem[0] != 'N' || bootSector->Oem[1] != 'T' || bootSector->Oem[2] != 'F' || bootSector->Oem[3] != 'S') {
printf( "\nNot an NTFS volume" ); goto exit; }
NtfsUnpackBios( &bpb, &bootSector->PackedBpb );
ClusterSize = bpb.BytesPerSector * bpb.SectorsPerCluster; if (bootSector->ClustersPerFileRecordSegment < 0) {
FrsSize = 1 << (-1 * bootSector->ClustersPerFileRecordSegment);
} else {
FrsSize = bootSector->ClustersPerFileRecordSegment * ClusterSize; } MftStart.QuadPart = ClusterSize * bootSector->MftStartLcn;
mftBytesRead = 0;
ReadStatus = NtReadFile( volumeHandle, NULL, // Event
NULL, // ApcRoutine
NULL, // ApcContext
&IoStatusBlock, mybuffer, FrsSize, &MftStart, // ByteOffset
NULL ); // Key
if (STATUS_SUCCESS != ReadStatus) {
printf( "\nMFT record 0 read failed with status %x", ReadStatus ); goto exit; }
mftBytesRead += IoStatusBlock.Information;
FindAttributeInFileRecord( (PFILE_RECORD_SEGMENT_HEADER) mybuffer, $DATA, NULL, &attr ); if (NULL == attr) {
printf( "\nMFT record 0 has no $DATA attribute" ); goto exit; }
if (attr->FormCode == RESIDENT_FORM) {
printf( "\nVolume has very few files, use dir /s" ); goto exit; }
//
// BUGBUG keithka 4/28/00 Handle MFT with more than 4billion entries.
//
ASSERT (attr->Form.Nonresident.FileSize <= MAXULONG); mftRecords = (ULONG) (attr->Form.Nonresident.FileSize / FrsSize); //
// Crack mapping pairs, read those clusters in a few big trnasfers,
// seek out given filename in those buffers.
//
nextVcn = attr->Form.Nonresident.LowestVcn; currentLcn = 0; extentCount = 0; RtlZeroMemory( Extents, sizeof(Extents) );
bsPtr = ((PUCHAR) attr) + attr->Form.Nonresident.MappingPairsOffset;
while (*bsPtr != 0) {
currentVcn = nextVcn;
//
// Variable names v and l used for consistency with comments in
// ATTRIBUTE_RECORD_HEADER struct explaining how to decompress
// mapping pair information.
//
v = (*bsPtr) & 0xf; l = ((*bsPtr) & 0xf0) >> 4;
bsPtr += 1;
for (vcnDelta = 0, i = 0; i < v; i++) {
vcnDelta += *(bsPtr++) << (8 * i); }
for (lcnDelta = 0, i = 0; i < l; i++) {
lcnDelta += *(bsPtr++) << (8 * i); }
//
// Sign extend.
//
if (0x80 & (*(bsPtr - 1))) { for(; i < sizeof(lcnDelta); i++) {
lcnDelta += 0xff << (8 * i); } }
currentLcn += lcnDelta; // printf( "\nVcn %I64x, Lcn %I64x, Length %I64x", currentVcn, currentLcn, vcnDelta );
if (extentCount < MAX_EXTENTS) {
Extents[extentCount].Vcn = currentVcn; Extents[extentCount].Lcn = currentLcn; Extents[extentCount].Length = vcnDelta;
extentCount += 1;
} else {
printf( "\nExcessive MFT fragmentation, redefine MAX_EXTENTS and recompile" ); }
currentVcn += vcnDelta; }
//
// Now we know where the MFT is, let's go read it.
//
fileNameLength = wcslen( FileName );
for (recordIndex = 0; recordIndex <= mftRecords; recordIndex++) {
ReadStatus = ReadFileRecord( volumeHandle, recordIndex, mybuffer ); if (STATUS_SUCCESS != ReadStatus) {
printf( "\nMFT record read failed with status %x", ReadStatus ); goto exit; }
if (FindNameInFileRecord( (PFILE_RECORD_SEGMENT_HEADER) mybuffer, FileName, fileNameLength )) {
//
// Found a match, open by id and retrieve name.
//
if (DebugLevel >= 1) {
printf( "\nFound match in file %08x %08x\n", ((PFILE_RECORD_SEGMENT_HEADER) mybuffer)->SequenceNumber, recordIndex );
} else {
printf( "\n" ); }
segRef.SegmentNumberLowPart = recordIndex; segRef.SegmentNumberHighPart = 0; segRef.SequenceNumber = ((PFILE_RECORD_SEGMENT_HEADER) mybuffer)->SequenceNumber;
FsTestOpenById( (PUCHAR) &segRef, volumeHandle ); }
//
// The number 0x400 is completely arbitrary. It's a reasonable interval
// of work to do before printing another period to tell the user we're
// making progress still.
//
if (0 == (recordIndex % 0x400)) {
printf( "." ); } }
exit: if (volumeHandle != NULL) {
CloseHandle( volumeHandle ); }
return 0; }
VOID FastFindHelp ( char *ExeName ) {
printf( "This program finds a file by scanning the MFT (ntfs only).\n\n" ); printf( "usage: %s x: filename\n", ExeName );
printf( "Where x: is the drive letter\n" ); printf( "example:\n" ); printf( "%s d: windows.h", ExeName ); }
VOID _cdecl main ( int argc, char *argv[] ) { WCHAR drive; ANSI_STRING fileName; WCHAR uniBuff[MAX_PATH]; UNICODE_STRING uniFileName;
//
// Get parameters.
//
if (argc < 3) {
FastFindHelp( argv[0] ); return; }
if (argc >= 4) {
sscanf( argv[3], "%x", &DebugLevel );
} else {
DebugLevel = 0; }
drive = *argv[1];
RtlInitAnsiString( &fileName, argv[2] ); uniFileName.Buffer = uniBuff; RtlAnsiStringToUnicodeString( &uniFileName, &fileName, FALSE ); FastFind( uniFileName.Buffer, &drive ); return; }
|