Skip to content

Improvements #86

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 25 additions & 53 deletions BlueDucky.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,38 +5,31 @@
import subprocess
import os


from utils.color import color
from utils.menu_functions import (main_menu, read_duckyscript, run, restart_bluetooth_daemon, get_target_address)
from utils.register_device import register_hid_profile, agent_loop

child_processes = []

# ANSI escape sequences for colors
class AnsiColorCode:
RED = '\033[91m'
GREEN = '\033[92m'
YELLOW = '\033[93m'
BLUE = '\033[94m'
WHITE = '\033[97m'
RESET = '\033[0m'

# Custom log level
NOTICE_LEVEL = 25

# Custom formatter class with added color for NOTICE
class ColorLogFormatter(logging.Formatter):
COLOR_MAP = {
logging.DEBUG: AnsiColorCode.BLUE,
logging.INFO: AnsiColorCode.GREEN,
logging.WARNING: AnsiColorCode.YELLOW,
logging.ERROR: AnsiColorCode.RED,
logging.CRITICAL: AnsiColorCode.RED,
NOTICE_LEVEL: AnsiColorCode.BLUE, # Color for NOTICE level
logging.DEBUG: color.BLUE,
logging.INFO: color.GREEN,
logging.WARNING: color.YELLOW,
logging.ERROR: color.RED,
logging.CRITICAL: color.RED,
NOTICE_LEVEL: color.BLUE, # Color for NOTICE level
}

def format(self, record):
color = self.COLOR_MAP.get(record.levelno, AnsiColorCode.WHITE)
fmt_color = self.COLOR_MAP.get(record.levelno, color.WHITE)
message = super().format(record)
return f'{color}{message}{AnsiColorCode.RESET}'
return f'{fmt_color}{message}{color.RESET}'


# Method to add to the Logger class
Expand All @@ -58,6 +51,8 @@ def setup_logging():
# Set the logging level to INFO to filter out DEBUG messages
logging.basicConfig(level=logging.INFO, handlers=[handler])

def print_error(msg):
print(f"{color.RESET}[{color.RED}!{color.RESET}] {color.RED}CRITICAL ERROR{color.RESET}:{color.RESET}", msg)

class ConnectionFailureException(Exception):
pass
Expand Down Expand Up @@ -267,20 +262,13 @@ def connect(self, timeout=None):
self.connected = True
log.debug("SUCCESS! connected on port %d" % self.port)
except Exception as ex:
# Color Definition Again just to avoid errors I should've made a class for this.
red = "\033[91m"
blue = "\033[94m"
reset = "\033[0m"

error = True
self.connected = False
log.error("ERROR connecting on port %d: %s" % (self.port, ex))
raise ConnectionFailureException(f"Connection failure on port {self.port}")
if (error == True & self.port == 14):
print("{reset}[{red}!{reset}] {red}CRITICAL ERROR{reset}: {reset}Attempted Connection to {red}{target_address} {reset}was {red}denied{reset}.")
print_error(f"{color.RESET}Attempted Connection to {color.RED}{target_address} {color.RESET}was {color.RED}denied{color.RESET}.")
return self.connected


raise ConnectionFailureException(f"Connection failure on port {self.port}")

return self.connected

Expand Down Expand Up @@ -640,36 +628,28 @@ def setup_and_connect(connection_manager, target_address, adapter_id):
def troubleshoot_bluetooth():
# Added this function to troubleshoot common issues before access to the application is granted

blue = "\033[0m"
red = "\033[91m"
reset = "\033[0m"
# Check if bluetoothctl is available
try:
subprocess.run(['bluetoothctl', '--version'], check=True, stdout=subprocess.PIPE)
except subprocess.CalledProcessError:
print("{reset}[{red}!{reset}] {red}CRITICAL{reset}: {blue}bluetoothctl {reset}is not installed or not working properly.")
print_error(f"{color.BLUE}bluetoothctl {color.RESET}is not installed or not working properly.")
return False

