Pinvoke NtOpenFile and NtQueryEaFile in order to read NTFS Extended Attributes in C#

1

I am trying to code a simple NTFS reader function for Extended Attributes (not Alternate Data Streams !) in C#. It will be used in some powershell scripting later, so i need to strick with C#.

So far i've gathered some infos about NtOpenFile:

 [StructLayout(LayoutKind.Sequential, Pack = 0)]
        public struct OBJECT_ATTRIBUTES
        {
            public Int32 Length;
            public IntPtr RootDirectory;
            public IntPtr ObjectName;
            public uint   Attributes;
            public IntPtr SecurityDescriptor;
            public IntPtr SecurityQualityOfService;

        }

        [StructLayout(LayoutKind.Sequential, Pack = 0)]
        public struct IO_STATUS_BLOCK
        {
            public uint status;
            public IntPtr information;
        }   

        [DllImport("ntdll.dll", ExactSpelling = true, SetLastError = true)]
        public static extern int NtOpenFile(
            out  IntPtr handle,
            System.IO.FileAccess access,
            ref OBJECT_ATTRIBUTES objectAttributes,
            out IO_STATUS_BLOCK ioStatus,
            System.IO.FileShare share,
            uint openOptions
            );

but still no infos on NtQueryEaFile and no demo code to invoke it and marshall its results, thanks for your help !

EDIT1 Getting a bit further with this new code, but still stuck at some point with an "Access Denied" after calling NtQueryEaFile. Any ideas ?

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.Reflection;
using System.IO;
using System.ComponentModel;
using HANDLE = System.IntPtr;

namespace ConsoleApplication1
{
    class Program
    {
        [StructLayout(LayoutKind.Sequential, Pack = 0)]
        public struct OBJECT_ATTRIBUTES
        {
            public Int32 Length;
            public IntPtr RootDirectory;
            public IntPtr ObjectName;
            public uint   Attributes;
            public IntPtr SecurityDescriptor;
            public IntPtr SecurityQualityOfService;

        }

        [StructLayout(LayoutKind.Sequential, Pack = 0)]
        public struct IO_STATUS_BLOCK
        {
            public uint status;
            public IntPtr information;
        }

        [StructLayout(LayoutKind.Sequential, Pack = 0)]
        public struct UNICODE_STRING
        {
            public ushort Length;
            public ushort MaximumLength;
            public IntPtr Buffer;

        }


        [DllImport("ntdll.dll", ExactSpelling = true, SetLastError = true)]
        public static extern uint NtOpenFile(
            out  IntPtr handle,
            System.IO.FileAccess access,
            ref OBJECT_ATTRIBUTES objectAttributes,
            out IO_STATUS_BLOCK ioStatus,
            System.IO.FileShare share,
            uint openOptions
            );


        [DllImport("ntdll.dll", ExactSpelling = true, SetLastError = true)]
        public static extern uint NtQueryEaFile(
            IntPtr handle,
            out IO_STATUS_BLOCK ioStatus,
            out IntPtr buffer,
            uint  length,
            bool retSingleEntry,
            IntPtr eaList,
            uint  eaListLength,
            IntPtr eaIndex,
            bool restartScan
            );

        [DllImport("ntdll.dll")]
        public static extern void RtlInitUnicodeString(
            out UNICODE_STRING DestinationString,
            [MarshalAs(UnmanagedType.LPWStr)] string SourceString);

        [DllImport("ntdll.dll")]
        public static extern uint RtlNtStatusToDosError(uint Status);

        [DllImport("kernel32.dll")]
        public static extern uint FormatMessage(int dwFlags, IntPtr lpSource, uint dwMessageId,
            int dwLanguageId, StringBuilder lpBuffer, int nSize, IntPtr Arguments);

        static void Main(string[] args)
        {
            UInt32 FILE_OPEN = 0x1;
            UInt32 OBJ_CASE_INSENSITIVE = 0x40;
            UInt32 FILE_READ_EA = 8;
            UInt32 FILE_RANDOM_ACCESS = 0x00000800;
            UInt32 FILE_DIRECTORY_FILE = 0x00000002;
            UInt32 FILE_NON_DIRECTORY_FILE = 0x00000040;
            UInt32 FILE_OPEN_FOR_BACKUP_INTENT = 0x00004000;
            uint NT_SUCCESS = 0x0;
            bool restartScan = false;
            bool returnSingleEntry = false;

            IntPtr _RootHandle; //This will need to be initialized with the root handle, can use CreateFile from kernel32.dll
            _RootHandle = IntPtr.Zero;

            UNICODE_STRING unicodeString;
            RtlInitUnicodeString(out unicodeString, @"\??\C:\temp");
            IntPtr unicodeIntPtr = Marshal.AllocHGlobal(Marshal.SizeOf(unicodeString));
            Marshal.StructureToPtr(unicodeString, unicodeIntPtr, false);

            OBJECT_ATTRIBUTES objAttributes = new OBJECT_ATTRIBUTES();
            IO_STATUS_BLOCK ioStatusBlock = new IO_STATUS_BLOCK();
            //Microsoft.Win32.SafeHandles.SafeFileHandle hFile;
            HANDLE hFile;


            objAttributes.Length = System.Convert.ToInt32(Marshal.SizeOf(objAttributes));
            objAttributes.ObjectName = unicodeIntPtr;
            objAttributes.RootDirectory = _RootHandle;
            objAttributes.Attributes = OBJ_CASE_INSENSITIVE;
            objAttributes.SecurityDescriptor = IntPtr.Zero;
            objAttributes.SecurityQualityOfService = IntPtr.Zero;

            uint status = NtOpenFile(out hFile, FileAccess.Read, ref objAttributes, out ioStatusBlock, FileShare.Read, FILE_DIRECTORY_FILE | FILE_READ_EA | FILE_OPEN_FOR_BACKUP_INTENT);
            if (status != NT_SUCCESS)
                ExitWithError(status);           

            IntPtr buffer = Marshal.AllocHGlobal(65535);
            status = NtQueryEaFile(hFile, out ioStatusBlock, out buffer, System.Convert.ToUInt32(Marshal.SizeOf(buffer)), returnSingleEntry, IntPtr.Zero, 0, IntPtr.Zero, restartScan);
            if (status != NT_SUCCESS)
                ExitWithError(status);   

        }

