Créer sa RubberDucky pour 6€

Hacking
2052 mots

Introduction

Bonjour à tous, dans cet article nous allons voir comment il est possible de créer sa propre Rubber Ducky.

Une Rubber Ducky, pour ceux qui ne le savent pas, est une clé USB capable d'émuler le comportement de périphériques connectés à votre ordinateur tels qu'un clavier ou une souris.

Ici nous émulerons un clavier pour générer des séquences de touches et voir comment les hackers pratiquent les Keystroke Injections

Pré-Requis

Voici ce dont on va avoir besoin pour créer notre objet :

  • Un IDE (Thonny de préférence)
  • Des bases en Python
  • Un gadget supportant Micropython (généralement ESP32, RP2040, RP2350, etc...)
  • Un cable supportant le chargement de données

Pour ceux qui ne souhaitent pas aller chercher dans l'aspect technique des micro-firmwares, achetez un Raspberry Pi Pico 1 ou 2 ça fera parfaitement l'affaire et c'est ce qu'on va utiliser ici.
Ca coute 4 à 6€ c'est pas excessif.

Micropython & Config Gadget

Micropython est une version simplifée de Python faite pour travailler avec les micro-programmes ce langage permet de faire tout un tas de choses, pour l'utiliser avec votre gadget, allez chercher le firmware correspondant au gadget que vous avez acheté sur ce lien :

Pour utiliser Micropython sur votre Raspberry Pico, c'est très simple voici le procédé :

  1. Branchez votre appareil sur votre ordinateur en maintenant le bouton blanc BOOTSEL (Ca fera entrer votre Raspbery en mode File Storage)
    • Vous devriez voir apparaître un nouveau disque dur sur votre ordinateur.
  2. Allez chercher le fichier .uf2 téléchargé sur le site de Micropython puis copiez-le dans le disque du Raspberry Pico
  3. Si tout a fonctionné correctement la page d'explorateur de fichier devrait se fermer ça veut dire que votre Pico redémarre
  4. Débranchez le puios rebranchez le sans appuyer sur le bouton cette fois-ci. Vous ne verrez pas apparaître de disque dur mais c'est tout a fait normal parceque maintenant il est branché en mode Serial

Config de l'éditeur

Pour développer sur des gadgets il faut pouvoir communiquer avec eux via leur port de série. A savoir que Thonny est vraiment beaucoup plus pratique que VScode pour ce type de programmation.

1. Thonny

Installez-le ici

  1. Allez dans le menu Outils > Options.
  2. Dans l'onglet Interpréteur, choisissez MicroPython (Raspberry Pi Pico) dans la liste déroulante des interpréteurs.
  3. Assurez-vous que le port série est correctement sélectionné. Si votre Raspberry Pi Pico est correctement connecté, Thonny devrait détecter automatiquement le port.

Si jamais Thonny ne trouve pas le port vous pouvez le trouver de la manière suivante, puis l'entrer manuellement :
Linux : dmesg | grep tty
Vous verrez un ttyACM0 ou ttyUSB0, c'est le port de série de votre Pico.

Windows : Ouvrez un PowerShell puis tappez -> Get-WMIObject Win32_SerialPort. Vous verrez pleins d'informations et à un endroit vous devriez voir une ligne comme celle-ci :

CreationClassName           : Win32_SerialPort
Description                 : Périphérique série USB  
DeviceID                    : COM3                                                                                    

COM3 c'est le port de série de votre Pico sur Windows.

Sur Mac, utilisez ls /dev/tty.usbmodem*

Et voilà vous êtes tout prêt. Vous devriez voir sur la barre latérale gauche votre système de fichier de l'ordinateur en haut et celui du pico (vide pour l'instant) en bas.

2. VSCode

Installation de VS Code et des extensions nécessaires

  1. Télécharger et installer VS Code

  2. Installer l'extension MicroPico

    • Ouvrez VS Code
    • Allez dans l'onglet "Extensions" (Ctrl + Shift + X)
    • Recherchez "MicroPico" et installez-le