# Check for Bluetooth adapters
result = subprocess.run(['bluetoothctl', 'list'], capture_output=True, text=True)
if "Controller" not in result.stdout:
print("{reset}[{red}!{reset}] {red}CRITICAL{reset}: No {blue}Bluetooth adapters{reset} have been detected.")
print_error(f"No {color.BLUE}Bluetooth adapters{color.RESET} have been detected.")
return False

# List devices to see if any are connected
result = subprocess.run(['bluetoothctl', 'devices'], capture_output=True, text=True)
if "Device" not in result.stdout:
print("{reset}[{red}!{reset}] {red}CRITICAL{reset}: No Compatible {blue}Bluetooth devices{reset} are connected.")
return False
log.warning(f"{color.RESET}Your {color.BLUE}Bluetooth devices{color.RESET} might not be supported.")

# if no issues are found then continue
return True

# Main function
def main():
blue = "\033[0m"
red = "\033[91m"
reset = "\033[0m"
parser = argparse.ArgumentParser(description="Bluetooth HID Attack Tool")
parser.add_argument('--adapter', type=str, default='hci0', help='Specify the Bluetooth adapter to use (default: hci0)')
args = parser.parse_args()
Expand All @@ -685,17 +665,11 @@ def main():
payload_folder = os.path.join(script_directory, 'payloads/') # Specify the relative path to the payloads folder.
payloads = os.listdir(payload_folder)

blue = "\033[0m"
red = "\033[91m"
reset = "\033[0m"
print(f"\nAvailable payloads{blue}:")
print(f"\nAvailable payloads{color.BLUE}:")
for idx, payload_file in enumerate(payloads, 1): # Check and enumerate the files inside the payload folder.
print(f"{reset}[{blue}{idx}{reset}]{blue}: {blue}{payload_file}")
print(f"{color.RESET}[{color.BLUE}{idx}{color.RESET}]{color.BLUE}: {color.BLUE}{payload_file}")

blue = "\033[0m"
red = "\033[91m"
reset = "\033[0m"
payload_choice = input(f"\n{blue}Enter the number that represents the payload you would like to load{reset}: {blue}")
payload_choice = input(f"\n{color.BLUE}Enter the number that represents the payload you would like to load{color.RESET}: {color.BLUE}")
selected_payload = None

try:
Expand All @@ -705,10 +679,10 @@ def main():
print(f"Invalid payload choice. No payload selected.")

if selected_payload is not None:
print(f"{blue}Selected payload{reset}: {blue}{selected_payload}")
print(f"{color.BLUE}Selected payload{color.RESET}: {color.BLUE}{selected_payload}")
duckyscript = read_duckyscript(selected_payload)
else:
print(f"{red}No payload selected.")
print(f"{color.RED}No payload selected.")



Expand All @@ -731,7 +705,7 @@ def main():
break # Exit loop if successful

except ReconnectionRequiredException as e:
log.info(f"{reset}Reconnection required. Attempting to reconnect{blue}...")
log.info(f"{color.RESET}Reconnection required. Attempting to reconnect{color.BLUE}...")
current_line = e.current_line
current_position = e.current_position
connection_manager.close_all()
Expand All @@ -740,12 +714,10 @@ def main():

finally:
# unpair the target device
blue = "\033[94m"
reset = "\033[0m"

command = f'echo -e "remove {target_address}\n" | bluetoothctl'
command = f'echo -e "\nremove {target_address}\n" | bluetoothctl'
subprocess.run(command, shell=True)
print(f"{blue}Successfully Removed device{reset}: {blue}{target_address}{reset}")
print(f"{color.BLUE}Successfully Removed device{color.RESET}: {color.BLUE}{target_address}{color.RESET}")

if __name__ == "__main__":
setup_logging()
Expand Down
10 changes: 10 additions & 0 deletions utils/color.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# ANSI escape sequences for colors
class AnsiColorCode:
RED = '\033[91m'
GREEN = '\033[92m'
YELLOW = '\033[93m'
BLUE = '\033[94m'
WHITE = '\033[97m'
RESET = '\033[0m'

