diff --git a/PInvoke/NtDll/Winternl.UnicodeString.cs b/PInvoke/NtDll/Winternl.UnicodeString.cs index 61360113..8123150e 100644 --- a/PInvoke/NtDll/Winternl.UnicodeString.cs +++ b/PInvoke/NtDll/Winternl.UnicodeString.cs @@ -140,16 +140,17 @@ namespace Vanara.PInvoke /// public class SafeUNICODE_STRING : SafeHANDLE { + private readonly int size; + /// Initializes a new instance of the class with a string value. /// The string value. public SafeUNICODE_STRING(string value) : base(IntPtr.Zero, true) { - var structLen = GetStructSize(); - + var structLen = GetStructSize(GetCurrentProcess()); if (string.IsNullOrEmpty(value)) - SetHandle(Marshal.AllocCoTaskMem(structLen)); + SetHandle(Marshal.AllocCoTaskMem(size = structLen)); else - SetHandle(InitMemForString(value, structLen)); + SetHandle(InitMemForString(value, structLen, out size)); } internal SafeUNICODE_STRING(IntPtr ptr, bool own) : base(ptr, own) @@ -160,10 +161,19 @@ namespace Vanara.PInvoke { } + /// The length, in bytes, of the string stored in Buffer. + public ushort Length => IsInvalid ? (ushort)0 : unchecked((ushort)Marshal.ReadInt16(handle, 0)); + + /// The length, in bytes, of Buffer. + public ushort MaximumLength => IsInvalid ? (ushort)0 : unchecked((ushort)Marshal.ReadInt16(handle, 2)); + + /// The size of the allocated memory holding the structure and the string. + public int Size => size; + /// Performs an implicit conversion from to . /// The string value. /// The resulting instance from the conversion. - public static implicit operator SafeUNICODE_STRING(string value) => new SafeUNICODE_STRING(value); + public static implicit operator SafeUNICODE_STRING(string value) => new(value); /// Performs an implicit conversion from to . /// The value. @@ -176,37 +186,43 @@ namespace Vanara.PInvoke public string ToString(HPROCESS hProc) { if (IsInvalid) return null; - var len = unchecked((ushort)Marshal.ReadInt16(handle, 2)); + var maxlen = unchecked((ushort)Marshal.ReadInt16(handle, 2)); hProc = hProc == default ? GetCurrentProcess() : hProc; - using var mem = new SafeCoTaskMemString(len); - if (IsWow64Process(hProc)) - return NtWow64ReadVirtualMemory64(hProc, handle.Offset(Marshal.OffsetOf(typeof(UNICODE_STRING_WOW64), "Buffer").ToInt64()).ToInt64(), ((IntPtr)mem).ToInt32(), mem.Size, out _).Succeeded ? mem : string.Empty; - return ReadProcessMemory(hProc, handle.Offset(Marshal.OffsetOf(typeof(UNICODE_STRING), "Buffer").ToInt64()), mem, mem.Size, out _) ? mem : string.Empty; + var bufOffset = GetBufferOffset(hProc); + var bufPtr = Marshal.ReadIntPtr(handle, bufOffset); + if (hProc == GetCurrentProcess()) + return StringHelper.GetString(bufPtr, CharSet.Unicode, MaximumLength) ?? string.Empty; + using var mem = new SafeCoTaskMemString(maxlen); + if (hProc.IsWow64()) + return NtWow64ReadVirtualMemory64(hProc, bufPtr.ToInt64(), ((IntPtr)mem).ToInt32(), mem.Size, out _).Succeeded ? mem : string.Empty; + return ReadProcessMemory(hProc, bufPtr, mem, mem.Size, out _) ? mem : string.Empty; } /// Extracts the string value from this structure by reading process specific memory. /// A that has the value. public override string ToString() => ToString(default); - internal static int GetStructSize() => Marshal.SizeOf(IsWow64Process() ? typeof(UNICODE_STRING_WOW64) : typeof(UNICODE_STRING)); + internal static int GetStructSize(HPROCESS hProc) => Marshal.SizeOf(hProc.IsWow64() ? typeof(UNICODE_STRING_WOW64) : typeof(UNICODE_STRING)); - internal static IntPtr InitMemForString(string value, int structLen) + internal static int GetBufferOffset(HPROCESS hProc) => Marshal.OffsetOf(hProc.IsWow64() ? typeof(UNICODE_STRING_WOW64) : typeof(UNICODE_STRING), "Buffer").ToInt32(); + + internal static IntPtr InitMemForString(string value, int structLen, out int allocatedSize) { // Collect lengths - var strLen = (ushort)StringHelper.GetByteCount(value, true, CharSet.Unicode); + var strLen = StringHelper.GetByteCount(value, true, CharSet.Unicode); // Create mem and append string after struct - IntPtr mem = Marshal.AllocCoTaskMem(structLen + strLen); + IntPtr mem = Marshal.AllocCoTaskMem(allocatedSize = structLen + strLen); IntPtr strOffset = mem.Offset(structLen); + Marshal.WriteInt16(mem, 0, (short)(strLen - 2)); + Marshal.WriteInt16(mem, 2, (short)strLen); StringHelper.Write(value, strOffset, out _, true, CharSet.Unicode, strLen); - RtlInitUnicodeString(mem, strOffset); + Marshal.WriteIntPtr(mem, GetBufferOffset(GetCurrentProcess()), strOffset); return mem; } /// protected override bool InternalReleaseHandle() { Marshal.FreeCoTaskMem(handle); return true; } - - private static bool IsWow64Process(HPROCESS hProc = default) => Kernel32.IsWow64Process(hProc == default ? GetCurrentProcess() : hProc, out var w) && w; } /// A custom marshaler for functions using UNICODE_STRING so that managed strings can be used. @@ -221,9 +237,9 @@ namespace Vanara.PInvoke void ICustomMarshaler.CleanUpNativeData(IntPtr pNativeData) => Marshal.FreeCoTaskMem(pNativeData); - int ICustomMarshaler.GetNativeDataSize() => SafeUNICODE_STRING.GetStructSize(); + int ICustomMarshaler.GetNativeDataSize() => SafeUNICODE_STRING.GetStructSize(GetCurrentProcess()); - IntPtr ICustomMarshaler.MarshalManagedToNative(object ManagedObj) => ManagedObj is string s ? SafeUNICODE_STRING.InitMemForString(s, SafeUNICODE_STRING.GetStructSize()) : IntPtr.Zero; + IntPtr ICustomMarshaler.MarshalManagedToNative(object ManagedObj) => ManagedObj is string s ? SafeUNICODE_STRING.InitMemForString(s, SafeUNICODE_STRING.GetStructSize(GetCurrentProcess()), out _) : IntPtr.Zero; object ICustomMarshaler.MarshalNativeToManaged(IntPtr pNativeData) => new SafeUNICODE_STRING(pNativeData, false).ToString(); } diff --git a/UnitTests/PInvoke/NtDll/NtDllTests.cs b/UnitTests/PInvoke/NtDll/NtDllTests.cs index f888eb5c..ce8c5510 100644 --- a/UnitTests/PInvoke/NtDll/NtDllTests.cs +++ b/UnitTests/PInvoke/NtDll/NtDllTests.cs @@ -31,5 +31,21 @@ namespace Vanara.PInvoke.Tests TestContext.WriteLine($"{bi.NumberOfProcessors} Cores; {pti.Count} Processes; {pti.Sum(t => t.Item2.Length)} Threads"); } + + [Test] + public void SafeUNICODE_STRING_Test() + { + const string testStr = "Testing. 1. 2. 3."; + SafeUNICODE_STRING sstr = null; + try + { + Assert.That(() => sstr = testStr, Throws.Nothing); + Assert.That((string)sstr, Is.EqualTo(testStr)); + } + finally + { + sstr?.Dispose(); + } + } } } \ No newline at end of file diff --git a/UnitTests/PInvoke/NtDll/WinternlTests.cs b/UnitTests/PInvoke/NtDll/WinternlTests.cs index e85d3603..900239b1 100644 --- a/UnitTests/PInvoke/NtDll/WinternlTests.cs +++ b/UnitTests/PInvoke/NtDll/WinternlTests.cs @@ -9,13 +9,6 @@ using static Vanara.PInvoke.NtDll; namespace Vanara.PInvoke.Tests { - public static class ExtMeth - { - public static readonly Version minWowOSVer = new Version(5, 1); - - public static bool IsWow64(this HPROCESS hProc) => Environment.OSVersion.Version >= minWowOSVer && Kernel32.IsWow64Process(hProc, out var b) && b; - } - [TestFixture] public partial class WinternlTests {