Configuration de VS Code pour MicroPython

  1. Configurer le port série

    • Branchez le Pico et identifiez son port :
      • Sur Windows, ouvrez le "Gestionnaire de périphériques" et cherchez le port sous "Ports (COM et LPT)" ou sinon dans PowerShell : Get-WMIObject Win32_SerialPort
      • Sur Linux, utilisez dmesg | grep tty
      • Sur Mac, utilisez ls /dev/tty.usbmodem*
  2. Configurer l'extension MicroPico

    • Ouvrez la palette de commande (Ctrl + Shift + P)
    • Tapez MicroPico: Configure Global Settings
    • Sélectionnez le port détecté
  3. Téléverser et exécuter le script

    • Ouvrez la palette de commande (Ctrl + Shift + P)
    • Tapez MicroPico: Upload Current File to Device
    • Pour ouvrir une console REPL : MicroPico: Open REPL

Votre Raspberry Pi Pico est maintenant configuré pour être utilisé avec VS Code et MicroPython ! 🚀

3. Tester la configuration

Informations

Par défault le firmware .uf2 va chercher a exécuter les fichiers dans un certain ordre sur Micropython :

  1. D'abord le boot.py si vous le créez assurez vous qu'il ne bloquera pas le reste du code sinon il faudra re-flasher le firmware et recommencer
  2. Ensuite le main.py, en général c'est par celui-ci qu'on va commencer
  3. Pour les modules le dossier /lib/ est reconnu automatiquement

Tester un script MicroPython

On va écrire un script qui va allumer et éteindre la LED intégrée au Pico en boucle pour voir si ça fonctionne correctement.

  1. Créer un fichier main.py
    from machine import Pin
    import time
    
    led = Pin(25, Pin.OUT)  # LED intégrée sur Raspberry Pi Pico
    
    while True: # Boucle pour allumer et éteindre la LED
        led.toggle()
        time.sleep(1)
    

Si ce script fonctionne on peut passer à la suite

Transformer Notre Clé USB en Clavier

Installer les librairies

L'organisme qui a créé micropython à aussi créé des librairies parcequ'ils sont super cools et il y en a une qui va permettre à notre Raspberry d'être detecté comme un périphérique type ***HID (Human Interface Device)

**Voici comment les utiliser : **

  • Cloner le dépôt Github : git clone https://github.com/micropython/micropython-lib.git
    • Un dossier micropython-lib ca être ajouté sur votre ordinateur.
  • Allez sur votre IDE et dans le Raspberry créez un dossier /lib
  • Allez dans le dossier micropython-lib/micropython vous y verrez un dossier usb. Copiez dans le dossier lib de votre Pico.

Maintenant nous avons la librairie USB c'est parfait on va pouvoir l'utiliser !

Keyboard Script

On va devoir utiliser les fonctionnalités de ces librairies dans notre main.py pour qu'au branchement notre Pico exécute notre séquence de touches,

Dans votre main commencez par importer les modules nécessaires :

import usb.device # Librairie Micropython
from usb.device.keyboard import KeyboardInterface # Module pour être detecté comme clavier
import time # Librairie pour mettre des délais necessaires

Ensuite il faudra initialiser le périphérique en tant que clavier grâce a la class KeyboardInterface pour qu'il soit reconnu comme clavier et puisse utiliser les touches :

class MyKeyboard(KeyboardInterface):
    def on_led_update(self, led_mask):
        pass

Maintenant que notre périphérique est initialisé comem clavier on va pouvoir passer au code qui nous interresse :

def BadUsb(): # Fonction qui va contenir tout le comportement de notre clavier
    k = MyKeyboard() # On récupère le clavier dans une variable
    usb.device.get().init(k, builtin_driver=True) # On charge ses modules
    
    # Boucle de vérification, si le clavier n'est pas initialisé le pico ne fera rien.
    while not k.is_open():
        time.sleep(0.01)

Pour gagner un maximum de temps avant de passer à la suite de notre main.py on va aller créer notre propre module dans /lib/utils.py (nommez le comme vous voulez pas obligé que ce soit utils)

Redaction du lib/utils.py

from usb.device.keyboard import KeyboardInterface, KeyCode
import time
import os

# Fonction pour lire la configuration utilisateur qu'on va faire après
def read_config(file_path):
    config = {}
    with open(file_path, 'r') as file:
        for line in file:
            # Ignorer les lignes vides ou les commentaires
            if line.strip() and not line.strip().startswith("#"):
                key, value = line.strip().split("=", 1)
                config[key.strip()] = value.strip()
    return config

# On va mapper les touches pour que les variables soit moins longues et pour pouvoir adapter le comportement du Pico selon la disposition clavier (Qwerty, Azerty)
#Utils
SPACE = KeyCode.SPACE
BACKSPACE = KeyCode.BACKSPACE
ENTER = KeyCode.ENTER
ESCAPE = KeyCode.ESCAPE
TAB = KeyCode.TAB