color = AnsiColorCode()
76 changes: 28 additions & 48 deletions utils/menu_functions.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import os, bluetooth, re, subprocess, time, curses
import logging as log

from utils.color import color

##########################
# UI Redesign by Lamento #
##########################

def get_target_address():
blue = "\033[94m"
reset = "\033[0m"
print(f"\n What is the target address{blue}? {reset}Leave blank and we will scan for you{blue}!{reset}")
target_address = input(f"\n {blue}> ")
print(f"\n What is the target address{color.BLUE}? {color.RESET}Leave blank and we will scan for you{color.BLUE}!{color.RESET}")
target_address = input(f"\n {color.BLUE}> ")

if target_address == "":
devices = scan_for_devices()
Expand All @@ -18,16 +18,16 @@ def get_target_address():
if len(devices) == 1 and isinstance(devices[0], tuple) and len(devices[0]) == 2:
# A single known device was chosen, no need to ask for selection
# I think it would be better to ask, as sometimes I do not want to chose this device and actually need solely to scan for actual devices.
confirm = input(f"\n Would you like to register this device{blue}:\n{reset}{devices[0][1]} {devices[0][0]}{blue}? {blue}({reset}y{blue}/{reset}n{blue}) {blue}").strip().lower()
confirm = input(f"\n Would you like to register this device{color.BLUE}:\n{color.RESET}{devices[0][1]} {devices[0][0]}{color.BLUE}? {color.BLUE}({color.RESET}y{color.BLUE}/{color.RESET}n{color.BLUE}) {color.BLUE}").strip().lower()
if confirm == 'y' or confirm == 'yes':
return devices[0][0]
elif confirm != 'y' or 'yes':
return
else:
# Show list of scanned devices for user selection
for idx, (addr, name) in enumerate(devices):
print(f"{reset}[{blue}{idx + 1}{reset}] {blue}Device Name{reset}: {blue}{name}, {blue}Address{reset}: {blue}{addr}")
selection = int(input(f"\n{reset}Select a device by number{blue}: {blue}")) - 1
print(f"{color.RESET}[{color.BLUE}{idx + 1}{color.RESET}] {color.BLUE}Device Name{color.RESET}: {color.BLUE}{name}, {color.BLUE}Address{color.RESET}: {color.BLUE}{addr}")
selection = int(input(f"\n{color.RESET}Select a device by number{color.BLUE}: {color.BLUE}")) - 1
if 0 <= selection < len(devices):
target_address = devices[selection][0]
else:
Expand Down Expand Up @@ -92,40 +92,24 @@ def save_devices_to_file(devices, filename='known_devices.txt'):
def scan_for_devices():
main_menu()

blue = "\033[94m"
error = "\033[91m"
reset = "\033[0m"

# Load known devices
known_devices = load_known_devices()
if known_devices:
blue = "\033[94m"
error = "\033[91m"
reset = "\033[0m"
print(f"\n{reset}Known devices{blue}:")
print(f"\n{color.RESET}Known devices{color.BLUE}:")
for idx, (addr, name) in enumerate(known_devices):
blue = "\033[94m"
error = "\033[91m"
reset = "\033[0m"
print(f"{blue}{idx + 1}{reset}: Device Name: {blue}{name}, Address: {blue}{addr}")

blue = "\033[94m"
error = "\033[91m"
reset = "\033[0m"
use_known_device = input(f"\n{reset}Do you want to use one of these known devices{blue}? {blue}({reset}yes{blue}/{reset}no{blue}): ")
print(f"{color.BLUE}{idx + 1}{color.RESET}: Device Name: {color.BLUE}{name}, Address: {color.BLUE}{addr}")

use_known_device = input(f"\n{color.RESET}Do you want to use one of these known devices{color.BLUE}? {color.BLUE}({color.RESET}yes{color.BLUE}/{color.RESET}no{color.BLUE}): ")
if use_known_device.lower() == 'yes':
device_choice = int(input(f"{reset}Enter the index number of the device to attack{blue}: "))
device_choice = int(input(f"{color.RESET}Enter the index number of the device to attack{color.BLUE}: "))
return [known_devices[device_choice - 1]]

