Skip to content

Latest commit

 

History

History
275 lines (230 loc) · 9.38 KB

cs50.md

File metadata and controls

275 lines (230 loc) · 9.38 KB

duckyshell

Video Demo: coming soon

A command line tool for flashing the USB Rubber Ducky on the go written in Python with cmd!

General

We start of with declaring the config file path for the USB at the start

CONFIG_FILE_PATH = "/usr/bin/duckyshell/config.yaml"

And at the end I just have the function for running the application

if __name__ == '__main__':
    shell = DuckyShell()
    shell.cmdloop()

Duckyshell class

This is the class for the whole CLI program.

When it runs it starts of with clearing the screen (as msfconsole does), prints out the intro, setting the prompt and declaring the USB and file path variables as None.

# Clears the screen
 os.system(f"clear")
    
# Prints out the intro
intro = "\nHak5 USB Rubber Ducky CLI\n\nType 'help' to list available commands or 'help <command>' to get help for a specific command.\n"    
# Prompt for the CLI
prompt = "ducky_sh3ll > "
    
# Setting the usb path to None for now
usb_path = None
file_path = None

And we declare the help messages for each command

# Help messages for each command
help_messages = {
        'usb': 'Set the USB path for connected USB devices.',
        'file': 'Set the file to the designated inject.bin you want.',
        'list': 'List the connected USB devices.',
        'run': 'Copy the inject.bin to the USB Rubber Ducky.',
        'quit': 'Exit the DuckyShell.',
        'exit': 'Exit the DuckyShell.',
}

Load USB path

I wrote this so you can "set and forget" the path of the USB so you don't need to set it each time you run the program. It reads the config file and sets the usb_path to the path inside of the config.

def load_usb_path(self):
        # Load the USB path from the config file
        if os.path.exists(CONFIG_FILE_PATH):
            with open(CONFIG_FILE_PATH, 'r') as config_file:
                config = yaml.safe_load(config_file)
                self.usb_path = config.get('usb_path')

Here is the function for saving the usb path to the config file.

def save_usb_path(self):
        # Save the USB path to the config file
        config = {'usb_path': self.usb_path}
        with open(CONFIG_FILE_PATH, 'w') as config_file:
            yaml.safe_dump(config, config_file)

Autocomplete tab feature on USB and files command

This is done by getting the current directory of where the user is and filtering the list on what the user is typing and returning the different possibilities.

I wanted this feature since I use it so much in the terminal, so I implemented it in this project.

def complete_file(self, text, line, begidx, endidx):
        # Get a list of files and directories in the current directory
        current_dir = os.getcwd()
        files_and_dirs = os.listdir(current_dir)

        # Filter the list based on the current text being entered
        options = [entry for entry in files_and_dirs if entry.startswith(text)]

        return options

def complete_usb(self, text, line, begidx, endidx):
        # Get a list of files and directories in the current directory
        current_dir = os.getcwd()
        files_and_dirs = os.listdir(current_dir)

        # Filter the list based on the current text being entered
        options = [entry for entry in files_and_dirs if entry.startswith(text)]

        # Returning results
        return options

Return connected USB devices

This is done by searching the dev/sd folder in Linux with regex to only return the devices that are present.

def get_connected_usb_devices(self):
        devices = glob.glob('/dev/sd*[!0-9]')
        return [os.path.basename(device) for device in devices]

Commands

Run

Is the command that runs the flashing for the USB. It checks if both the usb_path and file_path are set, and if it is an .bin file extention. Then it copies it over to the USB, replacing the old inject.bin file.

def do_run(self, arg):
        # Check for USB path provided
        if not self.usb_path:
            print("USB path not provided. Use 'set_usb_path' command to set it.")
            return
        # Check for file path provided
        if not self.file_path:
            print("File path not provided. Use 'set_file_path' command to set it.")
            return

        # Check if the file is a binary file
        if not self.file_path.endswith('.bin'):
            print(f"Invalid file type: {self.file_path} is not a text file (.txt)")
            return

        # Copy the binary file to the USB Rubber Ducky
        os.system(f"cp {self.file_path} {self.usb_path}")
        print(f"File copied to USB drive: {self.usb_path}")

        # Delete the temporary text file
        os.remove(self.file_path)

