2

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 :-/

Usul
  • 129
  • 8
  • My advice is to get some working C++ code and start from there. Then you have something known to work and all that remains is the interop. Why use Pack = 0? What made you use SetLastError = true? – David Heffernan Dec 06 '14 at 18:15
  • I can tell you that the second param buffer is all wrong. It's not an `out` param. It's a buffer that you allocate. So `[Out] byte[]` would be a good option. Anyway, since this is an undocumented function, it's hard for us to know what's right or wrong. Are you sure you need to use undocumented apis? – David Heffernan Dec 06 '14 at 18:19
  • For the use of Pack = 0 (default value is 0 anyway afaik) and the SetLastError, it comes from this sample https://easyhook.codeplex.com/discussions/468770 – Usul Dec 06 '14 at 22:03
  • SetLastError is wrong. This function doesn't call SetLastError. – David Heffernan Dec 06 '14 at 22:44
  • You are right about the SetLastError, removed it. But anyway it was not the cause of the "Access denied". After some more searches, i've stumbled upon this source code : http://jbutera.net/mirror/git/alexpux/Cygwin/winsup/cygwin/ntea.cc and i changed the DLLImport for NtOpenFile : Access is now Uint32 instead of System.IO.FileAccess so i can use READ_CONTROL | FILE_READ_EA as values, i will update my question with new details – Usul Dec 07 '14 at 12:52
  • Personally I won't look more until I see working C++ code. At that point the interop is easy. – David Heffernan Dec 07 '14 at 13:06
  • My second comment was unclear. I was referring to the buffer parameter of `NtQueryEaFile`. I still wish you would show C++ code. – David Heffernan Dec 08 '14 at 10:44
  • Found this C++ code using NtQueryEaFile, not very documented... https://github.com/amdf/xattrlib – Usul Dec 08 '14 at 15:01
  • My strategy would be to produce working C++ code and than translate to C++. Are you sure you need to use undocumented apis? – David Heffernan Dec 09 '14 at 07:30

1 Answers1

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
);
Florian S.
  • 206
  • 1
  • 5