# Normal Bluetooth scan
blue = "\033[94m"
error = "\033[91m"
reset = "\033[0m"
print(f"\n{reset}Attempting to scan now{blue}...")
print(f"\n{color.RESET}Attempting to scan now{color.BLUE}...")
nearby_devices = bluetooth.discover_devices(duration=8, lookup_names=True, flush_cache=True, lookup_class=True)
device_list = []
if len(nearby_devices) == 0:
print(f"\n{reset}[{error}+{reset}] No nearby devices found.")
print(f"\n{color.RESET}[{error}+{color.RESET}] No nearby devices found.")
else:
print("\nFound {} nearby device(s):".format(len(nearby_devices)))
for idx, (addr, name, _) in enumerate(nearby_devices):
Expand All @@ -137,7 +121,7 @@ def scan_for_devices():
known_devices += new_devices
save_devices_to_file(known_devices)
for idx, (addr, name) in enumerate(new_devices):
print(f"{reset}{idx + 1}{blue}: {blue}Device Name{reset}: {blue}{name}{reset}, {blue}Address{reset}: {blue}{addr}")
print(f"{color.RESET}{idx + 1}{color.BLUE}: {color.BLUE}Device Name{color.RESET}: {color.BLUE}{name}{color.RESET}, {color.BLUE}Address{color.RESET}: {color.BLUE}{addr}")
return device_list

def getterm():
Expand All @@ -146,22 +130,20 @@ def getterm():


def print_menu():
blue = '\033[94m'
reset = "\033[0m"
title = "BlueDucky - Bluetooth Device Attacker"
vertext = "Ver 2.1"
motd1 = f"Remember, you can still attack devices without visibility.."
motd2 = f"If you have their MAC address.."
terminal_width = getterm()
separator = "=" * terminal_width

print(blue + separator) # Blue color for separator
print(reset + title.center(len(separator))) # Centered Title in blue
print(blue + vertext.center(len(separator))) # Centered Version
print(blue + separator + reset) # Blue color for separator
print(color.BLUE + separator) # Blue color for separator
print(color.RESET + title.center(len(separator))) # Centered Title in blue
print(color.BLUE + vertext.center(len(separator))) # Centered Version
print(color.BLUE + separator + color.RESET) # Blue color for separator
print(motd1.center(len(separator)))# used the same method for centering
print(motd2.center(len(separator)))# used the same method for centering
print(blue + separator + reset) # Blue color for separator
print(color.BLUE + separator + color.RESET) # Blue color for separator

def main_menu():
clear_screen()
Expand Down Expand Up @@ -196,13 +178,11 @@ def load_known_devices(filename='known_devices.txt'):
vertext = "Ver 2.1"
terminal_width = getterm()
separator = "=" * terminal_width
blue = "\033[0m"
reset = "\033[0m"

print(blue + separator) # Blue color for separator
print(reset + title.center(len(separator))) # White color for title
print(blue + vertext.center(len(separator))) # White blue for version number
print(blue + separator + reset) # Blue color for separator
print(f"{reset}Remember, you can still attack devices without visibility{blue}.." + reset)
print(f"{blue}If you have their {reset}MAC address{blue}.." + reset)
print(blue + separator + reset) # Blue color for separator

print(color.BLUE + separator) # Blue color for separator
print(color.RESET + title.center(len(separator))) # White color for title
print(color.BLUE + vertext.center(len(separator))) # White blue for version number
print(color.BLUE + separator + color.RESET) # Blue color for separator
print(f"{color.RESET}Remember, you can still attack devices without visibility{color.BLUE}.." + color.RESET)
print(f"{color.BLUE}If you have their {color.RESET}MAC address{color.BLUE}.." + color.RESET)
print(color.BLUE + separator + color.RESET) # Blue color for separator