LSHIFT = KeyCode.LEFT_SHIFT
RSHIFT = KeyCode.RIGHT_SHIFT

LALT = KeyCode.LEFT_ALT
RALT = KeyCode.RIGHT_ALT

LCTRL = KeyCode.LEFT_CTRL
RCTRL = KeyCode.RIGHT_CTRL

RIGHT = KeyCode.RIGHT
LEFT = KeyCode.LEFT
DOWN = KeyCode.DOWN
UP = KeyCode.UP

#<> for Azerty FR
INFERIOR = 100
SUPERIOR = (KeyCode.LEFT_SHIFT, INFERIOR)

#Miscellanous
LINUX_TERMINAL = (LCTRL, LALT, KeyCode.T)
MAC_TERMINAL = (KeyCode.LEFT_UI, SPACE) 
WINDOWS_POWERSHELL = (KeyCode.LEFT_UI)
#Debug

#Mappage
azertyFR = {
    #Letters
    'a': KeyCode.Q, 'b': KeyCode.B, 'c': KeyCode.C, 'd': KeyCode.D,
    'e': KeyCode.E, 'f': KeyCode.F, 'g': KeyCode.G, 'h': KeyCode.H,
    'i': KeyCode.I, 'j': KeyCode.J, 'k': KeyCode.K, 'l': KeyCode.L,
    'm': KeyCode.SEMICOLON, 'n': KeyCode.N, 'o': KeyCode.O, 'p': KeyCode.P,
    'q': KeyCode.A, 'r': KeyCode.R, 's': KeyCode.S, 't': KeyCode.T,
    'u': KeyCode.U, 'v': KeyCode.V, 'w': KeyCode.Z, 'x': KeyCode.X,
    'y': KeyCode.Y, 'z': KeyCode.W,
    
    #Special Chars
    'à': KeyCode.N0, '&': KeyCode.N1, 'é': KeyCode.N2, '"': KeyCode.N3,
    "'": KeyCode.N4, '(': KeyCode.N5, '-': KeyCode.N6, 'è': KeyCode.N7,
    '_': KeyCode.N8, 'ç': KeyCode.N9, ')': KeyCode.MINUS, '=': KeyCode.EQUAL, '+': (KeyCode.LEFT_SHIFT, KeyCode.EQUAL),
    
    #Numbers
    '1': (KeyCode.LEFT_SHIFT, KeyCode.N1), '2': (KeyCode.LEFT_SHIFT, KeyCode.N2), '3': (KeyCode.LEFT_SHIFT, KeyCode.N3),
    '4': (KeyCode.LEFT_SHIFT, KeyCode.N4), '5': (KeyCode.LEFT_SHIFT, KeyCode.N5), '6': (KeyCode.LEFT_SHIFT, KeyCode.N6),
    '7': (KeyCode.LEFT_SHIFT, KeyCode.N7), '8': (KeyCode.LEFT_SHIFT, KeyCode.N8), '9': (KeyCode.LEFT_SHIFT, KeyCode.N9),
    '0': (KeyCode.LEFT_SHIFT, KeyCode.N0), 
    
    #Utils & Special Chars
    '´': (KeyCode.RIGHT_ALT, KeyCode.N1), '~': (KeyCode.RIGHT_ALT, KeyCode.N2), '#': (KeyCode.RIGHT_ALT, KeyCode.N3),
    '{': (KeyCode.RIGHT_ALT, KeyCode.N4), '[': (KeyCode.RIGHT_ALT, KeyCode.N5), '|': (KeyCode.RIGHT_ALT, KeyCode.N6),
    '`': (KeyCode.RIGHT_ALT, KeyCode.N7), '\\': (KeyCode.RIGHT_ALT, KeyCode.N8), '^': (KeyCode.RIGHT_ALT, KeyCode.N9),
    '@': (KeyCode.RIGHT_ALT, KeyCode.N0), ']': (KeyCode.RIGHT_ALT, KeyCode.MINUS), '}': (KeyCode.RIGHT_ALT, KeyCode.EQUAL),
    '/': (KeyCode.LEFT_SHIFT, KeyCode.DOT), 
    
    ' ': KeyCode.SPACE,
    
    '.': (KeyCode.LEFT_SHIFT, KeyCode.COMMA),
    ',': KeyCode.M,
    ';': KeyCode.COMMA,
    ':': KeyCode.DOT,
    
    '!': KeyCode.SLASH,
    '?': (KeyCode.LEFT_SHIFT, KeyCode.M),
    '$': KeyCode.CLOSE_BRACKET,
    '>': SUPERIOR,
    '<': INFERIOR,
    '*': KeyCode.BACKSLASH,
}

