Search code examples
c++windowsntfs

Query metadata for NTFS special files as unprivileged user?


From an unprivileged user context, how can I query the size of the NTFS special files?

The size is the most important piece of metadata for me, but if I could get everything that is typically found in WIN32_FIND_DATA I would not mind it.

The NTFS special files I mean are (among others): $Mft, $MftMirr, $LogFile, $BadClus et cetera.

In order to open the MFT, I'd have to acquire certain privileges, open the volume and then parse the MFT. So that's out.

Also it seems to be impossible to open these files by name (for the majority of them), which precludes NtQueryInformationFile() and GetFileInformationByHandle(). Or maybe there is a combination of flags I haven't tried and it is possible to open them somehow for querying the file information?

Last but not least I don't get these files returned when using the respective Win32 APIs (FindFirstFile() et. al.), nor with NtQueryDirectoryFile() nor by using IRP_MN_QUERY_DIRECTORY directly.


Yes, I understand that I can effectively get the size of the MFT using FSCTL_GET_NTFS_VOLUME_DATA, but that's just one of these special files.


Solution

  • on ntfs volume we can enumerate all file records with FSCTL_GET_NTFS_FILE_RECORD. unfortunatelly format of FileRecordBuffer is undocumented/undeclared in windows headers. but this is common ntfs structs. buffer begin with NTFS_RECORD_HEADER (base class) after which will be several NTFS_ATTRIBUTE records. partial and custom definitions:

    union NTFS_FILE_ID 
    {
        LONGLONG IndexNumber;
    
        struct  
        {
            LONGLONG MftRecordIndex : 48;
            LONGLONG SequenceNumber : 16;
        };
    };
    
    struct NTFS_RECORD_HEADER 
    {
        enum {
            FILE = 'ELIF',
            INDX = 'XDNI',
            BAAD = 'DAAB',
            HOLE = 'ELOH',
            CHKD = 'DKHC'
        } Type;
        USHORT UsaOffset;
        USHORT UsaCount;
        USN Usn;
    };
    
    struct NTFS_FILE_RECORD_HEADER : public NTFS_RECORD_HEADER
    {
        USHORT SequenceNumber;
        USHORT LinkCount;
        USHORT AttributesOffset;
        USHORT Flags;
        ULONG BytesInUse;
        ULONG BytesAllocated;
        ULONGLONG BaseFileRecord;
        USHORT NextAttributeNumber;
    
        enum{
            flgInUse = 1, flgDirectory = 2
        };
    };
    
    struct NTFS_ATTRIBUTE 
    {
        enum ATTRIBUTE_TYPE {
            StandardInformation = 0x10,
            AttributeList = 0x20,
            FileName = 0x30,
            ObjectId = 0x40,
            SecurityDescriptor = 0x50,
            VolumeName = 0x60,
            VolumeInformation = 0x70,
            Data = 0x80,
            IndexRoot = 0x90,
            IndexAllocation = 0xa0,
            Bitmap = 0xb0,
            ReparsePoint = 0xc0,
            EAInformation = 0xd0,
            EA = 0xe0,
            PropertySet = 0xf0,
            LoggedUtilityStream = 0x100,
            StopTag = MAXDWORD
        } Type;
        ULONG Length;
        BOOLEAN Nonresident;
        UCHAR NameLength;
        USHORT NameOffset;
        USHORT Flags;// 1 = Compresed
        USHORT AttributeNumber;
    };
    
    struct NTFS_RESIDENT_ATTRIBUTE : public NTFS_ATTRIBUTE 
    {
        ULONG ValueLength;
        USHORT ValueOffset;
        USHORT Flags;
    };
    
    struct NTFS_NONRESIDENT_ATTRIBUTE : public NTFS_ATTRIBUTE 
    {
        LONGLONG LowVcn;
        LONGLONG HighVcn;
        USHORT RunArrayOffset;
        UCHAR CompressionUnit;
        UCHAR Unknown[5];
        LONGLONG AllocationSize;
        LONGLONG DataSize;
        LONGLONG InitializedSize;
        LONGLONG CompressedSize;
    };
    
    struct NTFS_ATTRIBUTE_LIST
    {
        NTFS_ATTRIBUTE::ATTRIBUTE_TYPE Type;
        USHORT Length;
        UCHAR NameLength;
        UCHAR NameOffset;
        LONGLONG LowVcn;
        LONGLONG FileReferenceNumber : 48;
        LONGLONG FileReferenceNumber2 : 16;
        USHORT AttributeNumber;
        USHORT Unknown[3];
    };
    
    struct NTFS_STANDARD_ATTRIBUTE 
    {
        LONGLONG CreationTime;
        LONGLONG ChangeTime;
        LONGLONG LastWriteTime;
        LONGLONG LastAccessTime;
        ULONG FileAttributes;
        ULONG Unknown[3];
        ULONG QuotaId;
        ULONG SecurityId;
        ULONGLONG QuotaChange;
        USN Usn;
    };
    
    struct NTFS_FILENAME_ATTRIBUTE
    {
        NTFS_FILE_ID DirectoryId;
        LONGLONG CreationTime;
        LONGLONG ChangeTime;
        LONGLONG LastWriteTime;
        LONGLONG LastAccessTime;
        LONGLONG AllocationSize;
        LONGLONG DataSize;
        ULONG FileAttributes;
        ULONG EaSize;
        UCHAR FileNameLength;// in symbols !!
        UCHAR NameType;
        WCHAR FileName[];
    
        enum {
            systemName , longName, shortName, systemName2
        };
    };
    

    the code of enumeration all files can look like:

    inline ULONG BOOL_TO_ERROR(BOOL f)
    {
        return f ? NOERROR : GetLastError();
    }
    
    ULONG QFMD(PCWSTR szVolumeName)
    {
        HANDLE hVolume = CreateFile(szVolumeName, FILE_GENERIC_READ, FILE_SHARE_VALID_FLAGS, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0);
    
        if (hVolume == INVALID_HANDLE_VALUE)
        {
            return GetLastError();
        }
    
        ULONG cb, BytesReturned;
        NTFS_VOLUME_DATA_BUFFER nvdb;
    
        ULONG err = BOOL_TO_ERROR(DeviceIoControl(hVolume, FSCTL_GET_NTFS_VOLUME_DATA, 0, 0, &nvdb, sizeof(nvdb), &BytesReturned, 0));
    
        if (err == NOERROR)
        {
            NTFS_FILE_RECORD_INPUT_BUFFER nfrib;
    
            cb = FIELD_OFFSET(NTFS_FILE_RECORD_OUTPUT_BUFFER, FileRecordBuffer[nvdb.BytesPerFileRecordSegment]);
    
            PNTFS_FILE_RECORD_OUTPUT_BUFFER pnfrob = (PNTFS_FILE_RECORD_OUTPUT_BUFFER)alloca(cb);
    
            // search for maximum valid FileReferenceNumber
            LONG a = 0, b = MAXLONG, o;
            do 
            {
                nfrib.FileReferenceNumber.QuadPart = o = (a + b) >> 1;
    
                err = BOOL_TO_ERROR(DeviceIoControl(hVolume, FSCTL_GET_NTFS_FILE_RECORD, 
                    &nfrib, sizeof nfrib, pnfrob, cb, &BytesReturned, 0));
    
                err ? b = o : a = o + 1;
    
            } while(a < b);
    
            nfrib.FileReferenceNumber.QuadPart--;
    
            DbgPrint("MftRecordCount=%u\n", nfrib.FileReferenceNumber.LowPart);
    
    
            union {
                PVOID FileRecordBuffer;
                PBYTE pb;
                NTFS_RECORD_HEADER* pnrh;
                NTFS_FILE_RECORD_HEADER* pnfrh;
                NTFS_ATTRIBUTE* pna;
                NTFS_RESIDENT_ATTRIBUTE* pnra;
                NTFS_NONRESIDENT_ATTRIBUTE* pnaa;
            };
    
            NTFS_FILE_ID nfi;
            UNICODE_STRING us = { sizeof (nfi), sizeof (nfi), (PWSTR)&nfi };
            OBJECT_ATTRIBUTES oa = { sizeof(oa), hVolume, &us };
    
            do 
            {
                FileRecordBuffer = pnfrob->FileRecordBuffer;
    
                if (err = BOOL_TO_ERROR(DeviceIoControl(hVolume, FSCTL_GET_NTFS_FILE_RECORD, 
                    &nfrib, sizeof nfrib, pnfrob, cb, &BytesReturned, 0)))
                {
                    break;
                }
    
                // are really file
                if (
                    pnrh->Type != NTFS_RECORD_HEADER::FILE ||
                    !(pnfrh->Flags & NTFS_FILE_RECORD_HEADER::flgInUse) ||
                    pnfrh->BaseFileRecord
                    )
                {
                    continue;
                }
    
                ULONG FileAttributes = INVALID_FILE_ATTRIBUTES;
                ULONGLONG FileSize = 0;
    
                nfi.MftRecordIndex = pnfrob->FileReferenceNumber.QuadPart;
                nfi.SequenceNumber = pnfrh->SequenceNumber;
    
                pb += pnfrh->AttributesOffset;
    
                for( ; ; ) 
                {
                    NTFS_FILENAME_ATTRIBUTE* pnfa;
                    NTFS_STANDARD_ATTRIBUTE* pnsa;
    
                    switch (pna->Type)
                    {
                    case NTFS_ATTRIBUTE::StopTag:
                        goto __end;
    
                    case NTFS_ATTRIBUTE::FileName:
    
                        pnfa = (NTFS_FILENAME_ATTRIBUTE*)RtlOffsetToPointer(pnra, pnra->ValueOffset);
    
                        if (pnfa->NameType == NTFS_FILENAME_ATTRIBUTE::longName)
                        {
                            //DbgPrint("<< %.*S\n", pnfa->FileNameLength, pnfa->FileName);
                        }
                        break;
    
                    case NTFS_ATTRIBUTE::StandardInformation:
    
                        pnsa = (NTFS_STANDARD_ATTRIBUTE*)RtlOffsetToPointer(pnra, pnra->ValueOffset);
                        FileAttributes = pnsa->FileAttributes;
                        break;
    
                    case NTFS_ATTRIBUTE::Data:
                        FileSize += pna->Nonresident ? pnaa->DataSize : pnra->ValueLength;
                        break;
                    }
    
                    pb += pna->Length;
                }
    
    __end:;
    
                //HANDLE hFile;
                //IO_STATUS_BLOCK iosb;
    
                //NTSTATUS status = NtOpenFile(&hFile, FILE_READ_ATTRIBUTES, &oa, &iosb, FILE_SHARE_VALID_FLAGS,
                //  FILE_OPEN_REPARSE_POINT| FILE_OPEN_BY_FILE_ID | FILE_OPEN_FOR_BACKUP_INTENT);
    
                //if (0 <= status)
                //{
                //  NtClose(hFile);
                //}
    
    
            } while (0 <= (nfrib.FileReferenceNumber.QuadPart = pnfrob->FileReferenceNumber.QuadPart - 1));
    
        }
    
        CloseHandle(hVolume);
    
        return err;
    }
    

    some NTFS System Files, but this list already old, exist more system files. if want concrete system file query need assign it number to NTFS_FILE_RECORD_INPUT_BUFFER. little changed code for query sys files only:

    ULONG QFMD(PCWSTR szVolumeName)
    {
        HANDLE hVolume = CreateFile(szVolumeName, FILE_GENERIC_READ, FILE_SHARE_VALID_FLAGS, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0);
    
        if (hVolume == INVALID_HANDLE_VALUE)
        {
            return GetLastError();
        }
    
        ULONG cb, BytesReturned;
        NTFS_VOLUME_DATA_BUFFER nvdb;
    
        ULONG err = BOOL_TO_ERROR(DeviceIoControl(hVolume, FSCTL_GET_NTFS_VOLUME_DATA, 0, 0, &nvdb, sizeof(nvdb), &BytesReturned, 0));
    
        if (err == NOERROR)
        {
            NTFS_FILE_RECORD_INPUT_BUFFER nfrib;
    
            nfrib.FileReferenceNumber.QuadPart = 0x30;
    
            cb = FIELD_OFFSET(NTFS_FILE_RECORD_OUTPUT_BUFFER, FileRecordBuffer[nvdb.BytesPerFileRecordSegment]);
    
            PNTFS_FILE_RECORD_OUTPUT_BUFFER pnfrob = (PNTFS_FILE_RECORD_OUTPUT_BUFFER)alloca(cb);
    
            union {
                PVOID FileRecordBuffer;
                PBYTE pb;
                NTFS_RECORD_HEADER* pnrh;
                NTFS_FILE_RECORD_HEADER* pnfrh;
                NTFS_ATTRIBUTE* pna;
                NTFS_RESIDENT_ATTRIBUTE* pnra;
                NTFS_NONRESIDENT_ATTRIBUTE* pnaa;
            };
    
            NTFS_FILE_ID nfi;
            UNICODE_STRING us = { sizeof (nfi), sizeof (nfi), (PWSTR)&nfi };
            OBJECT_ATTRIBUTES oa = { sizeof(oa), hVolume, &us };
    
            do 
            {
                FileRecordBuffer = pnfrob->FileRecordBuffer;
    
                if (err = BOOL_TO_ERROR(DeviceIoControl(hVolume, FSCTL_GET_NTFS_FILE_RECORD, 
                    &nfrib, sizeof nfrib, pnfrob, cb, &BytesReturned, 0)))
                {
                    break;
                }
    
                // are really file
                if (
                    pnrh->Type != NTFS_RECORD_HEADER::FILE ||
                    !(pnfrh->Flags & NTFS_FILE_RECORD_HEADER::flgInUse) ||
                    pnfrh->BaseFileRecord
                    )
                {
                    continue;
                }
    
                ULONG FileAttributes = INVALID_FILE_ATTRIBUTES;
                ULONGLONG FileSize = 0;
                PCWSTR ShortName = 0, LongName = 0, SystemName = 0;
                UCHAR ShortNameLength = 0, LongNameLength = 0, SystemNameLength = 0;
    
                nfi.MftRecordIndex = pnfrob->FileReferenceNumber.QuadPart;
                nfi.SequenceNumber = pnfrh->SequenceNumber;
    
                pb += pnfrh->AttributesOffset;
    
                BOOL bSysFile = FALSE;
    
                for( ; ; ) 
                {
                    union {
                        NTFS_FILENAME_ATTRIBUTE* pnfa;
                        NTFS_STANDARD_ATTRIBUTE* pnsa;
                    };
    
                    switch (pna->Type)
                    {
                    case NTFS_ATTRIBUTE::StopTag:
                        goto __end;
    
                    case NTFS_ATTRIBUTE::FileName:
    
                        pnfa = (NTFS_FILENAME_ATTRIBUTE*)RtlOffsetToPointer(pnra, pnra->ValueOffset);
    
                        switch (pnfa->NameType)
                        {
                        case NTFS_FILENAME_ATTRIBUTE::systemName:
                        case NTFS_FILENAME_ATTRIBUTE::systemName2:
                            bSysFile = TRUE;
                            SystemName = pnfa->FileName, SystemNameLength = pnfa->FileNameLength;
                            break;
                        case NTFS_FILENAME_ATTRIBUTE::longName:
                            LongName = pnfa->FileName, LongNameLength = pnfa->FileNameLength;
                            break;
                        case NTFS_FILENAME_ATTRIBUTE::shortName:
                            ShortName = pnfa->FileName, ShortNameLength = pnfa->FileNameLength;
                            break;
                        }
                        break;
    
                    case NTFS_ATTRIBUTE::StandardInformation:
    
                        pnsa = (NTFS_STANDARD_ATTRIBUTE*)RtlOffsetToPointer(pnra, pnra->ValueOffset);
                        FileAttributes = pnsa->FileAttributes;
                        break;
    
                    case NTFS_ATTRIBUTE::Data:
                        FileSize += pna->Nonresident ? pnaa->DataSize : pnra->ValueLength;
                        break;
                    }
    
                    pb += pna->Length;
                }
    
    __end:;
    
                if (bSysFile)
                {
                    HANDLE hFile;
                    IO_STATUS_BLOCK iosb;
    
                    NTSTATUS status = NtOpenFile(&hFile, FILE_READ_ATTRIBUTES, &oa, &iosb, FILE_SHARE_VALID_FLAGS,
                        FILE_OPEN_REPARSE_POINT| FILE_OPEN_BY_FILE_ID | FILE_OPEN_FOR_BACKUP_INTENT);
    
                    if (0 <= status)
                    {
                        NtClose(hFile);
                    }
    
                    char sz[32];
                    StrFormatByteSize64A(FileSize, sz, RTL_NUMBER_OF(sz));
                    DbgPrint("%I64u: %08x %s [%x] %.*S\n", pnfrob->FileReferenceNumber.QuadPart, 
                        FileAttributes, sz, status, SystemNameLength, SystemName);
                }
    
    
            } while (0 <= (nfrib.FileReferenceNumber.QuadPart = pnfrob->FileReferenceNumber.QuadPart - 1));
    
        }
    
        CloseHandle(hVolume);
    
        return err;
    }
    

    with it i got next result:

    38: 10000006 0 bytes [0] $Deleted
    34: 00000020 10.0 MB [0] $TxfLogContainer00000000000000000002
    33: 00000020 10.0 MB [0] $TxfLogContainer00000000000000000001
    32: 00000020 64.0 KB [0] $TxfLog.blf
    31: 00000026 1.00 MB [0] $Tops
    30: 80000006 0 bytes [0] $Txf
    29: 00000006 0 bytes [0] $TxfLog
    28: 00000026 27.0 MB [0] $Repair
    27: 00000006 0 bytes [0] $RmMetadata
    26: 20000026 0 bytes [c0000034] $Reparse
    25: 20000026 0 bytes [c0000034] $ObjId
    24: 20000026 0 bytes [c0000034] $Quota
    11: 00000006 0 bytes [0] $Extend
    10: 00000006 128 KB [0] $UpCase
    9: 20000006 0 bytes [c0000034] $Secure
    8: 00000006 237 GB [c0000022] $BadClus
    7: 00000006 8.00 KB [c0000022] $Boot
    6: 00000006 7.42 MB [c0000022] $Bitmap
    5: 00000806 0 bytes [0] .
    4: 00000006 2.50 KB [0] $AttrDef
    3: 00000006 0 bytes [0] $Volume
    2: 00000006 64.0 MB [c0000022] $LogFile
    1: 00000006 4.00 KB [0] $MFTMirr
    0: 00000006 212 MB [0] $MFT