USB

Set the path for only connected USB devices

def do_usb(self, line):
        usb_path = line.strip()
        # Check if the USB device is connected
        usb_devices = [device[0] for device in self.get_connected_usb_devices()]
        if usb_path not in usb_devices:
            print("USB device is not connected.")
            return    
        # Set the USB path for future commands
        self.usb_path = usb_path
        self.save_usb_path()
        print(f"USB path set to: {usb_path}")

File

Set the file to the file you want. It must be an ìnject.bin file (Hak5) so it checks for that with the use of Regular expressions.

def do_file(self, line):
        file_path = line.strip()

        # Check if the file path exists
        if not os.path.exists(file_path):
            print("File path does not exist.")
            return

        # Check if the file path has the correct extension
        if not re.match(r"inject.bin", file_path):
            print("Invalid file format. Only 'inject.bin' files are allowed.")
            return

        # Set the file path for future copy operations
        self.file_path = file_path
        print(f"File path set to: {file_path}")

List

This lists the connected USB devices and I think this function is really cool. We use the lsblk command in Linux to filter out and look for only the connected USB devices the user can provide as input.

def do_list(self, arg):
        output = subprocess.check_output(['lsblk', '-o', 'NAME,MODEL', '-n', '-l']).decode('utf-8').strip()
        lines = output.split('\n')
        devices = [line.split() for line in lines]
        usb_devices = [device for device in devices if device and (device[0].startswith('sd') or device[0].startswith('hd'))]
        if usb_devices:
            print("Connected USB devices:")
            for device in usb_devices:
                if len(device) >= 2:
                    print(f"Name: {device[0]}   Model: {device[1]}")
                else:
                    print(f"Name: {device[0]}   Model: Unknown")
        else:
            print("No USB devices found.")

Empty Line

If you press enter in the terminal, it returns nothing! Soooo we need to implement that with just checking for and empty line and using pass in Python!

def emptyline(self):
        pass

Help!

This is the help command. As previous we declared the help sentences, so for this we look for the command, and if it is present, we print out the help_message for the designated command!

def do_help(self, arg):
        """
        List available commands with their usage.
        Usage: help [command]
        """
        if arg:
            # Show help for a specific command
            if arg in self.help_messages:
                print(self.help_messages[arg])
            else:
                print(f"Command '{arg}' not found.")
        else:
            # Show help for all commands
            print("\nAvailable commands:")
            for command, help_message in self.help_messages.items():
                print(f"{command}: {help_message}")
            print("\n")

Okay but how exit? :q!

To quit the program, you have two choises. You could either run quit or exit to quit or exit the program when you are done!

def do_quit(self, arg):
        # Exit the DuckyShell
        print("Exiting DuckyShell...")
        return True
def do_exit(self, arg):
        # Exit the DuckyShell
        print("\nExiting DuckyShell...\n")
        return True

And thats all for the duckyshell.py file!

I want other people to use this program. So I wanted to share it via writing an install.sh file that takes care of everything!

#!/bin/bash

echo -e "Installer script for duckyshell\n\n"

# Clone the GitHub repository
echo -e "Cloning the Github repository\n"
git clone https://github.com/DanielBoye/duckyshell.git

# Copy the folder to /usr/bin
echo -e "\nCopying folders\n"
sudo cp -R duckyshell/ /usr/bin/duckyshell

# Create an alias for running duckyshell.py as 'ducky'
echo -e "Creating aliases\n"
echo "alias ducky='python3 /usr/bin/duckyshell/duckyshell.py'" >> ~/.bashrc

# Reload the bashrc file to apply the alias
echo -e "Reload the .bashrc file\n"
source ~/.bashrc

# Provide executable permissions to the program
echo -e "Make the program executable\n"
sudo chmod +x /usr/bin/duckyshell/duckyshell.py

# Cleanup: remove the cloned repository
echo -e "Clean up install\n" 
rm -rf duckyshell/

# This fixes something
source ~/.bashrc

# Installation completed
echo -e "DuckyShell has been installed.\n"
echo -e "You can now use 'ducky' command to run it.\n"