qwertyUS = {
    # Letters
    'a': KeyCode.A, 'b': KeyCode.B, 'c': KeyCode.C, 'd': KeyCode.D,
    'e': KeyCode.E, 'f': KeyCode.F, 'g': KeyCode.G, 'h': KeyCode.H,
    'i': KeyCode.I, 'j': KeyCode.J, 'k': KeyCode.K, 'l': KeyCode.L,
    'm': KeyCode.M, 'n': KeyCode.N, 'o': KeyCode.O, 'p': KeyCode.P,
    'q': KeyCode.Q, 'r': KeyCode.R, 's': KeyCode.S, 't': KeyCode.T,
    'u': KeyCode.U, 'v': KeyCode.V, 'w': KeyCode.W, 'x': KeyCode.X,
    'y': KeyCode.Y, 'z': KeyCode.Z,
    
    # Numbers (direct keys)
    '1': KeyCode.N1, '2': KeyCode.N2, '3': KeyCode.N3, '4': KeyCode.N4,
    '5': KeyCode.N5, '6': KeyCode.N6, '7': KeyCode.N7, '8': KeyCode.N8,
    '9': KeyCode.N9, '0': KeyCode.N0,
    
    # Shifted Numbers (special characters)
    '!': (KeyCode.LEFT_SHIFT, KeyCode.N1), '@': (KeyCode.LEFT_SHIFT, KeyCode.N2),
    '#': (KeyCode.LEFT_SHIFT, KeyCode.N3), '$': (KeyCode.LEFT_SHIFT, KeyCode.N4),
    '%': (KeyCode.LEFT_SHIFT, KeyCode.N5), '^': (KeyCode.LEFT_SHIFT, KeyCode.N6),
    '&': (KeyCode.LEFT_SHIFT, KeyCode.N7), '*': (KeyCode.LEFT_SHIFT, KeyCode.N8),
    '(': (KeyCode.LEFT_SHIFT, KeyCode.N9), ')': (KeyCode.LEFT_SHIFT, KeyCode.N0),
    
    # Special characters (direct keys)
    '-': KeyCode.MINUS, '=': KeyCode.EQUAL, '[': KeyCode.OPEN_BRACKET, ']': KeyCode.CLOSE_BRACKET,
    '\\': KeyCode.BACKSLASH, ';': KeyCode.SEMICOLON, "'": KeyCode.QUOTE,
    '`': KeyCode.GRAVE, ',': KeyCode.COMMA, '.': KeyCode.DOT, '/': KeyCode.SLASH,
    
    # Shifted special characters
    '_': (KeyCode.LEFT_SHIFT, KeyCode.MINUS), '+': (KeyCode.LEFT_SHIFT, KeyCode.EQUAL),
    '{': (KeyCode.LEFT_SHIFT, KeyCode.OPEN_BRACKET), '}': (KeyCode.LEFT_SHIFT, KeyCode.CLOSE_BRACKET),
    '|': (KeyCode.LEFT_SHIFT, KeyCode.BACKSLASH), ':': (KeyCode.LEFT_SHIFT, KeyCode.SEMICOLON),
    '"': (KeyCode.LEFT_SHIFT, KeyCode.QUOTE), '~': (KeyCode.LEFT_SHIFT, KeyCode.GRAVE),
    '<': (KeyCode.LEFT_SHIFT, KeyCode.COMMA), '>': (KeyCode.LEFT_SHIFT, KeyCode.DOT),
    '?': (KeyCode.LEFT_SHIFT, KeyCode.SLASH),
    
    # Miscellaneous
    ' ': KeyCode.SPACE,
}

### Ensuite on va faire 2 fonctions
# La premiere stringSeq pour pouvoir écrire des chaines de caractères qui soit comprises et retransmises ça évite de faire des tableaux à rallonge comme [KeyCode.H, KeyCode.E, etc.......]
# La deuxième sendKeys pour automatiser l'envoi des touches