        public static void ExitWithError(uint errorCode)
        {
            Console.WriteLine(GetSystemMessage(RtlNtStatusToDosError(errorCode)));
            Environment.Exit(1);
        }

        public static string GetSystemMessage(uint errorCode)
        {
            int capacity = 512;
            int FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000;
            StringBuilder sb = new StringBuilder(capacity);
            FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, IntPtr.Zero, errorCode, 0,
                sb, sb.Capacity, IntPtr.Zero);
            int i = sb.Length;
            if (i > 0 && sb[i - 1] == 10) i--;
            if (i > 0 && sb[i - 1] == 13) i--;
            sb.Length = i;
            return sb.ToString();
        }

    }
}

EDIT2 After having reviewed this code : http://jbutera.net/mirror/git/alexpux/Cygwin/winsup/cygwin/ntea.cc i modified mine with the following :

NtOpenFile is now :

[DllImport("ntdll.dll", ExactSpelling = true)]
        public static extern uint NtOpenFile(
            out  SafeFileHandle  handle,
            UInt32 access,
            ref OBJECT_ATTRIBUTES objectAttributes,
            out IO_STATUS_BLOCK ioStatus,
            System.IO.FileShare share,
            uint openOptions
            );

NtQueryEaFile is now :

[DllImport("ntdll.dll", ExactSpelling = true)]
public static extern uint NtQueryEaFile(
    SafeFileHandle handle,
    out IO_STATUS_BLOCK ioStatus,
    out IntPtr buffer,
    uint  length,
    bool retSingleEntry,
    IntPtr eaList,
    uint  eaListLength,
    IntPtr eaIndex,
    bool restartScan
    );

The call to NtOpenFile is now :

UInt32 FILE_OPEN = 0x1;
            UInt32 OBJ_CASE_INSENSITIVE = 0x40;
            UInt32 FILE_READ_EA = 8;
            UInt32 FILE_RANDOM_ACCESS = 0x00000800;
            UInt32 FILE_DIRECTORY_FILE = 0x00000002;
            UInt32 FILE_NON_DIRECTORY_FILE = 0x00000040;
            UInt32 FILE_OPEN_FOR_BACKUP_INTENT = 0x00004000;
            UInt32 READ_CONTROL = 0x00020000;
            const UInt32 STATUS_NO_EAS_ON_FILE = 0xC0000052;

            uint status = NtOpenFile(out hFile, READ_CONTROL | FILE_READ_EA, ref objAttributes, out ioStatusBlock, FileShare.ReadWrite | FileShare.Delete, FILE_OPEN_FOR_BACKUP_INTENT);
            if (status != NT_SUCCESS)
                ExitWithError(status);           

            IntPtr buffer = Marshal.AllocHGlobal(65535);

            // status = NtQueryEaFile(fileHandle, &ioStatus, qbuf, sizeof(FILE_FULL_EA_INFORMATION), TRUE, NULL, 0, &QueryEAIndex, FALSE);
            status = NtQueryEaFile(hFile, out ioStatusBlock,out buffer, System.Convert.ToUInt32(Marshal.SizeOf(buffer)), returnSingleEntry, IntPtr.Zero, 0, IntPtr.Zero, restartScan);
            switch (status)
            {
                case STATUS_NO_EAS_ON_FILE:
                    Console.WriteLine("No EAs found");
                    break;

                case NT_SUCCESS:
                    Console.WriteLine("EAs found !");
                    break;

                default:
                    ExitWithError(status);
                    break;
            }                    

And guess what ? It's working ! Well.. Almost... Reading a directory with no EAs correctly throws a STATUS_NO_EAS_ON_FILE. But reading a directory with EAs throws a STATUS_BUFFER_TOO_SMALL (according to http://msdn.microsoft.com/fr-fr/library/cc704588.aspx) so i really have some pbr with my buffer definition/allocation :-/

c#
pinvoke
ntfs
asked on Stack Overflow Dec 5, 2014 by Usul • edited Dec 7, 2014 by Usul

1 Answer

0

Documentation for NtOpenFile is available on MSDN. For NtQueryEaFile, I think you could just refer to the ZwQueryEaFile routine in the Windows Driver Kit (see on MSDN). Most of the Zw... routines replicate the native NT API calls (Nt...), so the signature should be the same.

P/Invoke for NtQueryEaFile (from ZwQueryEaFile):

[DllImport("ntdll.dll", CharSet = CharSet.Unicode)]
public static extern UInt32 NtQueryEaFile(
    [In] SafeFileHandle FileHandle,
    [Out] out IO_STATUS_BLOCK IoStatusBlock,
    [Out] out IntPtr Buffer,
    [In] UInt32 Length,
    [In] Boolean ReturnSingleEntry,
    [In, Optional] IntPtr EaList,
    [In] UInt32 EaListLength,
    [In, Optional] UInt32 EaIndex,
    [In] Boolean RestartScan
);
answered on Stack Overflow Sep 8, 2016 by Florian S.

User contributions licensed under CC BY-SA 3.0