├── CsWhispers.Tests ├── GlobalUsings.cs ├── CsWhispers.txt ├── SyscallTest.cs ├── GenericTests.cs └── CsWhispers.Tests.csproj ├── .gitattributes ├── CsWhispers.Generator ├── Source │ ├── SECTION_INHERIT.cs │ ├── MEMORY_INFORMATION_CLASS.cs │ ├── CLIENT_ID.cs │ ├── PS_ATTRIBUTE_LIST.cs │ ├── UNICODE_STRING.cs │ ├── Usings.cs │ ├── USER_THREAD_START_ROUTINE.cs │ ├── MEMORY_BASIC_INFORMATION.cs │ ├── IO_STATUS_BLOCK.cs │ ├── OBJECT_ATTRIBUTES.cs │ ├── HANDLE.cs │ ├── PWSTR.cs │ ├── PCWSTR.cs │ ├── NtClose.cs │ ├── NTSTATUS.cs │ ├── NtUnmapViewOfSection.cs │ ├── NtFreeVirtualMemory.cs │ ├── NtReadVirtualMemory.cs │ ├── NtWriteVirtualMemory.cs │ ├── NtProtectVirtualMemory.cs │ ├── NtQueryVirtualMemory.cs │ ├── Constants.cs │ ├── NtMapViewOfSection.cs │ ├── Native.cs │ ├── NtCreateThreadEx.cs │ ├── NtOpenProcess.cs │ ├── NtCreateSection.cs │ ├── NtOpenFile.cs │ ├── NtAllocateVirtualMemory.cs │ ├── Syscalls.cs │ └── DynamicInvoke.cs ├── Properties │ └── launchSettings.json ├── SourceGenerator.cs └── CsWhispers.Generator.csproj ├── CsWhispers.Sample ├── CsWhispers.txt ├── Properties │ └── AssemblyInfo.cs ├── Program.cs └── CsWhispers.Sample.csproj ├── LICENSE ├── CsWhispers.sln ├── README.md └── .gitignore /CsWhispers.Tests/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | global using Xunit; -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /CsWhispers.Tests/CsWhispers.txt: -------------------------------------------------------------------------------- 1 | NtOpenProcess 2 | 3 | NTSTATUS 4 | HANDLE 5 | OBJECT_ATTRIBUTES 6 | CLIENT_ID 7 | UNICODE_STRING 8 | PWSTR 9 | PCWSTR -------------------------------------------------------------------------------- /CsWhispers.Generator/Source/SECTION_INHERIT.cs: -------------------------------------------------------------------------------- 1 | namespace CsWhispers; 2 | 3 | public enum SECTION_INHERIT 4 | { 5 | ViewShare = 1, 6 | ViewUnmap = 2, 7 | } -------------------------------------------------------------------------------- /CsWhispers.Generator/Source/MEMORY_INFORMATION_CLASS.cs: -------------------------------------------------------------------------------- 1 | namespace CsWhispers; 2 | 3 | public enum MEMORY_INFORMATION_CLASS 4 | { 5 | MemoryBasicInformation 6 | } -------------------------------------------------------------------------------- /CsWhispers.Generator/Source/CLIENT_ID.cs: -------------------------------------------------------------------------------- 1 | namespace CsWhispers; 2 | 3 | public struct CLIENT_ID 4 | { 5 | public HANDLE UniqueProcess; 6 | public HANDLE UniqueThread; 7 | } -------------------------------------------------------------------------------- /CsWhispers.Generator/Source/PS_ATTRIBUTE_LIST.cs: -------------------------------------------------------------------------------- 1 | namespace CsWhispers; 2 | 3 | public struct PS_ATTRIBUTE_LIST 4 | { 5 | public nuint TotalLength; 6 | public unsafe void* Attributes; 7 | } -------------------------------------------------------------------------------- /CsWhispers.Generator/Source/UNICODE_STRING.cs: -------------------------------------------------------------------------------- 1 | namespace CsWhispers; 2 | 3 | public struct UNICODE_STRING 4 | { 5 | public ushort Length; 6 | public ushort MaximumLength; 7 | public PWSTR Buffer; 8 | } -------------------------------------------------------------------------------- /CsWhispers.Generator/Source/Usings.cs: -------------------------------------------------------------------------------- 1 | global using System; 2 | global using System.Collections.Generic; 3 | 4 | global using CsWhispers; 5 | global using static CsWhispers.Syscalls; 6 | global using static CsWhispers.Constants; 7 | 8 | global using DInvoke; -------------------------------------------------------------------------------- /CsWhispers.Generator/Source/USER_THREAD_START_ROUTINE.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | 3 | namespace CsWhispers; 4 | 5 | [UnmanagedFunctionPointer(CallingConvention.StdCall)] 6 | public unsafe delegate NTSTATUS USER_THREAD_START_ROUTINE(void* threadParameter); -------------------------------------------------------------------------------- /CsWhispers.Generator/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/launchsettings.json", 3 | "profiles": { 4 | "DebugRoslynSourceGenerator": { 5 | "commandName": "DebugRoslynComponent", 6 | "targetProject": "../CsWhispers.Sample/CsWhispers.Sample.csproj" 7 | } 8 | } 9 | } -------------------------------------------------------------------------------- /CsWhispers.Generator/Source/MEMORY_BASIC_INFORMATION.cs: -------------------------------------------------------------------------------- 1 | namespace CsWhispers; 2 | 3 | public unsafe struct MEMORY_BASIC_INFORMATION 4 | { 5 | void* BaseAddress; 6 | void* AllocationBase; 7 | uint AllocationProtect; 8 | ushort PartitionId; 9 | nuint RegionSize; 10 | uint State; 11 | uint Protect; 12 | uint Type; 13 | } -------------------------------------------------------------------------------- /CsWhispers.Generator/Source/IO_STATUS_BLOCK.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | 3 | namespace CsWhispers; 4 | 5 | public struct IO_STATUS_BLOCK 6 | { 7 | public AnonymousUnion Union; 8 | public nuint Information; 9 | 10 | [StructLayout(LayoutKind.Explicit)] 11 | public unsafe struct AnonymousUnion 12 | { 13 | [FieldOffset(0)] 14 | public NTSTATUS Status; 15 | 16 | [FieldOffset(0)] 17 | public void* Pointer; 18 | } 19 | } -------------------------------------------------------------------------------- /CsWhispers.Sample/CsWhispers.txt: -------------------------------------------------------------------------------- 1 | NtAllocateVirtualMemory 2 | NtClose 3 | NtCreateSection 4 | NtFreeVirtualMemory 5 | NtMapViewOfSection 6 | NtOpenFile 7 | NtOpenProcess 8 | NtProtectVirtualMemory 9 | NtQueryVirtualMemory 10 | NtReadVirtualMemory 11 | NtUnmapViewOfSection 12 | NtWriteVirtualMemory 13 | NtCreateThreadEx 14 | 15 | NTSTATUS 16 | HANDLE 17 | OBJECT_ATTRIBUTES 18 | CLIENT_ID 19 | UNICODE_STRING 20 | PWSTR 21 | PCWSTR 22 | SECTION_INHERIT 23 | IO_STATUS_BLOCK 24 | MEMORY_INFORMATION_CLASS 25 | PS_ATTRIBUTE_LIST 26 | USER_THREAD_START_ROUTINE -------------------------------------------------------------------------------- /CsWhispers.Tests/SyscallTest.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | 3 | namespace CsWhispers.Tests; 4 | 5 | public sealed class SyscallTest 6 | { 7 | [Fact] 8 | public static unsafe void OpenProcess() 9 | { 10 | using var self = Process.GetCurrentProcess(); 11 | 12 | NTSTATUS status; 13 | HANDLE hProcess; 14 | OBJECT_ATTRIBUTES oa; 15 | 16 | CLIENT_ID cid = new() 17 | { 18 | UniqueProcess = (HANDLE)self.Id 19 | }; 20 | 21 | status = NtOpenProcess( 22 | &hProcess, 23 | PROCESS_ALL_ACCESS, 24 | &oa, 25 | &cid); 26 | 27 | Assert.Equal(NTSTATUS.Severity.Success, status.SeverityCode); 28 | Assert.NotEqual(HANDLE.Null, hProcess); 29 | } 30 | } -------------------------------------------------------------------------------- /CsWhispers.Tests/GenericTests.cs: -------------------------------------------------------------------------------- 1 | namespace CsWhispers.Tests; 2 | 3 | public sealed class GenericTests 4 | { 5 | [Fact] 6 | public void GetPebAddress() 7 | { 8 | var peb = Generic.GetPebAddress(); 9 | Assert.NotEqual(IntPtr.Zero, peb); 10 | } 11 | 12 | [Fact] 13 | public void GetLoadedLibraryAddress() 14 | { 15 | var hFunction = Generic.GetLibraryAddress( 16 | "kernel32.dll", 17 | "OpenProcess"); 18 | 19 | Assert.NotEqual(IntPtr.Zero, hFunction); 20 | } 21 | 22 | [Fact] 23 | public void GetUnloadedLibraryAddress() 24 | { 25 | var hFunction = Generic.GetLibraryAddress( 26 | "secur32.dll", 27 | "LsaConnectUntrusted", 28 | true); 29 | 30 | Assert.NotEqual(IntPtr.Zero, hFunction); 31 | } 32 | } -------------------------------------------------------------------------------- /CsWhispers.Generator/Source/OBJECT_ATTRIBUTES.cs: -------------------------------------------------------------------------------- 1 | namespace CsWhispers; 2 | 3 | public unsafe struct OBJECT_ATTRIBUTES 4 | { 5 | public uint Length; 6 | public HANDLE RootDirectory; 7 | public UNICODE_STRING* ObjectName; 8 | public uint Attributes; 9 | public void* SecurityDescriptor; 10 | public void* SecurityQualityOfService; 11 | } 12 | 13 | public static partial class Constants 14 | { 15 | public const int OBJ_HANDLE_TAGBITS = 3; 16 | public const int OBJ_INHERIT = 2; 17 | public const int OBJ_PERMANENT = 16; 18 | public const int OBJ_EXCLUSIVE = 32; 19 | public const int OBJ_CASE_INSENSITIVE = 64; 20 | public const int OBJ_OPENIF = 128; 21 | public const int OBJ_OPENLINK = 256; 22 | public const int OBJ_KERNEL_HANDLE = 512; 23 | public const int OBJ_FORCE_ACCESS_CHECK = 1024; 24 | public const int OBJ_IGNORE_IMPERSONATED_DEVICEMAP = 2048; 25 | public const int OBJ_DONT_REPARSE = 4096; 26 | public const int OBJ_VALID_ATTRIBUTES = 8178; 27 | } -------------------------------------------------------------------------------- /CsWhispers.Generator/Source/HANDLE.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | 3 | namespace CsWhispers; 4 | 5 | [DebuggerDisplay("{Value}")] 6 | public readonly struct HANDLE(IntPtr value) : IEquatable 7 | { 8 | public readonly IntPtr Value = value; 9 | 10 | public static HANDLE Null => default; 11 | 12 | public bool IsNull => Value == default; 13 | 14 | public static implicit operator IntPtr(HANDLE value) => value.Value; 15 | 16 | public static explicit operator HANDLE(IntPtr value) => new(value); 17 | 18 | public static bool operator ==(HANDLE left, HANDLE right) => left.Value == right.Value; 19 | 20 | public static bool operator !=(HANDLE left, HANDLE right) => !(left == right); 21 | 22 | public bool Equals(HANDLE other) => Value == other.Value; 23 | 24 | public override bool Equals(object obj) => obj is HANDLE other && Equals(other); 25 | 26 | public override int GetHashCode() => Value.GetHashCode(); 27 | 28 | public override string ToString() => $"0x{Value:x}"; 29 | } -------------------------------------------------------------------------------- /CsWhispers.Generator/Source/PWSTR.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | 3 | namespace CsWhispers; 4 | 5 | [DebuggerDisplay("{Value}")] 6 | public readonly unsafe struct PWSTR : IEquatable 7 | { 8 | public readonly char* Value; 9 | 10 | public PWSTR(char* value) => Value = value; 11 | 12 | public static implicit operator char*(PWSTR value) => value.Value; 13 | 14 | public static implicit operator PWSTR(char* value) => new(value); 15 | 16 | public static bool operator ==(PWSTR left, PWSTR right) => left.Value == right.Value; 17 | 18 | public static bool operator !=(PWSTR left, PWSTR right) => !(left == right); 19 | 20 | public bool Equals(PWSTR other) => Value == other.Value; 21 | 22 | public override bool Equals(object obj) => obj is PWSTR other && Equals(other); 23 | 24 | public override int GetHashCode() => (int)Value; 25 | 26 | public override string ToString() => new PCWSTR(Value).ToString(); 27 | 28 | public static implicit operator PCWSTR(PWSTR value) => new(value.Value); 29 | 30 | public int Length => new PCWSTR(Value).Length; 31 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Rasta Mouse 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /CsWhispers.Generator/Source/PCWSTR.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | 3 | namespace CsWhispers; 4 | 5 | [DebuggerDisplay("{" + nameof(DebuggerDisplay) + "}")] 6 | public readonly unsafe struct PCWSTR : IEquatable 7 | { 8 | public readonly char* Value; 9 | 10 | public PCWSTR(char* value) => Value = value; 11 | 12 | public static explicit operator char*(PCWSTR value) => value.Value; 13 | 14 | public static implicit operator PCWSTR(char* value) => new(value); 15 | 16 | public bool Equals(PCWSTR other) => Value == other.Value; 17 | 18 | public override bool Equals(object obj) => obj is PCWSTR other && Equals(other); 19 | 20 | public override int GetHashCode() => (int)Value; 21 | 22 | public int Length 23 | { 24 | get 25 | { 26 | var p = Value; 27 | 28 | if (p is null) 29 | return 0; 30 | 31 | while (*p != '\0') 32 | p++; 33 | 34 | return checked((int)(p - Value)); 35 | } 36 | } 37 | 38 | public override string ToString() => Value is null ? string.Empty : new string(Value); 39 | 40 | private string DebuggerDisplay => ToString(); 41 | } -------------------------------------------------------------------------------- /CsWhispers.Generator/Source/NtClose.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | 3 | namespace CsWhispers; 4 | 5 | public static unsafe partial class Syscalls 6 | { 7 | private const string ZwCloseHash = "F11693417BD581AAA27083765DB7A812"; 8 | 9 | public static NTSTATUS NtClose(HANDLE handle) 10 | { 11 | var stub = GetSyscallStub(ZwCloseHash); 12 | 13 | fixed (byte* buffer = stub) 14 | { 15 | var ptr = (IntPtr)buffer; 16 | var size = new IntPtr(stub.Length); 17 | 18 | Native.NtProtectVirtualMemory( 19 | new HANDLE((IntPtr)(-1)), 20 | ref ptr, 21 | ref size, 22 | 0x00000020, 23 | out var oldProtect); 24 | 25 | var ntClose = Marshal.GetDelegateForFunctionPointer(ptr); 26 | 27 | var status = ntClose(handle); 28 | 29 | Native.NtProtectVirtualMemory( 30 | new HANDLE((IntPtr)(-1)), 31 | ref ptr, 32 | ref size, 33 | oldProtect, 34 | out _); 35 | 36 | return status; 37 | } 38 | } 39 | 40 | [UnmanagedFunctionPointer(CallingConvention.StdCall)] 41 | private delegate NTSTATUS ZwClose(HANDLE handle); 42 | } -------------------------------------------------------------------------------- /CsWhispers.Generator/Source/NTSTATUS.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | 3 | namespace CsWhispers; 4 | 5 | [DebuggerDisplay("{Value}")] 6 | public readonly struct NTSTATUS : IEquatable 7 | { 8 | public readonly int Value; 9 | 10 | public NTSTATUS(int value) => Value = value; 11 | 12 | public static implicit operator int(NTSTATUS value) => value.Value; 13 | 14 | public static explicit operator NTSTATUS(int value) => new(value); 15 | 16 | public static bool operator ==(NTSTATUS left, NTSTATUS right) => left.Value == right.Value; 17 | 18 | public static bool operator !=(NTSTATUS left, NTSTATUS right) => !(left == right); 19 | 20 | public bool Equals(NTSTATUS other) => Value == other.Value; 21 | 22 | public override bool Equals(object obj) => obj is NTSTATUS other && Equals(other); 23 | 24 | public override int GetHashCode() => Value.GetHashCode(); 25 | 26 | public override string ToString() => $"0x{Value:x}"; 27 | 28 | public static implicit operator uint(NTSTATUS value) => (uint)value.Value; 29 | 30 | public static explicit operator NTSTATUS(uint value) => new((int)value); 31 | 32 | public Severity SeverityCode => (Severity)(((uint)Value & 0xc0000000) >> 30); 33 | 34 | public enum Severity 35 | { 36 | Success, 37 | Informational, 38 | Warning, 39 | Error, 40 | } 41 | } -------------------------------------------------------------------------------- /CsWhispers.Sample/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | [assembly: AssemblyTitle("CsWhispers.Sample")] 8 | [assembly: AssemblyDescription("")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("")] 11 | [assembly: AssemblyProduct("CsWhispers.Sample")] 12 | [assembly: AssemblyCopyright("Copyright © 2024")] 13 | [assembly: AssemblyTrademark("")] 14 | [assembly: AssemblyCulture("")] 15 | 16 | // Setting ComVisible to false makes the types in this assembly not visible 17 | // to COM components. If you need to access a type in this assembly from 18 | // COM, set the ComVisible attribute to true on that type. 19 | [assembly: ComVisible(false)] 20 | 21 | // The following GUID is for the ID of the typelib if this project is exposed to COM 22 | [assembly: Guid("F3EFEF07-0F1A-4162-A2D8-56CE9028CB9D")] 23 | 24 | // Version information for an assembly consists of the following four values: 25 | // 26 | // Major Version 27 | // Minor Version 28 | // Build Number 29 | // Revision 30 | // 31 | // You can specify all the values or you can default the Build and Revision Numbers 32 | // by using the '*' as shown below: 33 | // [assembly: AssemblyVersion("1.0.*")] 34 | [assembly: AssemblyVersion("1.0.0.0")] 35 | [assembly: AssemblyFileVersion("1.0.0.0")] -------------------------------------------------------------------------------- /CsWhispers.Generator/Source/NtUnmapViewOfSection.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | 3 | namespace CsWhispers; 4 | 5 | public static unsafe partial class Syscalls 6 | { 7 | private const string ZwUnmapViewOfSectionHash = "57447AEA45A1F4B2C045B0E316FE8F12"; 8 | 9 | public static NTSTATUS NtUnmapViewOfSection( 10 | HANDLE processHandle, 11 | [Optional] void* baseAddress) 12 | { 13 | var stub = GetSyscallStub(ZwUnmapViewOfSectionHash); 14 | 15 | fixed (byte* buffer = stub) 16 | { 17 | var ptr = (IntPtr)buffer; 18 | var size = new IntPtr(stub.Length); 19 | 20 | Native.NtProtectVirtualMemory( 21 | new HANDLE((IntPtr)(-1)), 22 | ref ptr, 23 | ref size, 24 | 0x00000020, 25 | out var oldProtect); 26 | 27 | var ntUnmapViewOfSection = Marshal.GetDelegateForFunctionPointer(ptr); 28 | 29 | var status = ntUnmapViewOfSection( 30 | processHandle, 31 | baseAddress); 32 | 33 | Native.NtProtectVirtualMemory( 34 | new HANDLE((IntPtr)(-1)), 35 | ref ptr, 36 | ref size, 37 | oldProtect, 38 | out _); 39 | 40 | return status; 41 | } 42 | } 43 | 44 | [UnmanagedFunctionPointer(CallingConvention.StdCall)] 45 | private delegate NTSTATUS ZwUnmapViewOfSection( 46 | HANDLE processHandle, 47 | [Optional] void* baseAddress); 48 | } -------------------------------------------------------------------------------- /CsWhispers.Tests/CsWhispers.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | disable 7 | false 8 | true 9 | 10 | 11 | 12 | true 13 | 14 | 15 | 16 | true 17 | 18 | 19 | 20 | 21 | 22 | 23 | runtime; build; native; contentfiles; analyzers; buildtransitive 24 | all 25 | 26 | 27 | runtime; build; native; contentfiles; analyzers; buildtransitive 28 | all 29 | 30 | 31 | 32 | 33 | 34 | false 35 | Analyzer 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /CsWhispers.Generator/Source/NtFreeVirtualMemory.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | 3 | namespace CsWhispers; 4 | 5 | public static unsafe partial class Syscalls 6 | { 7 | private const string ZwFreeVirtualMemoryHash = "A2356248E8839427AE2D390367DC1A40"; 8 | 9 | public static NTSTATUS NtFreeVirtualMemory( 10 | HANDLE processHandle, 11 | void* baseAddress, 12 | uint* regionSize, 13 | uint freeType) 14 | { 15 | var stub = GetSyscallStub(ZwCloseHash); 16 | 17 | fixed (byte* buffer = stub) 18 | { 19 | var ptr = (IntPtr)buffer; 20 | var size = new IntPtr(stub.Length); 21 | 22 | Native.NtProtectVirtualMemory( 23 | new HANDLE((IntPtr)(-1)), 24 | ref ptr, 25 | ref size, 26 | 0x00000020, 27 | out var oldProtect); 28 | 29 | var ntFreeVirtualMemory = Marshal.GetDelegateForFunctionPointer(ptr); 30 | 31 | var status = ntFreeVirtualMemory( 32 | processHandle, 33 | baseAddress, 34 | regionSize, 35 | freeType); 36 | 37 | Native.NtProtectVirtualMemory( 38 | new HANDLE((IntPtr)(-1)), 39 | ref ptr, 40 | ref size, 41 | oldProtect, 42 | out _); 43 | 44 | return status; 45 | } 46 | } 47 | 48 | [UnmanagedFunctionPointer(CallingConvention.StdCall)] 49 | private delegate NTSTATUS ZwFreeVirtualMemory( 50 | HANDLE processHandle, 51 | void* baseAddress, 52 | uint* regionSize, 53 | uint freeType); 54 | } 55 | 56 | public static partial class Constants 57 | { 58 | public const uint MEM_DECOMMIT = 0x00004000; 59 | public const uint MEM_RELEASE = 0x00008000; 60 | } -------------------------------------------------------------------------------- /CsWhispers.Generator/Source/NtReadVirtualMemory.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | 3 | namespace CsWhispers; 4 | 5 | public static unsafe partial class Syscalls 6 | { 7 | private const string ZwReadVirtualMemoryHash = "A2EAF21913E6BA93656FC0BEC4F1B7B1"; 8 | 9 | public static NTSTATUS NtReadVirtualMemory( 10 | HANDLE processHandle, 11 | void* baseAddress, 12 | void* buffer, 13 | uint numberOfBytesToRead, 14 | [Optional] uint* numberOfBytesRead) 15 | { 16 | var stub = GetSyscallStub(ZwReadVirtualMemoryHash); 17 | 18 | fixed (byte* temp = stub) 19 | { 20 | var ptr = (IntPtr)temp; 21 | var size = new IntPtr(stub.Length); 22 | 23 | Native.NtProtectVirtualMemory( 24 | new HANDLE((IntPtr)(-1)), 25 | ref ptr, 26 | ref size, 27 | 0x00000020, 28 | out var oldProtect); 29 | 30 | var ntReadVirtualMemory = Marshal.GetDelegateForFunctionPointer(ptr); 31 | 32 | var status = ntReadVirtualMemory( 33 | processHandle, 34 | baseAddress, 35 | buffer, 36 | numberOfBytesToRead, 37 | numberOfBytesRead); 38 | 39 | Native.NtProtectVirtualMemory( 40 | new HANDLE((IntPtr)(-1)), 41 | ref ptr, 42 | ref size, 43 | oldProtect, 44 | out _); 45 | 46 | return status; 47 | } 48 | } 49 | 50 | [UnmanagedFunctionPointer(CallingConvention.StdCall)] 51 | private delegate NTSTATUS ZwReadVirtualMemory( 52 | HANDLE processHandle, 53 | void* baseAddress, 54 | void* buffer, 55 | uint numberOfBytesToRead, 56 | [Optional] uint* numberOfBytesRead); 57 | } -------------------------------------------------------------------------------- /CsWhispers.Generator/Source/NtWriteVirtualMemory.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | 3 | namespace CsWhispers; 4 | 5 | public static unsafe partial class Syscalls 6 | { 7 | private const string ZwWriteVirtualMemoryHash = "8ECD3BD989CAB9EFE34A0625285BAA0F"; 8 | 9 | public static NTSTATUS NtWriteVirtualMemory( 10 | HANDLE processHandle, 11 | void* baseAddress, 12 | void* buffer, 13 | uint numberOfBytesToWrite, 14 | [Optional] uint* numberOfBytesWritten) 15 | { 16 | var stub = GetSyscallStub(ZwWriteVirtualMemoryHash); 17 | 18 | fixed (byte* temp = stub) 19 | { 20 | var ptr = (IntPtr)temp; 21 | var size = new IntPtr(stub.Length); 22 | 23 | Native.NtProtectVirtualMemory( 24 | new HANDLE((IntPtr)(-1)), 25 | ref ptr, 26 | ref size, 27 | 0x00000020, 28 | out var oldProtect); 29 | 30 | var ntWriteVirtualMemory = Marshal.GetDelegateForFunctionPointer(ptr); 31 | 32 | var status = ntWriteVirtualMemory( 33 | processHandle, 34 | baseAddress, 35 | buffer, 36 | numberOfBytesToWrite, 37 | numberOfBytesWritten); 38 | 39 | Native.NtProtectVirtualMemory( 40 | new HANDLE((IntPtr)(-1)), 41 | ref ptr, 42 | ref size, 43 | oldProtect, 44 | out _); 45 | 46 | return status; 47 | } 48 | } 49 | 50 | [UnmanagedFunctionPointer(CallingConvention.StdCall)] 51 | private delegate NTSTATUS ZwWriteVirtualMemory( 52 | HANDLE processHandle, 53 | void* baseAddress, 54 | void* buffer, 55 | uint numberOfBytesToWrite, 56 | [Optional] uint* numberOfBytesWritten); 57 | } -------------------------------------------------------------------------------- /CsWhispers.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CsWhispers.Generator", "CsWhispers.Generator\CsWhispers.Generator.csproj", "{30CE2E3A-61D4-4D06-9280-DC73CA4C32E1}" 4 | EndProject 5 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CsWhispers.Tests", "CsWhispers.Tests\CsWhispers.Tests.csproj", "{DE6EC43A-33E7-48D5-AB5A-848F86F26448}" 6 | EndProject 7 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CsWhispers.Sample", "CsWhispers.Sample\CsWhispers.Sample.csproj", "{F3EFEF07-0F1A-4162-A2D8-56CE9028CB9D}" 8 | EndProject 9 | Global 10 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 11 | Debug|Any CPU = Debug|Any CPU 12 | Release|Any CPU = Release|Any CPU 13 | EndGlobalSection 14 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 15 | {30CE2E3A-61D4-4D06-9280-DC73CA4C32E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 16 | {30CE2E3A-61D4-4D06-9280-DC73CA4C32E1}.Debug|Any CPU.Build.0 = Debug|Any CPU 17 | {30CE2E3A-61D4-4D06-9280-DC73CA4C32E1}.Release|Any CPU.ActiveCfg = Release|Any CPU 18 | {30CE2E3A-61D4-4D06-9280-DC73CA4C32E1}.Release|Any CPU.Build.0 = Release|Any CPU 19 | {DE6EC43A-33E7-48D5-AB5A-848F86F26448}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 20 | {DE6EC43A-33E7-48D5-AB5A-848F86F26448}.Debug|Any CPU.Build.0 = Debug|Any CPU 21 | {DE6EC43A-33E7-48D5-AB5A-848F86F26448}.Release|Any CPU.ActiveCfg = Release|Any CPU 22 | {DE6EC43A-33E7-48D5-AB5A-848F86F26448}.Release|Any CPU.Build.0 = Release|Any CPU 23 | {F3EFEF07-0F1A-4162-A2D8-56CE9028CB9D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 24 | {F3EFEF07-0F1A-4162-A2D8-56CE9028CB9D}.Debug|Any CPU.Build.0 = Debug|Any CPU 25 | {F3EFEF07-0F1A-4162-A2D8-56CE9028CB9D}.Release|Any CPU.ActiveCfg = Release|Any CPU 26 | {F3EFEF07-0F1A-4162-A2D8-56CE9028CB9D}.Release|Any CPU.Build.0 = Release|Any CPU 27 | EndGlobalSection 28 | EndGlobal 29 | -------------------------------------------------------------------------------- /CsWhispers.Generator/Source/NtProtectVirtualMemory.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | 3 | namespace CsWhispers; 4 | 5 | public static unsafe partial class Syscalls 6 | { 7 | private const string ZwProtectVirtualMemoryHash = "95429C4A177A230D88166479D035C143"; 8 | 9 | public static NTSTATUS NtProtectVirtualMemory( 10 | HANDLE processHandle, 11 | void* baseAddress, 12 | uint* numberOfBytesToProtect, 13 | uint newAccessProtection, 14 | uint* oldAccessProtection) 15 | { 16 | var stub = GetSyscallStub(ZwProtectVirtualMemoryHash); 17 | 18 | fixed (byte* buffer = stub) 19 | { 20 | var ptr = (IntPtr)buffer; 21 | var size = new IntPtr(stub.Length); 22 | 23 | Native.NtProtectVirtualMemory( 24 | new HANDLE((IntPtr)(-1)), 25 | ref ptr, 26 | ref size, 27 | 0x00000020, 28 | out var oldProtect); 29 | 30 | var ntProtectVirtualMemory = Marshal.GetDelegateForFunctionPointer(ptr); 31 | 32 | var status = ntProtectVirtualMemory( 33 | processHandle, 34 | baseAddress, 35 | numberOfBytesToProtect, 36 | newAccessProtection, 37 | oldAccessProtection); 38 | 39 | Native.NtProtectVirtualMemory( 40 | new HANDLE((IntPtr)(-1)), 41 | ref ptr, 42 | ref size, 43 | oldProtect, 44 | out _); 45 | 46 | return status; 47 | } 48 | } 49 | 50 | [UnmanagedFunctionPointer(CallingConvention.StdCall)] 51 | private delegate NTSTATUS ZwProtectVirtualMemory( 52 | HANDLE processHandle, 53 | void* baseAddress, 54 | uint* numberOfBytesToProtect, 55 | uint newAccessProtection, 56 | uint* oldAccessProtection); 57 | } -------------------------------------------------------------------------------- /CsWhispers.Generator/Source/NtQueryVirtualMemory.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | 3 | namespace CsWhispers; 4 | 5 | public static unsafe partial class Syscalls 6 | { 7 | private const string ZwQueryVirtualMemoryHash = "0F4ECCBB2DFD9932319A2C18098B34E6"; 8 | 9 | public static NTSTATUS NtQueryVirtualMemory( 10 | HANDLE processHandle, 11 | void* baseAddress, 12 | MEMORY_INFORMATION_CLASS memoryInformationClass, 13 | void* buffer, 14 | uint length, 15 | [Optional] uint* resultLength) 16 | { 17 | var stub = GetSyscallStub(ZwQueryVirtualMemoryHash); 18 | 19 | fixed (byte* temp = stub) 20 | { 21 | var ptr = (IntPtr)temp; 22 | var size = new IntPtr(stub.Length); 23 | 24 | Native.NtProtectVirtualMemory( 25 | new HANDLE((IntPtr)(-1)), 26 | ref ptr, 27 | ref size, 28 | 0x00000020, 29 | out var oldProtect); 30 | 31 | var ntQueryVirtualMemory = Marshal.GetDelegateForFunctionPointer(ptr); 32 | 33 | var status = ntQueryVirtualMemory( 34 | processHandle, 35 | baseAddress, 36 | memoryInformationClass, 37 | buffer, 38 | length, 39 | resultLength); 40 | 41 | Native.NtProtectVirtualMemory( 42 | new HANDLE((IntPtr)(-1)), 43 | ref ptr, 44 | ref size, 45 | oldProtect, 46 | out _); 47 | 48 | return status; 49 | } 50 | } 51 | 52 | [UnmanagedFunctionPointer(CallingConvention.StdCall)] 53 | private delegate NTSTATUS ZwQueryVirtualMemory( 54 | HANDLE processHandle, 55 | void* baseAddress, 56 | MEMORY_INFORMATION_CLASS memoryInformationClass, 57 | void* buffer, 58 | uint length, 59 | [Optional] uint* resultLength); 60 | } -------------------------------------------------------------------------------- /CsWhispers.Generator/Source/Constants.cs: -------------------------------------------------------------------------------- 1 | namespace CsWhispers; 2 | 3 | public static partial class Constants 4 | { 5 | public const uint DELETE = 65536; 6 | public const uint READ_CONTROL = 131072; 7 | public const uint SYNCHRONIZE = 1048576; 8 | public const uint WRITE_DAC = 262144; 9 | public const uint WRITE_OWNER = 524288; 10 | 11 | public const uint GENERIC_READ = 2147483648; 12 | public const uint GENERIC_WRITE = 1073741824; 13 | public const uint GENERIC_EXECUTE = 536870912; 14 | public const uint GENERIC_ALL = 268435456; 15 | 16 | public const uint STANDARD_RIGHTS_READ = 131072; 17 | public const uint STANDARD_RIGHTS_WRITE = 131072; 18 | public const uint STANDARD_RIGHTS_EXECUTE = 131072; 19 | public const uint STANDARD_RIGHTS_REQUIRED = 983040; 20 | public const uint STANDARD_RIGHTS_ALL = 2031616; 21 | 22 | public const uint THREAD_TERMINATE = 0x00000001; 23 | public const uint THREAD_SUSPEND_RESUME = 0x00000002; 24 | public const uint THREAD_GET_CONTEXT = 0x00000008; 25 | public const uint THREAD_SET_CONTEXT = 0x00000010; 26 | public const uint THREAD_SET_INFORMATION = 0x00000020; 27 | public const uint THREAD_QUERY_INFORMATION = 0x00000040; 28 | public const uint THREAD_SET_THREAD_TOKEN = 0x00000080; 29 | public const uint THREAD_IMPERSONATE = 0x00000100; 30 | public const uint THREAD_DIRECT_IMPERSONATION = 0x00000200; 31 | public const uint THREAD_SET_LIMITED_INFORMATION = 0x00000400; 32 | public const uint THREAD_QUERY_LIMITED_INFORMATION = 0x00000800; 33 | public const uint THREAD_RESUME = 0x00001000; 34 | public const uint THREAD_ALL_ACCESS = 0x001FFFFF; 35 | public const uint THREAD_DELETE = 0x00010000; 36 | public const uint THREAD_READ_CONTROL = 0x00020000; 37 | public const uint THREAD_WRITE_DAC = 0x00040000; 38 | public const uint THREAD_WRITE_OWNER = 0x00080000; 39 | public const uint THREAD_SYNCHRONIZE = 0x00100000; 40 | public const uint THREAD_STANDARD_RIGHTS_REQUIRED = 0x000F0000; 41 | } -------------------------------------------------------------------------------- /CsWhispers.Sample/Program.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.IO; 3 | using System.Runtime.InteropServices; 4 | 5 | namespace CsWhispers.Sample; 6 | 7 | internal static unsafe class Program 8 | { 9 | public static void Main(string[] args) 10 | { 11 | NTSTATUS status; 12 | HANDLE hProcess; 13 | OBJECT_ATTRIBUTES oa; 14 | 15 | // read shellcode 16 | var shellcode = File.ReadAllBytes(@"C:\Payloads\msgbox.bin"); 17 | 18 | // inject into self 19 | using var self = Process.GetCurrentProcess(); 20 | 21 | var cid = new CLIENT_ID 22 | { 23 | UniqueProcess = new HANDLE((IntPtr)self.Id) 24 | }; 25 | 26 | status = NtOpenProcess( 27 | &hProcess, 28 | PROCESS_ALL_ACCESS, 29 | &oa, 30 | &cid); 31 | 32 | // allocate memory 33 | void* baseAddress; 34 | var szShellcode = (uint)shellcode.Length; 35 | 36 | status = NtAllocateVirtualMemory( 37 | hProcess, 38 | &baseAddress, 39 | 0, 40 | &szShellcode, 41 | MEM_COMMIT | MEM_RESERVE, 42 | PAGE_EXECUTE_READWRITE); 43 | 44 | // write shellcode 45 | fixed (void* buffer = shellcode) 46 | { 47 | status = NtWriteVirtualMemory( 48 | hProcess, 49 | baseAddress, 50 | buffer, 51 | szShellcode, 52 | null); 53 | } 54 | 55 | // create thread 56 | HANDLE hThread; 57 | 58 | var routine = Marshal.GetDelegateForFunctionPointer((IntPtr)baseAddress); 59 | 60 | status = NtCreateThreadEx( 61 | &hThread, 62 | THREAD_ALL_ACCESS, 63 | null, 64 | hProcess, 65 | routine, 66 | null, 67 | 0, 68 | 0, 69 | 0, 70 | 0, 71 | null); 72 | } 73 | } -------------------------------------------------------------------------------- /CsWhispers.Generator/SourceGenerator.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | 3 | using Microsoft.CodeAnalysis; 4 | 5 | namespace CsWhispers.Generator; 6 | 7 | [Generator] 8 | public sealed class SourceGenerator : ISourceGenerator 9 | { 10 | public void Initialize(GeneratorInitializationContext context) 11 | { 12 | // not required 13 | } 14 | 15 | public void Execute(GeneratorExecutionContext context) 16 | { 17 | // add default files 18 | context.AddSource("Syscalls.g.cs", GetEmbeddedResource("Syscalls")); 19 | context.AddSource("DynamicInvoke.g.cs", GetEmbeddedResource("DynamicInvoke")); 20 | context.AddSource("Native.g.cs", GetEmbeddedResource("Native")); 21 | context.AddSource("Constants.g.cs", GetEmbeddedResource("Constants")); 22 | context.AddSource("Usings.g.cs", GetEmbeddedResource("Usings")); 23 | 24 | // read config file 25 | var configFile = context.AdditionalFiles 26 | .FirstOrDefault(f => f.Path.EndsWith("CsWhispers.txt")); 27 | 28 | // read and parse content 29 | var content = configFile?.GetText(); 30 | 31 | if (content is null) 32 | return; 33 | 34 | var entries = content.Lines.Distinct(); 35 | 36 | // loop over each entry 37 | foreach (var entry in entries) 38 | { 39 | var line = entry.ToString(); 40 | 41 | if (string.IsNullOrWhiteSpace(line)) 42 | continue; 43 | 44 | // get source file 45 | var src = GetEmbeddedResource(line); 46 | 47 | if (string.IsNullOrWhiteSpace(src)) 48 | { 49 | Console.Error.WriteLine($"No source found for {line}."); 50 | continue; 51 | } 52 | 53 | // add to compilation 54 | context.AddSource($"{line}.g.cs", src); 55 | } 56 | } 57 | 58 | private static string GetEmbeddedResource(string name) 59 | { 60 | using var rs = Assembly.GetCallingAssembly() 61 | .GetManifestResourceStream($"CsWhispers.Generator.Source.{name}.cs"); 62 | 63 | if (rs is null) 64 | return string.Empty; 65 | 66 | using var sr = new StreamReader(rs); 67 | return sr.ReadToEnd().Trim(); 68 | } 69 | } -------------------------------------------------------------------------------- /CsWhispers.Generator/Source/NtMapViewOfSection.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | 3 | namespace CsWhispers; 4 | 5 | public static unsafe partial class Syscalls 6 | { 7 | private const string ZwMapViewOfSectionHash = "C72A45E418708097B7D23865D6187D5E"; 8 | 9 | public static NTSTATUS NtMapViewOfSection( 10 | HANDLE sectionHandle, 11 | HANDLE processHandle, 12 | void* baseAddress, 13 | nuint zeroBits, 14 | nuint commitSize, 15 | [Optional] long* sectionOffset, 16 | nuint* viewSize, 17 | SECTION_INHERIT inheritDisposition, 18 | uint allocationType, 19 | uint win32Protect) 20 | { 21 | var stub = GetSyscallStub(ZwMapViewOfSectionHash); 22 | 23 | fixed (byte* buffer = stub) 24 | { 25 | var ptr = (IntPtr)buffer; 26 | var size = new IntPtr(stub.Length); 27 | 28 | Native.NtProtectVirtualMemory( 29 | new HANDLE((IntPtr)(-1)), 30 | ref ptr, 31 | ref size, 32 | 0x00000020, 33 | out var oldProtect); 34 | 35 | var ntMapViewOfSection = Marshal.GetDelegateForFunctionPointer(ptr); 36 | 37 | var status = ntMapViewOfSection( 38 | sectionHandle, 39 | processHandle, 40 | baseAddress, 41 | zeroBits, 42 | commitSize, 43 | sectionOffset, 44 | viewSize, 45 | inheritDisposition, 46 | allocationType, 47 | win32Protect); 48 | 49 | Native.NtProtectVirtualMemory( 50 | new HANDLE((IntPtr)(-1)), 51 | ref ptr, 52 | ref size, 53 | oldProtect, 54 | out _); 55 | 56 | return status; 57 | } 58 | } 59 | 60 | [UnmanagedFunctionPointer(CallingConvention.StdCall)] 61 | private delegate NTSTATUS ZwMapViewOfSection( 62 | HANDLE sectionHandle, 63 | HANDLE processHandle, 64 | void* baseAddress, 65 | nuint zeroBits, 66 | nuint commitSize, 67 | [Optional] long* sectionOffset, 68 | nuint* viewSize, 69 | SECTION_INHERIT inheritDisposition, 70 | uint allocationType, 71 | uint win32Protect); 72 | } -------------------------------------------------------------------------------- /CsWhispers.Generator/Source/Native.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | 3 | namespace DInvoke; 4 | 5 | public static unsafe partial class Native 6 | { 7 | private const string NtDll = "ntdll.dll"; 8 | 9 | public static NTSTATUS NtProtectVirtualMemory( 10 | HANDLE processHandle, 11 | ref IntPtr baseAddress, 12 | ref IntPtr regionSize, 13 | uint newProtect, 14 | out uint oldProtect) 15 | { 16 | object[] parameters = [processHandle, baseAddress, regionSize, newProtect, (uint)0]; 17 | 18 | var status = Generic.DynamicApiInvoke( 19 | NtDll, 20 | "NtProtectVirtualMemory", 21 | typeof(NtProtectVirtualMemoryD), 22 | ref parameters); 23 | 24 | oldProtect = (uint)parameters[4]; 25 | return status; 26 | } 27 | 28 | public static void RtlInitUnicodeString( 29 | UNICODE_STRING* destination, 30 | [MarshalAs(UnmanagedType.LPWStr)] string source) 31 | { 32 | var hFunction = Generic.GetLibraryAddress(NtDll, "RtlInitUnicodeString"); 33 | var rtlInitUnicodeString = Marshal.GetDelegateForFunctionPointer(hFunction); 34 | 35 | rtlInitUnicodeString( 36 | destination, 37 | source); 38 | } 39 | 40 | public static NTSTATUS LdrLoadDll(UNICODE_STRING* moduleFileName, HANDLE* moduleHandle) 41 | { 42 | var hFunction = Generic.GetLibraryAddress(NtDll, "LdrLoadDll"); 43 | var ldrLoadDll = Marshal.GetDelegateForFunctionPointer(hFunction); 44 | 45 | return ldrLoadDll( 46 | null, 47 | 0, 48 | moduleFileName, 49 | moduleHandle); 50 | } 51 | 52 | [UnmanagedFunctionPointer(CallingConvention.StdCall)] 53 | private delegate NTSTATUS NtProtectVirtualMemoryD( 54 | HANDLE processHandle, 55 | ref IntPtr baseAddress, 56 | ref IntPtr regionSize, 57 | uint newProtect, 58 | ref uint oldProtect); 59 | 60 | [UnmanagedFunctionPointer(CallingConvention.StdCall)] 61 | private delegate void RtlInitUnicodeStringD( 62 | UNICODE_STRING* destinationString, 63 | [MarshalAs(UnmanagedType.LPWStr)] string sourceString); 64 | 65 | [UnmanagedFunctionPointer(CallingConvention.StdCall)] 66 | private delegate NTSTATUS LdrLoadDllD( 67 | [Optional] char* pathToFile, 68 | [Optional] uint flags, 69 | UNICODE_STRING* moduleFileName, 70 | HANDLE* moduleHandle); 71 | } -------------------------------------------------------------------------------- /CsWhispers.Generator/Source/NtCreateThreadEx.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | 3 | namespace CsWhispers; 4 | 5 | public static unsafe partial class Syscalls 6 | { 7 | private const string ZwCreateThreadExHash = "434AE47989589026C54B874E5D12D365"; 8 | 9 | public static NTSTATUS NtCreateThreadEx( 10 | HANDLE* threadHandle, 11 | uint desiredAccess, 12 | [Optional] OBJECT_ATTRIBUTES* objectAttributes, 13 | HANDLE processHandle, 14 | USER_THREAD_START_ROUTINE startRoutine, 15 | [Optional] void* argument, 16 | uint createFlags, 17 | nuint zeroBits, 18 | nuint stackSize, 19 | nuint maximumStackSize, 20 | [Optional] PS_ATTRIBUTE_LIST* attributeList) 21 | { 22 | var stub = GetSyscallStub(ZwCreateThreadExHash); 23 | 24 | fixed (byte* buffer = stub) 25 | { 26 | var ptr = (IntPtr)buffer; 27 | var size = new IntPtr(stub.Length); 28 | 29 | Native.NtProtectVirtualMemory( 30 | new HANDLE((IntPtr)(-1)), 31 | ref ptr, 32 | ref size, 33 | 0x00000020, 34 | out var oldProtect); 35 | 36 | var ntCreateThreadEx = Marshal.GetDelegateForFunctionPointer(ptr); 37 | 38 | var status = ntCreateThreadEx( 39 | threadHandle, 40 | desiredAccess, 41 | objectAttributes, 42 | processHandle, 43 | startRoutine, 44 | argument, 45 | createFlags, 46 | zeroBits, 47 | stackSize, 48 | maximumStackSize, 49 | attributeList); 50 | 51 | Native.NtProtectVirtualMemory( 52 | new HANDLE((IntPtr)(-1)), 53 | ref ptr, 54 | ref size, 55 | oldProtect, 56 | out _); 57 | 58 | return status; 59 | } 60 | } 61 | 62 | [UnmanagedFunctionPointer(CallingConvention.StdCall)] 63 | private delegate NTSTATUS ZwCreateThreadEx( 64 | HANDLE* threadHandle, 65 | uint desiredAccess, 66 | [Optional] OBJECT_ATTRIBUTES* objectAttributes, 67 | HANDLE processHandle, 68 | USER_THREAD_START_ROUTINE startRoutine, 69 | [Optional] void* argument, 70 | uint createFlags, 71 | nuint zeroBits, 72 | nuint stackSize, 73 | nuint maximumStackSize, 74 | [Optional] PS_ATTRIBUTE_LIST* attributeList); 75 | } 76 | 77 | public static partial class Constants 78 | { 79 | public const uint THREAD_CREATE_FLAGS_CREATE_SUSPENDED = 0x00000001; 80 | public const uint THREAD_CREATE_FLAGS_SKIP_THREAD_ATTACH = 0x00000002; 81 | public const uint THREAD_CREATE_FLAGS_HIDE_FROM_DEBUGGER = 0x00000004; 82 | public const uint THREAD_CREATE_FLAGS_LOADER_WORKER = 0x00000010; 83 | public const uint THREAD_CREATE_FLAGS_SKIP_LOADER_INIT = 0x00000020; 84 | public const uint THREAD_CREATE_FLAGS_BYPASS_PROCESS_FREEZE = 0x00000040; 85 | } -------------------------------------------------------------------------------- /CsWhispers.Generator/Source/NtOpenProcess.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | 3 | namespace CsWhispers; 4 | 5 | public static unsafe partial class Syscalls 6 | { 7 | private const string ZwOpenProcessHash = "00B6CA92B16374B1C81FC54DFE03DF52"; 8 | 9 | public static NTSTATUS NtOpenProcess( 10 | HANDLE* processHandle, 11 | uint desiredAccess, 12 | OBJECT_ATTRIBUTES* objectAttributes, 13 | CLIENT_ID* clientId) 14 | { 15 | var stub = GetSyscallStub(ZwOpenProcessHash); 16 | 17 | fixed (byte* buffer = stub) 18 | { 19 | var ptr = (IntPtr)buffer; 20 | var size = new IntPtr(stub.Length); 21 | 22 | Native.NtProtectVirtualMemory( 23 | new HANDLE((IntPtr)(-1)), 24 | ref ptr, 25 | ref size, 26 | 0x00000020, 27 | out var oldProtect); 28 | 29 | var ntOpenProcess = Marshal.GetDelegateForFunctionPointer(ptr); 30 | 31 | var status = ntOpenProcess( 32 | processHandle, 33 | desiredAccess, 34 | objectAttributes, 35 | clientId); 36 | 37 | Native.NtProtectVirtualMemory( 38 | new HANDLE((IntPtr)(-1)), 39 | ref ptr, 40 | ref size, 41 | oldProtect, 42 | out _); 43 | 44 | return status; 45 | } 46 | } 47 | 48 | [UnmanagedFunctionPointer(CallingConvention.StdCall)] 49 | private delegate NTSTATUS ZwOpenProcess( 50 | HANDLE* processHandle, 51 | uint desiredAccess, 52 | OBJECT_ATTRIBUTES* objectAttributes, 53 | CLIENT_ID* clientId); 54 | } 55 | 56 | public static partial class Constants 57 | { 58 | public const uint PROCESS_TERMINATE = 0x00000001; 59 | public const uint PROCESS_CREATE_THREAD = 0x00000002; 60 | public const uint PROCESS_SET_SESSIONID = 0x00000004; 61 | public const uint PROCESS_VM_OPERATION = 0x00000008; 62 | public const uint PROCESS_VM_READ = 0x00000010; 63 | public const uint PROCESS_VM_WRITE = 0x00000020; 64 | public const uint PROCESS_DUP_HANDLE = 0x00000040; 65 | public const uint PROCESS_CREATE_PROCESS = 0x00000080; 66 | public const uint PROCESS_SET_QUOTA = 0x00000100; 67 | public const uint PROCESS_SET_INFORMATION = 0x00000200; 68 | public const uint PROCESS_QUERY_INFORMATION = 0x00000400; 69 | public const uint PROCESS_SUSPEND_RESUME = 0x00000800; 70 | public const uint PROCESS_QUERY_LIMITED_INFORMATION = 0x00001000; 71 | public const uint PROCESS_SET_LIMITED_INFORMATION = 0x00002000; 72 | public const uint PROCESS_ALL_ACCESS = 0x001FFFFF; 73 | public const uint PROCESS_DELETE = 0x00010000; 74 | public const uint PROCESS_READ_CONTROL = 0x00020000; 75 | public const uint PROCESS_WRITE_DAC = 0x00040000; 76 | public const uint PROCESS_WRITE_OWNER = 0x00080000; 77 | public const uint PROCESS_SYNCHRONIZE = 0x00100000; 78 | public const uint PROCESS_STANDARD_RIGHTS_REQUIRED = 0x000F0000; 79 | } -------------------------------------------------------------------------------- /CsWhispers.Generator/Source/NtCreateSection.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | 3 | namespace CsWhispers; 4 | 5 | public static unsafe partial class Syscalls 6 | { 7 | private const string ZwCreateSectionHash = "12C4C6E5EB9B290330CA3A7E5D43D0FA"; 8 | 9 | public static NTSTATUS NtCreateSection( 10 | HANDLE* sectionHandle, 11 | uint desiredAccess, 12 | [Optional] OBJECT_ATTRIBUTES* objectAttributes, 13 | [Optional] long* maximumSize, 14 | uint sectionPageProtection, 15 | uint allocationAttributes, 16 | [Optional] HANDLE fileHandle) 17 | { 18 | var stub = GetSyscallStub(ZwCreateSectionHash); 19 | 20 | fixed (byte* buffer = stub) 21 | { 22 | var ptr = (IntPtr)buffer; 23 | var size = new IntPtr(stub.Length); 24 | 25 | Native.NtProtectVirtualMemory( 26 | new HANDLE((IntPtr)(-1)), 27 | ref ptr, 28 | ref size, 29 | 0x00000020, 30 | out var oldProtect); 31 | 32 | var ntCreateSection = Marshal.GetDelegateForFunctionPointer(ptr); 33 | 34 | var status = ntCreateSection( 35 | sectionHandle, 36 | desiredAccess, 37 | objectAttributes, 38 | maximumSize, 39 | sectionPageProtection, 40 | allocationAttributes, 41 | fileHandle); 42 | 43 | Native.NtProtectVirtualMemory( 44 | new HANDLE((IntPtr)(-1)), 45 | ref ptr, 46 | ref size, 47 | oldProtect, 48 | out _); 49 | 50 | return status; 51 | } 52 | } 53 | 54 | [UnmanagedFunctionPointer(CallingConvention.StdCall)] 55 | private delegate NTSTATUS ZwCreateSection( 56 | HANDLE* sectionHandle, 57 | uint desiredAccess, 58 | [Optional] OBJECT_ATTRIBUTES* objectAttributes, 59 | [Optional] long* maximumSize, 60 | uint sectionPageProtection, 61 | uint allocationAttributes, 62 | [Optional] HANDLE fileHandle); 63 | } 64 | 65 | public static partial class Constants 66 | { 67 | public const int SECTION_QUERY = 1; 68 | public const int SECTION_MAP_WRITE = 2; 69 | public const int SECTION_MAP_READ = 4; 70 | public const int SECTION_MAP_EXECUTE = 8; 71 | public const int SECTION_EXTEND_SIZE = 16; 72 | public const int SECTION_MAP_EXECUTE_EXPLICIT = 32; 73 | 74 | public const uint SECTION_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED | SECTION_QUERY | 75 | SECTION_MAP_WRITE | SECTION_MAP_READ | 76 | SECTION_MAP_EXECUTE | SECTION_EXTEND_SIZE; 77 | 78 | public const uint SEC_COMMIT = 0x8000000; 79 | public const uint SEC_IMAGE = 0x1000000; 80 | public const uint SEC_IMAGE_NO_EXECUTE = 0x11000000; 81 | public const uint SEC_LARGE_PAGES = 0x80000000; 82 | public const uint SEC_NOCACHE = 0x10000000; 83 | public const uint SEC_RESERVE = 0x4000000; 84 | public const uint SEC_WRITECOMBINE = 0x40000000; 85 | } -------------------------------------------------------------------------------- /CsWhispers.Sample/CsWhispers.Sample.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | Debug 7 | AnyCPU 8 | {F3EFEF07-0F1A-4162-A2D8-56CE9028CB9D} 9 | Exe 10 | Properties 11 | CsWhispers.Sample 12 | CsWhispers.Sample 13 | v4.8.1 14 | 512 15 | true 16 | 12 17 | 18 | 19 | AnyCPU 20 | true 21 | full 22 | false 23 | bin\Debug\ 24 | DEBUG;TRACE 25 | prompt 26 | 4 27 | true 28 | false 29 | 30 | 31 | AnyCPU 32 | pdbonly 33 | true 34 | bin\Release\ 35 | TRACE 36 | prompt 37 | 4 38 | true 39 | false 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | {30ce2e3a-61d4-4d06-9280-dc73ca4c32e1} 54 | CsWhispers.Generator 55 | false 56 | Analyzer 57 | 58 | 59 | 60 | 61 | 62 | 63 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /CsWhispers.Generator/Source/NtOpenFile.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | 3 | namespace CsWhispers; 4 | 5 | public static unsafe partial class Syscalls 6 | { 7 | private const string ZwOpenFileHash = "568DFAF213A08F28C9D58D1234D4218A"; 8 | 9 | public static NTSTATUS NtOpenFile( 10 | HANDLE* fileHandle, 11 | uint desiredAccess, 12 | OBJECT_ATTRIBUTES* objectAttributes, 13 | IO_STATUS_BLOCK* ioStatusBlock, 14 | uint shareAccess, 15 | uint openOptions) 16 | { 17 | var stub = GetSyscallStub(ZwOpenFileHash); 18 | 19 | fixed (byte* buffer = stub) 20 | { 21 | var ptr = (IntPtr)buffer; 22 | var size = new IntPtr(stub.Length); 23 | 24 | Native.NtProtectVirtualMemory( 25 | new HANDLE((IntPtr)(-1)), 26 | ref ptr, 27 | ref size, 28 | 0x00000020, 29 | out var oldProtect); 30 | 31 | var ntOpenFile = Marshal.GetDelegateForFunctionPointer(ptr); 32 | 33 | var status = ntOpenFile( 34 | fileHandle, 35 | desiredAccess, 36 | objectAttributes, 37 | ioStatusBlock, 38 | shareAccess, 39 | openOptions); 40 | 41 | Native.NtProtectVirtualMemory( 42 | new HANDLE((IntPtr)(-1)), 43 | ref ptr, 44 | ref size, 45 | oldProtect, 46 | out _); 47 | 48 | return status; 49 | } 50 | } 51 | 52 | [UnmanagedFunctionPointer(CallingConvention.StdCall)] 53 | private delegate NTSTATUS ZwOpenFile( 54 | HANDLE* fileHandle, 55 | uint desiredAccess, 56 | OBJECT_ATTRIBUTES* objectAttributes, 57 | IO_STATUS_BLOCK* ioStatusBlock, 58 | uint shareAccess, 59 | uint openOptions); 60 | } 61 | 62 | public static partial class Constants 63 | { 64 | public const int FILE_READ_DATA = 1; 65 | public const int FILE_WRITE_DATA = 2; 66 | public const int FILE_APPEND_DATA = 4; 67 | public const int FILE_READ_EA = 8; 68 | public const int FILE_WRITE_EA = 16; 69 | public const int FILE_EXECUTE = 32; 70 | public const int FILE_READ_ATTRIBUTES = 128; 71 | public const int FILE_WRITE_ATTRIBUTES = 256; 72 | 73 | public const int FILE_SHARE_READ = 1; 74 | public const int FILE_SHARE_WRITE = 2; 75 | public const int FILE_SHARE_DELETE = 4; 76 | 77 | public const int FILE_DIRECTORY_FILE = 1; 78 | public const int FILE_WRITE_THROUGH = 2; 79 | public const int FILE_SEQUENTIAL_ONLY = 4; 80 | public const int FILE_NO_INTERMEDIATE_BUFFERING = 8; 81 | public const int FILE_SYNCHRONOUS_IO_ALERT = 16; 82 | public const int FILE_SYNCHRONOUS_IO_NONALERT = 32; 83 | public const int FILE_NON_DIRECTORY_FILE = 64; 84 | public const int FILE_CREATE_TREE_CONNECTION = 128; 85 | public const int FILE_COMPLETE_IF_OPLOCKED = 256; 86 | public const int FILE_NO_EA_KNOWLEDGE = 512; 87 | public const int FILE_OPEN_REMOTE_INSTANCE = 1024; 88 | public const int FILE_RANDOM_ACCESS = 2048; 89 | public const int FILE_DELETE_ON_CLOSE = 4096; 90 | public const int FILE_OPEN_BY_FILE_ID = 8192; 91 | public const int FILE_OPEN_FOR_BACKUP_INTENT = 16384; 92 | public const int FILE_NO_COMPRESSION = 32768; 93 | public const int FILE_OPEN_REQUIRING_OPLOCK = 65536; 94 | public const int FILE_DISALLOW_EXCLUSIVE = 131072; 95 | public const int FILE_SESSION_AWARE = 262144; 96 | public const int FILE_RESERVE_OPFILTER = 1048576; 97 | public const int FILE_OPEN_REPARSE_POINT = 2097152; 98 | public const int FILE_OPEN_NO_RECALL = 4194304; 99 | public const int FILE_OPEN_FOR_FREE_SPACE_QUERY = 8388608; 100 | public const int FILE_CONTAINS_EXTENDED_CREATE_INFORMATION = 268435456; 101 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CsWhispers 2 | 3 | Source generator to add D/Invoke and indirect syscall methods to a C# project. 4 | 5 | ## Quick Start 6 | 7 | Add the latest NuGet package to your project and allow unsafe code. 8 | 9 | ```xml 10 | 11 | 12 | 13 | Exe 14 | net481 15 | enable 16 | enable 17 | 12 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | true 28 | 29 | 30 | 31 | true 32 | 33 | 34 | 35 | ``` 36 | 37 | Create a file in your project called `CsWhispers.txt` and set its build action properties to `AdditionalFiles`. 38 | 39 | ```xml 40 | 41 | 42 | 43 | 44 | ``` 45 | 46 | Add each NT API and any supporting structs/enums that you want to be included in your project. Each must be on its own line, for example: 47 | 48 | ```text 49 | NtOpenProcess 50 | 51 | HANDLE 52 | NTSTATUS 53 | CLIENT_ID 54 | UNICODE_STRING 55 | OBJECT_ATTRIBUTES 56 | 57 | PWSTR 58 | PCWSTR 59 | ``` 60 | 61 | **See the project Wiki for a full list of supported APIs.** 62 | 63 | Global namespaces are automatically added to allow for clean code. 64 | 65 | ```c# 66 | public static unsafe void Main() 67 | { 68 | // use self as example 69 | using var self = Process.GetCurrentProcess(); 70 | 71 | HANDLE hProcess; 72 | OBJECT_ATTRIBUTES oa; 73 | CLIENT_ID cid = new() 74 | { 75 | UniqueProcess = new HANDLE((IntPtr)self.Id) 76 | }; 77 | 78 | var status = NtOpenProcess( 79 | &hProcess, 80 | PROCESS_ALL_ACCESS, 81 | &oa, 82 | &cid); 83 | 84 | Console.WriteLine("Status: {0}", status.SeverityCode); 85 | Console.WriteLine("HANDLE: 0x{0:X}", hProcess.Value.ToInt64()); 86 | } 87 | ``` 88 | 89 | ## D/Invoke 90 | 91 | CsWhispers includes a minimalised version of D/Invoke, so you may also call `Generic.GetLibraryAddress`, `Generic.DynamicFunctionInvoke`, etc. 92 | 93 | ## Extending 94 | 95 | All of the generated code goes into a partial `CsWhispers.Syscalls` class, which you can extend to add your own APIs. For example, create `MyAPIs.cs` and add: 96 | 97 | ```c# 98 | namespace CsWhispers; 99 | 100 | public static partial class Syscalls 101 | { 102 | public static NTSTATUS NtCreateThreadEx() 103 | { 104 | // whatever 105 | return new NTSTATUS(0); 106 | } 107 | } 108 | ``` 109 | 110 | This can then be called in your main code without having to add any additional using statements. 111 | 112 | ```c# 113 | namespace ConsoleApp1; 114 | 115 | internal static class Program 116 | { 117 | public static void Main() 118 | { 119 | var status = NtCreateThreadEx(); 120 | } 121 | } 122 | ``` 123 | 124 | ## TODO 125 | 126 | - Add 32-bit support. 127 | - Randomise API hashes on each build. 128 | - Add additional configuration options to choose between direct and indirect syscalls. 129 | - Implicitly add structs/enums for APIs without having to declare them in `CsWhispers.txt`. 130 | 131 | ## Acknowledgements 132 | 133 | This project was inspired by the previous versions of SysWhipsers and SharpWhispers in particular. So hat's off to [@Jackson_T](https://twitter.com/Jackson_T), [@KlezVirus](https://twitter.com/KlezVirus), [@d_glenx](https://twitter.com/d_glenx), and everyone else that has contribured code and/or ideas. -------------------------------------------------------------------------------- /CsWhispers.Generator/Source/NtAllocateVirtualMemory.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | 3 | namespace CsWhispers; 4 | 5 | public static unsafe partial class Syscalls 6 | { 7 | private const string ZwAllocateVirtualMemoryHash = "D80FB8F3EA00B69B2CAAB144EB70BE34"; 8 | 9 | public static NTSTATUS NtAllocateVirtualMemory( 10 | HANDLE processHandle, 11 | void* baseAddress, 12 | uint zeroBits, 13 | uint* regionSize, 14 | uint allocationType, 15 | uint protect) 16 | { 17 | var stub = GetSyscallStub(ZwAllocateVirtualMemoryHash); 18 | 19 | fixed (byte* buffer = stub) 20 | { 21 | var ptr = (IntPtr)buffer; 22 | var size = new IntPtr(stub.Length); 23 | 24 | Native.NtProtectVirtualMemory( 25 | new HANDLE((IntPtr)(-1)), 26 | ref ptr, 27 | ref size, 28 | 0x00000020, 29 | out var oldProtect); 30 | 31 | var ntAllocateVirtualMemory = Marshal.GetDelegateForFunctionPointer(ptr); 32 | 33 | var status = ntAllocateVirtualMemory( 34 | processHandle, 35 | baseAddress, 36 | zeroBits, 37 | regionSize, 38 | allocationType, 39 | protect); 40 | 41 | Native.NtProtectVirtualMemory( 42 | new HANDLE((IntPtr)(-1)), 43 | ref ptr, 44 | ref size, 45 | oldProtect, 46 | out _); 47 | 48 | return status; 49 | } 50 | } 51 | 52 | [UnmanagedFunctionPointer(CallingConvention.StdCall)] 53 | private delegate NTSTATUS ZwAllocateVirtualMemory( 54 | HANDLE processHandle, 55 | void* baseAddress, 56 | uint zeroBits, 57 | uint* regionSize, 58 | uint allocationType, 59 | uint protect); 60 | } 61 | 62 | public static partial class Constants 63 | { 64 | public const uint MEM_COMMIT = 0x00001000; 65 | public const uint MEM_RESERVE = 0x00002000; 66 | public const uint MEM_RESET = 0x00080000; 67 | public const uint MEM_RESET_UNDO = 0x01000000; 68 | public const uint MEM_REPLACE_PLACEHOLDER = 0x00004000; 69 | public const uint MEM_LARGE_PAGES = 0x20000000; 70 | public const uint MEM_RESERVE_PLACEHOLDER = 0x00040000; 71 | public const uint MEM_FREE = 0x00010000; 72 | 73 | public const uint PAGE_NOACCESS = 0x00000001; 74 | public const uint PAGE_READONLY = 0x00000002; 75 | public const uint PAGE_READWRITE = 0x00000004; 76 | public const uint PAGE_WRITECOPY = 0x00000008; 77 | public const uint PAGE_EXECUTE = 0x00000010; 78 | public const uint PAGE_EXECUTE_READ = 0x00000020; 79 | public const uint PAGE_EXECUTE_READWRITE = 0x00000040; 80 | public const uint PAGE_EXECUTE_WRITECOPY = 0x00000080; 81 | public const uint PAGE_GUARD = 0x00000100; 82 | public const uint PAGE_NOCACHE = 0x00000200; 83 | public const uint PAGE_WRITECOMBINE = 0x00000400; 84 | public const uint PAGE_GRAPHICS_NOACCESS = 0x00000800; 85 | public const uint PAGE_GRAPHICS_READONLY = 0x00001000; 86 | public const uint PAGE_GRAPHICS_READWRITE = 0x00002000; 87 | public const uint PAGE_GRAPHICS_EXECUTE = 0x00004000; 88 | public const uint PAGE_GRAPHICS_EXECUTE_READ = 0x00008000; 89 | public const uint PAGE_GRAPHICS_EXECUTE_READWRITE = 0x00010000; 90 | public const uint PAGE_GRAPHICS_COHERENT = 0x00020000; 91 | public const uint PAGE_GRAPHICS_NOCACHE = 0x00040000; 92 | public const uint PAGE_ENCLAVE_THREAD_CONTROL = 0x80000000; 93 | public const uint PAGE_REVERT_TO_FILE_MAP = 0x80000000; 94 | public const uint PAGE_TARGETS_NO_UPDATE = 0x40000000; 95 | public const uint PAGE_TARGETS_INVALID = 0x40000000; 96 | public const uint PAGE_ENCLAVE_UNVALIDATED = 0x20000000; 97 | public const uint PAGE_ENCLAVE_MASK = 0x10000000; 98 | public const uint PAGE_ENCLAVE_DECOMMIT = 0x10000000; 99 | public const uint PAGE_ENCLAVE_SS_FIRST = 0x10000001; 100 | public const uint PAGE_ENCLAVE_SS_REST = 0x10000002; 101 | } -------------------------------------------------------------------------------- /CsWhispers.Generator/Source/Syscalls.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | using System.Security.Cryptography; 3 | using System.Text; 4 | 5 | namespace CsWhispers; 6 | 7 | public static partial class Syscalls 8 | { 9 | private const long Key = 0xdeadbeef; 10 | private static readonly List SyscallList = []; 11 | 12 | private static readonly byte[] X64IndirectSyscallStub = 13 | [ 14 | 0x49, 0x89, 0xCA, // mov r10, rcx 15 | 0xB8, 0x00, 0x00, 0x00, 0x00, // mov eax, ssn 16 | 0x49, 0xBB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // movabs r11, address 17 | 0x41, 0xFF, 0xE3 // jmp r11 18 | ]; 19 | 20 | private static byte[] GetSyscallStub(string functionHash) 21 | { 22 | var ssn = GetSyscallNumber(functionHash); 23 | var syscall = SyscallList[ssn]; 24 | 25 | var stub = X64IndirectSyscallStub; 26 | stub[4] = (byte)ssn; 27 | 28 | var address = BitConverter.GetBytes((long)syscall.Address + 18); 29 | Buffer.BlockCopy(address, 0, stub, 10, address.Length); 30 | 31 | return stub; 32 | } 33 | 34 | private static int GetSyscallNumber(string functionHash) 35 | { 36 | var hModule = Generic.GetLoadedModuleAddress("ntdll.dll"); 37 | 38 | if (!PopulateSyscallList(hModule)) 39 | return -1; 40 | 41 | for (var i = 0; i < SyscallList.Count; i++) 42 | if (functionHash.Equals(SyscallList[i].Hash, StringComparison.OrdinalIgnoreCase)) 43 | return i; 44 | 45 | return -1; 46 | } 47 | 48 | private static bool PopulateSyscallList(IntPtr moduleBase) 49 | { 50 | // Return early if the list is already populated. 51 | if (SyscallList.Count > 0) 52 | return true; 53 | 54 | var functionPtr = IntPtr.Zero; 55 | 56 | // Temp Entry to assign the attributes values before adding the element to the list 57 | SYSCALL_ENTRY Temp_Entry; 58 | 59 | // Traverse the PE header in memory 60 | var peHeader = Marshal.ReadInt32((IntPtr)(moduleBase.ToInt64() + 0x3C)); 61 | var optHeader = moduleBase.ToInt64() + peHeader + 0x18; 62 | var magic = Marshal.ReadInt16((IntPtr)optHeader); 63 | var pExport = magic == 0x010b ? optHeader + 0x60 : optHeader + 0x70; 64 | 65 | var exportRva = Marshal.ReadInt32((IntPtr)pExport); 66 | var ordinalBase = Marshal.ReadInt32((IntPtr)(moduleBase.ToInt64() + exportRva + 0x10)); 67 | var numberOfNames = Marshal.ReadInt32((IntPtr)(moduleBase.ToInt64() + exportRva + 0x18)); 68 | var functionsRva = Marshal.ReadInt32((IntPtr)(moduleBase.ToInt64() + exportRva + 0x1C)); 69 | var namesRva = Marshal.ReadInt32((IntPtr)(moduleBase.ToInt64() + exportRva + 0x20)); 70 | var ordinalsRva = Marshal.ReadInt32((IntPtr)(moduleBase.ToInt64() + exportRva + 0x24)); 71 | 72 | for (var i = 0; i < numberOfNames; i++) 73 | { 74 | var functionName = Marshal.PtrToStringAnsi((IntPtr)(moduleBase.ToInt64() + 75 | Marshal.ReadInt32((IntPtr)(moduleBase.ToInt64() + 76 | namesRva + i * 4)))); 77 | 78 | if (string.IsNullOrWhiteSpace(functionName)) 79 | continue; 80 | 81 | // Check if is a syscall 82 | if (!functionName.StartsWith("Zw")) 83 | continue; 84 | 85 | var functionOrdinal = Marshal.ReadInt16((IntPtr)(moduleBase.ToInt64() + ordinalsRva + i * 2)) + 86 | ordinalBase; 87 | 88 | var functionRva = 89 | Marshal.ReadInt32((IntPtr)(moduleBase.ToInt64() + functionsRva + 90 | 4 * (functionOrdinal - ordinalBase))); 91 | functionPtr = (IntPtr)((long)moduleBase + functionRva); 92 | 93 | Temp_Entry.Hash = HashSyscall(functionName); 94 | Temp_Entry.Address = functionPtr; 95 | 96 | // Add syscall to the list 97 | SyscallList.Add(Temp_Entry); 98 | } 99 | 100 | 101 | // Sort the list by address in ascending order. 102 | for (var i = 0; i < SyscallList.Count - 1; i++) 103 | { 104 | for (var j = 0; j < SyscallList.Count - i - 1; j++) 105 | { 106 | if (SyscallList[j].Address.ToInt64() > SyscallList[j + 1].Address.ToInt64()) 107 | { 108 | // Swap entries. 109 | SYSCALL_ENTRY TempSwapEntry; 110 | 111 | TempSwapEntry.Hash = SyscallList[j].Hash; 112 | TempSwapEntry.Address = SyscallList[j].Address; 113 | 114 | Temp_Entry.Hash = SyscallList[j + 1].Hash; 115 | Temp_Entry.Address = SyscallList[j + 1].Address; 116 | 117 | SyscallList[j] = Temp_Entry; 118 | SyscallList[j + 1] = TempSwapEntry; 119 | } 120 | } 121 | } 122 | 123 | return true; 124 | } 125 | 126 | private static string HashSyscall(string functionName) 127 | { 128 | return GetApiHash(functionName, Key); 129 | } 130 | 131 | private static string GetApiHash(string name, long key) 132 | { 133 | var data = Encoding.UTF8.GetBytes(name.ToLower()); 134 | var bytes = BitConverter.GetBytes(key); 135 | 136 | using var hmac = new HMACMD5(bytes); 137 | var bHash = hmac.ComputeHash(data); 138 | 139 | return BitConverter.ToString(bHash).Replace("-", ""); 140 | } 141 | 142 | private struct SYSCALL_ENTRY 143 | { 144 | public string Hash; 145 | public IntPtr Address; 146 | } 147 | } 148 | 149 | -------------------------------------------------------------------------------- /CsWhispers.Generator/CsWhispers.Generator.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | enable 6 | enable 7 | 12 8 | true 9 | true 10 | false 11 | CsWhispers 12 | 0.0.3 13 | @_RastaMouse 14 | MIT 15 | README.md 16 | https://github.com/rasta-mouse/CsWhispers 17 | git 18 | CsWhispers 19 | Source generator to add D/Invoke and indirect syscall methods to a C# project. 20 | Copyright (c) Daniel Duggan 2024 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | true 29 | 30 | 31 | 32 | true 33 | 34 | 35 | 36 | 37 | all 38 | runtime; build; native; contentfiles; analyzers; buildtransitive 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Ww][Ii][Nn]32/ 27 | [Aa][Rr][Mm]/ 28 | [Aa][Rr][Mm]64/ 29 | bld/ 30 | [Bb]in/ 31 | [Oo]bj/ 32 | [Ll]og/ 33 | [Ll]ogs/ 34 | 35 | # Visual Studio 2015/2017 cache/options directory 36 | .vs/ 37 | # Uncomment if you have tasks that create the project's static files in wwwroot 38 | #wwwroot/ 39 | 40 | # Visual Studio 2017 auto generated files 41 | Generated\ Files/ 42 | 43 | # MSTest test Results 44 | [Tt]est[Rr]esult*/ 45 | [Bb]uild[Ll]og.* 46 | 47 | # NUnit 48 | *.VisualState.xml 49 | TestResult.xml 50 | nunit-*.xml 51 | 52 | # Build Results of an ATL Project 53 | [Dd]ebugPS/ 54 | [Rr]eleasePS/ 55 | dlldata.c 56 | 57 | # Benchmark Results 58 | BenchmarkDotNet.Artifacts/ 59 | 60 | # .NET Core 61 | project.lock.json 62 | project.fragment.lock.json 63 | artifacts/ 64 | 65 | # ASP.NET Scaffolding 66 | ScaffoldingReadMe.txt 67 | 68 | # StyleCop 69 | StyleCopReport.xml 70 | 71 | # Files built by Visual Studio 72 | *_i.c 73 | *_p.c 74 | *_h.h 75 | *.ilk 76 | *.meta 77 | *.obj 78 | *.iobj 79 | *.pch 80 | *.pdb 81 | *.ipdb 82 | *.pgc 83 | *.pgd 84 | *.rsp 85 | *.sbr 86 | *.tlb 87 | *.tli 88 | *.tlh 89 | *.tmp 90 | *.tmp_proj 91 | *_wpftmp.csproj 92 | *.log 93 | *.tlog 94 | *.vspscc 95 | *.vssscc 96 | .builds 97 | *.pidb 98 | *.svclog 99 | *.scc 100 | 101 | # Chutzpah Test files 102 | _Chutzpah* 103 | 104 | # Visual C++ cache files 105 | ipch/ 106 | *.aps 107 | *.ncb 108 | *.opendb 109 | *.opensdf 110 | *.sdf 111 | *.cachefile 112 | *.VC.db 113 | *.VC.VC.opendb 114 | 115 | # Visual Studio profiler 116 | *.psess 117 | *.vsp 118 | *.vspx 119 | *.sap 120 | 121 | # Visual Studio Trace Files 122 | *.e2e 123 | 124 | # TFS 2012 Local Workspace 125 | $tf/ 126 | 127 | # Guidance Automation Toolkit 128 | *.gpState 129 | 130 | # ReSharper is a .NET coding add-in 131 | _ReSharper*/ 132 | *.[Rr]e[Ss]harper 133 | *.DotSettings.user 134 | 135 | # TeamCity is a build add-in 136 | _TeamCity* 137 | 138 | # DotCover is a Code Coverage Tool 139 | *.dotCover 140 | 141 | # AxoCover is a Code Coverage Tool 142 | .axoCover/* 143 | !.axoCover/settings.json 144 | 145 | # Coverlet is a free, cross platform Code Coverage Tool 146 | coverage*.json 147 | coverage*.xml 148 | coverage*.info 149 | 150 | # Visual Studio code coverage results 151 | *.coverage 152 | *.coveragexml 153 | 154 | # NCrunch 155 | _NCrunch_* 156 | .*crunch*.local.xml 157 | nCrunchTemp_* 158 | 159 | # MightyMoose 160 | *.mm.* 161 | AutoTest.Net/ 162 | 163 | # Web workbench (sass) 164 | .sass-cache/ 165 | 166 | # Installshield output folder 167 | [Ee]xpress/ 168 | 169 | # DocProject is a documentation generator add-in 170 | DocProject/buildhelp/ 171 | DocProject/Help/*.HxT 172 | DocProject/Help/*.HxC 173 | DocProject/Help/*.hhc 174 | DocProject/Help/*.hhk 175 | DocProject/Help/*.hhp 176 | DocProject/Help/Html2 177 | DocProject/Help/html 178 | 179 | # Click-Once directory 180 | publish/ 181 | 182 | # Publish Web Output 183 | *.[Pp]ublish.xml 184 | *.azurePubxml 185 | # Note: Comment the next line if you want to checkin your web deploy settings, 186 | # but database connection strings (with potential passwords) will be unencrypted 187 | *.pubxml 188 | *.publishproj 189 | 190 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 191 | # checkin your Azure Web App publish settings, but sensitive information contained 192 | # in these scripts will be unencrypted 193 | PublishScripts/ 194 | 195 | # NuGet Packages 196 | *.nupkg 197 | # NuGet Symbol Packages 198 | *.snupkg 199 | # The packages folder can be ignored because of Package Restore 200 | **/[Pp]ackages/* 201 | # except build/, which is used as an MSBuild target. 202 | !**/[Pp]ackages/build/ 203 | # Uncomment if necessary however generally it will be regenerated when needed 204 | #!**/[Pp]ackages/repositories.config 205 | # NuGet v3's project.json files produces more ignorable files 206 | *.nuget.props 207 | *.nuget.targets 208 | 209 | # Microsoft Azure Build Output 210 | csx/ 211 | *.build.csdef 212 | 213 | # Microsoft Azure Emulator 214 | ecf/ 215 | rcf/ 216 | 217 | # Windows Store app package directories and files 218 | AppPackages/ 219 | BundleArtifacts/ 220 | Package.StoreAssociation.xml 221 | _pkginfo.txt 222 | *.appx 223 | *.appxbundle 224 | *.appxupload 225 | 226 | # Visual Studio cache files 227 | # files ending in .cache can be ignored 228 | *.[Cc]ache 229 | # but keep track of directories ending in .cache 230 | !?*.[Cc]ache/ 231 | 232 | # Others 233 | ClientBin/ 234 | ~$* 235 | *~ 236 | *.dbmdl 237 | *.dbproj.schemaview 238 | *.jfm 239 | *.pfx 240 | *.publishsettings 241 | orleans.codegen.cs 242 | 243 | # Including strong name files can present a security risk 244 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 245 | #*.snk 246 | 247 | # Since there are multiple workflows, uncomment next line to ignore bower_components 248 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 249 | #bower_components/ 250 | 251 | # RIA/Silverlight projects 252 | Generated_Code/ 253 | 254 | # Backup & report files from converting an old project file 255 | # to a newer Visual Studio version. Backup files are not needed, 256 | # because we have git ;-) 257 | _UpgradeReport_Files/ 258 | Backup*/ 259 | UpgradeLog*.XML 260 | UpgradeLog*.htm 261 | ServiceFabricBackup/ 262 | *.rptproj.bak 263 | 264 | # SQL Server files 265 | *.mdf 266 | *.ldf 267 | *.ndf 268 | 269 | # Business Intelligence projects 270 | *.rdl.data 271 | *.bim.layout 272 | *.bim_*.settings 273 | *.rptproj.rsuser 274 | *- [Bb]ackup.rdl 275 | *- [Bb]ackup ([0-9]).rdl 276 | *- [Bb]ackup ([0-9][0-9]).rdl 277 | 278 | # Microsoft Fakes 279 | FakesAssemblies/ 280 | 281 | # GhostDoc plugin setting file 282 | *.GhostDoc.xml 283 | 284 | # Node.js Tools for Visual Studio 285 | .ntvs_analysis.dat 286 | node_modules/ 287 | 288 | # Visual Studio 6 build log 289 | *.plg 290 | 291 | # Visual Studio 6 workspace options file 292 | *.opt 293 | 294 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 295 | *.vbw 296 | 297 | # Visual Studio 6 auto-generated project file (contains which files were open etc.) 298 | *.vbp 299 | 300 | # Visual Studio 6 workspace and project file (working project files containing files to include in project) 301 | *.dsw 302 | *.dsp 303 | 304 | # Visual Studio 6 technical files 305 | *.ncb 306 | *.aps 307 | 308 | # Visual Studio LightSwitch build output 309 | **/*.HTMLClient/GeneratedArtifacts 310 | **/*.DesktopClient/GeneratedArtifacts 311 | **/*.DesktopClient/ModelManifest.xml 312 | **/*.Server/GeneratedArtifacts 313 | **/*.Server/ModelManifest.xml 314 | _Pvt_Extensions 315 | 316 | # Paket dependency manager 317 | .paket/paket.exe 318 | paket-files/ 319 | 320 | # FAKE - F# Make 321 | .fake/ 322 | 323 | # CodeRush personal settings 324 | .cr/personal 325 | 326 | # Python Tools for Visual Studio (PTVS) 327 | __pycache__/ 328 | *.pyc 329 | 330 | # Cake - Uncomment if you are using it 331 | # tools/** 332 | # !tools/packages.config 333 | 334 | # Tabs Studio 335 | *.tss 336 | 337 | # Telerik's JustMock configuration file 338 | *.jmconfig 339 | 340 | # BizTalk build output 341 | *.btp.cs 342 | *.btm.cs 343 | *.odx.cs 344 | *.xsd.cs 345 | 346 | # OpenCover UI analysis results 347 | OpenCover/ 348 | 349 | # Azure Stream Analytics local run output 350 | ASALocalRun/ 351 | 352 | # MSBuild Binary and Structured Log 353 | *.binlog 354 | 355 | # NVidia Nsight GPU debugger configuration file 356 | *.nvuser 357 | 358 | # MFractors (Xamarin productivity tool) working folder 359 | .mfractor/ 360 | 361 | # Local History for Visual Studio 362 | .localhistory/ 363 | 364 | # Visual Studio History (VSHistory) files 365 | .vshistory/ 366 | 367 | # BeatPulse healthcheck temp database 368 | healthchecksdb 369 | 370 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 371 | MigrationBackup/ 372 | 373 | # Ionide (cross platform F# VS Code tools) working folder 374 | .ionide/ 375 | 376 | # Fody - auto-generated XML schema 377 | FodyWeavers.xsd 378 | 379 | # VS Code files for those working on multiple tools 380 | .vscode/* 381 | !.vscode/settings.json 382 | !.vscode/tasks.json 383 | !.vscode/launch.json 384 | !.vscode/extensions.json 385 | *.code-workspace 386 | 387 | # Local History for Visual Studio Code 388 | .history/ 389 | 390 | # Windows Installer files from build outputs 391 | *.cab 392 | *.msi 393 | *.msix 394 | *.msm 395 | *.msp 396 | 397 | # JetBrains Rider 398 | *.sln.iml 399 | .idea 400 | -------------------------------------------------------------------------------- /CsWhispers.Generator/Source/DynamicInvoke.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace DInvoke; 5 | 6 | public static partial class Generic 7 | { 8 | /// 9 | /// Dynamically invoke an arbitrary function from a DLL, providing its name, function prototype, and arguments. 10 | /// 11 | /// The Wover (@TheRealWover) 12 | /// Name of the DLL. 13 | /// Name of the function. 14 | /// Prototype for the function, represented as a Delegate object. 15 | /// Parameters to pass to the function. Can be modified if function uses call by reference. 16 | /// Whether the DLL may be loaded from disk if it is not already loaded. Default is false. 17 | /// Whether or not to resolve export forwards. Default is true. 18 | /// Object returned by the function. Must be unmarshalled by the caller. 19 | public static T DynamicApiInvoke(string dllName, string functionName, Type functionDelegateType, ref object[] parameters, bool canLoadFromDisk = false, bool resolveForwards = true) 20 | { 21 | var pFunction = GetLibraryAddress(dllName, functionName, canLoadFromDisk, resolveForwards); 22 | return DynamicFunctionInvoke(pFunction, functionDelegateType, ref parameters); 23 | } 24 | 25 | /// 26 | /// Dynamically invokes an arbitrary function from a pointer. Useful for manually mapped modules or loading/invoking unmanaged code from memory. 27 | /// 28 | /// The Wover (@TheRealWover) 29 | /// A pointer to the unmanaged function. 30 | /// Prototype for the function, represented as a delegate. 31 | /// Arbitrary set of parameters to pass to the function. 32 | /// Object returned by the function. Must be unmarshalled by the caller. 33 | public static T DynamicFunctionInvoke(IntPtr functionPointer, Type functionDelegateType, ref object[] parameters) 34 | { 35 | var funcDelegate = Marshal.GetDelegateForFunctionPointer(functionPointer, functionDelegateType); 36 | return (T)funcDelegate.DynamicInvoke(parameters); 37 | } 38 | 39 | public static T DynamicAsmInvoke(byte[] asmStub, Type functionDelegateType, ref object[] parameters) 40 | { 41 | unsafe 42 | { 43 | fixed (byte* buffer = asmStub) 44 | { 45 | var ptr = (IntPtr)buffer; 46 | var size = new IntPtr(asmStub.Length); 47 | 48 | Native.NtProtectVirtualMemory( 49 | new HANDLE((IntPtr)(-1)), 50 | ref ptr, 51 | ref size, 52 | 0x00000040, 53 | out var oldProtect); 54 | 55 | var result = DynamicFunctionInvoke(ptr, functionDelegateType, ref parameters); 56 | 57 | Native.NtProtectVirtualMemory( 58 | new HANDLE((IntPtr)(-1)), 59 | ref ptr, 60 | ref size, 61 | oldProtect, 62 | out _); 63 | 64 | return result; 65 | } 66 | } 67 | } 68 | 69 | /// 70 | /// Helper for getting the base address of a module loaded by the current process. This base 71 | /// address could be passed to GetProcAddress/LdrGetProcedureAddress or it could be used for 72 | /// manual export parsing. This function uses the .NET System.Diagnostics.Process class. 73 | /// 74 | /// Ruben Boonen (@FuzzySec) 75 | /// The name of the DLL (e.g. "ntdll.dll"). 76 | /// IntPtr base address of the loaded module or IntPtr.Zero if the module is not found. 77 | public static IntPtr GetLoadedModuleAddress(string dllName) 78 | { 79 | using var process = Process.GetCurrentProcess(); 80 | 81 | foreach (ProcessModule module in process.Modules) 82 | { 83 | if (module.ModuleName.Equals(dllName, StringComparison.OrdinalIgnoreCase)) 84 | return module.BaseAddress; 85 | } 86 | 87 | return IntPtr.Zero; 88 | } 89 | 90 | /// 91 | /// Helper for getting the pointer to a function from a DLL loaded by the process. 92 | /// 93 | /// Ruben Boonen (@FuzzySec) 94 | /// The name of the DLL (e.g. "ntdll.dll" or "C:\Windows\System32\ntdll.dll"). 95 | /// Name of the exported procedure. 96 | /// Optional, indicates if the function can try to load the DLL from disk if it is not found in the loaded module list. 97 | /// Whether or not to resolve export forwards. Default is true. 98 | /// IntPtr for the desired function. 99 | public static IntPtr GetLibraryAddress(string dllName, string functionName, bool canLoadFromDisk = false, bool resolveForwards = true) 100 | { 101 | var hModule = GetLoadedModuleAddress(dllName); 102 | 103 | if (hModule == IntPtr.Zero && canLoadFromDisk) 104 | hModule = LoadModuleFromDisk(dllName); 105 | 106 | return GetExportAddress(hModule, functionName, resolveForwards); 107 | } 108 | 109 | /// 110 | /// Given a module base address, resolve the address of a function by manually walking the module export table. 111 | /// 112 | /// Ruben Boonen (@FuzzySec) 113 | /// A pointer to the base address where the module is loaded in the current process. 114 | /// The name of the export to search for (e.g. "NtAlertResumeThread"). 115 | /// Whether or not to resolve export forwards. Default is true. 116 | /// IntPtr for the desired function. 117 | public static IntPtr GetExportAddress(IntPtr moduleBase, string exportName, bool resolveForwards = true) 118 | { 119 | var functionPtr = IntPtr.Zero; 120 | 121 | try 122 | { 123 | // Traverse the PE header in memory 124 | var peHeader = Marshal.ReadInt32((IntPtr)(moduleBase.ToInt64() + 0x3C)); 125 | var optHeader = moduleBase.ToInt64() + peHeader + 0x18; 126 | var magic = Marshal.ReadInt16((IntPtr)optHeader); 127 | long pExport; 128 | 129 | if (magic == 0x010b) pExport = optHeader + 0x60; 130 | else pExport = optHeader + 0x70; 131 | 132 | var exportRva = Marshal.ReadInt32((IntPtr)pExport); 133 | var ordinalBase = Marshal.ReadInt32((IntPtr)(moduleBase.ToInt64() + exportRva + 0x10)); 134 | var numberOfNames = Marshal.ReadInt32((IntPtr)(moduleBase.ToInt64() + exportRva + 0x18)); 135 | var functionsRva = Marshal.ReadInt32((IntPtr)(moduleBase.ToInt64() + exportRva + 0x1C)); 136 | var namesRva = Marshal.ReadInt32((IntPtr)(moduleBase.ToInt64() + exportRva + 0x20)); 137 | var ordinalsRva = Marshal.ReadInt32((IntPtr)(moduleBase.ToInt64() + exportRva + 0x24)); 138 | 139 | for (var i = 0; i < numberOfNames; i++) 140 | { 141 | var functionName = Marshal.PtrToStringAnsi((IntPtr)(moduleBase.ToInt64() + Marshal.ReadInt32((IntPtr)(moduleBase.ToInt64() + namesRva + i * 4)))); 142 | if (string.IsNullOrWhiteSpace(functionName)) continue; 143 | if (!functionName.Equals(exportName, StringComparison.OrdinalIgnoreCase)) continue; 144 | 145 | var functionOrdinal = Marshal.ReadInt16((IntPtr)(moduleBase.ToInt64() + ordinalsRva + i * 2)) + ordinalBase; 146 | 147 | var functionRva = Marshal.ReadInt32((IntPtr)(moduleBase.ToInt64() + functionsRva + 4 * (functionOrdinal - ordinalBase))); 148 | functionPtr = (IntPtr)((long)moduleBase + functionRva); 149 | 150 | if (resolveForwards) 151 | functionPtr = GetForwardAddress(functionPtr); 152 | 153 | break; 154 | } 155 | } 156 | catch 157 | { 158 | throw new InvalidOperationException("Failed to parse module exports."); 159 | } 160 | 161 | if (functionPtr == IntPtr.Zero) 162 | throw new MissingMethodException(exportName + ", export not found."); 163 | 164 | return functionPtr; 165 | } 166 | 167 | /// 168 | /// Check if an address to an exported function should be resolved to a forward. If so, return the address of the forward. 169 | /// 170 | /// The Wover (@TheRealWover) 171 | /// Function of an exported address, found by parsing a PE file's export table. 172 | /// Optional, indicates if the function can try to load the DLL from disk if it is not found in the loaded module list. 173 | /// IntPtr for the forward. If the function is not forwarded, return the original pointer. 174 | public static IntPtr GetForwardAddress(IntPtr exportAddress, bool canLoadFromDisk = false) 175 | { 176 | var functionPtr = exportAddress; 177 | 178 | try 179 | { 180 | var forwardNames = Marshal.PtrToStringAnsi(functionPtr); 181 | if (string.IsNullOrWhiteSpace(forwardNames)) return functionPtr; 182 | 183 | var values = forwardNames.Split('.'); 184 | 185 | if (values.Length > 1) 186 | { 187 | var forwardModuleName = values[0]; 188 | var forwardExportName = values[1]; 189 | 190 | var apiSet = GetApiSetMapping(); 191 | var lookupKey = forwardModuleName.Substring(0, forwardModuleName.Length - 2) + ".dll"; 192 | 193 | if (apiSet.TryGetValue(lookupKey, out var value)) 194 | forwardModuleName = value; 195 | else 196 | forwardModuleName = forwardModuleName + ".dll"; 197 | 198 | var hModule = GetPebLdrModuleEntry(forwardModuleName); 199 | 200 | if (hModule == IntPtr.Zero && canLoadFromDisk) 201 | hModule = LoadModuleFromDisk(forwardModuleName); 202 | 203 | if (hModule != IntPtr.Zero) 204 | functionPtr = GetExportAddress(hModule, forwardExportName); 205 | } 206 | } 207 | catch 208 | { 209 | // Do nothing, it was not a forward 210 | } 211 | 212 | return functionPtr; 213 | } 214 | 215 | /// 216 | /// This function uses dynamic assembly invocation to obtain a pointer to the PEB. 217 | /// __readgsqword(0x60) or __readfsdword(0x30) 218 | /// 219 | /// Base address of the PEB as an IntPtr. 220 | public static IntPtr GetPebAddress() 221 | { 222 | byte[] stub; 223 | 224 | if (IntPtr.Size == 8) 225 | { 226 | stub = 227 | [ 228 | 0x65, 0x48, 0x8B, 0x04, 0x25, 0x60, // mov rax, qword ptr gs:[0x60] 229 | 0x00, 0x00, 0x00, 230 | 0xc3 // ret 231 | ]; 232 | } 233 | else 234 | { 235 | stub = 236 | [ 237 | 0x64, 0xA1, 0x30, 0x00, 0x00, 0x00, // mov eax,dword ptr fs:[30] 238 | 0xC3 // ret 239 | ]; 240 | } 241 | 242 | object[] parameters = []; 243 | 244 | return DynamicAsmInvoke( 245 | stub, 246 | typeof(ReadGs), 247 | ref parameters); 248 | } 249 | 250 | /// 251 | /// Helper for getting the base address of a module loaded by the current process. This base 252 | /// address could be passed to GetProcAddress/LdrGetProcedureAddress or it could be used for 253 | /// manual export parsing. This function parses the _PEB_LDR_DATA structure. 254 | /// 255 | /// Ruben Boonen (@FuzzySec) 256 | /// The name of the DLL (e.g. "ntdll.dll"). 257 | /// IntPtr base address of the loaded module or IntPtr.Zero if the module is not found. 258 | public static IntPtr GetPebLdrModuleEntry(string dllName) 259 | { 260 | // Set function variables 261 | uint ldrDataOffset; 262 | uint inLoadOrderModuleListOffset; 263 | 264 | if (IntPtr.Size == 4) 265 | { 266 | ldrDataOffset = 0xc; 267 | inLoadOrderModuleListOffset = 0xC; 268 | } 269 | else 270 | { 271 | ldrDataOffset = 0x18; 272 | inLoadOrderModuleListOffset = 0x10; 273 | } 274 | 275 | // Get _PEB pointer 276 | var pPeb = GetPebAddress(); 277 | 278 | // Get module InLoadOrderModuleList -> _LIST_ENTRY 279 | var pebLdrData = Marshal.ReadIntPtr((IntPtr)((ulong)pPeb + ldrDataOffset)); 280 | var pInLoadOrderModuleList = (IntPtr)((ulong)pebLdrData + inLoadOrderModuleListOffset); 281 | var le = Marshal.PtrToStructure(pInLoadOrderModuleList); 282 | 283 | // Loop entries 284 | var flink = le.Flink; 285 | var hModule = IntPtr.Zero; 286 | var dte = Marshal.PtrToStructure(flink); 287 | 288 | while (dte.InLoadOrderLinks.Flink != le.Blink) 289 | { 290 | // Match module name 291 | var moduleName = dte.BaseDllName.Buffer.ToString(); 292 | 293 | if (!string.IsNullOrWhiteSpace(moduleName) && moduleName.Equals(dllName, StringComparison.OrdinalIgnoreCase)) 294 | { 295 | hModule = dte.DllBase; 296 | break; 297 | } 298 | 299 | // Move Ptr 300 | flink = dte.InLoadOrderLinks.Flink; 301 | dte = Marshal.PtrToStructure(flink); 302 | } 303 | 304 | return hModule; 305 | } 306 | 307 | /// 308 | /// Resolve host DLL for API Set DLL. 309 | /// 310 | /// Ruben Boonen (@FuzzySec), The Wover (@TheRealWover) 311 | /// Dictionary, a combination of Key:APISetDLL and Val:HostDLL. 312 | public static Dictionary GetApiSetMapping() 313 | { 314 | var apiSetMapOffset = IntPtr.Size == 4 ? (uint)0x38 : 0x68; 315 | var apiSetDict = new Dictionary(); 316 | 317 | var peb = GetPebAddress(); 318 | 319 | var pApiSetNamespace = Marshal.ReadIntPtr((IntPtr)((ulong)peb + apiSetMapOffset)); 320 | var apiSetNamespace = Marshal.PtrToStructure(pApiSetNamespace); 321 | 322 | for (var i = 0; i < apiSetNamespace.Count; i++) 323 | { 324 | var setEntry = new ApiSetNamespaceEntry(); 325 | 326 | var pSetEntry = (IntPtr)((ulong)pApiSetNamespace + (ulong)apiSetNamespace.EntryOffset + (ulong)(i * Marshal.SizeOf(setEntry))); 327 | setEntry = Marshal.PtrToStructure(pSetEntry); 328 | 329 | var apiSetEntryName = Marshal.PtrToStringUni((IntPtr)((ulong)pApiSetNamespace + (ulong)setEntry.NameOffset), setEntry.NameLength / 2); 330 | var apiSetEntryKey = apiSetEntryName.Substring(0, apiSetEntryName.Length - 2) + ".dll" ; // Remove the patch number and add .dll 331 | 332 | var valueEntry = new ApiSetValueEntry(); 333 | var pSetValue = IntPtr.Zero; 334 | 335 | switch (setEntry.ValueLength) 336 | { 337 | case 1: 338 | pSetValue = (IntPtr)((ulong)pApiSetNamespace + (ulong)setEntry.ValueOffset); 339 | break; 340 | 341 | case > 1: 342 | { 343 | for (var j = 0; j < setEntry.ValueLength; j++) 344 | { 345 | var host = (IntPtr)((ulong)pApiSetNamespace + (ulong)setEntry.ValueOffset + (ulong)Marshal.SizeOf(valueEntry) * (ulong)j); 346 | 347 | if (Marshal.PtrToStringUni(host) != apiSetEntryName) 348 | pSetValue = (IntPtr)((ulong)pApiSetNamespace + (ulong)setEntry.ValueOffset + (ulong)Marshal.SizeOf(valueEntry) * (ulong)j); 349 | } 350 | 351 | if (pSetValue == IntPtr.Zero) 352 | pSetValue = (IntPtr)((ulong)pApiSetNamespace + (ulong)setEntry.ValueOffset); 353 | 354 | break; 355 | } 356 | } 357 | 358 | valueEntry = Marshal.PtrToStructure(pSetValue); 359 | 360 | var apiSetValue = string.Empty; 361 | if (valueEntry.ValueCount != 0) 362 | { 363 | var pValue = (IntPtr)((ulong)pApiSetNamespace + (ulong)valueEntry.ValueOffset); 364 | apiSetValue = Marshal.PtrToStringUni(pValue, valueEntry.ValueCount / 2); 365 | } 366 | 367 | apiSetDict.Add(apiSetEntryKey, apiSetValue); 368 | } 369 | 370 | return apiSetDict; 371 | } 372 | 373 | /// 374 | /// Resolves LdrLoadDll and uses that function to load a DLL from disk. 375 | /// 376 | /// Ruben Boonen (@FuzzySec) 377 | /// The path to the DLL on disk. Uses the LoadLibrary convention. 378 | /// IntPtr base address of the loaded module or IntPtr.Zero if the module was not loaded successfully. 379 | public static unsafe IntPtr LoadModuleFromDisk(string dllPath) 380 | { 381 | var uModuleName = new UNICODE_STRING(); 382 | Native.RtlInitUnicodeString(&uModuleName, dllPath); 383 | 384 | HANDLE hModule; 385 | Native.LdrLoadDll(&uModuleName, &hModule); 386 | 387 | return hModule; 388 | } 389 | 390 | [UnmanagedFunctionPointer(CallingConvention.Cdecl)] 391 | private delegate IntPtr ReadGs(); 392 | 393 | [StructLayout(LayoutKind.Sequential)] 394 | private struct LIST_ENTRY 395 | { 396 | public IntPtr Flink; 397 | public IntPtr Blink; 398 | } 399 | 400 | [StructLayout(LayoutKind.Sequential)] 401 | private struct LDR_DATA_TABLE_ENTRY 402 | { 403 | public LIST_ENTRY InLoadOrderLinks; 404 | public LIST_ENTRY InMemoryOrderLinks; 405 | public LIST_ENTRY InInitializationOrderLinks; 406 | public IntPtr DllBase; 407 | public IntPtr EntryPoint; 408 | public uint SizeOfImage; 409 | public UNICODE_STRING FullDllName; 410 | public UNICODE_STRING BaseDllName; 411 | } 412 | 413 | [StructLayout(LayoutKind.Explicit)] 414 | private struct ApiSetNamespace 415 | { 416 | [FieldOffset(0x0C)] 417 | public int Count; 418 | 419 | [FieldOffset(0x10)] 420 | public int EntryOffset; 421 | } 422 | 423 | [StructLayout(LayoutKind.Explicit)] 424 | private struct ApiSetNamespaceEntry 425 | { 426 | [FieldOffset(0x04)] 427 | public int NameOffset; 428 | 429 | [FieldOffset(0x08)] 430 | public int NameLength; 431 | 432 | [FieldOffset(0x10)] 433 | public int ValueOffset; 434 | 435 | [FieldOffset(0x14)] 436 | public int ValueLength; 437 | } 438 | 439 | [StructLayout(LayoutKind.Explicit)] 440 | private struct ApiSetValueEntry 441 | { 442 | [FieldOffset(0x00)] 443 | public int Flags; 444 | 445 | [FieldOffset(0x04)] 446 | public int NameOffset; 447 | 448 | [FieldOffset(0x08)] 449 | public int NameCount; 450 | 451 | [FieldOffset(0x0C)] 452 | public int ValueOffset; 453 | 454 | [FieldOffset(0x10)] 455 | public int ValueCount; 456 | } 457 | } --------------------------------------------------------------------------------