Skip to content

Commit 11fb752

Browse files
committedApr 23, 2021
initial version
1 parent 6150b5f commit 11fb752

File tree

4 files changed

+278
-0
lines changed

4 files changed

+278
-0
lines changed
 

‎.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.DS_Store
2+
.idea
3+
venv

‎BullDog.py

+245
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
1+
#!/usr/bin/env python3
2+
3+
import argparse
4+
import logging
5+
from re import sub
6+
from sys import exit
7+
from time import sleep
8+
9+
10+
class SerialKeyboard:
11+
SIMPLE_CHARS = {'a': 0x04, 'b': 0x05, 'c': 0x06, 'd': 0x07, 'e': 0x08, 'f': 0x09, 'g': 0x0A, 'h': 0x0B, 'i': 0x0C,
12+
'j': 0x0D, 'k': 0x0E, 'l': 0x0F, 'm': 0x10, 'n': 0x11, 'o': 0x12, 'p': 0x13, 'q': 0x14, 'r': 0x15,
13+
's': 0x16, 't': 0x17, 'u': 0x18, 'v': 0x19, 'w': 0x1A, 'x': 0x1B, 'y': 0x1C, 'z': 0x1D, '1': 0x1E,
14+
'2': 0x1F, '3': 0x20, '4': 0x21, '5': 0x22, '6': 0x23, '7': 0x24, '8': 0x25, '9': 0x26, '0': 0x27,
15+
' ': 0x2C, '-': 0x2D, '=': 0x2E, '[': 0x2F, ']': 0x30, '\\': 0x31, '#': 0x32, ';': 0x33, '\'': 0x34,
16+
'`': 0x35, ',': 0x36, '.': 0x37, '/': 0x38}
17+
18+
SHIFT_CHARS = {'A': 0x04, 'B': 0x05, 'C': 0x06, 'D': 0x07, 'E': 0x08, 'F': 0x09, 'G': 0x0A, 'H': 0x0B, 'I': 0x0C,
19+
'J': 0x0D, 'K': 0x0E, 'L': 0x0F, 'M': 0x10, 'N': 0x11, 'O': 0x12, 'P': 0x13, 'Q': 0x14, 'R': 0x15,
20+
'S': 0x16, 'T': 0x17, 'U': 0x18, 'V': 0x19, 'W': 0x1A, 'X': 0x1B, 'Y': 0x1C, 'Z': 0x1D, '!': 0x1E,
21+
'@': 0x1F, '#': 0x20, '$': 0x21, '%': 0x22, '^': 0x23, '&': 0x24, '*': 0x25, '(': 0x26, ')': 0x27,
22+
'_': 0x2D, '+': 0x2E, '{': 0x2F, '}': 0x30, '|': 0x31, '~': 0x32, ':': 0x33, '"': 0x34, '<': 0x36,
23+
'>': 0x37, '?': 0x38}
24+
25+
COMMAND_KEYS = {'ENTER': 0x28, 'ESCAPE': 0x29, 'BACKSPACE': 0x2A, 'TAB': 0x2B, 'SPACE': 0x2C, 'CAPS_LOCK': 0x39,
26+
'F1': 0x3A, 'F2': 0x3B, 'F3': 0x3C, 'F4': 0x3D, 'F5': 0x3E, 'F6': 0x3F, 'F7': 0x40, 'F8': 0x41,
27+
'F9': 0x42, 'F10': 0x43, 'F11': 0x44, 'F12': 0x45, 'PRINT': 0x46, 'SCROLL_LOCK': 0x47,
28+
'PAUSE': 0x48, 'INSERT': 0x49, 'HOME': 0x4A, 'PAGE_UP': 0x4B, 'DELETE': 0x4C, 'END': 0x4D,
29+
'PAGE_DOWN': 0x4E, 'RIGHT_ARROW': 0x4F, 'LEFT_ARROW': 0x50, 'DOWN_ARROW': 0x51, 'UP_ARROW': 0x52,
30+
'LEFT_CONTROL': 0xE0, 'LEFT_SHIFT': 0xE1, 'LEFT_ALT': 0xE2, 'LEFT_GUI': 0xE3, 'RIGHT_CONTROL': 0xE4,
31+
'RIGHT_SHIFT': 0xE5, 'RIGHT_ALT': 0xE6, 'RIGHT_GUI': 0xE7}
32+
33+
##################################################################
34+
# not all are in use yet (just prepared for further development) #
35+
##################################################################
36+
MODIFIER_KEY = {'LEFT_CONTROL': 0x01, 'LEFT_SHIFT': 0x02, 'LEFT_ALT': 0x04, 'LEFT_GUI': 0x08, 'RIGHT_CONTROL': 0x10,
37+
'RIGHT_SHIFT': 0x20, 'RIGHT_ALT': 0x40, 'RIGHT_GUI ': 0x80}
38+
39+
def __init__(self):
40+
"""
41+
Class constructor to assign user arguments
42+
and to set default variables
43+
"""
44+
self.__release_keys = bytearray(8)
45+
46+
# initialize logging
47+
self.__logger = logging.getLogger(__name__)
48+
49+
# set argument description/epilog
50+
description = 'OTG USB HID'
51+
epilog = 'The author of this code take no responsibility for your use or misuse'
52+
parser = argparse.ArgumentParser(description=description, epilog=epilog)
53+
54+
# set optional arguments
55+
parser.add_argument('-d', '--delay', help="default delay between all inputs, default is 0", default=0)
56+
parser.add_argument('-t', '--test', help='just debug input file ', default=False, action='store_true')
57+
parser.add_argument('-v', "--verbosity", help="increase output verbosity", action="count")
58+
59+
# set mandatory arguments
60+
parser.add_argument('bark', help='your bark script')
61+
62+
# read arguments by user
63+
args = parser.parse_args()
64+
65+
# set logging level
66+
if args.verbosity == 2:
67+
logging.basicConfig(level=logging.DEBUG)
68+
elif args.verbosity == 1:
69+
logging.basicConfig(level=logging.INFO)
70+
else:
71+
logging.basicConfig(level=logging.ERROR)
72+
73+
# set default delay
74+
if args.delay and float(args.delay) > 0:
75+
self.__DELAY = args.delay
76+
else:
77+
self.__DELAY = 0
78+
79+
# set debug mode
80+
if args.test:
81+
self.__DEBUG = True
82+
else:
83+
self.__DEBUG = False
84+
85+
# set filename
86+
if len(args.bark.strip()) == 0:
87+
print('You did not provide any bark script?')
88+
exit(1)
89+
else:
90+
self.__FILENAME = args.bark
91+
92+
self.__logger.debug("Delay: {:<5} Filename: {}".format(self.__DELAY, self.__FILENAME))
93+
94+
def read_file(self):
95+
"""
96+
Read file by line, provided by user as argument
97+
and call next method '__process_file_line'
98+
"""
99+
with open(self.__FILENAME, 'r') as file_handler:
100+
101+
for line in file_handler:
102+
line_string = line.rstrip("\n")
103+
104+
if not line_string.strip():
105+
continue
106+
else:
107+
self.__logger.debug("{}".format(line_string))
108+
self.__process_file_line(line_string)
109+
110+
def __process_file_line(self, line_string):
111+
"""
112+
Parse each line of the file
113+
and split into commands, delays or character
114+
115+
:param line_string: line of file
116+
:type line_string: str
117+
"""
118+
if not (line_string.startswith('#')):
119+
120+
if line_string.__contains__('[CMD]'):
121+
line = line_string.replace('[CMD]', '').strip()
122+
self.__process_command(line)
123+
elif line_string.__contains__('[DELAY]'):
124+
value = float(line_string.replace('[DELAY]', '').strip())
125+
self.__logger.debug("Sleep for {} seconds".format(value))
126+
sleep(value)
127+
else:
128+
list_string = list(line_string.strip())
129+
130+
for character in list_string:
131+
self.__process_character(character)
132+
133+
def __process_command(self, line):
134+
"""
135+
Check for non-modified or modified commands
136+
and convert commands to bytearray
137+
138+
:param line: commands to convert
139+
:type line: str
140+
"""
141+
# create empty report (8 bytes)
142+
report = bytearray(8)
143+
144+
# replace modifier
145+
# currently all modifiers are replaced by left-*
146+
line = sub('(.*)CONTROL', 'LEFT_CONTROL', line)
147+
line = sub('(.*)CTRL', 'LEFT_CONTROL', line)
148+
line = sub('(.*)SHIFT', 'LEFT_SHIFT', line)
149+
line = sub('(.*)ALT', 'LEFT_ALT', line)
150+
line = sub('(.*)GUI', 'LEFT_GUI', line)
151+
line = sub('(.*)WIN', 'LEFT_GUI', line)
152+
153+
# count words
154+
word_count = len(line.split())
155+
156+
#########################################################
157+
# @ToDo: check for a maximum of simultaneous keystrokes #
158+
#########################################################
159+
160+
# single command
161+
if word_count == 1 and line in self.COMMAND_KEYS:
162+
report[2] = self.COMMAND_KEYS[line]
163+
164+
if self.__DEBUG:
165+
print("CMD: {:<25} DEC: {:<7} {}".format(line, self.COMMAND_KEYS[line], report))
166+
else:
167+
self.write_report_to_dev(report)
168+
self.write_report_to_dev(self.__release_keys)
169+
170+
# double command (modifier)
171+
if word_count == 2:
172+
dec1 = dec2 = None
173+
word_list = ' '.join([line]).split()
174+
175+
if word_list[0] in self.MODIFIER_KEY:
176+
dec1 = self.MODIFIER_KEY[word_list[0]]
177+
report[0] = dec1
178+
179+
if word_list[1] in self.SIMPLE_CHARS:
180+
dec2 = self.SIMPLE_CHARS[word_list[1]]
181+
report[2] = dec2
182+
183+
if word_list[1] in self.COMMAND_KEYS:
184+
dec2 = self.COMMAND_KEYS[word_list[1]]
185+
report[2] = dec2
186+
187+
if self.__DEBUG:
188+
print("CMD: {:<12} {:<12} DEC: {:<3} {:<3} {}".format(word_list[0], word_list[1], dec1, dec2, report))
189+
else:
190+
self.write_report_to_dev(report)
191+
self.write_report_to_dev(self.__release_keys)
192+
193+
##########################################
194+
# @ToDo: triple and quadruple keystrokes #
195+
##########################################
196+
if word_count >= 3:
197+
pass
198+
199+
def __process_character(self, character):
200+
"""
201+
Check for non-shifted or shifted character
202+
and convert character to bytearray
203+
204+
:param character: character to convert
205+
:type character: str
206+
"""
207+
# create empty report (8 bytes)
208+
report = bytearray(8)
209+
210+
# default characters
211+
if character in self.SIMPLE_CHARS:
212+
report[2] = self.SIMPLE_CHARS[character]
213+
214+
if self.__DEBUG:
215+
print("CHR: {:<25} DEC: {:<7} {}".format(character, self.SIMPLE_CHARS[character], report))
216+
else:
217+
self.write_report_to_dev(report)
218+
self.write_report_to_dev(self.__release_keys)
219+
220+
# modified characters
221+
if character in self.SHIFT_CHARS:
222+
report[0] = self.MODIFIER_KEY['LEFT_SHIFT']
223+
report[2] = self.SHIFT_CHARS[character]
224+
225+
if self.__DEBUG:
226+
print("CHR: {:<25} DEC: {:<7} {}".format(character, self.SHIFT_CHARS[character], report))
227+
else:
228+
self.write_report_to_dev(report)
229+
self.write_report_to_dev(self.__release_keys)
230+
231+
@staticmethod
232+
def write_report_to_dev(report):
233+
"""
234+
Write report to /dev/hidg0 device
235+
236+
:param report: bytearray to type on keyboard
237+
:type report: bytearray
238+
"""
239+
with open('/dev/hidg0', 'rb+') as file_handler:
240+
file_handler.write(report)
241+
242+
243+
if __name__ == '__main__':
244+
keyboard = SerialKeyboard()
245+
keyboard.read_file()

‎README.md

+24
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,26 @@
11
# BullDog
2+
23
Python USB OTG HID (Keyboard)
4+
5+
> Inspired by RubberDucky, O.MG and other nice tools.
6+
7+
## Usage
8+
9+
```shell
10+
# simple barking
11+
$ ./BullDog.py woof.txt
12+
13+
# simple barking with default delay
14+
$ ./BullDog.py woof.txt -d 0.05
15+
```
16+
17+
the woof.txt (_text file_)...
18+
19+
```
20+
# This is my test script
21+
[CMD] GUI SPACE
22+
Terminal
23+
[CMD] ENTER
24+
who am i
25+
[CMD] ENTER
26+
```

‎woof.txt

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# This is my test script
2+
[CMD] GUI SPACE
3+
Terminal
4+
[CMD] GUI
5+
who am i
6+
[CMD] ENTER

0 commit comments

Comments
 (0)
Please sign in to comment.