def stringSeq(string):
    
    config = read_config("config.txt")
    language = config["lang"]
    
    keycodes = []
    if language == "FR":
        for char in string:
            if char.isupper():
                keycodes.append((KeyCode.LEFT_SHIFT, azertyFR[char.lower()]))
            elif char in azertyFR:
                key = azertyFR[char]
                if isinstance(key, tuple):  # Cas des combinaisons (SHIFT, etc.)
                    keycodes.append(key)
                else:
                    keycodes.append((key,))
            else:
                raise ValueError(f"Caractère Incompatible :")
    elif language == "US":
        for char in string:
            if char.isupper():
                keycodes.append((KeyCode.LEFT_SHIFT, qwertyUS[char.lower()]))
            elif char in qwertyUS:
                key = qwertyUS[char]
                if isinstance(key, tuple):  # Cas des combinaisons (SHIFT, etc.)
                    keycodes.append(key)
                else:
                    keycodes.append((key,))
            else:
                raise ValueError(f"Caractère Incompatible :")
    return keycodes

#Function to Send Keys Properly
def sendKeys(seq, parameter, timesleep):
    
    for key in seq:
        if isinstance(key, tuple):
             parameter.send_keys(list(key))
        elif isinstance(key, str):  # Si la clé est une chaîne (caractère Unicode)
            parameter.send_keys([key])
        else:
            parameter.send_keys([key])
            
        time.sleep(timesleep)
        parameter.send_keys([])
        time.sleep(timesleep)

Création de la config.txt

A la racine de votre appareil créez un fichier config.txt (qui sera lu grâce a la fonction de read_config) elle va nous permettre de préciser si le clavier est azerty ou qwerty ça évitera que les Q devienne des A et inversement.

lang=FR ou lang=US 

Une fois que c'est fait on peut repasser à notre cher main et commencer a rédiger des fonctions qui nous interresse. Revenons sur notre main.py:

from utils import * # Import de notre module personnalisé

Puis créons une fonction par exemple pour entrer un username et un mot de passe pour pouvoir se connecter comme un flemmard au démarrage de linux ou windows:

def OnlyKeyDump(vlue, username, password):

    # Initialisation de la séquence
    sequence = []
    
    # Pour chaque lettres dans username on la tappe à une vitesse de 0.01s par touches puis on appuie sur TAB
    for letters in stringSeq(username):
        sequence.append(letters)
    sequence.append(TAB)
    # Meme chose pour password et on appuie sur entrée en ajoutant tout a la séquence 
    for letters in stringSeq(password):
        sequence.append(letters)
    sequence.append(ENTER)
    
    # Envoi des touches
    sendKeys(sequence, vlue, 0.1)

Et voilà vous êtes fin prêt voici le résultat final du main.py :

import usb.device # Librairie Micropython
from usb.device.keyboard import KeyboardInterface # Module pour être detecté comme clavier
import time # Librairie pour mettre des délais necessaires
from utils import *

class MyKeyboard(KeyboardInterface):
    def on_led_update(self, led_mask):
        pass

# Fonction precedemment créée
def OnlyKeyDump(vlue, username, password):
    
    # Rentrez vos valeurs en paramètres
    username = "DonaldDumb"
    password = "Don4ldDumB!"

    # Initialisation de la séquence
    sequence = []
    
    # Pour chaque lettres dans username on la tappe à une vitesse de 0.01s par touches puis on appuie sur TAB
    for letters in stringSeq(username):
        sequence.append(letters)
    sequence.append(TAB)
    # Meme chose pour password et on appuie sur entrée en ajoutant tout a la séquence 
    for letters in stringSeq(password):
        sequence.append(letters)
    sequence.append(ENTER)
    
    # Envoi des touches
    sendKeys(sequence, vlue, 0.1)


# Fonction principale
def BadUsb(): # Fonction qui va contenir tout le comportement de notre clavier
    k = MyKeyboard() # On récupère le clavier dans une variable
    usb.device.get().init(k, builtin_driver=True) # On charge ses modules
    
    # Boucle de vérification, si le clavier n'est pas initialisé le pico ne fera rien.
    while not k.is_open():
        time.sleep(0.01)

    OnleyKeyDump(k, "DonaldDumb", "Don4ldDumb!")

# On lance la fonction main.
BadUsb()

Vous pouvez débranchez votre Pico lorsque vous le rebrancherez il tappera votre username et votre mot de passe.

Conclusion

Voilà maintenant vous savez comment les hackers crée des Bad USB j'espère que ça vous aura plus. Si jamais vous souhaitez m'aider vous pouvez M'acheter un Café ?