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 :-/
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
);
User contributions licensed under CC BY-SA 3.0