├── .gitignore ├── LICENSE ├── README.md ├── createdizz.pl ├── dizzy.py ├── lib └── std_string_lib.txt ├── parse_lsusb.pl ├── tools.py ├── usb.py └── usb ├── Facedancer.py ├── MAXUSBApp.py ├── USB.py ├── USBClass.py ├── USBConfiguration.py ├── USBDevice.py ├── USBEndpoint.py ├── USBFtdi.py ├── USBInterface.py ├── USBKeyboard.py ├── USBMassStorage.py ├── USBSerial.py ├── USBVendor.py └── util.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | *.egg-info/ 23 | .installed.cfg 24 | *.egg 25 | 26 | # PyInstaller 27 | # Usually these files are written by a python script from a template 28 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 29 | *.manifest 30 | *.spec 31 | 32 | # Installer logs 33 | pip-log.txt 34 | pip-delete-this-directory.txt 35 | 36 | # Unit test / coverage reports 37 | htmlcov/ 38 | .tox/ 39 | .coverage 40 | .coverage.* 41 | .cache 42 | nosetests.xml 43 | coverage.xml 44 | *,cover 45 | 46 | # Translations 47 | *.mo 48 | *.pot 49 | 50 | # Django stuff: 51 | *.log 52 | 53 | # Sphinx documentation 54 | docs/_build/ 55 | 56 | # PyBuilder 57 | target/ 58 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, ERNW 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of dizzy nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Intoduction to *dizzy* 2 | ====================== 3 | 4 | Structure of *.dizz* files 5 | -------------------------- 6 | 7 | A single packet is described by a so called *.dizz* file. Some example files can be found in the [dizzfiles](https://github.com/ernw/dizzfiles) repository. These files are Python code, so they have to be written in proper Python syntax. They consist of three variables which need to be defined. The first variable is the **name** of 8 | the packet: 9 | 10 | ```python 11 | name = "test" 12 | ``` 13 | 14 | The second variable is called **objects** and describes the packet fields. It's a Python list with all the fields listed: 15 | 16 | ```python 17 | objects = [ 18 | ... 19 | ] 20 | ``` 21 | 22 | Inside of that list you can use some pre-defined functions which generate the actual data during parsing. the functions are called *field()*, *list()*, *rand()* and *link()*. they take different arguments, as listed below: 23 | 24 | * The *field()* function takes 4 arguments, which are: the **name** of the field [a string], the **length** of the field (in bits!) [an int] OR *None* for a field with variable length, the default **value** for that field [a string] and the fuzzing **mode** for that field [can be *none* for not fuzzing that field at all, *std* for fuzzing some values on the upper and lower value border, and *full* for fuzzing all possible values]. 25 | 26 | ```python 27 | objects = [ 28 | field("len", 8, "\x00", none), 29 | ... 30 | ] 31 | ``` 32 | 33 | * The *list()* function takes 3 arguments: the **name** of the field [a string], the default **value** of the field [a string] and the **path** to a file, containing possible values for that field (one value per line, all values will be inserted while fuzzing). 34 | 35 | ```python 36 | objects = [ 37 | field("len", 8, "\x00", none), 38 | list("test4", "happens?", "lib/test.txt"), 39 | ... 40 | ] 41 | ``` 42 | 43 | * The *rand()* function takes 2 arguments: the **name** of the field [a string] and the **length** of the field (in bits!) [an int]. The value of that field will be a new random on, each time a packet is generated. 44 | 45 | ```python 46 | objects = [ 47 | field("len", 8, "\x00", none), 48 | list("test4", "happens?", "lib/test.txt"), 49 | rand("random", 12), 50 | ... 51 | ] 52 | ``` 53 | 54 | * The *link()* function takes 2 arguments: the **name** of the field [a string] and the **name** of an other (previous defined) field. The value of that field will always be the same as the source field, also the length will always be the same. 55 | 56 | ```python 57 | objects = [ 58 | field("len", 8, "\x00", none), 59 | list("test4", "happens?", "lib/test.txt"), 60 | rand("random", 12), 61 | link("same_random", "random"), 62 | ... 63 | ] 64 | ``` 65 | 66 | The third variable is called **functions** and it is also a Python list. It represents a set of functions that are called against the generated raw packet, before it is sent out. Currently the functions *length()*, *lambda\_legth()*, *csum()*, *lambda\_csum()* and *lambda2_csum()* are available. 67 | 68 | * The *length()* function takes 3 argument: the name of the **destination** field, where the value should be updated with the calculated length [a string], the name of the **first** field, that should be in the length calculation (the starting point) [a string] and the name of the **last** field, that should be in the length calculation (the end point). 69 | 70 | ```python 71 | functions = [ 72 | length("len", "test4", "same_random"), 73 | ... 74 | ] 75 | ``` 76 | 77 | * The *lambda\_length()* function takes 4 arguments: the name of the **destination** field, where the value should be updated with the calculated length [a string], the name of the **first** field, that should be in the length calculation (the starting point) [a string], the name of the **last** field, that should be in the length calculation (the end point) and a function, which will be called after the length is calculated, with the length as an argument [int]. 78 | 79 | ```python 80 | functions = [ 81 | length("len", "align-mod", "value"), 82 | lambda_length("len2", "align-nomod", "align-mod", lambda x: x + 2), 83 | ... 84 | ] 85 | ``` 86 | 87 | * The *csum()* function takes 4 arguments: the name of the **destination** field, which value should be updated with the calculated checksum [a string], the name of the **first** field, that should be the input of the checksum calculation (the starting point) [a string], the name of the **last** field, that should be the input of the checksum calculation (the end point) [a string] and the name of the **checksum** [a string], were currently only 'inet' (rfc1071) is implemented. 88 | 89 | ```python 90 | functions = [ 91 | length("len", "align-mod", "value"), 92 | lambda_length("len2", "align-nomod", "align-mod", lambda x: x + 2), 93 | csum("csum", "align-mod", "value", "inet"), 94 | ... 95 | ] 96 | ``` 97 | 98 | *Note*: There are some weird looking *.dizz* files, which are auto-generated from an old dizzy version. They are working and will be replaced by more readable ones in the future. 99 | 100 | 101 | Structure of *.act* files 102 | ------------------------- 103 | 104 | Once you want to get stateful, you need to write interaction in *.act* files. These file are Python code as well. they also got 3 variables, **name** for the name of the interaction [a string], **objects** which is a Python list of dizzes (you can use a pre-defined function here as well) and **functions**, which also is a Python list. 105 | 106 | * The *dizz()* function takes 2 arguments: the **name** of the paket [a string] and the **path** of the *.dizz* file [a string]. These are the single packets of the interaction. 107 | 108 | ```python 109 | objects = [ 110 | dizz("test_dizz", "dizzes/test.dizz"), 111 | dizz("2nd step", "dizzes/example.dizz"), 112 | ... 113 | ] 114 | ``` 115 | 116 | There is a **functions** variable as well, which contains either *copy()* or *adv\_copy()* functions: 117 | 118 | * The *copy()* function takes 4 arguments: the **step** in which the function should be executed (1=on recv after the first packet [.dizz file], 2=on recv after the second, ...) [an int], the name of the **destination** field in the second dizz [a string] and two offsets, the **start** and the **end** point of the data that should be copied [ints]. These offsets are byte offsets inside of the received data (depending on the used session the received data starts at the ethernet dst [L2] or the tcp/udp/sctp payload [L4]). 119 | 120 | ```python 121 | functions = [ 122 | copy(1, "TEID", 4, 8), 123 | ... 124 | ] 125 | ``` 126 | 127 | * The *adv\_copy()* function takes 2 arguments: the **step** in which the function should be executed [int] and a function **reference**. The function given will be called with the received data and the dizz of the next step (this should not be used without deep knowledge of the dizzy code ;) 128 | -------------------------------------------------------------------------------- /createdizz.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | $debug=1; 4 | $myproto="icmpv6"; 5 | $tshark="tshark"; 6 | 7 | 8 | $_dizzfile="my.dizz"; 9 | $capturefile="/tmp/test2.pcap"; 10 | $filter="frame.number==1"; 11 | 12 | open(OUT,">$_dizzfile"); 13 | print OUT < createdizz.xml`; 46 | open(XML,"createdizz.xml"); 47 | while () 48 | { 49 | ($tag)=/^\s*<\/{0,1}(\w+)/; 50 | ($name)=/\sname="([\w\d\.]*)"/; 51 | $name="unknown" if length($name)<1; 52 | ($pos)=/\spos="(\d*)"/; 53 | ($value)=/\svalue="([\w\d]*)"/; 54 | print $_ if $debug; 55 | print "($tag,$name,$pos,$value)\n" if $debug; 56 | 57 | if (($tag eq "proto") && ($name eq $myproto)) 58 | { 59 | $start++; 60 | print "*****START*****\n" if $debug; 61 | next; 62 | } 63 | if (($start==1)&&($tag eq "proto")) 64 | { 65 | last; 66 | } 67 | 68 | if (($start==1)&&(defined $value)&&($pos!=$lpos)) 69 | { 70 | next if /">$/; 71 | push(@dizz_1,$name); 72 | push(@dizz_2,$value); 73 | print "PUSHED $name $value\n" if $debug; 74 | } 75 | 76 | $lpos=$pos; 77 | } 78 | close XML 79 | } 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /dizzy.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # dizzy.py 4 | # 5 | # Copyright 2011 Daniel Mende 6 | # 7 | 8 | # Redistribution and use in source and binary forms, with or without 9 | # modification, are permitted provided that the following conditions are 10 | # met: 11 | # 12 | # * Redistributions of source code must retain the above copyright 13 | # notice, this list of conditions and the following disclaimer. 14 | # * Redistributions in binary form must reproduce the above 15 | # copyright notice, this list of conditions and the following disclaimer 16 | # in the documentation and/or other materials provided with the 17 | # distribution. 18 | # * Neither the name of the nor the names of its 19 | # contributors may be used to endorse or promote products derived from 20 | # this software without specific prior written permission. 21 | # 22 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 23 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 24 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 25 | # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 26 | # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 27 | # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 28 | # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 29 | # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 30 | # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 31 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 32 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 33 | 34 | import binascii 35 | import copy 36 | import ctypes 37 | import fcntl 38 | import io 39 | import math 40 | from optparse import OptionParser 41 | import os 42 | import platform 43 | import pprint 44 | import random 45 | import select 46 | import ssl 47 | import socket 48 | import struct 49 | import subprocess 50 | import sys 51 | import time 52 | import traceback 53 | 54 | import tools 55 | 56 | if sys.version_info.major < 3: 57 | print("This script is intended for use with python >= 3!") 58 | sys.exit(1) 59 | 60 | VERSION = "0.8.3" 61 | PLATFORM = platform.system() 62 | DEBUG = False 63 | DEBUG2 = False 64 | DEBUG3 = False 65 | 66 | RANDOM_SEED="1l0v3D1zzYc4us31tsR4nd0m1sr3Pr0duc4bl3!" 67 | random.seed(RANDOM_SEED) 68 | #CODEC = "ISO-8859-1" 69 | CODEC = "utf-8" 70 | 71 | SCTP_STREAM = 1 72 | SCTP_PPID = 1 73 | SCTP_FLAGS = 0 #MSG_ADDR_OVER ? 74 | 75 | interaction_globals = {} 76 | 77 | class ifreq(ctypes.Structure): 78 | _fields_ = [("ifr_ifrn", ctypes.c_char * 16), 79 | ("ifr_flags", ctypes.c_short)] 80 | 81 | #rfc6458 => on linux only draft-ietf-tsvwg-sctpsocket-07.txt 82 | class sctp_sndrcvinfo(ctypes.Structure): 83 | _fields_ = [("sinfo_stream", ctypes.c_uint16), 84 | ("sinfo_ssn", ctypes.c_uint16), 85 | ("sinfo_flags", ctypes.c_uint16), 86 | ("sinfo_ppid", ctypes.c_uint32), 87 | ("sinfo_context", ctypes.c_uint32), 88 | ("sinfo_timetolive", ctypes.c_uint32), 89 | ("sinfo_tsn", ctypes.c_uint32), 90 | ("sinfo_cumtsn", ctypes.c_uint32), 91 | ("sinfo_assoc_id", ctypes.c_int)] 92 | 93 | try: 94 | import usb 95 | usb_present = True 96 | except Exception as e: 97 | print(e) 98 | usb_present = False 99 | print("No GoodFETMAXUSB libs found. USB support disabled!") 100 | 101 | class dizz_sessionException(Exception): 102 | pass 103 | 104 | class dizz_session(object): 105 | IFF_PROMISC = 0x100 106 | SIOCGIFFLAGS = 0x8913 107 | SIOCSIFFLAGS = 0x8914 108 | ETH_P_ALL = 0x0003 109 | SCTP_SNDRCV = 0x1 110 | SOL_SCTP = 0x84 111 | SCTP_DEFAULT_SEND_PARAM = 0xa 112 | 113 | def __init__(self, session_type, interface=None, dest=None, 114 | dport=None, src='', sport=None, timeout=1, recv_buffer=4096, 115 | filename=None, cmd=None, auto_reopen=True, client_cert=None, 116 | client_key=None, server_side=False, connect_retry=3): 117 | self.session_type = session_type 118 | self.timeout = timeout 119 | self.recv_buffer = recv_buffer 120 | self.is_open = False 121 | self.auto_reopen = auto_reopen 122 | self.server_side = server_side 123 | self.connect_retry = connect_retry 124 | if server_side: 125 | if self.session_type == "eth" or self.session_type == "stdout" or self.session_type == "cmd" or self.session_type == "file": 126 | raise dizz_sessionException("no server side support for session session_type '%s'" % self.session_type) 127 | if session_type == "eth": 128 | self.interface = interface 129 | return 130 | elif session_type == "udp": 131 | self.sock = socket.SOCK_DGRAM 132 | elif session_type == "tcp" or session_type == "tls": 133 | self.sock = socket.SOCK_STREAM 134 | elif session_type == "sctp": 135 | self.sock = socket.SOCK_SEQPACKET 136 | elif session_type == "stdout" or session_type == "stdout-hex": 137 | self.maxsize = None 138 | return 139 | elif session_type == "cmd": 140 | self.maxsize = None 141 | self.cmd = cmd 142 | return 143 | elif session_type == "file": 144 | self.filename = filename 145 | self.filecount = 0 146 | elif session_type == "usb-dscr": 147 | self.filename = filename 148 | self.fuzz_dscr = dest 149 | elif session_type == "usb-endp": 150 | self.filename = filename 151 | else: 152 | raise dizz_sessionException("unknown session_type: %s" % session_type) 153 | if session_type == "udp" or session_type == "tcp" or session_type == "tls" or session_type == "sctp": 154 | try: 155 | tmp = socket.inet_aton(dest) 156 | self.af = socket.AF_INET 157 | except Exception as e: 158 | try: 159 | tmp = socket.inet_pton(socket.AF_INET6, dest) 160 | self.af = socket.AF_INET6 161 | except Exception as f: 162 | raise dizz_sessionException("unknown address family: %s: %s, %s" % (dest, str(e), str(f))) 163 | if src != '': 164 | try: 165 | tmp = socket.inet_aton(src) 166 | except Exception as e: 167 | try: 168 | tmp = socket.inet_pton(socket.AF_INET6, src) 169 | except Exception as f: 170 | raise dizz_sessionException("unknown address family: %s: %s, %s" % (src, str(e), str(f))) 171 | else: 172 | if not self.af == socket.AF_INET6: 173 | raise dizz_sessionException("address family missmatch: %s - %s" % (dest, src)) 174 | else: 175 | if not self.af == socket.AF_INET: 176 | raise dizz_sessionException("address family missmatch: %s - %s" % (dest, src)) 177 | if session_type == "sctp": 178 | self.sndrcvinfo = sctp_sndrcvinfo() 179 | self.sndrcvinfo.sinfo_stream = SCTP_STREAM 180 | self.sndrcvinfo.sinfo_ppid = socket.htonl(SCTP_PPID) 181 | self.cs = None 182 | self.dest = dest 183 | self.src = src 184 | self.dport = dport 185 | self.sport = sport 186 | self.client_cert = client_cert 187 | self.client_key = client_key 188 | self.maxsize = 65534 189 | 190 | def open(self): 191 | try: 192 | if self.session_type == "eth": 193 | self.s = socket.socket(socket.PF_PACKET, socket.SOCK_RAW, self.ETH_P_ALL) 194 | #set interface 195 | self.s.bind((self.interface, self.ETH_P_ALL)) 196 | #enable promisc 197 | #windows: 198 | #self.s.ioctl(socket.SIO_RCVALL, socket.RCVALL_ON) 199 | #linux: 200 | self.ifr = ifreq() 201 | ifname = ctypes.create_string_buffer(self.interface.encode(CODEC)) 202 | self.ifr.ifr_ifrn = ifname.value 203 | fcntl.ioctl(self.s.fileno(), self.SIOCGIFFLAGS, self.ifr) # G for Get 204 | self.ifr.ifr_flags |= self.IFF_PROMISC 205 | fcntl.ioctl(self.s.fileno(), self.SIOCSIFFLAGS, self.ifr) # S for Set 206 | self.maxsize = 1500 207 | elif self.session_type == "file": 208 | filename = "%s-%i" % (self.filename, self.filecount) 209 | self.f = open(filename, 'w') 210 | self.filecount += 1 211 | elif self.session_type == "stdout" or self.session_type == "stdout-hex": 212 | self.f = sys.stdout.buffer 213 | elif self.session_type == "cmd": 214 | pass 215 | elif self.session_type == "usb-dscr": 216 | if not usb_present: 217 | raise dizz_sessionException("USB support disabled.") 218 | elif self.session_type == "usb-endp": 219 | if usb_present: 220 | self.u = usb.dizzyUSB(self.filename, self.timeout) 221 | self.u.open(self.dest) 222 | else: 223 | raise dizz_sessionException("USB support disabled.") 224 | else: 225 | self.s = socket.socket(self.af, self.sock) 226 | if self.dest == "255.255.255.255": 227 | self.s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) 228 | self.s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 229 | self.s.settimeout(self.timeout) 230 | sendbuf = self.s.getsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF) 231 | if sendbuf < self.maxsize: 232 | self.maxsize = sendbuf 233 | if self.session_type == "sctp": 234 | self.s.setsockopt(self.SOL_SCTP, self.SCTP_DEFAULT_SEND_PARAM, self.sndrcvinfo) 235 | if self.sport: 236 | self.s.bind((self.src, self.sport)) 237 | if self.session_type == "tls": 238 | self.s = ssl.SSLSocket(self.s, self.client_key, self.client_cert, ssl_version=3) 239 | if self.session_type == "tcp" or self.session_type == "tls": 240 | if self.server_side: 241 | self.s.listen(1) 242 | (self.cs, (rip, rport)) = self.s.accept() 243 | if self.dport: 244 | while self.dport != rport or self.src != rip: 245 | if DEBUG: 246 | if self.dport != rport: 247 | print("remote port %i not destination port %i" % (rport, self.dport)) 248 | if self.src != rip: 249 | print("remote ip %s not destination ip %i" % (rip, self.dst)) 250 | (self.cs, (sip, rport)) = self.s.accept() 251 | self.cs.settimeout(self.timeout) 252 | else: 253 | connected = False 254 | attempt = 1 255 | try: 256 | self.s.connect((self.dest, self.dport)) 257 | connected = True 258 | except (socket.timeout, ssl.SSLError): 259 | print("Connection attempt %d timed out." % (attempt)) 260 | while not connected and attempt <= self.connect_retry: 261 | try: 262 | (r, w, x) = select.select([], [self.s], [], self.timeout) 263 | if self.s in w: 264 | connected = True 265 | except: pass 266 | attempt += 1 267 | if not connected: 268 | raise dizz_sessionException("too much connection attempts") 269 | except Exception as e: 270 | if DEBUG: 271 | traceback.print_exc() 272 | raise dizz_sessionException("cant open session: %s" % str(e)) 273 | else: 274 | self.is_open = True 275 | 276 | def close(self): 277 | if self.session_type == "eth": 278 | self.ifr.ifr_flags &= ~self.IFF_PROMISC 279 | fcntl.ioctl(self.s.fileno(), self.SIOCSIFFLAGS, self.ifr) 280 | elif self.session_type == "file": 281 | self.f.close() 282 | elif self.session_type == "stdout" or self.session_type == "stdout-hex": 283 | pass 284 | elif self.session_type == "cmd": 285 | pass 286 | elif self.session_type == "usb-dscr": 287 | self.u.close() 288 | elif self.session_type == "usb-endp": 289 | self.u.close() 290 | else: 291 | self.s.close() 292 | self.s = None 293 | if self.cs: 294 | self.cs.close() 295 | self.cs = None 296 | self.is_open = False 297 | 298 | def send(self, data): 299 | try: 300 | if not self.maxsize is None and len(data) > self.maxsize: 301 | data = data[:self.maxsize-1] 302 | if DEBUG: 303 | print("Truncated data to %d byte." % self.maxsize) 304 | if self.session_type == "eth": 305 | self.s.send(data) 306 | elif self.session_type == "file": 307 | self.f.write(data) 308 | elif self.session_type == "stdout": 309 | self.f.write(data + b"\n") 310 | elif self.session_type == "stdout-hex": 311 | self.f.write(binascii.hexlify(data)) 312 | elif self.session_type == "cmd": 313 | try: 314 | subprocess.call("%s %s" % (self.cmd, binascii.hexlify(data).upper()), shell=True) 315 | except Exception as e: 316 | raise dizz_sessionException("error on executing %s: '%s'" % (self.cmd, str(e))) 317 | elif self.session_type == "usb-dscr": 318 | self.u = usb.dizzyUSB(self.filename, self.timeout, data=data, fuzz_dscr=self.fuzz_dscr) 319 | self.u.open() 320 | self.u.close() 321 | elif self.session_type == "usb-endp": 322 | if not self.u.opened: 323 | raise dizz_sessionException("usb connection closed...") 324 | try: 325 | self.u.write(data) 326 | except ValueError as e: 327 | raise dizz_sessionException("error sending to endpoint: %s" % str(e)) 328 | elif self.session_type == "tcp" or self.session_type == "tls": 329 | if self.server_side: 330 | if not self.cs: 331 | raise dizz_sessionException("no client connection, cant send") 332 | self.cs.send(data) 333 | else: 334 | self.s.send(data) 335 | #~ elif self.session_type == "sctp": 336 | #~ self.s.sendmsg([data], [(socket.IPPROTO_SCTP, self.SCTP_SNDRCV, self.sndrcvinfo)], 0, (self.dest, self.dport)) 337 | else: 338 | self.s.sendto(data, (self.dest, self.dport)) 339 | except Exception as e: 340 | if self.auto_reopen: 341 | if DEBUG: 342 | print("session got closed '%s', autoreopening..." % str(e)) 343 | traceback.print_exc() 344 | self.close() 345 | self.open() 346 | else: 347 | self.close() 348 | raise dizz_sessionException("error on sending '%s', connection closed." % str(e)) 349 | 350 | def recv(self): 351 | if self.session_type == "eth": 352 | return self.s.recv(2048) 353 | elif self.session_type == "file": 354 | return None 355 | elif self.session_type == "cmd": 356 | return None 357 | elif self.session_type == "usb": 358 | if not self.u.opened: 359 | raise dizz_sessionException("usb connection closed...") 360 | return self.u.read() 361 | elif self.session_type == "stdout" or self.session_type == "stdout-hex": 362 | line = sys.stdin.readline() 363 | if line == ".\n": 364 | return None 365 | else: 366 | return line 367 | else: 368 | if self.server_side: 369 | return self.cs.recv(self.recv_buffer) 370 | else: 371 | return self.s.recv(self.recv_buffer) 372 | 373 | class dizz_library(object): 374 | def __init__(self): 375 | self.lib = {} 376 | self.load_strings("lib/std_string_lib.txt") 377 | 378 | def get_next(self, obj): 379 | libidx = obj["length"] 380 | if libidx is None: 381 | if not obj["encoding"] is None: 382 | cur = obj["cur"].decode(obj["encoding"]) 383 | else: 384 | cur = obj["cur"].decode(CODEC) 385 | else: 386 | cur = obj["cur"] 387 | if obj["_type"] == "list": 388 | libidx = obj["listname"] 389 | if not libidx in self.lib: 390 | self.gen_entries(libidx) 391 | if cur not in self.lib[libidx]: 392 | if libidx == None: 393 | return self.lib[libidx][0] 394 | return None 395 | return self.lib[libidx][self.lib[libidx].index(cur) + 1] 396 | 397 | def gen_entries(self, length): 398 | bytelen = length // 8 399 | if length % 8 > 0: 400 | bytelen += 1 401 | if length >= 4: 402 | entr = [] 403 | entr += [tools.pack_with_length(0, length)] 404 | entr += [tools.pack_with_length(1, length)] 405 | entr += [tools.pack_with_length(2, length)] 406 | entr += [tools.pack_with_length(3, length)] 407 | entr += [tools.pack_with_length(4, length)] 408 | if length > 8: 409 | entr += [tools.pack_with_length(1, length, endian="<")] 410 | entr += [tools.pack_with_length(2, length, endian="<")] 411 | entr += [tools.pack_with_length(3, length, endian="<")] 412 | entr += [tools.pack_with_length(4, length, endian="<")] 413 | max = int(math.pow(2, length)) - 1 414 | if length > 8: 415 | entr += [tools.pack_with_length(max - 4, length, endian="<")] 416 | entr += [tools.pack_with_length(max - 3, length, endian="<")] 417 | entr += [tools.pack_with_length(max - 2, length, endian="<")] 418 | entr += [tools.pack_with_length(max - 1, length, endian="<")] 419 | entr += [tools.pack_with_length(max - 4, length)] 420 | entr += [tools.pack_with_length(max - 3, length)] 421 | entr += [tools.pack_with_length(max - 2, length)] 422 | entr += [tools.pack_with_length(max - 1, length)] 423 | entr += [tools.pack_with_length(max, length)] 424 | entr += [None] 425 | elif length == 3: 426 | entr = ["\x00", "\x01", "\x02", "\x03", "\x04", "\x05", "\x06", "\x07", None] 427 | elif length == 2: 428 | entr = ["\x00", "\x01", "\x02", "\x03", None] 429 | elif length == 1: 430 | entr = ["\x00", "\x01", None] 431 | self.lib[length] = tools.unique(entr) 432 | 433 | def load_strings(self, filename, listname=None, ascii=True): 434 | if listname in self.lib: 435 | return 436 | self.lib[listname] = [ "", None ] 437 | with open(filename, 'r') as f: 438 | for l in f: 439 | if l.rstrip('\n') in self.lib[listname]: 440 | pass 441 | if ascii: 442 | self.lib[listname].insert(-1, l.rstrip('\n')) 443 | else: 444 | self.lib[listname].insert(-1, bytes(l.rstrip('\n'), CODEC).decode("unicode_escape").encode(CODEC)) 445 | 446 | class dizz_parseException(Exception): 447 | pass 448 | 449 | class dizz_runtimeException(Exception): 450 | pass 451 | 452 | class dizz(object): 453 | def __init__(self, library=None): 454 | self.objects = [] 455 | self.obj_dict = {} 456 | self.functions = [] 457 | self.cur_obj = None 458 | self.last_obj = None 459 | self.null_obj = False 460 | if not library: 461 | library = dizz_library() 462 | self.library = library 463 | self.filename = None 464 | 465 | def update_obj_dict(self): 466 | self.obj_dict = {} 467 | for i in self.objects: 468 | self.obj_dict[i["_name"]] = i 469 | 470 | def load(self, filename): 471 | self.filename = filename 472 | ns = { "field" : self.basic_dizz, 473 | "list" : self.list_dizz, 474 | "rand" : self.rand_dizz, 475 | "link" : self.link_dizz, 476 | "fill" : self.fill_dizz, 477 | "padding" : self.padding_dizz, 478 | "grow" : self.grow_dizz, 479 | 480 | "none" : "none", 481 | "std" : "std", 482 | "full" : "full", 483 | 484 | "run_cmd" : self.run_cmd, 485 | "time" : self.basic_time, 486 | "time_no_fracs" : self.basic_time_no_fracs, 487 | "length" : self.basic_length, 488 | "ascii_length" : self.ascii_length, 489 | "lambda_length" : self.lambda_length, 490 | "csum" : self.basic_csum, 491 | "lambda_csum" : self.lambda_csum, 492 | "lambda2_csum" : self.lambda2_csum, 493 | } 494 | with open(filename) as f: 495 | exec(compile(f.read(), filename, 'exec'), ns) 496 | self.name = ns["name"] 497 | self.objects = ns["objects"] 498 | self.functions = ns["functions"] 499 | self.update_obj_dict() 500 | 501 | def save(self, filename): 502 | pp = pprint.PrettyPrinter(indent=4) 503 | with io.open(filename, 'w', encoding=CODEC) as f: 504 | f.write("#%s %s autogenerated\n" % (self.__class__.__name__, VERSION)) 505 | f.write("name = \"%s\"\n\n" % self.name) 506 | class ndict(dict): 507 | def __repr__(self): 508 | if self["_type"] == "basic": 509 | if self["length"] == None: 510 | return "field('%s', None, %s, '%s')" % (self["_name"], self["default"].encode("unicode_escape"), self["fuzz"]) 511 | else: 512 | return "field('%s', %d, %s, '%s')" % (self["_name"], self["length"], self["default"].encode("unicode_escape"), self["fuzz"]) 513 | elif self["_type"] == "list": 514 | return "list('%s', %s, '%s')" % (self["_name"], self["default"].encode("unicode_escape"), self["listname"]) 515 | elif self["_type"] == "rand": 516 | return "rand('%s', %d)" % (self["_name"], self["length"]) 517 | elif self["_type"] == "link": 518 | return "link('%s', '%s')" % (self["_name"], self["source"]) 519 | elif self["_type"] == "padding": 520 | return "padding('%s', '%s', '%s', %d, %s)" % (self["_name"], self["start"], self["end"], self["modulo"], self["default"].encode("unicode_escape")) 521 | tmpobj = [] 522 | for i in self.objects: 523 | n = ndict(i) 524 | tmpobj += [ n ] 525 | f.write("objects = %s\n\n" % pp.pformat(tmpobj)) 526 | class ndict(dict): 527 | def __repr__(self): 528 | if self["func"] == "length": 529 | return "length('%s', '%s', '%s')" % (self["dest"], self["start"], self["end"]) 530 | if self["func"] == "ascii_length": 531 | return "ascii_length('%s', '%s', '%s')" % (self["dest"], self["start"], self["end"]) 532 | elif self["func"] == "csum": 533 | return "csum('%s', '%s', '%s', '%s')" % (self["dest"], self["start"], self["end"], self["type"]) 534 | elif self["func"] == "time": 535 | if self["flavour"] == "no_fracs": 536 | return "time_no_fracs('%s', %d)" % (self["dest"], self["offset"]) 537 | else: 538 | return "time('%s', %d)" % (self["dest"], self["offset"]) 539 | tmpobj = [] 540 | for i in self.functions: 541 | n = ndict(i) 542 | tmpobj += [ n ] 543 | f.write("functions = %s\n" % pp.pformat(tmpobj)) 544 | 545 | def _alignmod(self, obj, mod): 546 | _DEBUG = DEBUG3 547 | out = b"" 548 | if _DEBUG: print("cur: %s" % binascii.hexlify(obj["cur"])) 549 | out += bytes([(obj["cur"][0] << 8 - mod) & 0xff]) 550 | if _DEBUG: print("out: %s" % binascii.hexlify(out)) 551 | for j in range(1, obj["bytelen"]): 552 | #~ if DEBUG: 553 | #~ print("out %s obj[\"cur\"] %s" % (type(out), type(obj["cur"]))) 554 | #~ print("out[-1:] %s %s" % (type(out[-1:]), out[-1:])) 555 | #~ print("obj[\"cur\"][j] %s %s" % (type(obj["cur"][j]), obj["cur"][j])) 556 | #~ print("out[-1:] %s out[-1] %s" % (out[-1:], out[-1])) 557 | if _DEBUG: print("j: %d" % j) 558 | tmp = out[-1] | (obj["cur"][j] >> mod) 559 | if _DEBUG: print("tmp: %x" % tmp) 560 | out = out[:-1] + bytes([tmp]) 561 | if _DEBUG: print("out: %s" % binascii.hexlify(out)) 562 | tmp2 = (obj["cur"][j] << 8 - mod) & 0xff 563 | if _DEBUG: print("tmp2: %x" % tmp2) 564 | out += bytes([tmp2]) 565 | if _DEBUG: print("out: %s" % binascii.hexlify(out)) 566 | return out 567 | 568 | def generate(self, offset=0, leading_data=b"\x00"): 569 | if len(self.objects) == 0: 570 | return b"" 571 | return self._get_obj_data(0, len(self.objects) - 1, offset, leading_data) 572 | 573 | def _nullobj(self, obj): 574 | if obj["fuzz"] == "none": 575 | return 576 | if obj["bytelen"]: 577 | obj["cur"] = bytes([ 0x00 for i in range(obj["bytelen"]) ]) 578 | else: 579 | obj["cur"] = b"" 580 | 581 | def _nextobj(self, reset=True, null=True): 582 | if reset: 583 | self.objects[self.cur_obj]["cur"] = self.objects[self.cur_obj]["default"] 584 | self.cur_obj += 1 585 | if self.cur_obj < len(self.objects): 586 | if null: 587 | self._nullobj(self.objects[self.cur_obj]) 588 | else: 589 | return False 590 | return True 591 | 592 | def _get_obj_length(self, start, end): 593 | if isinstance(start, int): 594 | start_index = start 595 | elif isinstance(start, str): 596 | start_index = self.objects.index(self.obj_dict[start]) 597 | else: 598 | raise dizz_runtimeException("start marker is not string, nor int, but '%s'!" % type(start)) 599 | if isinstance(end, int): 600 | end_index = end 601 | elif isinstance(end, str): 602 | end_index = self.objects.index(self.obj_dict[end]) 603 | else: 604 | raise dizz_runtimeException("end marker is not string, nor int, but '%s'!" % type(end)) 605 | length = 0 606 | for i in range(start_index, end_index + 1): 607 | if not self.objects[i]["length"] is None: 608 | length += self.objects[i]["length"] 609 | else: 610 | length += len(self.objects[i]["cur"]) * 8 611 | return length 612 | 613 | def _get_obj_data(self, start, end, offset=0, leading_data=b"\x00"): 614 | _DEBUG = DEBUG2 615 | if isinstance(start, int): 616 | start_index = start 617 | elif isinstance(start, str): 618 | start_index = self.objects.index(self.obj_dict[start]) 619 | else: 620 | raise dizz_runtimeException("start marker is not string, nor int, but '%s'!" % type(start)) 621 | if isinstance(end, int): 622 | end_index = end 623 | elif isinstance(end, str): 624 | end_index = self.objects.index(self.obj_dict[end]) 625 | else: 626 | raise dizz_runtimeException("end marker is not string, nor int, but '%s'!" % type(end)) 627 | if offset == 0: 628 | out = b"" 629 | else: 630 | out = leading_data 631 | for i in range(start_index, end_index + 1): 632 | i = self.objects[i] 633 | if DEBUG: 634 | print("name: " + i["_name"]) 635 | print("cur: " + str(type(i["cur"]))) 636 | if _DEBUG: print("offset: %d" % offset) 637 | if i["length"] is None: 638 | bytelen = len(i["cur"]) 639 | length = bytelen * 8 640 | else: 641 | length = i["length"] 642 | bytelen = i["bytelen"] 643 | modulo = length % 8 644 | if _DEBUG: print("modulo: %d" % modulo) 645 | if offset != 0: 646 | if modulo != 0: 647 | cur = self._alignmod(i, modulo) 648 | else: 649 | cur = i["cur"] 650 | if _DEBUG: print("cur: %s" % binascii.hexlify(cur)) 651 | for j in range(bytelen): 652 | if _DEBUG: print("j: %d" % j) 653 | tmp2 = bytes([out[-1] | (cur[j] >> 8 - offset)]) 654 | if _DEBUG: print("tmp2: %s" % tmp2) 655 | out = out[:-1] + tmp2 656 | if _DEBUG: print("out: %s" % out) 657 | tmp = bytes([(cur[j] << offset) & 0xff]) 658 | if _DEBUG: print("tmp: %s" % binascii.hexlify(tmp)) 659 | if j == bytelen - 1: 660 | if offset - modulo == 0: 661 | if _DEBUG: 662 | print("breaking byte aligned") 663 | break 664 | if modulo - offset > 0: 665 | if _DEBUG: 666 | print("breaking out of obj data") 667 | break 668 | out += tmp 669 | offset = abs(offset - modulo) 670 | else: 671 | if modulo != 0: 672 | out += self._alignmod(i, modulo) 673 | offset = 8 - modulo 674 | else: 675 | out += i["cur"] 676 | if _DEBUG: 677 | print(binascii.hexlify(out)) 678 | return out 679 | 680 | def _find_first_obj(self): 681 | self.cur_obj = 0 682 | while self.objects[self.cur_obj]["fuzz"] == "none": 683 | self.cur_obj += 1 684 | if self.cur_obj >= len(self.objects): 685 | self.cur_obj = None 686 | return False 687 | return True 688 | 689 | def get_current(self, recurse=False): 690 | if self.cur_obj is None: 691 | return None 692 | if self.last_obj is None and recurse: 693 | recurse = False 694 | obj = self.objects[self.cur_obj] 695 | if recurse: 696 | obj2 = self.objects[self.last_obj] 697 | return "%s-%s: %s-%s" % (obj["_name"], obj2["_name"], obj["cur"][:1024], obj2["cur"][:1024]) 698 | else: 699 | return "%s: %s" % (obj["_name"], obj["cur"][:1024]) 700 | 701 | def mutate(self, recurse): 702 | _DEBUG = DEBUG2 703 | done = False 704 | if len(self.objects) == 0: 705 | return False 706 | while not done: 707 | if _DEBUG: 708 | print("cur_obj: %s last_obj: %s null_obj: %s" % (self.cur_obj, self.last_obj, self.null_obj)) 709 | if self.cur_obj == None: 710 | if not self._find_first_obj(): 711 | return False 712 | #raise dizz_runtimeException("No mutable object found!") 713 | self._nullobj(self.objects[self.cur_obj]) 714 | break 715 | if self.cur_obj >= len(self.objects): 716 | if _DEBUG: print("EOMutate: %d %d" % (self.cur_obj, len(self.objects))) 717 | self.cur_obj = None 718 | return False 719 | else: 720 | if self.null_obj: 721 | while self.cur_obj != self.null_obj: 722 | if self.objects[self.cur_obj]["fuzz"] != "none": 723 | if _DEBUG: 724 | print("NULLing %i" % self.cur_obj) 725 | self._nullobj(self.objects[self.cur_obj]) 726 | self._nextobj(False, False) 727 | self._find_first_obj() 728 | self.null_obj = False 729 | obj = self.objects[self.cur_obj] 730 | if obj["fuzz"] == "none": 731 | if recurse: 732 | if self.cur_obj == self.last_obj or self.last_obj is None: 733 | if self._nextobj(False, True): 734 | self.last_obj = self.cur_obj 735 | if _DEBUG: print("setting last_obj to %s" % self.cur_obj) 736 | else: 737 | self._nextobj(False, False) 738 | else: 739 | self._nextobj() 740 | elif obj["fuzz"] == "full": 741 | if _DEBUG: 742 | print("%s: cur: %s, int(cur): %d, max: %d" % (obj["_name"], binascii.hexlify(obj["cur"]), int(binascii.hexlify(obj["cur"]), 16), int(math.pow(2, obj["length"])) - 1)) 743 | 744 | if int(binascii.hexlify(obj["cur"]), 16) >= math.pow(2, obj["length"]) - 1: 745 | if recurse: 746 | if self.cur_obj == self.last_obj or self.last_obj is None: 747 | if self._nextobj(False, True): 748 | self.last_obj = self.cur_obj 749 | if _DEBUG: print("setting last_obj to %s" % self.cur_obj) 750 | self.null_obj = self.cur_obj 751 | done = True 752 | else: 753 | self._nextobj(False, False) 754 | else: 755 | self._nextobj() 756 | done = True 757 | else: 758 | #obj["cur"] = pack_with_length(long(obj["cur"].encode("hex"), 16) + 1, obj["length"], obj["endian"]) 759 | obj["cur"] = tools.pack_with_length(int(binascii.hexlify(obj["cur"]), 16) + 1, obj["length"]) 760 | if recurse: 761 | self.null_obj = self.cur_obj 762 | done = True 763 | elif obj["fuzz"] == "std": 764 | nextval = self.library.get_next(obj) 765 | if _DEBUG: print("%s: len: %s cur: %s next: %s" % (obj["_name"], obj["length"], obj["cur"], nextval)) 766 | if nextval is None: 767 | if recurse: 768 | if self.cur_obj == self.last_obj or self.last_obj is None: 769 | if self._nextobj(False, True): 770 | if _DEBUG: print("setting last_obj to %s" % self.cur_obj) 771 | self.last_obj = self.cur_obj 772 | self.null_obj = self.cur_obj 773 | done = True 774 | else: 775 | self._nextobj(False, False) 776 | else: 777 | self._nextobj() 778 | done = True 779 | else: 780 | if obj["length"] is None: 781 | if not obj["encoding"] is None: 782 | nextval = nextval.encode(obj["encoding"]) 783 | else: 784 | nextval = nextval.encode(CODEC) 785 | #if obj["endian"] == "<": 786 | # next = pack_with_length(next, obj["length"], obj["endian"]) 787 | obj["cur"] = nextval 788 | if recurse: 789 | self.null_obj = self.cur_obj 790 | done = True 791 | else: 792 | raise dizz_runtimeException("unknown fuzzing type: %s" % obj["fuzz"]) 793 | 794 | if _DEBUG: 795 | print("cur_obj: %s last_obj: %s null_obj: %s" % (self.cur_obj, self.last_obj, self.null_obj)) 796 | if self.cur_obj >= len(self.objects): 797 | if _DEBUG: print("EOMutate: %d %d" % (self.cur_obj, len(self.objects))) 798 | self.cur_obj = None 799 | return False 800 | if recurse: 801 | self._find_first_obj() 802 | return True 803 | 804 | def operate(self): 805 | _DEBUG = DEBUG2 806 | for i in self.objects: 807 | if i["_type"] == "rand": 808 | new_rand = [ random.randint(0x00, 0xff) for j in range(i["bytelen"]) ] 809 | i["cur"] = bytes(new_rand) 810 | elif i["_type"] == "link": 811 | i["cur"] = self.obj_dict[i["source"]]["cur"] 812 | elif i["_type"] == "fill": 813 | if len(self.obj_dict[i["source"]]["cur"]) % i["fillto"] != 0: 814 | i["cur"] = i["fillwith"] * (i["fillto"] - (len(self.obj_dict[i["source"]]["cur"]) % i["fillto"])) 815 | elif i["_type"] == "padding": 816 | length = self._get_obj_length(i["start"], i["end"]) 817 | mod = length % i["modulo"] 818 | if mod != 0: 819 | i["length"] = i["modulo"] - mod 820 | i["bytelen"] = i["length"] // 8 821 | if i["length"] % 8 > 0: 822 | i["bytelen"] += 1 823 | i["cur"] = i["default"] * i["bytelen"] 824 | else: 825 | i["length"] = 0 826 | i["bytelen"] = 0 827 | i["cur"] = b"" 828 | elif i["_type"] == "grow": 829 | index = self.objects.index(self.obj_dict[i["_name"]]) 830 | if index == self.cur_obj: 831 | print("deb1") 832 | if i["length"] < i["maxlen"]: 833 | i["length"] = i["length"] + 1 834 | i["bytelen"] = i["length"] // 8 835 | if i["length"] % 8 > 0: 836 | i["bytelen"] += 1 837 | times = (i["bytelen"] - len(i["default"])) // len(i["fill"]) 838 | i["cur"] = i["default"] + i["fill"] * times 839 | else: 840 | i["length"] = i["orglen"] 841 | i["cur"] = i["default"] 842 | for i in self.functions: 843 | if i["func"] == "length": 844 | len_index = self.objects.index(self.obj_dict[i["dest"]]) 845 | if len_index != self.cur_obj and len_index != self.last_obj: 846 | start_index = self.objects.index(self.obj_dict[i["start"]]) 847 | end_index = self.objects.index(self.obj_dict[i["end"]]) 848 | length = 0 849 | for j in range(start_index, end_index + 1): 850 | if not self.objects[j]["length"] is None: 851 | length += self.objects[j]["length"] 852 | else: 853 | length += len(self.objects[j]["cur"]) * 8 854 | length = length // 8 855 | if "lambda" in i: 856 | length = i["lambda"](length) 857 | if i["flavour"] == "ascii": 858 | try: 859 | self.objects[len_index]["cur"] = bytes(str(length).encode(CODEC)) 860 | except: 861 | if DEBUG: 862 | print("Can't update ascii_length, zeroing...") 863 | self.objects[len_index]["cur"] = bytes("0".encode(CODEC)) 864 | else: 865 | try: 866 | self.objects[len_index]["cur"] = tools.pack_with_length(length, self.objects[len_index]["length"], i["endian"]) 867 | except: 868 | if DEBUG: 869 | print("Can't update length, maxing out...") 870 | self.objects[len_index]["cur"] = tools.pack_with_length(int(math.pow(2, self.objects[len_index]["length"])) - 1, self.objects[len_index]["length"]) 871 | if _DEBUG: 872 | print("LENGTH: dest: %s, start: %s, end: %s, len: %d" % (i["dest"], i["start"], i["end"], length)) 873 | elif i["func"] == "csum": 874 | sum_index = self.objects.index(self.obj_dict[i["dest"]]) 875 | if sum_index != self.cur_obj: 876 | self.objects[sum_index]["cur"] = self.objects[sum_index]["default"] 877 | inp = self._get_obj_data(i["start"], i["end"]) 878 | if "lambda_in" in i: 879 | inp = i["lambda_in"](self, inp) 880 | if i["type"] == "custom": 881 | output = i["callback"](inp) 882 | else: 883 | output = self.CHECKSUM[i["type"]]["call"](inp) 884 | if "lambda_out" in i: 885 | output = i["lambda_out"](self, output) 886 | self.objects[sum_index]["cur"] = output 887 | elif i["func"] == "time": 888 | time_index = self.objects.index(self.obj_dict[i["dest"]]) 889 | if time_index != self.cur_obj: 890 | now = time.time() + i["offset"] 891 | secs = int(now) 892 | if i["flavour"] == "no_fracs": 893 | self.objects[time_index]["cur"] = tools.pack_with_length(secs, 64) 894 | else: 895 | fracs = int((now - secs) * 65536) 896 | self.objects[time_index]["cur"] = tools.pack_with_length(secs, 48) + tools.pack_with_length(fracs, 18) 897 | elif i["func"] == "run_cmd": 898 | try: 899 | if DEBUG: 900 | print("running '%s'" % i["cmd"]) 901 | subprocess.call(i["cmd"], shell=True) 902 | except Exception as e: 903 | raise dizz_sessionException("error on executing %s: '%s'" % (self.cmd, str(e))) 904 | 905 | ######### OBJECTS ########## 906 | 907 | def basic_dizz(self, name, length, default, fuzz, endian='!', encoding=None): 908 | if not name: 909 | raise dizz_parseException("no name defined!") 910 | if not isinstance(length, int) and not length is None: 911 | raise dizz_parseException("length must be int or None") 912 | if isinstance(length, int) and length == 0: 913 | raise dizz_parseException("length 0 objects are not allowed!") 914 | if isinstance(length, int): 915 | bytelen = length // 8 916 | if length % 8 != 0: 917 | bytelen += 1 918 | if len(default) != bytelen: 919 | raise dizz_parseException("length of default value doesnt match length attribute: %s" % name) 920 | else: 921 | bytelen = None 922 | if length is None and fuzz == "full": 923 | raise dizz_parseException("cannot make dizz without length full fuzzable: %s" % name) 924 | if isinstance(length, int) and length > sys.maxsize and fuzz == "full": 925 | raise dizz_parseException("cannot make dizz with length '%d' full fuzzable, this would take ages: %s" % (length, name)) 926 | if name in self.obj_dict: 927 | raise dizz_parseException("dizz with name '%s' already exists!" % name) 928 | if endian != '<' and endian != '>' and endian != '!': 929 | raise dizz_parseException("invalid endianness '%s': %s" % (endian, name)) 930 | if endian != '!' and isinstance(length, int) and length % 16 > 0: 931 | raise dizz_parseException("endianness can only be set for fields with (len = 16 * n): %s" % name) 932 | if isinstance(default, str): 933 | cur = bytes(default.encode(CODEC)) 934 | else: 935 | cur = bytes(default) 936 | obj = { "_type" : "basic", 937 | "_name" : name, 938 | "length" : length, 939 | "default" : cur, 940 | "fuzz" : fuzz, 941 | "endian" : endian, 942 | "encoding" : encoding, 943 | 944 | "bytelen" : bytelen, 945 | "cur" : cur 946 | } 947 | self.obj_dict[name] = obj 948 | return obj 949 | 950 | def fill_dizz(self, name, source, fillto, fillwith): 951 | if source not in self.obj_dict: 952 | raise dizz_parseException("cannot find source dizz %s" % source) 953 | if not (self.obj_dict[source]["length"] is None): 954 | raise dizz_parseException("cannot create fill dizz for source with len!=None: %s" % name) 955 | dflt = '' 956 | if len(self.obj_dict[source]["cur"]) % fillto != 0: 957 | dflt = fillwith * (fillto - (len(self.obj_dict[source]["cur"]) % fillto)) 958 | obj = self.basic_dizz(name, None, dflt, "none") 959 | obj["_type"] = "fill" 960 | obj["source"] = source 961 | obj["fillto"] = fillto 962 | obj["fillwith"] = fillwith 963 | return obj 964 | 965 | def list_dizz(self, name, default, listname, ascii=True): 966 | obj = self.basic_dizz(name, None, default, "std") 967 | obj["_type"] = "list" 968 | obj["listname"] = listname 969 | self.library.load_strings(listname, listname, ascii=ascii) 970 | return obj 971 | 972 | def link_dizz(self, name, source): 973 | if source not in self.obj_dict: 974 | raise dizz_parseException("cannot find source dizz %s" % source) 975 | obj = self.basic_dizz(name, self.obj_dict[source]["length"], self.obj_dict[source]["default"], "none") 976 | obj["_type"] = "link" 977 | obj["source"] = source 978 | return obj 979 | 980 | def rand_dizz(self, name, length, encoding=None): 981 | if not length: 982 | raise dizz_parseException("cannot create random dizz without length: %s" % name) 983 | bytelen = length // 8 984 | if length % 8 != 0: 985 | bytelen += 1 986 | obj = self.basic_dizz(name, length, "\x00" * bytelen, "none", encoding=encoding) 987 | obj["_type"] = "rand" 988 | return obj 989 | 990 | def padding_dizz(self, name, start, end, modulo, value): 991 | if start not in self.obj_dict: 992 | raise dizz_parseException("start field '%s' unknown!" % start) 993 | if end not in self.obj_dict: 994 | raise dizz_parseException("end field '%s' unknown!" % end) 995 | obj = self.basic_dizz(name, None, value, "none") 996 | obj["_type"] = "padding" 997 | obj["start"] = start 998 | obj["end"] = end 999 | obj["modulo"] = modulo 1000 | return obj 1001 | 1002 | def grow_dizz(self, name, length, default, fuzz, fill, maxlen, endian='!', encoding=None): 1003 | obj = self.basic_dizz(name, length, default, fuzz, endian, encoding) 1004 | obj["_type"] = "grow" 1005 | obj["orglen"] = length 1006 | obj["fill"] = fill 1007 | obj["maxlen"] = maxlen 1008 | return obj 1009 | 1010 | ######### FUNCTIONS ########## 1011 | 1012 | def run_cmd(self, cmd): 1013 | return { "func" : "run_cmd", 1014 | "cmd" : cmd 1015 | } 1016 | 1017 | def basic_time(self, dest, offset=0): 1018 | if dest not in self.obj_dict: 1019 | raise dizz_parseException("destination field '%s' unknown!" % dest) 1020 | if self.obj_dict[dest]["length"] != 64: 1021 | raise dizz_parseException("destination of time '%s' got len != 64!" % dest) 1022 | if not isinstance(offset, int) and not isinstance(offset, float): 1023 | raise dizz_parseException("offset must be of type int or float!") 1024 | return { "func" : "time", 1025 | "dest" : dest, 1026 | "offset" : offset, 1027 | "flavour" : "default" 1028 | } 1029 | 1030 | def basic_time_no_fracs(self, dest, offset=0): 1031 | func = self.basic_time(dest, offset) 1032 | func["flavour"] = "no_fracs" 1033 | return func 1034 | 1035 | def basic_length(self, dest, start, end, endian="!"): 1036 | if dest not in self.obj_dict: 1037 | raise dizz_parseException("destination field '%s' unknown!" % dest) 1038 | if start not in self.obj_dict: 1039 | raise dizz_parseException("start field '%s' unknown!" % start) 1040 | if end not in self.obj_dict: 1041 | raise dizz_parseException("end field '%s' unknown!" % end) 1042 | if endian != '<' and endian != '>' and endian != '!': 1043 | raise dizz_parseException("invalid endianness '%s': %s" % endian) 1044 | return { "func" : "length", 1045 | "dest" : dest, 1046 | "start" : start, 1047 | "end" : end, 1048 | "endian" : endian, 1049 | "flavour" : "default" 1050 | } 1051 | 1052 | def ascii_length(self, dest, start, end, endian="!"): 1053 | func = self.basic_length(dest, start, end, endian) 1054 | func["flavour"] = "ascii" 1055 | return func 1056 | 1057 | def lambda_length(self, dest, start, end, lamb, endian="!"): 1058 | func = self.basic_length(dest, start, end, endian) 1059 | func["lambda"] = lamb 1060 | return func 1061 | 1062 | def basic_csum(self, dest, start, end, cstype, callback=None): 1063 | if dest not in self.obj_dict: 1064 | raise dizz_parseException("destination field '%s' unknown!" % dest) 1065 | if start not in self.obj_dict: 1066 | raise dizz_parseException("start field '%s' unknown!" % start) 1067 | if end not in self.obj_dict: 1068 | raise dizz_parseException("end field '%s' unknown!" % end) 1069 | if cstype == "custom": 1070 | if callback is None: 1071 | raise dizz_parseException("no callback for custom checksum defined!") 1072 | else: 1073 | if cstype not in self.CHECKSUM: 1074 | raise dizz_parseException("unknown checksum '%s'!" % cstype) 1075 | if self.CHECKSUM[cstype]["length"] != self.obj_dict[dest]["length"]: 1076 | raise dizz_parseException("length of destination field doesnt match checksum length: %i != %i" % (self.CHECKSUM[cstype]["length"], self.obj_dict[dest]["length"])) 1077 | return { "func" : "csum", 1078 | "dest" : dest, 1079 | "start" : start, 1080 | "end" : end, 1081 | "type" : cstype, 1082 | "callback" : callback, 1083 | } 1084 | 1085 | def lambda_csum(self, dest, start, end, cstype, lamb): 1086 | func = self.basic_csum(dest, start, end, cstype) 1087 | func["lambda_out"] = lamb 1088 | return func 1089 | 1090 | def lambda2_csum(self, dest, start, end, cstype, lambin, lambout): 1091 | func = self.basic_csum(dest, start, end, cstype) 1092 | func["lambda_in"] = lambin 1093 | func["lambda_out"] = lambout 1094 | return func 1095 | 1096 | def csum_inet(data, csum=0): 1097 | for i in range(0,len(data),2): 1098 | if i + 1 >= len(data): 1099 | csum += data[i] & 0xFF 1100 | else: 1101 | w = ((data[i] << 8) & 0xFF00) + (data[i+1] & 0xFF) 1102 | csum += w 1103 | while (csum >> 16) > 0: 1104 | csum = (csum & 0xFFFF) + (csum >> 16) 1105 | csum = ~csum 1106 | return struct.pack("!H", csum & 0xFFFF) 1107 | 1108 | CHECKSUM = { "inet" : { "length" : 16, 1109 | "call" : csum_inet 1110 | }, 1111 | "none" : { "length" : None, 1112 | "call" : lambda x: x 1113 | } 1114 | #"custom" is reserved 1115 | } 1116 | 1117 | class interact_parseException(Exception): 1118 | pass 1119 | 1120 | class interaction(object): 1121 | def __init__(self, library=None, cur_obj=0): 1122 | self.objects = [] 1123 | self.functions = [] 1124 | if not library: 1125 | library = dizz_library() 1126 | self.library = library 1127 | self.cur_obj = cur_obj 1128 | self.gen_obj = 0 1129 | 1130 | def load(self, filename): 1131 | self.filename = filename 1132 | global interaction_globals 1133 | ns = { "dizz" : self.dizz_obj, 1134 | "null_dizz" : self.null_dizz_obj, 1135 | "copy" : self.basic_copy, 1136 | "adv_copy" : self.adv_copy, 1137 | "print_dizz": self.print_dizz, 1138 | "print_field" : self.print_field, 1139 | "global" : interaction_globals, 1140 | } 1141 | with open(filename) as f: 1142 | exec(compile(f.read(), filename, 'exec'), ns) 1143 | self.name = ns["name"] 1144 | self.objects = ns["objects"] 1145 | self.functions = ns["functions"] 1146 | 1147 | def save(self, filename): 1148 | pp = pprint.PrettyPrinter(indent=4) 1149 | with open(filename, 'w') as f: 1150 | f.write("#%s %s autogenerated\n" % (self.__class__.__name__, VERSION)) 1151 | f.write("name = \"%s\"\n\n" % self.name) 1152 | class ndict(dict): 1153 | def __repr__(self): 1154 | if self["type"] == "null_dizz": 1155 | return "null_dizz()" 1156 | else: 1157 | return "dizz('%s', '%s')" % (self["name"], self["dizz"].filename) 1158 | tmpobj = [] 1159 | for i in self.objects: 1160 | n = ndict(i) 1161 | tmpobj += [ n ] 1162 | f.write("objects = %s\n\n" % pp.pformat(tmpobj)) 1163 | class ndict(dict): 1164 | def __repr__(self): 1165 | if self["func"] == "copy": 1166 | return "copy(%d, '%s', %d, %d)" % (self["step"], self["dest"], self["start"], self["end"]) 1167 | elif self["func"] == "adv_copy": 1168 | return "adv_copy(%d, '%s')" % (self["step"], self["callback"].__name__) 1169 | elif self["func"] == "print_dizz": 1170 | return "print_dizz(%d)" % self["step"] 1171 | elif self["func"] == "print_field": 1172 | return "print_field(%d, '%s')" % (self["step"], self["field"]) 1173 | tmpobj = [] 1174 | for i in self.functions: 1175 | n = ndict(i) 1176 | tmpobj += [ n ] 1177 | f.write("funktions = %s\n" % pp.pformat(tmpobj)) 1178 | 1179 | def dizz_obj(self, name, filename, readlen=None): 1180 | if not name: 1181 | raise interact_parseException("no name defined!") 1182 | 1183 | obj = {} 1184 | obj["type"] = "dizz" 1185 | obj["name"] = name 1186 | obj["dizz"] = dizz(self.library) 1187 | obj["dizz"].load(filename) 1188 | obj["dizz"].operate() 1189 | obj["readlen"] = readlen 1190 | return obj 1191 | 1192 | def null_dizz_obj(self, name, readlen=None): 1193 | if not name: 1194 | raise interact_parseException("no name defined!") 1195 | 1196 | obj = {} 1197 | obj["type"] = "null_dizz" 1198 | obj["name"] = name 1199 | obj["dizz"] = dizz(self.library) 1200 | obj["readlen"] = readlen 1201 | return obj 1202 | 1203 | def _next_obj(self): 1204 | if self.cur_obj + 1 < len(self.objects): 1205 | self.cur_obj += 1 1206 | return True 1207 | return False 1208 | 1209 | def get_current(self, recurse=False): 1210 | if self.cur_obj == self.gen_obj: 1211 | return "%s: %s" % (self.objects[self.gen_obj]["name"], self.objects[self.gen_obj]["dizz"].get_current(recurse)) 1212 | return None 1213 | 1214 | def generate(self, recurse, test=False): 1215 | _DEBUG = DEBUG2 1216 | ret = b"" 1217 | rlen = None 1218 | done = False 1219 | 1220 | if _DEBUG: 1221 | print("cur: %d\tgen: %d" % (self.cur_obj, self.gen_obj)) 1222 | 1223 | if self.gen_obj < self.cur_obj: 1224 | self.objects[self.gen_obj]["dizz"].operate() 1225 | ret = self.objects[self.gen_obj]["dizz"].generate() 1226 | rlen = self.objects[self.gen_obj]["readlen"] 1227 | self.gen_obj += 1 1228 | else: 1229 | if not test: 1230 | more = self.objects[self.cur_obj]["dizz"].mutate(recurse) 1231 | else: 1232 | more = False 1233 | if not more: 1234 | self.objects[self.cur_obj]["dizz"].operate() 1235 | ret = self.objects[self.cur_obj]["dizz"].generate() 1236 | rlen = self.objects[self.cur_obj]["readlen"] 1237 | if not self._next_obj(): 1238 | if _DEBUG: 1239 | print("EOInteract") 1240 | done = True 1241 | else: 1242 | self.gen_obj = 0 1243 | self.objects[self.cur_obj]["dizz"].operate() 1244 | ret = self.objects[self.cur_obj]["dizz"].generate() 1245 | rlen = self.objects[self.cur_obj]["readlen"] 1246 | return (ret, rlen, done) 1247 | 1248 | def operate(self, inp=None): 1249 | _DEBUG = DEBUG2 1250 | if _DEBUG: 1251 | print("in: %s" % binascii.hexlify(inp)) 1252 | print("cur: %d" % self.gen_obj) 1253 | if inp and inp != "": 1254 | for i in self.functions: 1255 | if i["step"] == self.gen_obj or i["step"] == -1: 1256 | if i["func"] == "copy": 1257 | self.objects[self.gen_obj]["dizz"].obj_dict[i["dest"]]["cur"] = inp[i["start"]:i["end"]] 1258 | elif i["func"] == "adv_copy": 1259 | i["callback"](self.objects[self.gen_obj], inp) 1260 | elif i["func"] == "print_dizz": 1261 | pp = pprint.PrettyPrinter() 1262 | pp.pprint(self.objects[self.gen_obj]["dizz"].objects) 1263 | elif i["func"] == "print_field": 1264 | if not i["field"] is None: 1265 | print(self.objects[self.gen_obj]["dizz"].obj_dict[i["field"]]["cur"]) 1266 | else: 1267 | obj = self.objects[self.gen_obj]["dizz"].cur_obj 1268 | if not obj is None: #ugly!!! 1269 | print(self.objects[self.gen_obj]["dizz"].objects[obj]["cur"]) 1270 | 1271 | def basic_copy(self, step, dest, start, end): 1272 | obj = { "func" : "copy", 1273 | "step" : step, 1274 | "dest" : dest, 1275 | "start" : start, 1276 | "end" : end, 1277 | "callback" : None, 1278 | } 1279 | return obj 1280 | 1281 | def adv_copy(self, step, call): 1282 | obj = { "func" : "adv_copy", 1283 | "step" : step, 1284 | "callback" : call, 1285 | } 1286 | return obj 1287 | 1288 | def print_dizz(self, step): 1289 | obj = { "func" : "print_dizz", 1290 | "step" : step 1291 | } 1292 | return obj 1293 | 1294 | def print_field(self, step, field): 1295 | obj = { "func" : "print_field", 1296 | "step" : step, 1297 | "field" : field 1298 | } 1299 | return obj 1300 | 1301 | def get_session(options): 1302 | if options.out_type == "eth": 1303 | if os.geteuid() != 0: 1304 | print("You must be root to send on eth.") 1305 | sys.exit(1) 1306 | if options.bind_addr: 1307 | parser.error("option -b only valid for udp/tcp/tls/sctp output") 1308 | if options.server: 1309 | parser.error("option -s only valid for udp/tcp/tls/sctp") 1310 | try: 1311 | s = dizz_session(options.out_type, interface=options.out_dest, timeout=options.wait_recv) 1312 | except Exception as e: 1313 | if DEBUG: 1314 | traceback.print_exc() 1315 | parser.error("invalid arguments %s:%s : %s" % (options.out_dest, options.out_extra, str(e))) 1316 | elif options.out_type == "file": 1317 | if options.bind_addr: 1318 | parser.error("option -b only valid for udp/tcp/tls/sctp output") 1319 | if options.server: 1320 | parser.error("option -s only valid for udp/tcp/tls/sctp") 1321 | try: 1322 | options.wait_send = 0 1323 | options.reconnect = True 1324 | s = dizz_session(options.out_type, filename=options.out_dest) 1325 | except Exception as e: 1326 | if DEBUG: 1327 | traceback.print_exc() 1328 | parser.error("invalid arguments %s : %s" % (options.out_dest, str(e))) 1329 | elif options.out_type == "stdout" or options.out_type == "stdout-hex": 1330 | if options.bind_addr: 1331 | parser.error("option -b only valid for udp/tcp/tls/sctp output") 1332 | if options.server: 1333 | parser.error("option -s only valid for udp/tcp/tls/sctp") 1334 | s = dizz_session(options.out_type) 1335 | elif options.out_type == "cmd": 1336 | if options.bind_addr: 1337 | parser.error("option -b only valid for udp/tcp/tls/sctp output") 1338 | if options.server: 1339 | parser.error("option -s only valid for udp/tcp/tls/sctp") 1340 | s = dizz_session(options.out_type, cmd=options.out_dest) 1341 | elif options.out_type == "usb-dscr": 1342 | if options.bind_addr: 1343 | parser.error("option -b only valid for udp/tcp/tls/sctp output") 1344 | if options.server: 1345 | parser.error("option -s only valid for udp/tcp/tls/sctp") 1346 | s = dizz_session(options.out_type, filename=options.out_dest, dest=options.out_extra, timeout=options.wait_recv) 1347 | elif options.out_type == "usb-endp": 1348 | if options.bind_addr: 1349 | parser.error("option -b only valid for udp/tcp/tls/sctp output") 1350 | if options.server: 1351 | parser.error("option -s only valid for udp/tcp/tls/sctp") 1352 | s = dizz_session(options.out_type, filename=options.out_dest, dest=options.out_extra, timeout=options.wait_recv) 1353 | else: 1354 | if not options.out_extra: 1355 | parser.error("no src/dst ports given. use -e") 1356 | if options.out_type == "tls": 1357 | if options.client_cert != None and options.client_key == None: 1358 | parser.error("no private key given") 1359 | if options.client_cert == None and options.client_key != None: 1360 | parser.error("no certificate given") 1361 | try: 1362 | if options.server and not options.bind_addr: 1363 | parser.error("no bind address given") 1364 | ports = options.out_extra.split(":") 1365 | if ports[0] == 'rand': 1366 | if options.server: 1367 | parser.error("cant listen on random port") 1368 | sport = None 1369 | else: 1370 | sport = int(ports[0]) 1371 | if ports[1] == 'rand': 1372 | if not options.server: 1373 | parser.error("cant send to random port") 1374 | dport = None 1375 | else: 1376 | dport = int(ports[1]) 1377 | s = dizz_session(options.out_type, dest=options.out_dest, src=options.bind_addr, sport=sport, dport=dport, timeout=options.wait_recv, client_cert=options.client_cert, client_key=options.client_key, server_side=options.server) 1378 | except Exception as e: 1379 | if DEBUG: 1380 | traceback.print_exc() 1381 | if options.bind_addr != '': 1382 | parser.error("invalid arguments %s, %s, %s : %s" % (options.out_dest, options.out_extra, options.bind_addr, str(e))) 1383 | else: 1384 | parser.error("invalid arguments %s, %s : %s" % (options.out_dest, options.out_extra, str(e))) 1385 | return s 1386 | 1387 | def read(session, options, rlen=None, interaction=None): 1388 | d = b"" 1389 | reconnect = False 1390 | try: 1391 | reading = True 1392 | while reading: 1393 | r = session.recv() 1394 | if not r: 1395 | reading = False 1396 | if DEBUG: 1397 | print("Read end on NONE") 1398 | else: 1399 | d += r 1400 | if options.verbose > 2: 1401 | outp = binascii.hexlify(d[:1024]) 1402 | if len(d) > 1024: 1403 | outp += b"..." 1404 | print("r: %s" % outp) 1405 | if options.verbose > 3: 1406 | outp = d[:1024] 1407 | if len(d) > 1024: 1408 | outp += b"..." 1409 | print("r: %s" % outp) 1410 | #print str_to_bin(d) 1411 | if not rlen is None: 1412 | if len(d) >= rlen: 1413 | reading = False 1414 | except (socket.timeout, ssl.SSLError): 1415 | if options.verbose > 2: 1416 | print("Read end on timeout") 1417 | pass 1418 | except socket.error as e: 1419 | if options.verbose > 2: 1420 | print("Read end on sock error '%s', reopening..." % str(e)) 1421 | if not interaction is None: 1422 | interaction.gen_obj = 0 1423 | reconnect = True 1424 | if options.exit: 1425 | sys.exit(1) 1426 | except Exception as e: 1427 | print("Cant read input: %s" % str(e)) 1428 | if options.verbose > 2: 1429 | traceback.print_exc() 1430 | sys.exit(1) 1431 | return (d, reconnect) 1432 | 1433 | if __name__ == '__main__': 1434 | parser = OptionParser(usage="usage: %s [options] {dizzfile | ackfile}" % os.path.basename(sys.argv[0]), version=VERSION) 1435 | parser.add_option("-v", help="Be verbose", dest="verbose", action="count", default=0) 1436 | parser.add_option("-t", help="Dont mutate, just send first package", dest="test", action="store_true", default=False) 1437 | parser.add_option("-o", help="Output type {eth, udp, tcp, tls, sctp, file, stdout, stdout-hex, cmd, usb-desc, usb-endp}", choices=["eth", "udp", "tcp", "tls", "sctp", "file", "stdout", "stdout-hex", "cmd", "usb-dscr", "usb-endp"], dest="out_type", default="stdout") 1438 | parser.add_option("-d", type="string", help="Output destination (interface for eth, dst-ip for udp/tcp/tls/sctp, filename for file, command for cmd, usb device descriptor file for usb-*)", dest="out_dest", default=None) 1439 | parser.add_option("-e", type="string", help="Output extra args (src:dst port for udp/tcp/sctp, src may be 'rand' for client side and dst may be 'rand' for server side, [DD|CD] for usb-dscr, EP nr. for usb-endp)", dest="out_extra", default=None) 1440 | parser.add_option("-b", type="string", help="Interface address to bind udp/tcp/tls/sctp socket to", dest="bind_addr", default='') 1441 | parser.add_option("-w", type="float", help="Time to wait between mutations (default 1)", metavar="SEC", dest="wait_send", default=1) 1442 | parser.add_option("-W", type="float", help="Time to wait on receive (default 10)", metavar="SEC", dest="wait_recv", default=10) 1443 | parser.add_option("-c", type="string", help="Certificate (PEM) for [d]tls client authentication", dest="client_cert", default=None) 1444 | parser.add_option("-k", type="string", help="Private key (PEM) for [d]tls client authentication", dest="client_key", default=None) 1445 | parser.add_option("-r", help="Reset connection after each mutation", dest="reconnect", action="store_true", default=False) 1446 | parser.add_option("-R", help="Use recursive mutation mode (a lot of mutations!)", dest="recurse", action="store_true", default=False) 1447 | parser.add_option("-s", help="Run in server side mode (accept connections)", dest="server", action="store_true", default=False) 1448 | parser.add_option("-S", type="float", help="Start at the given step", dest="start_at", default=0) 1449 | parser.add_option("-x", help="Exit on error", dest="exit", action="store_true", default=False) 1450 | parser.add_option("-a", help="Read targets answer when running in non-interactive mode", dest="answer", action="store_true", default=False) 1451 | parser.add_option("-q", help="Don't output any status messages", dest="quiet", action="store_true", default=False) 1452 | parser.add_option("-B", help="Perform baseline request matching in non-interactive mode (implies -a)", dest="baseline", action="store_true", default=False) 1453 | (options, args) = parser.parse_args() 1454 | if len(args) != 1: 1455 | parser.error("incorrect number of arguments") 1456 | 1457 | if options.baseline and not options.answer: 1458 | options.answer = True 1459 | 1460 | if not options.quiet: 1461 | print("Verbosity %d" % options.verbose) 1462 | 1463 | if options.verbose > 2: 1464 | DEBUG=True 1465 | if usb_present: 1466 | usb.DEBUG=True 1467 | if options.verbose > 3: 1468 | DEBUG2=True 1469 | if options.verbose > 4: 1470 | DEBUG3=True 1471 | 1472 | session = get_session(options) 1473 | 1474 | l = dizz_library() 1475 | dot = args[0].rfind(".") 1476 | try: 1477 | if args[0][dot:] == ".act": 1478 | i = interaction(l, int(options.start_at)) 1479 | try: 1480 | i.load(args[0]) 1481 | except Exception as e: 1482 | if DEBUG: 1483 | traceback.print_exc() 1484 | parser.error("invalid argument: %s: %s" % (args[0], str(e))) 1485 | session.open() 1486 | d = None 1487 | done = False 1488 | num = 1 1489 | nxt = 1 1490 | seq = 0 1491 | while not done: 1492 | in_sequence = True 1493 | while in_sequence and not done: 1494 | reconnect = False 1495 | (o, rlen, done) = i.generate(options.recurse, options.test) 1496 | if options.verbose > 0: 1497 | current = i.get_current(options.recurse) 1498 | if not current is None: 1499 | print(str(num) + ": " + current) 1500 | if options.verbose > 2: 1501 | outp = binascii.hexlify(o[:1024]) 1502 | if len(o) > 1024: 1503 | outp += b"..." 1504 | print("w: %s" % outp) 1505 | if options.verbose > 3: 1506 | outp = o[:1024] 1507 | if len(o) > 1024: 1508 | outp += b"..." 1509 | print("w: %s" % outp) 1510 | #print str_to_bin(o) 1511 | try: 1512 | session.send(o) 1513 | except Exception as e: 1514 | if not options.quiet: 1515 | print("Cant write output: %s" % str(e)) 1516 | if options.verbose > 2: 1517 | traceback.print_exc() 1518 | if options.exit: 1519 | sys.exit(1) 1520 | (d, reconnect) = read(session, options, rlen, i) 1521 | i.operate(d) 1522 | if options.reconnect or reconnect: 1523 | session.close() 1524 | session.open() 1525 | if i.gen_obj == 0: 1526 | in_sequence = False 1527 | if reconnect: 1528 | i.gen_obj = 0 1529 | if options.test: 1530 | break 1531 | if num >= nxt and options.verbose == 0: 1532 | if seq < 16: 1533 | seq = seq + 1 1534 | nxt = math.pow(2,seq) 1535 | else: 1536 | nxt = nxt + math.pow(2,seq) 1537 | print(num) 1538 | num = num + 1 1539 | time.sleep(options.wait_send) 1540 | else: 1541 | d = dizz(l) 1542 | try: 1543 | d.load(args[0]) 1544 | except Exception as e: 1545 | if options.verbose > 2: 1546 | traceback.print_exc() 1547 | parser.error("invalid argument: %s: %s" % (args[0], str(e))) 1548 | run = True 1549 | num = 1 1550 | nxt = 1 1551 | seq = 0 1552 | start = int(options.start_at) 1553 | baseline = b"" 1554 | session.open() 1555 | if options.baseline: 1556 | if options.verbose > 0: 1557 | print("Performing baseline request") 1558 | newd = copy.deepcopy(d) 1559 | newd.operate() 1560 | o = newd.generate() 1561 | session.send(o) 1562 | (baseline, _) = read(session, options) 1563 | if options.verbose > 1: 1564 | print("Received baseline answer of length %d" % len(baseline)) 1565 | if start > 0: 1566 | while start > 0 and run: 1567 | run = d.mutate(options.recurse) 1568 | start = start - 1 1569 | num = int(options.start_at) 1570 | if not run: 1571 | sys.exit(0) 1572 | while run: 1573 | d.operate() 1574 | o = d.generate() 1575 | if options.verbose > 0: 1576 | current = d.get_current(options.recurse) 1577 | if not current is None: 1578 | print(str(num) + ": " + current) 1579 | if options.verbose > 2: 1580 | print(binascii.hexlify(o[:1024])) 1581 | #print str_to_bin(o) 1582 | try: 1583 | session.send(o) 1584 | except Exception as e: 1585 | if not options.quiet: 1586 | print("Cant write output: %s" % str(e)) 1587 | if options.verbose > 2: 1588 | traceback.print_exc() 1589 | if options.exit: 1590 | sys.exit(1) 1591 | if options.answer: 1592 | (r, reconnect) = read(session, options) 1593 | if reconnect: 1594 | session.close() 1595 | session.open() 1596 | if options.baseline: 1597 | if r != baseline: 1598 | print("!!! Baseline missmatch !!!") 1599 | if options.verbose > 0 and options.verbose < 3: 1600 | if len(r) > 1024: 1601 | r = r[:1024] + b"..." 1602 | print("Received %s" % r) 1603 | if options.test: 1604 | break 1605 | run = d.mutate(options.recurse) 1606 | if num >= nxt and options.verbose == 0: 1607 | if seq < 16: 1608 | seq = seq + 1 1609 | nxt = math.pow(2,seq) 1610 | else: 1611 | nxt = nxt + math.pow(2,seq) 1612 | print(num) 1613 | num = num + 1 1614 | time.sleep(options.wait_send) 1615 | if options.reconnect: 1616 | session.close() 1617 | session.open() 1618 | except KeyboardInterrupt: 1619 | if session.is_open: 1620 | if not options.quiet: 1621 | print("closing session...") 1622 | session.close() 1623 | except Exception as e: 1624 | print(e) 1625 | if options.verbose > 1: 1626 | traceback.print_exc() 1627 | sys.exit(0) 1628 | -------------------------------------------------------------------------------- /parse_lsusb.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl -w 2 | # 3 | # parse_lsusb.pl 4 | # 5 | # Copyright 2013 Daniel "The Man" Mende, aka The Weazy Master of Mass Destruction (sorry, my girlfriend was here) 6 | # 7 | # This program is free software; you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation; either version 2 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program; if not, write to the Free Software 19 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 20 | # MA 02110-1301, USA. 21 | 22 | 23 | # use strictness 24 | use strict; 25 | # print non-fatal warnings 26 | use warnings; 27 | use Encode qw/encode/; 28 | 29 | binmode STDOUT, ":raw"; 30 | 31 | my $device; 32 | my %dd; #device_descriptor 33 | my %cd; #configuration_descriptor 34 | my %id; #interface descriptor 35 | my %ed; #endpoint descriptor 36 | 37 | my $in_devicedescr = 0; 38 | my $in_configurationdescr = 0; 39 | my $in_interfacedescr = 0; 40 | my $in_endpointdescr = 0; 41 | 42 | sub push_dd { 43 | print STDOUT << "EOF"; 44 | DD={ "bLength" : $dd{"bLength"}, 45 | "bDescriptorType" : $dd{"bDescriptorType"}, 46 | "bcdUSB" : "$dd{"bcdUSB"}", 47 | "bDeviceClass" : $dd{"bDeviceClass"}, 48 | "bDeviceSubClass" : $dd{"bDeviceSubClass"}, 49 | "bDeviceProtocol" : $dd{"bDeviceProtocol"}, 50 | "bMaxPacketSize" : $dd{"bMaxPacketSize0"}, 51 | "idVendor" : $dd{"idVendor"}, 52 | "idProduct" : $dd{"idProduct"}, 53 | "bcdDevice" : "$dd{"bcdDevice"}", 54 | "iManufacturer" : $dd{"iManufacturer"}, 55 | "iManufacturer_str" : "$dd{"iManufacturer_str"}", 56 | "iProduct" : $dd{"iProduct"}, 57 | "iProduct_str" : "$dd{"iProduct_str"}", 58 | "iSerial" : $dd{"iSerial"}, 59 | "iSerial_str" : "$dd{"iSerial_str"}", 60 | "bNumConfigurations" : $dd{"bNumConfigurations"}, 61 | "CD" : [ 62 | EOF 63 | } 64 | 65 | sub end_dd { 66 | print STDOUT << "EOF"; 67 | ] #end-CD 68 | } 69 | EOF 70 | } 71 | 72 | sub push_cd { 73 | print STDOUT << "EOF"; 74 | { "bLength" : $cd{"bLength"}, 75 | "bDescriptorType" : $cd{"bDescriptorType"}, 76 | "wTotalLength" : $cd{"wTotalLength"}, 77 | "bNumInterfaces" : $cd{"bNumInterfaces"}, 78 | "bConfigurationValue" : $cd{"bConfigurationValue"}, 79 | "iConfiguration" : $cd{"iConfiguration"}, 80 | "iConfiguration_str" : "$cd{"iConfiguration_str"}", 81 | "bmAttributes" : $cd{"bmAttributes"}, 82 | "MaxPower" : $cd{"MaxPower"}, 83 | "ID" : [ 84 | EOF 85 | } 86 | 87 | sub end_cd { 88 | print STDOUT << "EOF"; 89 | ] #end-ID 90 | }, 91 | EOF 92 | } 93 | 94 | sub push_id { 95 | print STDOUT << "EOF"; 96 | { "bLength" : $id{"bLength"}, 97 | "bDescriptorType" : $id{"bDescriptorType"}, 98 | "bInterfaceNumber" : $id{"bInterfaceNumber"}, 99 | "bAlternateSetting" : $id{"bAlternateSetting"}, 100 | "bNumEndpoints" : $id{"bNumEndpoints"}, 101 | "bInterfaceClass" : $id{"bInterfaceClass"}, 102 | "bInterfaceSubClass": $id{"bInterfaceSubClass"}, 103 | "bInterfaceProtocol": $id{"bInterfaceProtocol"}, 104 | "iInterface" : $id{"iInterface"}, 105 | "iInterface_str" : "$id{"iInterface_str"}", 106 | "EP" : [ 107 | EOF 108 | } 109 | 110 | sub end_id { 111 | print STDOUT << "EOF"; 112 | ] #end-EP 113 | }, 114 | EOF 115 | } 116 | 117 | sub push_ed { 118 | print STDOUT << "EOF"; 119 | { "bLength" : $ed{"bLength"}, 120 | "bDescriptorType" : $ed{"bDescriptorType"}, 121 | "bEndpointAddress" : $ed{"bEndpointAddress"}, 122 | "bmAttributes" : $ed{"bmAttributes"}, 123 | "wMaxPacketSize" : $ed{"wMaxPacketSize"}, 124 | "bInterval" : $ed{"bInterval"}, 125 | }, 126 | EOF 127 | } 128 | 129 | while () { 130 | 131 | next if ($_ =~ m/^$/); #skip empty lines 132 | next if ($_ =~ m/^\s*#/); #skip comments 133 | 134 | if ($_ =~ m/^Bus\s+(\d+)\s+Device\s+(\d+):\s+ID\s+([0-9a-fA-F]+):([0-9a-fA-F]+)\s+/) { 135 | #~ print "Device found:\n"; 136 | #~ print $1.":".$2."\t"."ID: ".$3.":".$4."\n"; 137 | $device = $3.":".$4; 138 | next; 139 | } 140 | if ($_ =~ m/^Device Descriptor:/) { 141 | #print "Creating new Device Descriptor\n"; 142 | if ($in_configurationdescr == 1) { 143 | push_cd(); 144 | end_cd(); 145 | end_dd(); 146 | $in_configurationdescr = 0; 147 | } elsif ($in_interfacedescr == 1) { 148 | push_id(); 149 | end_id(); 150 | end_cd(); 151 | end_dd(); 152 | $in_interfacedescr = 0; 153 | } elsif ($in_endpointdescr == 1) { 154 | push_ed(); 155 | end_id(); 156 | end_cd(); 157 | end_dd(); 158 | $in_endpointdescr = 0; 159 | } 160 | %dd = qw(); 161 | $in_devicedescr = 1; 162 | next; 163 | } 164 | if ($_ =~ m/\s+bLength\s+(\d+)/) { 165 | #print "bLength: ".$1."\n"; 166 | $dd{"bLength"} = $1 if $in_devicedescr; 167 | $cd{"bLength"} = $1 if $in_configurationdescr; 168 | $id{"bLength"} = $1 if $in_interfacedescr; 169 | $ed{"bLength"} = $1 if $in_endpointdescr; 170 | next; 171 | } 172 | if ($_ =~ m/\s+bDescriptorType\s+(\d+)/) { 173 | #print "bDescriptorType: ".$1."\n"; 174 | $dd{"bDescriptorType"} = $1 if $in_devicedescr; 175 | $cd{"bDescriptorType"} = $1 if $in_configurationdescr; 176 | $id{"bDescriptorType"} = $1 if $in_interfacedescr; 177 | $ed{"bDescriptorType"} = $1 if $in_endpointdescr; 178 | next; 179 | } 180 | 181 | 182 | if ($_ =~ m/\s+bcdUSB\s+(\d+\.\d+)/) { 183 | #print "bcdUSB: ".$1."\n"; 184 | $dd{"bcdUSB"} = $1 if $in_devicedescr; 185 | next; 186 | } 187 | if ($_ =~ m/\s+bDeviceClass\s+(\d+)/) { 188 | #print "bDeviceClass: ".$1."\n"; 189 | $dd{"bDeviceClass"} = $1 if $in_devicedescr; 190 | next; 191 | } 192 | if ($_ =~ m/\s+bDeviceSubClass\s+(\d+)/) { 193 | #print "bDeviceSubClass: ".$1."\n"; 194 | $dd{"bDeviceSubClass"} = $1 if $in_devicedescr; 195 | next; 196 | } 197 | if ($_ =~ m/\s+bDeviceProtocol\s+(\d+)/) { 198 | #print "bDeviceProtocol: ".$1."\n"; 199 | $dd{"bDeviceProtocol"} = $1 if $in_devicedescr; 200 | next; 201 | } 202 | if ($_ =~ m/\s+bMaxPacketSize0\s+(\d+)/) { 203 | #print "bMaxPacketSize0: ".$1."\n"; 204 | $dd{"bMaxPacketSize0"} = $1 if $in_devicedescr; 205 | next; 206 | } 207 | if ($_ =~ m/\s+idVendor\s+(0x[0-9a-fA-F]+)/) { 208 | #print "idVendor: ".$1."\n"; 209 | $dd{"idVendor"} = $1 if $in_devicedescr; 210 | next; 211 | } 212 | if ($_ =~ m/\s+idProduct\s+(0x[0-9a-fA-F]+)/) { 213 | #print "idProduct: ".$1."\n"; 214 | $dd{"idProduct"} = $1 if $in_devicedescr; 215 | next; 216 | } 217 | if ($_ =~ m/\s+bcdDevice\s+(\d+\.\d+)/) { 218 | #print "bcdDevice: ".$1."\n"; 219 | $dd{"bcdDevice"} = $1 if $in_devicedescr; 220 | next; 221 | } 222 | if ($_ =~ m/\s+iManufacturer\s+(\d+)/) { 223 | #print "iManufacturer: ".$1."\n"; 224 | $dd{"iManufacturer"} = $1 if $in_devicedescr; 225 | if ($_ =~ m/\s+iManufacturer\s+(\d+)\s(.+)$/) { 226 | $dd{"iManufacturer_str" } = $2; 227 | } else { 228 | $dd{"iManufacturer_str" } = ""; 229 | } 230 | next; 231 | } 232 | if ($_ =~ m/\s+iProduct\s+(\d+)/) { 233 | #print "iProduct: ".$1."\n"; 234 | $dd{"iProduct"} = $1 if $in_devicedescr; 235 | if ($_ =~ m/\s+iProduct\s+(\d+)\s(.+)$/) { 236 | $dd{"iProduct_str" } = $2; 237 | } else { 238 | $dd{"iProduct_str" } = ""; 239 | } 240 | next; 241 | } 242 | if ($_ =~ m/\s+iSerial\s+(\d+)/) { 243 | #print "iSerial: ".$1."\n"; 244 | $dd{"iSerial"} = $1 if $in_devicedescr; 245 | if ($_ =~ m/\s+iSerial\s+(\d+)\s(.+)$/) { 246 | $dd{"iSerial_str" } = $2; 247 | } else { 248 | $dd{"iSerial_str" } = ""; 249 | } 250 | next; 251 | } 252 | if ($_ =~ m/\s+bNumConfigurations\s+(\d+)/) { 253 | #print "bNumConfigurations: ".$1."\n"; 254 | $dd{"bNumConfigurations"} = $1 if $in_devicedescr; 255 | next; 256 | } 257 | 258 | 259 | ################# 260 | if ($_ =~ m/\s+Configuration Descriptor:/) { 261 | #print "Creating new Configuration Descriptor\n"; 262 | if ($in_devicedescr == 1) { 263 | push_dd(); 264 | $in_devicedescr = 0; 265 | } 266 | if ($in_configurationdescr == 1) { 267 | end_cd(); 268 | $in_configurationdescr = 0; 269 | } elsif ($in_interfacedescr == 1) { 270 | push_id(); 271 | end_id(); 272 | end_cd(); 273 | $in_interfacedescr = 0 274 | } elsif ($in_endpointdescr == 1) { 275 | push_ed(); 276 | end_id(); 277 | end_cd(); 278 | $in_endpointdescr = 0; 279 | } 280 | %cd = qw(); 281 | $in_configurationdescr = 1; 282 | next; 283 | } 284 | if ($_ =~ m/\s+wTotalLength\s+(\d+)/) { 285 | #print "wTotalLength: ".$1."\n"; 286 | $cd{"wTotalLength"} = $1 if $in_configurationdescr; 287 | next; 288 | } 289 | if ($_ =~ m/\s+bNumInterfaces\s+(\d+)/) { 290 | #print "bNumInterfaces: ".$1."\n"; 291 | $cd{"bNumInterfaces"} = $1 if $in_configurationdescr; 292 | next; 293 | } 294 | if ($_ =~ m/\s+bConfigurationValue\s+(\d+)/) { 295 | #print "bConfigurationValue: ".$1."\n"; 296 | $cd{"bConfigurationValue"} = $1 if $in_configurationdescr; 297 | next; 298 | } 299 | if ($_ =~ m/\s+iConfiguration\s+(\d+)/) { 300 | #print "iConfiguration: ".$1."\n"; 301 | $cd{"iConfiguration"} = $1 if $in_configurationdescr; 302 | if ($_ =~ m/\s+iConfiguration\s+(\d+)\s(.+)$/) { 303 | $cd{"iConfiguration_str" } = $2; 304 | } else { 305 | $cd{"iConfiguration_str" } = ""; 306 | } 307 | next; 308 | } 309 | if ($_ =~ m/\s+bmAttributes\s+(0x[0-9a-fA-F]+)/) { 310 | #print "bmAttributes: ".$1."\n"; 311 | $cd{"bmAttributes"} = $1 if $in_configurationdescr; 312 | next; 313 | } 314 | if ($_ =~ m/\s+MaxPower\s+(\d+)mA/) { 315 | #print "MaxPower: ".$1."\n"; 316 | $cd{"MaxPower"} = $1 / 2 if $in_configurationdescr; 317 | next; 318 | } 319 | 320 | ################# 321 | if ($_ =~ m/^\s+Interface Descriptor:/) { 322 | #print "Creating new Interface Descriptor\n"; 323 | #print "cd: ".$in_configurationdescr." id: ".$in_interfacedescr."ed: ".$in_endpointdescr."\n"; 324 | if ($in_configurationdescr == 1) { 325 | push_cd(); 326 | $in_configurationdescr = 0; 327 | } elsif ($in_interfacedescr == 1) { 328 | push_id(); 329 | end_id(); 330 | $in_interfacedescr = 0; 331 | } elsif ($in_endpointdescr == 1) { 332 | push_ed(); 333 | end_id(); 334 | $in_endpointdescr = 0; 335 | } 336 | %id = qw(); 337 | $in_interfacedescr = 1; 338 | next; 339 | } 340 | 341 | if ($_ =~ m/\s+bInterfaceNumber\s+(\d+)/) { 342 | #print "bInterfaceNumber: ".$1."\n"; 343 | $id{"bInterfaceNumber"} = $1 if $in_interfacedescr; 344 | next; 345 | } 346 | if ($_ =~ m/\s+bInterfaceNumber\s+(\d+)/) { 347 | #print "bInterfaceNumber: ".$1."\n"; 348 | $id{"bInterfaceNumber"} = $1 if $in_interfacedescr; 349 | next; 350 | } 351 | if ($_ =~ m/\s+bAlternateSetting\s+(\d+)/) { 352 | #print "bAlternateSetting: ".$1."\n"; 353 | $id{"bAlternateSetting"} = $1 if $in_interfacedescr; 354 | next; 355 | } 356 | if ($_ =~ m/\s+bNumEndpoints\s+(\d+)/) { 357 | #print "bNumEndpoints: ".$1."\n"; 358 | $id{"bNumEndpoints"} = $1 if $in_interfacedescr; 359 | next; 360 | } 361 | if ($_ =~ m/\s+bInterfaceClass\s+(\d+)/) { 362 | #print "bInterfaceClass: ".$1."\n"; 363 | $id{"bInterfaceClass"} = $1 if $in_interfacedescr; 364 | next; 365 | } 366 | if ($_ =~ m/\s+bInterfaceSubClass\s+(\d+)/) { 367 | #print "bInterfaceSubClass: ".$1."\n"; 368 | $id{"bInterfaceSubClass"} = $1 if $in_interfacedescr; 369 | next; 370 | } 371 | if ($_ =~ m/\s+bInterfaceProtocol\s+(\d+)/) { 372 | #print "bInterfaceProtocol: ".$1."\n"; 373 | $id{"bInterfaceProtocol"} = $1 if $in_interfacedescr; 374 | next; 375 | } 376 | if ($_ =~ m/\s+iInterface\s+(\d+)/) { 377 | #print "iInterface: ".$1."\n"; 378 | $id{"iInterface"} = $1 if $in_interfacedescr; 379 | if ($_ =~ m/\s+iInterface\s+(\d+)\s(.+)$/) { 380 | $id{"iInterface_str" } = $2; 381 | } else { 382 | $id{"iInterface_str" } = ""; 383 | } 384 | next; 385 | } 386 | 387 | 388 | 389 | ################# 390 | if ($_ =~ m/\s+Endpoint Descriptor:/) { 391 | #print "Creating new Endpoint Descriptor\n"; 392 | if ($in_interfacedescr == 1) { 393 | push_id(); 394 | $in_interfacedescr = 0; 395 | } elsif ($in_endpointdescr == 1) { 396 | push_ed(); 397 | } 398 | %ed = qw(); 399 | $in_endpointdescr = 1; 400 | next; 401 | } 402 | if ($_ =~ m/\s+bEndpointAddress\s+(0x[0-9a-fA-F]+)/) { 403 | #print "bEndpointAddress: ".$1."\n"; 404 | $ed{"bEndpointAddress"} = $1 if $in_endpointdescr; 405 | next; 406 | } 407 | if ($_ =~ m/\s+bmAttributes\s+(\d+)/) { 408 | #print "bmAttributes: ".$1."\n"; 409 | $ed{"bmAttributes"} = $1 if $in_endpointdescr; 410 | next; 411 | } 412 | if ($_ =~ m/\s+wMaxPacketSize\s+(0x[0-9a-fA-F]+)/) { 413 | #print "wMaxPacketSize: ".$1."\n"; 414 | $ed{"wMaxPacketSize"} = $1 if $in_endpointdescr; 415 | next; 416 | } 417 | if ($_ =~ m/\s+bInterval\s+(\d+)/) { 418 | #print "bInterval: ".$1."\n"; 419 | $ed{"bInterval"} = $1 if $in_endpointdescr; 420 | next; 421 | } 422 | 423 | #print STDERR ">>>> ".$_; 424 | 425 | } 426 | 427 | if ($in_configurationdescr == 1) { 428 | push_cd(); 429 | end_cd(); 430 | end_dd(); 431 | } elsif ($in_interfacedescr == 1) { 432 | push_id(); 433 | end_id(); 434 | end_cd(); 435 | end_dd(); 436 | } elsif ($in_endpointdescr == 1) { 437 | push_ed(); 438 | end_id(); 439 | end_cd(); 440 | end_dd(); 441 | } 442 | -------------------------------------------------------------------------------- /tools.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # tools.py 4 | # 5 | # Copyright 2013 Daniel Mende 6 | # 7 | 8 | # Redistribution and use in source and binary forms, with or without 9 | # modification, are permitted provided that the following conditions are 10 | # met: 11 | # 12 | # * Redistributions of source code must retain the above copyright 13 | # notice, this list of conditions and the following disclaimer. 14 | # * Redistributions in binary form must reproduce the above 15 | # copyright notice, this list of conditions and the following disclaimer 16 | # in the documentation and/or other materials provided with the 17 | # distribution. 18 | # * Neither the name of the nor the names of its 19 | # contributors may be used to endorse or promote products derived from 20 | # this software without specific prior written permission. 21 | # 22 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 23 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 24 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 25 | # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 26 | # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 27 | # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 28 | # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 29 | # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 30 | # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 31 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 32 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 33 | 34 | import struct 35 | 36 | DEBUG = False 37 | 38 | def unique(seq, idfun=None): 39 | # order preserving 40 | if idfun is None: 41 | def idfun(x): return x 42 | seen = {} 43 | result = [] 44 | for item in seq: 45 | marker = idfun(item) 46 | if marker in seen: continue 47 | seen[marker] = 1 48 | result.append(item) 49 | return result 50 | 51 | def read_with_length(data, length, endian='!'): 52 | try: 53 | #fill with zeros until length is correct. 54 | out = None 55 | if length <= 8: 56 | (out,) = struct.unpack("%sB" % endian, data[0]) 57 | elif length <= 16: 58 | (out,) = struct.unpack("%sH" % endian, data[0:1]) 59 | elif length <= 32: 60 | (out,) = struct.unpack("%sI" % endian, data[0:3]) 61 | elif length <= 64: 62 | (out,) = struct.unpack("%sQ" % endian, data[0:7]) 63 | else: 64 | raise dizz_runtimeException("cant read with len >64") 65 | except Exception as e: 66 | if DEBUG: 67 | print("Can't unpack %s: %s" %(data, str(e))) 68 | raise e 69 | 70 | def pack_with_length(data, length, endian='!'): 71 | try: 72 | if length <= 8: 73 | return struct.pack("%sB" % endian, data) 74 | elif length <= 16: 75 | return struct.pack("%sH" % endian, data) 76 | elif length <= 32: 77 | out = struct.pack("%sI" % endian, data) 78 | #~ if length < 64: 79 | #~ return struct.pack("!Q", data) 80 | else: 81 | out = b"" 82 | for i in range(0, length, 32): 83 | if endian == '!' or endian == '<': 84 | out = out + struct.pack("%sI" % endian, data & 0xffffffff) 85 | else: 86 | out = struct.pack("%sI" % endian, data & 0xffffffff) + out 87 | data = data >> 32 88 | bl = length // 8 89 | if length % 8 > 0: 90 | bl += 1 91 | if endian == '!' or endian == '>': 92 | return out[-bl:] 93 | else: 94 | return out[:bl] 95 | except Exception as e: 96 | if DEBUG: 97 | print("Can't pack %s: %s" %(data, str(e))) 98 | raise e 99 | 100 | def chr_to_bin(c): 101 | out = "" 102 | for i in range(0,8): 103 | if i == 4: 104 | out += " " 105 | out += "%d" % (((ord(c) << i) & 0x80) >> 7) 106 | return out 107 | 108 | def str_to_bin(s): 109 | out = "" 110 | c = 1 111 | for i in s: 112 | out += chr_to_bin(i) 113 | if c % 8 == 0: 114 | out += "\n" 115 | else: 116 | out += " " 117 | c += 1 118 | return out[:-2] 119 | -------------------------------------------------------------------------------- /usb.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # usb.py 4 | # 5 | # Copyright 2013 Daniel Mende 6 | # 7 | 8 | # Redistribution and use in source and binary forms, with or without 9 | # modification, are permitted provided that the following conditions are 10 | # met: 11 | # 12 | # * Redistributions of source code must retain the above copyright 13 | # notice, this list of conditions and the following disclaimer. 14 | # * Redistributions in binary form must reproduce the above 15 | # copyright notice, this list of conditions and the following disclaimer 16 | # in the documentation and/or other materials provided with the 17 | # distribution. 18 | # * Neither the name of the nor the names of its 19 | # contributors may be used to endorse or promote products derived from 20 | # this software without specific prior written permission. 21 | # 22 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 23 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 24 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 25 | # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 26 | # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 27 | # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 28 | # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 29 | # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 30 | # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 31 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 32 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 33 | 34 | import os 35 | import select 36 | import sys 37 | import threading 38 | import traceback 39 | 40 | root = os.path.dirname(__file__) or os.getcwd() 41 | sys.path.append(os.path.join(root, "usb")) 42 | del root 43 | 44 | from USB import * 45 | from USBDevice import * 46 | from USBConfiguration import * 47 | from USBInterface import * 48 | from USBEndpoint import * 49 | from Facedancer import * 50 | from MAXUSBApp import * 51 | from serial import Serial, PARITY_NONE 52 | from serial.serialutil import SerialException 53 | 54 | import tools 55 | 56 | DEBUG = False 57 | DEFAULT_TTY = "/dev/ttyUSB0" 58 | 59 | class dizzyUSBDevice(USBDevice): 60 | name = "dizzy USB device" 61 | 62 | class dizzyUSBInterface(USBInterface): 63 | name = "dizzy USB interface" 64 | 65 | def __init__(self, ID, verbose=0): 66 | endpoints = [ USBEndpoint( 67 | ID["EP"].index(i), # endpoint number 68 | 69 | ################# fill in data from file ################ 70 | USBEndpoint.direction_in, 71 | USBEndpoint.transfer_type_interrupt, 72 | USBEndpoint.sync_type_none, 73 | USBEndpoint.usage_type_data, 74 | ######################################################### 75 | 76 | i["wMaxPacketSize"], # max packet size 77 | i["bInterval"], # polling interval, see USB 2.0 spec Table 9-13 78 | self.handle_buffer_available # handler function 79 | ) for i in ID["EP"] ] 80 | 81 | USBInterface.__init__( 82 | self, 83 | ID["bInterfaceNumber"], # interface number 84 | ID["bAlternateSetting"], # alternate setting 85 | ID["bInterfaceClass"], # interface class 86 | ID["bInterfaceSubClass"], # subclass 87 | ID["bInterfaceProtocol"], # protocol 88 | ID["iInterface"], # string index 89 | verbose, 90 | endpoints 91 | ) 92 | 93 | def handle_buffer_available(self): 94 | pass 95 | 96 | def __init__(self, maxusb_app, DD, verbose=0, data="", fuzz_dscr=""): 97 | config = [ USBConfiguration( 98 | DD["CD"].index(i), # index 99 | i["iConfiguration_str"], # string desc 100 | [ self.dizzyUSBInterface(j, verbose=verbose) 101 | for j in i["ID"] ] # interfaces 102 | ) for i in DD["CD"] ] 103 | 104 | USBDevice.__init__( 105 | self, 106 | maxusb_app, 107 | DD["bDeviceClass"], # device class 108 | DD["bDeviceSubClass"], # device subclass 109 | DD["bDeviceProtocol"], # protocol release number 110 | DD["bMaxPacketSize"], # max packet size for endpoint 0 111 | DD["idVendor"], # vendor id 112 | DD["idProduct"], # product id 113 | self.bcd2int(DD["bcdDevice"]), # device revision 114 | DD["iManufacturer_str"], # manufacturer string 115 | DD["iProduct_str"], # product string 116 | DD["iSerial_str"], # serial number string 117 | config, 118 | verbose=verbose 119 | ) 120 | for i in DD["CD"]: 121 | for j in i["ID"]: 122 | self.strings.insert(j["iInterface"], j["iInterface_str"]) 123 | self.data = data 124 | self.fuzz_dscr = fuzz_dscr 125 | self.dd_sent = False 126 | self.cd_sent = False 127 | self.scr_recieved = False 128 | 129 | def bcd2int(self, bcd): 130 | tmp = bcd.split(".") 131 | return (int(tmp[0]) << 8) + int(tmp[1]) 132 | 133 | def handle_get_descriptor_request(self, req): 134 | dtype = (req.value >> 8) & 0xff 135 | dindex = req.value & 0xff 136 | lang = req.index 137 | n = req.length 138 | 139 | response = None 140 | 141 | if self.verbose > 2: 142 | print(self.name, ("received GET_DESCRIPTOR req %d, index %d, " \ 143 | + "language 0x%04x, length %d") \ 144 | % (dtype, dindex, lang, n)) 145 | 146 | if dtype == USB.desc_type_device and self.fuzz_dscr == "DD": 147 | response = self.data 148 | elif dtype == USB.desc_type_configuration and self.fuzz_dscr == "CD": 149 | response = self.data 150 | #add IDs and EDs to response! 151 | else: 152 | response = self.descriptors.get(dtype, None) 153 | if callable(response): 154 | response = response(dindex) 155 | 156 | if not response is None: 157 | n = min(n, len(response)) 158 | self.maxusb_app.verbose += 1 159 | self.maxusb_app.send_on_endpoint(0, response[:n]) 160 | self.maxusb_app.verbose -= 1 161 | 162 | if self.verbose > 5: 163 | print(self.name, "sent", n, "bytes in response") 164 | else: 165 | self.maxusb_app.stall_ep0() 166 | 167 | if n == len(response): 168 | if dtype == USB.desc_type_device: 169 | self.dd_sent = True 170 | elif dtype == USB.desc_type_configuration: 171 | self.cd_sent = True 172 | 173 | def handle_set_configuration_request(self, req): 174 | if self.verbose > 2: 175 | print(self.name, "received SET_CONFIGURATION request") 176 | 177 | # configs are one-based 178 | self.config_num = req.value - 1 179 | self.configuration = self.configurations[self.config_num] 180 | self.state = USB.state_configured 181 | 182 | # collate endpoint numbers 183 | self.endpoints = { } 184 | for i in self.configuration.interfaces: 185 | for e in i.endpoints: 186 | self.endpoints[e.number] = e 187 | 188 | # HACK: blindly acknowledge request 189 | self.ack_status_stage() 190 | self.scr_recieved = True 191 | 192 | class dizzyUSB(object): 193 | def __init__(self, filename, timeout, device=DEFAULT_TTY, data="", fuzz_dscr=""): 194 | self.filename = filename 195 | self.timeout = timeout 196 | self.device = device 197 | self.data = data 198 | self.fuzz_dscr = fuzz_dscr 199 | self.sp = None 200 | self.d = None 201 | 202 | def open(self, dst=""): 203 | if DEBUG: 204 | verbose = 1 205 | else: 206 | verbose = 0 207 | ns = {} 208 | with open(self.filename) as f: 209 | exec(compile(f.read(), self.filename, 'exec'), ns) 210 | DD = ns["DD"] 211 | success = False 212 | if DEBUG: 213 | print("setting up facedancer") 214 | sys.__stdout__.flush() 215 | while not success: 216 | try: 217 | self.sp = Serial(self.device, 115200, parity=PARITY_NONE, timeout=2) 218 | self.fd = Facedancer(self.sp, verbose=verbose) 219 | self.app = MAXUSBApp(self.fd, verbose=verbose) 220 | self.d = dizzyUSBDevice(self.app, DD, verbose, self.data, self.fuzz_dscr) 221 | success = True 222 | except: 223 | time.sleep(0.1) 224 | 225 | self.d.connect() 226 | self.t = threading.Thread(target=self.run) 227 | self.ep = None 228 | self.opened = False 229 | 230 | if not dst == "": 231 | self.ep = int(dst) 232 | self.t.start() 233 | self.opened = True 234 | if DEBUG: 235 | print("Waiting for USB to setup...") 236 | if self.fuzz_dscr == "": 237 | time.sleep(2) 238 | else: 239 | times = self.timeout 240 | while (not (self.d.dd_sent and self.d.cd_sent and self.d.scr_recieved and False)) and times > 0: 241 | if DEBUG: 242 | sys.__stdout__.write(".") 243 | sys.__stdout__.flush() 244 | time.sleep(0.1) 245 | times -= 1 246 | if DEBUG: 247 | sys.__stdout__.write("\n") 248 | sys.__stdout__.flush() 249 | if times <= 0 and DEBUG: 250 | print("timeout reached, canceled!") 251 | #raise 252 | return 253 | if DEBUG: 254 | print("USB setup complete.") 255 | 256 | def run(self): 257 | try: 258 | self.d.run() 259 | except SerialException: 260 | pass 261 | except select.error: 262 | pass 263 | except OSError: 264 | pass 265 | except TypeError: 266 | pass 267 | except IndexError: 268 | pass 269 | except Exception as e: 270 | if DEBUG: 271 | traceback.print_exc() 272 | print(e) 273 | self.opened = False 274 | 275 | def close(self): 276 | if not self.open: 277 | return 278 | if not self.d is None: 279 | try: 280 | self.d.disconnect() 281 | except IndexError: 282 | pass 283 | except SerialException: 284 | pass 285 | except ValueError: 286 | pass 287 | except Exception as e: 288 | if DEBUG: 289 | traceback.print_exc() 290 | print(e) 291 | if not self.sp is None: 292 | self.sp.close() 293 | self.open = False 294 | 295 | def read(self): 296 | pass 297 | 298 | def write(self, data): 299 | if not self.ep is None: 300 | while not self.opened: 301 | time.sleep(0.1) 302 | try: 303 | self.app.send_on_endpoint(self.ep, data) 304 | except Exception as e: 305 | #~ if DEBUG: 306 | #~ traceback.print_exc() 307 | #~ print(e) 308 | raise e 309 | 310 | -------------------------------------------------------------------------------- /usb/Facedancer.py: -------------------------------------------------------------------------------- 1 | # Facedancer.py 2 | # 3 | # Contains class definitions for Facedancer, FacedancerCommand, FacedancerApp, 4 | # and GoodFETMonitorApp. 5 | 6 | from util import * 7 | 8 | class Facedancer: 9 | def __init__(self, serialport, verbose=0): 10 | self.serialport = serialport 11 | self.verbose = verbose 12 | 13 | self.reset() 14 | self.monitor_app = GoodFETMonitorApp(self, verbose=self.verbose) 15 | self.monitor_app.announce_connected() 16 | 17 | def halt(self): 18 | self.serialport.setRTS(1) 19 | self.serialport.setDTR(1) 20 | 21 | def reset(self): 22 | if self.verbose > 1: 23 | print("Facedancer resetting...") 24 | 25 | self.halt() 26 | self.serialport.setDTR(0) 27 | 28 | c = self.readcmd() 29 | 30 | if self.verbose > 0: 31 | print("Facedancer reset") 32 | 33 | def read(self, n): 34 | """Read raw bytes.""" 35 | 36 | b = self.serialport.read(n) 37 | 38 | if self.verbose > 3: 39 | print("Facedancer received", len(b), "bytes;", 40 | self.serialport.inWaiting(), "bytes remaining") 41 | 42 | if self.verbose > 2: 43 | print("Facedancer Rx:", bytes_as_hex(b)) 44 | 45 | return b 46 | 47 | def readcmd(self): 48 | """Read a single command.""" 49 | 50 | b = self.read(4) 51 | 52 | app = b[0] 53 | verb = b[1] 54 | n = b[2] + (b[3] << 8) 55 | 56 | if n > 0: 57 | data = self.read(n) 58 | else: 59 | data = b'' 60 | 61 | if len(data) != n: 62 | raise ValueError('Facedancer expected ' + str(n) \ 63 | + ' bytes but received only ' + str(len(data))) 64 | 65 | cmd = FacedancerCommand(app, verb, data) 66 | 67 | if self.verbose > 1: 68 | print("Facedancer Rx command:", cmd) 69 | 70 | return cmd 71 | 72 | def write(self, b): 73 | """Write raw bytes.""" 74 | 75 | if self.verbose > 2: 76 | print("Facedancer Tx:", bytes_as_hex(b)) 77 | 78 | self.serialport.write(b) 79 | 80 | def writecmd(self, c): 81 | """Write a single command.""" 82 | self.write(c.as_bytestring()) 83 | 84 | if self.verbose > 1: 85 | print("Facedancer Tx command:", c) 86 | 87 | 88 | class FacedancerCommand: 89 | def __init__(self, app=None, verb=None, data=None): 90 | self.app = app 91 | self.verb = verb 92 | self.data = data 93 | 94 | def __str__(self): 95 | s = "app 0x%02x, verb 0x%02x, len %d" % (self.app, self.verb, 96 | len(self.data)) 97 | 98 | if len(self.data) > 0: 99 | s += ", data " + bytes_as_hex(self.data) 100 | 101 | return s 102 | 103 | def long_string(self): 104 | s = "app: " + str(self.app) + "\n" \ 105 | + "verb: " + str(self.verb) + "\n" \ 106 | + "len: " + str(len(self.data)) 107 | 108 | if len(self.data) > 0: 109 | try: 110 | s += "\n" + self.data.decode("utf-8") 111 | except UnicodeDecodeError: 112 | s += "\n" + bytes_as_hex(self.data) 113 | 114 | return s 115 | 116 | def as_bytestring(self): 117 | n = len(self.data) 118 | 119 | b = bytearray(n + 4) 120 | b[0] = self.app 121 | b[1] = self.verb 122 | b[2] = n & 0xff 123 | b[3] = n >> 8 124 | b[4:] = self.data 125 | 126 | return b 127 | 128 | 129 | class FacedancerApp: 130 | app_name = "override this" 131 | app_num = 0x00 132 | 133 | def __init__(self, device, verbose=0): 134 | self.device = device 135 | self.verbose = verbose 136 | 137 | self.init_commands() 138 | 139 | if self.verbose > 0: 140 | print(self.app_name, "initialized") 141 | 142 | def init_commands(self): 143 | pass 144 | 145 | def enable(self): 146 | for i in range(3): 147 | self.device.writecmd(self.enable_app_cmd) 148 | self.device.readcmd() 149 | 150 | if self.verbose > 0: 151 | print(self.app_name, "enabled") 152 | 153 | 154 | class GoodFETMonitorApp(FacedancerApp): 155 | app_name = "GoodFET monitor" 156 | app_num = 0x00 157 | 158 | def read_byte(self, addr): 159 | d = [ addr & 0xff, addr >> 8 ] 160 | cmd = FacedancerCommand(0, 2, d) 161 | 162 | self.device.writecmd(cmd) 163 | resp = self.device.readcmd() 164 | 165 | return resp.data[0] 166 | 167 | def get_infostring(self): 168 | return bytes([ self.read_byte(0xff0), self.read_byte(0xff1) ]) 169 | 170 | def get_clocking(self): 171 | return bytes([ self.read_byte(0x57), self.read_byte(0x56) ]) 172 | 173 | def print_info(self): 174 | infostring = self.get_infostring() 175 | clocking = self.get_clocking() 176 | 177 | print("MCU", bytes_as_hex(infostring, delim="")) 178 | print("clocked at", bytes_as_hex(clocking, delim="")) 179 | 180 | def list_apps(self): 181 | cmd = FacedancerCommand(self.app_num, 0x82, b'0x0') 182 | self.device.writecmd(cmd) 183 | 184 | resp = self.device.readcmd() 185 | print("build date:", resp.data.decode("utf-8")) 186 | 187 | print("firmware apps:") 188 | while True: 189 | resp = self.device.readcmd() 190 | if len(resp.data) == 0: 191 | break 192 | print(resp.data.decode("utf-8")) 193 | 194 | def echo(self, s): 195 | b = bytes(s, encoding="utf-8") 196 | 197 | cmd = FacedancerCommand(self.app_num, 0x81, b) 198 | self.device.writecmd(cmd) 199 | 200 | resp = self.device.readcmd() 201 | 202 | return resp.data == b 203 | 204 | def announce_connected(self): 205 | cmd = FacedancerCommand(self.app_num, 0xb1, b'') 206 | self.device.writecmd(cmd) 207 | resp = self.device.readcmd() 208 | 209 | -------------------------------------------------------------------------------- /usb/MAXUSBApp.py: -------------------------------------------------------------------------------- 1 | # MAXUSBApp.py 2 | # 3 | # Contains class definition for MAXUSBApp. 4 | 5 | import time 6 | 7 | from util import * 8 | from Facedancer import * 9 | from USB import * 10 | from USBDevice import USBDeviceRequest 11 | 12 | class MAXUSBApp(FacedancerApp): 13 | app_name = "MAXUSB" 14 | app_num = 0x40 15 | 16 | reg_ep0_fifo = 0x00 17 | reg_ep1_out_fifo = 0x01 18 | reg_ep2_in_fifo = 0x02 19 | reg_ep3_in_fifo = 0x03 20 | reg_setup_data_fifo = 0x04 21 | reg_ep0_byte_count = 0x05 22 | reg_ep1_out_byte_count = 0x06 23 | reg_ep2_in_byte_count = 0x07 24 | reg_ep3_in_byte_count = 0x08 25 | reg_ep_stalls = 0x09 26 | reg_clr_togs = 0x0a 27 | reg_endpoint_irq = 0x0b 28 | reg_endpoint_interrupt_enable = 0x0c 29 | reg_usb_irq = 0x0d 30 | reg_usb_interrupt_enable = 0x0e 31 | reg_usb_control = 0x0f 32 | reg_cpu_control = 0x10 33 | reg_pin_control = 0x11 34 | reg_revision = 0x12 35 | reg_function_address = 0x13 36 | reg_io_pins = 0x14 37 | 38 | # bitmask values for reg_endpoint_irq = 0x0b 39 | is_setup_data_avail = 0x20 # SUDAVIRQ 40 | is_in3_buffer_avail = 0x10 # IN3BAVIRQ 41 | is_in2_buffer_avail = 0x08 # IN2BAVIRQ 42 | is_out1_data_avail = 0x04 # OUT1DAVIRQ 43 | is_out0_data_avail = 0x02 # OUT0DAVIRQ 44 | is_in0_buffer_avail = 0x01 # IN0BAVIRQ 45 | 46 | # bitmask values for reg_usb_control = 0x0f 47 | usb_control_vbgate = 0x40 48 | usb_control_connect = 0x08 49 | 50 | # bitmask values for reg_pin_control = 0x11 51 | interrupt_level = 0x08 52 | full_duplex = 0x10 53 | 54 | def __init__(self, device, verbose=0): 55 | FacedancerApp.__init__(self, device, verbose) 56 | 57 | self.connected_device = None 58 | 59 | self.enable() 60 | 61 | if verbose > 0: 62 | rev = self.read_register(self.reg_revision) 63 | print(self.app_name, "revision", rev) 64 | 65 | # set duplex and negative INT level (from GoodFEDMAXUSB.py) 66 | self.write_register(self.reg_pin_control, 67 | self.full_duplex | self.interrupt_level) 68 | 69 | def init_commands(self): 70 | self.read_register_cmd = FacedancerCommand(self.app_num, 0x00, b'') 71 | self.write_register_cmd = FacedancerCommand(self.app_num, 0x00, b'') 72 | self.enable_app_cmd = FacedancerCommand(self.app_num, 0x10, b'') 73 | self.ack_cmd = FacedancerCommand(self.app_num, 0x00, b'\x01') 74 | 75 | def read_register(self, reg_num, ack=False): 76 | if self.verbose > 1: 77 | print(self.app_name, "reading register 0x%02x" % reg_num) 78 | 79 | self.read_register_cmd.data = bytearray([ reg_num << 3, 0 ]) 80 | if ack: 81 | self.read_register_cmd.data[0] |= 1 82 | 83 | self.device.writecmd(self.read_register_cmd) 84 | 85 | resp = self.device.readcmd() 86 | 87 | if self.verbose > 2: 88 | print(self.app_name, "read register 0x%02x has value 0x%02x" % 89 | (reg_num, resp.data[1])) 90 | 91 | return resp.data[1] 92 | 93 | def write_register(self, reg_num, value, ack=False): 94 | if self.verbose > 2: 95 | print(self.app_name, "writing register 0x%02x with value 0x%02x" % 96 | (reg_num, value)) 97 | 98 | self.write_register_cmd.data = bytearray([ (reg_num << 3) | 2, value ]) 99 | if ack: 100 | self.write_register_cmd.data[0] |= 1 101 | 102 | self.device.writecmd(self.write_register_cmd) 103 | self.device.readcmd() 104 | 105 | def get_version(self): 106 | return self.read_register(self.reg_revision) 107 | 108 | def ack_status_stage(self): 109 | if self.verbose > 5: 110 | print(self.app_name, "sending ack!") 111 | 112 | self.device.writecmd(self.ack_cmd) 113 | self.device.readcmd() 114 | 115 | def connect(self, usb_device): 116 | if self.read_register(self.reg_usb_control) & self.usb_control_connect: 117 | self.write_register(self.reg_usb_control, self.usb_control_vbgate) 118 | time.sleep(.1) 119 | 120 | self.write_register(self.reg_usb_control, self.usb_control_vbgate | 121 | self.usb_control_connect) 122 | 123 | self.connected_device = usb_device 124 | 125 | if self.verbose > 0: 126 | print(self.app_name, "connected device", self.connected_device.name) 127 | 128 | def disconnect(self): 129 | self.write_register(self.reg_usb_control, self.usb_control_vbgate) 130 | 131 | if self.verbose > 0: 132 | print(self.app_name, "disconnected device", self.connected_device.name) 133 | self.connected_device = None 134 | 135 | def clear_irq_bit(self, reg, bit): 136 | self.write_register(reg, bit) 137 | 138 | def read_bytes(self, reg, n): 139 | if self.verbose > 2: 140 | print(self.app_name, "reading", n, "bytes from register", reg) 141 | 142 | data = bytes([ (reg << 3) ] + ([0] * n)) 143 | cmd = FacedancerCommand(self.app_num, 0x00, data) 144 | 145 | self.device.writecmd(cmd) 146 | resp = self.device.readcmd() 147 | 148 | if self.verbose > 3: 149 | print(self.app_name, "read", len(resp.data) - 1, "bytes from register", reg) 150 | 151 | return resp.data[1:] 152 | 153 | def write_bytes(self, reg, data): 154 | data = bytes([ (reg << 3) | 3 ]) + data 155 | cmd = FacedancerCommand(self.app_num, 0x00, data) 156 | 157 | self.device.writecmd(cmd) 158 | self.device.readcmd() # null response 159 | 160 | if self.verbose > 3: 161 | print(self.app_name, "wrote", len(data) - 1, "bytes to register", reg) 162 | 163 | # HACK: but given the limitations of the MAX chips, it seems necessary 164 | def send_on_endpoint(self, ep_num, data): 165 | if ep_num == 0: 166 | fifo_reg = self.reg_ep0_fifo 167 | bc_reg = self.reg_ep0_byte_count 168 | elif ep_num == 2: 169 | fifo_reg = self.reg_ep2_in_fifo 170 | bc_reg = self.reg_ep2_in_byte_count 171 | elif ep_num == 3: 172 | fifo_reg = self.reg_ep3_in_fifo 173 | bc_reg = self.reg_ep3_in_byte_count 174 | else: 175 | raise ValueError('endpoint ' + str(ep_num) + ' not supported') 176 | 177 | # FIFO buffer is only 64 bytes, must loop 178 | while len(data) > 64: 179 | self.write_bytes(fifo_reg, data[:64]) 180 | self.write_register(bc_reg, 64, ack=True) 181 | 182 | data = data[64:] 183 | 184 | self.write_bytes(fifo_reg, data) 185 | self.write_register(bc_reg, len(data), ack=True) 186 | 187 | if self.verbose > 1: 188 | print(self.app_name, "wrote", bytes_as_hex(data), "to endpoint", 189 | ep_num) 190 | 191 | # HACK: but given the limitations of the MAX chips, it seems necessary 192 | def read_from_endpoint(self, ep_num): 193 | if ep_num != 1: 194 | return b'' 195 | 196 | byte_count = self.read_register(self.reg_ep1_out_byte_count) 197 | if byte_count == 0: 198 | return b'' 199 | 200 | data = self.read_bytes(self.reg_ep1_out_fifo, byte_count) 201 | 202 | if self.verbose > 1: 203 | print(self.app_name, "read", bytes_as_hex(data), "from endpoint", 204 | ep_num) 205 | 206 | return data 207 | 208 | def stall_ep0(self): 209 | if self.verbose > 0: 210 | print(self.app_name, "stalling endpoint 0") 211 | 212 | self.write_register(self.reg_ep_stalls, 0x23) 213 | 214 | def service_irqs(self): 215 | while True: 216 | irq = self.read_register(self.reg_endpoint_irq) 217 | 218 | if self.verbose > 3: 219 | print(self.app_name, "read endpoint irq: 0x%02x" % irq) 220 | 221 | if self.verbose > 2: 222 | if irq & ~ (self.is_in0_buffer_avail \ 223 | | self.is_in2_buffer_avail | self.is_in3_buffer_avail): 224 | print(self.app_name, "notable irq: 0x%02x" % irq) 225 | 226 | if irq & self.is_setup_data_avail: 227 | self.clear_irq_bit(self.reg_endpoint_irq, self.is_setup_data_avail) 228 | 229 | b = self.read_bytes(self.reg_setup_data_fifo, 8) 230 | req = USBDeviceRequest(b) 231 | self.connected_device.handle_request(req) 232 | 233 | if irq & self.is_out1_data_avail: 234 | data = self.read_from_endpoint(1) 235 | if data: 236 | self.connected_device.handle_data_available(1, data) 237 | self.clear_irq_bit(self.reg_endpoint_irq, self.is_out1_data_avail) 238 | 239 | if irq & self.is_in2_buffer_avail: 240 | self.connected_device.handle_buffer_available(2) 241 | 242 | if irq & self.is_in3_buffer_avail: 243 | self.connected_device.handle_buffer_available(3) 244 | 245 | -------------------------------------------------------------------------------- /usb/USB.py: -------------------------------------------------------------------------------- 1 | # USB.py 2 | # 3 | # Contains definition of USB class, which is just a container for a bunch of 4 | # constants/enums associated with the USB protocol. 5 | # 6 | # TODO: would be nice if this module could re-export the other USB* classes so 7 | # one need import only USB to get all the functionality 8 | 9 | class USB: 10 | state_detached = 0 11 | state_attached = 1 12 | state_powered = 2 13 | state_default = 3 14 | state_address = 4 15 | state_configured = 5 16 | state_suspended = 6 17 | 18 | request_direction_host_to_device = 0 19 | request_direction_device_to_host = 1 20 | 21 | request_type_standard = 0 22 | request_type_class = 1 23 | request_type_vendor = 2 24 | 25 | request_recipient_device = 0 26 | request_recipient_interface = 1 27 | request_recipient_endpoint = 2 28 | request_recipient_other = 3 29 | 30 | feature_endpoint_halt = 0 31 | feature_device_remote_wakeup = 1 32 | feature_test_mode = 2 33 | 34 | desc_type_device = 1 35 | desc_type_configuration = 2 36 | desc_type_string = 3 37 | desc_type_interface = 4 38 | desc_type_endpoint = 5 39 | desc_type_device_qualifier = 6 40 | desc_type_other_speed_configuration = 7 41 | desc_type_interface_power = 8 42 | desc_type_hid = 33 43 | desc_type_report = 34 44 | 45 | # while this holds for HID, it may not be a correct model for the USB 46 | # ecosystem at large 47 | if_class_to_desc_type = { 48 | 3 : desc_type_hid 49 | } 50 | 51 | def interface_class_to_descriptor_type(interface_class): 52 | return USB.if_class_to_desc_type.get(interface_class, None) 53 | 54 | -------------------------------------------------------------------------------- /usb/USBClass.py: -------------------------------------------------------------------------------- 1 | # USBClass.py 2 | # 3 | # Contains class definition for USBClass, intended as a base class (in the OO 4 | # sense) for implementing device classes (in the USB sense), eg, HID devices, 5 | # mass storage devices. 6 | 7 | class USBClass: 8 | name = "generic USB device class" 9 | 10 | # maps bRequest to handler function 11 | request_handlers = { } 12 | 13 | def __init__(self, verbose=0): 14 | self.interface = None 15 | self.verbose = verbose 16 | 17 | self.setup_request_handlers() 18 | 19 | def set_interface(self, interface): 20 | self.interface = interface 21 | 22 | def setup_request_handlers(self): 23 | """To be overridden for subclasses to modify self.class_request_handlers""" 24 | pass 25 | 26 | -------------------------------------------------------------------------------- /usb/USBConfiguration.py: -------------------------------------------------------------------------------- 1 | # USBConfiguration.py 2 | # 3 | # Contains class definition for USBConfiguration. 4 | 5 | class USBConfiguration: 6 | def __init__(self, configuration_index, configuration_string, interfaces): 7 | self.configuration_index = configuration_index 8 | self.configuration_string = configuration_string 9 | self.configuration_string_index = 0 10 | self.interfaces = interfaces 11 | 12 | self.attributes = 0xe0 13 | self.max_power = 0x01 14 | 15 | self.device = None 16 | 17 | for i in self.interfaces: 18 | i.set_configuration(self) 19 | 20 | def set_device(self, device): 21 | self.device = device 22 | 23 | def set_configuration_string_index(self, i): 24 | self.configuration_string_index = i 25 | 26 | def get_descriptor(self): 27 | interface_descriptors = bytearray() 28 | for i in self.interfaces: 29 | interface_descriptors += i.get_descriptor() 30 | 31 | total_len = len(interface_descriptors) + 9 32 | 33 | d = bytes([ 34 | 9, # length of descriptor in bytes 35 | 2, # descriptor type 2 == configuration 36 | total_len & 0xff, 37 | (total_len >> 8) & 0xff, 38 | len(self.interfaces), 39 | self.configuration_index, 40 | self.configuration_string_index, 41 | self.attributes, 42 | self.max_power 43 | ]) 44 | 45 | return d + interface_descriptors 46 | 47 | -------------------------------------------------------------------------------- /usb/USBDevice.py: -------------------------------------------------------------------------------- 1 | # USBDevice.py 2 | # 3 | # Contains class definitions for USBDevice and USBDeviceRequest. 4 | 5 | from USB import * 6 | from USBClass import * 7 | 8 | class USBDevice: 9 | name = "generic device" 10 | 11 | def __init__(self, maxusb_app, device_class, device_subclass, 12 | protocol_rel_num, max_packet_size_ep0, vendor_id, product_id, 13 | device_rev, manufacturer_string, product_string, 14 | serial_number_string, configurations=[], descriptors={}, 15 | verbose=0): 16 | self.maxusb_app = maxusb_app 17 | self.verbose = verbose 18 | 19 | self.strings = [ ] 20 | 21 | self.usb_spec_version = 0x0001 22 | self.device_class = device_class 23 | self.device_subclass = device_subclass 24 | self.protocol_rel_num = protocol_rel_num 25 | self.max_packet_size_ep0 = max_packet_size_ep0 26 | self.vendor_id = vendor_id 27 | self.product_id = product_id 28 | self.device_rev = device_rev 29 | self.manufacturer_string_id = self.get_string_id(manufacturer_string) 30 | self.product_string_id = self.get_string_id(product_string) 31 | self.serial_number_string_id = self.get_string_id(serial_number_string) 32 | 33 | # maps from USB.desc_type_* to bytearray OR callable 34 | self.descriptors = descriptors 35 | self.descriptors[USB.desc_type_device] = self.get_descriptor 36 | self.descriptors[USB.desc_type_configuration] = self.handle_get_configuration_descriptor_request 37 | self.descriptors[USB.desc_type_string] = self.handle_get_string_descriptor_request 38 | 39 | self.config_num = -1 40 | self.configuration = None 41 | self.configurations = configurations 42 | 43 | for c in self.configurations: 44 | csi = self.get_string_id(c.configuration_string) 45 | c.set_configuration_string_index(csi) 46 | c.set_device(self) 47 | 48 | self.state = USB.state_detached 49 | self.ready = False 50 | 51 | self.address = 0 52 | 53 | self.setup_request_handlers() 54 | 55 | def get_string_id(self, s): 56 | try: 57 | i = self.strings.index(s) 58 | except ValueError: 59 | # string descriptors start at index 1 60 | self.strings.append(s) 61 | i = len(self.strings) 62 | 63 | return i 64 | 65 | def setup_request_handlers(self): 66 | # see table 9-4 of USB 2.0 spec, page 279 67 | self.request_handlers = { 68 | 0 : self.handle_get_status_request, 69 | 1 : self.handle_clear_feature_request, 70 | 3 : self.handle_set_feature_request, 71 | 5 : self.handle_set_address_request, 72 | 6 : self.handle_get_descriptor_request, 73 | 7 : self.handle_set_descriptor_request, 74 | 8 : self.handle_get_configuration_request, 75 | 9 : self.handle_set_configuration_request, 76 | 10 : self.handle_get_interface_request, 77 | 11 : self.handle_set_interface_request, 78 | 12 : self.handle_synch_frame_request 79 | } 80 | 81 | def connect(self): 82 | self.maxusb_app.connect(self) 83 | 84 | # skipping USB.state_attached may not be strictly correct (9.1.1.{1,2}) 85 | self.state = USB.state_powered 86 | 87 | def disconnect(self): 88 | self.maxusb_app.disconnect() 89 | 90 | self.state = USB.state_detached 91 | 92 | def run(self): 93 | self.maxusb_app.service_irqs() 94 | 95 | def ack_status_stage(self): 96 | self.maxusb_app.ack_status_stage() 97 | 98 | def get_descriptor(self, n): 99 | d = bytearray([ 100 | 18, # length of descriptor in bytes 101 | 1, # descriptor type 1 == device 102 | (self.usb_spec_version >> 8) & 0xff, 103 | self.usb_spec_version & 0xff, 104 | self.device_class, 105 | self.device_subclass, 106 | self.protocol_rel_num, 107 | self.max_packet_size_ep0, 108 | self.vendor_id & 0xff, 109 | (self.vendor_id >> 8) & 0xff, 110 | self.product_id & 0xff, 111 | (self.product_id >> 8) & 0xff, 112 | self.device_rev & 0xff, 113 | (self.device_rev >> 8) & 0xff, 114 | self.manufacturer_string_id, 115 | self.product_string_id, 116 | self.serial_number_string_id, 117 | len(self.configurations) 118 | ]) 119 | 120 | return d 121 | 122 | # IRQ handlers 123 | ##################################################### 124 | 125 | def handle_request(self, req): 126 | if self.verbose > 3: 127 | print(self.name, "received request", req) 128 | 129 | # figure out the intended recipient 130 | recipient_type = req.get_recipient() 131 | recipient = None 132 | index = req.get_index() 133 | if recipient_type == USB.request_recipient_device: 134 | recipient = self 135 | elif recipient_type == USB.request_recipient_interface: 136 | if index < len(self.configuration.interfaces): 137 | recipient = self.configuration.interfaces[index] 138 | elif recipient_type == USB.request_recipient_endpoint: 139 | recipient = self.endpoints.get(index, None) 140 | 141 | if not recipient: 142 | print(self.name, "invalid recipient, stalling") 143 | self.maxusb_app.stall_ep0() 144 | return 145 | 146 | # and then the type 147 | req_type = req.get_type() 148 | handler_entity = None 149 | if req_type == USB.request_type_standard: 150 | handler_entity = recipient 151 | elif req_type == USB.request_type_class: 152 | handler_entity = recipient.device_class 153 | elif req_type == USB.request_type_vendor: 154 | handler_entity = recipient.device_vendor 155 | 156 | if not handler_entity: 157 | print(self.name, "invalid handler entity, stalling") 158 | self.maxusb_app.stall_ep0() 159 | return 160 | 161 | handler = handler_entity.request_handlers.get(req.request, None) 162 | 163 | if not handler: 164 | print(self.name, "invalid handler, stalling") 165 | self.maxusb_app.stall_ep0() 166 | return 167 | 168 | handler(req) 169 | 170 | def handle_data_available(self, ep_num, data): 171 | if self.state == USB.state_configured and ep_num in self.endpoints: 172 | endpoint = self.endpoints[ep_num] 173 | if callable(endpoint.handler): 174 | endpoint.handler(data) 175 | 176 | def handle_buffer_available(self, ep_num): 177 | if self.state == USB.state_configured and ep_num in self.endpoints: 178 | endpoint = self.endpoints[ep_num] 179 | if callable(endpoint.handler): 180 | endpoint.handler() 181 | 182 | # standard request handlers 183 | ##################################################### 184 | 185 | # USB 2.0 specification, section 9.4.5 (p 282 of pdf) 186 | def handle_get_status_request(self, req): 187 | print(self.name, "received GET_STATUS request") 188 | 189 | # self-powered and remote-wakeup (USB 2.0 Spec section 9.4.5) 190 | response = b'\x03\x00' 191 | self.maxusb_app.send_on_endpoint(0, response) 192 | 193 | # USB 2.0 specification, section 9.4.1 (p 280 of pdf) 194 | def handle_clear_feature_request(self, req): 195 | print(self.name, "received CLEAR_FEATURE request with type 0x%02x and value 0x%02x" \ 196 | % (req.request_type, req.value)) 197 | 198 | # USB 2.0 specification, section 9.4.9 (p 286 of pdf) 199 | def handle_set_feature_request(self, req): 200 | print(self.name, "received SET_FEATURE request") 201 | 202 | # USB 2.0 specification, section 9.4.6 (p 284 of pdf) 203 | def handle_set_address_request(self, req): 204 | self.address = req.value 205 | self.state = USB.state_address 206 | self.ack_status_stage() 207 | 208 | if self.verbose > 2: 209 | print(self.name, "received SET_ADDRESS request for address", 210 | self.address) 211 | 212 | # USB 2.0 specification, section 9.4.3 (p 281 of pdf) 213 | def handle_get_descriptor_request(self, req): 214 | dtype = (req.value >> 8) & 0xff 215 | dindex = req.value & 0xff 216 | lang = req.index 217 | n = req.length 218 | 219 | response = None 220 | 221 | if self.verbose > 2: 222 | print(self.name, ("received GET_DESCRIPTOR req %d, index %d, " \ 223 | + "language 0x%04x, length %d") \ 224 | % (dtype, dindex, lang, n)) 225 | 226 | response = self.descriptors.get(dtype, None) 227 | if callable(response): 228 | response = response(dindex) 229 | 230 | if response: 231 | n = min(n, len(response)) 232 | self.maxusb_app.verbose += 1 233 | self.maxusb_app.send_on_endpoint(0, response[:n]) 234 | self.maxusb_app.verbose -= 1 235 | 236 | if self.verbose > 5: 237 | print(self.name, "sent", n, "bytes in response") 238 | else: 239 | self.maxusb_app.stall_ep0() 240 | 241 | def handle_get_configuration_descriptor_request(self, num): 242 | return self.configurations[num].get_descriptor() 243 | 244 | def handle_get_string_descriptor_request(self, num): 245 | if num == 0: 246 | # HACK: hard-coding baaaaad 247 | d = bytes([ 248 | 4, # length of descriptor in bytes 249 | 3, # descriptor type 3 == string 250 | 9, # language code 0, byte 0 251 | 4 # language code 0, byte 1 252 | ]) 253 | else: 254 | # string descriptors start at 1 255 | s = self.strings[num-1].encode('utf-16') 256 | 257 | # Linux doesn't like the leading 2-byte Byte Order Mark (BOM); 258 | # FreeBSD is okay without it 259 | s = s[2:] 260 | 261 | d = bytearray([ 262 | len(s) + 2, # length of descriptor in bytes 263 | 3 # descriptor type 3 == string 264 | ]) 265 | d += s 266 | 267 | return d 268 | 269 | # USB 2.0 specification, section 9.4.8 (p 285 of pdf) 270 | def handle_set_descriptor_request(self, req): 271 | print(self.name, "received SET_DESCRIPTOR request") 272 | 273 | # USB 2.0 specification, section 9.4.2 (p 281 of pdf) 274 | def handle_get_configuration_request(self, req): 275 | print(self.name, "received GET_CONFIGURATION request with data 0x%02x" \ 276 | % req.value) 277 | 278 | # USB 2.0 specification, section 9.4.7 (p 285 of pdf) 279 | def handle_set_configuration_request(self, req): 280 | print(self.name, "received SET_CONFIGURATION request") 281 | 282 | # configs are one-based 283 | self.config_num = req.value - 1 284 | self.configuration = self.configurations[self.config_num] 285 | self.state = USB.state_configured 286 | 287 | # collate endpoint numbers 288 | self.endpoints = { } 289 | for i in self.configuration.interfaces: 290 | for e in i.endpoints: 291 | self.endpoints[e.number] = e 292 | 293 | # HACK: blindly acknowledge request 294 | self.ack_status_stage() 295 | 296 | # USB 2.0 specification, section 9.4.4 (p 282 of pdf) 297 | def handle_get_interface_request(self, req): 298 | print(self.name, "received GET_INTERFACE request") 299 | 300 | if req.index == 0: 301 | # HACK: currently only support one interface 302 | self.maxusb_app.send_on_endpoint(0, b'\x00') 303 | else: 304 | self.maxusb_app.stall_ep0() 305 | 306 | # USB 2.0 specification, section 9.4.10 (p 288 of pdf) 307 | def handle_set_interface_request(self, req): 308 | print(self.name, "received SET_INTERFACE request") 309 | 310 | # USB 2.0 specification, section 9.4.11 (p 288 of pdf) 311 | def handle_synch_frame_request(self, req): 312 | print(self.name, "received SYNCH_FRAME request") 313 | 314 | 315 | class USBDeviceRequest: 316 | def __init__(self, raw_bytes): 317 | """Expects raw 8-byte setup data request packet""" 318 | 319 | self.request_type = raw_bytes[0] 320 | self.request = raw_bytes[1] 321 | self.value = (raw_bytes[3] << 8) | raw_bytes[2] 322 | self.index = (raw_bytes[5] << 8) | raw_bytes[4] 323 | self.length = (raw_bytes[7] << 8) | raw_bytes[6] 324 | 325 | def __str__(self): 326 | s = "dir=%d, type=%d, rec=%d, r=%d, v=%d, i=%d, l=%d" \ 327 | % (self.get_direction(), self.get_type(), self.get_recipient(), 328 | self.request, self.value, self.index, self.length) 329 | return s 330 | 331 | def raw(self): 332 | """returns request as bytes""" 333 | b = bytes([ self.request_type, self.request, 334 | self.value & 0xff, (self.value >> 8) & 0xff, 335 | self.index & 0xff, (self.index >> 8) & 0xff, 336 | self.length & 0xff, (self.length >> 8) & 0xff 337 | ]) 338 | return b 339 | 340 | def get_direction(self): 341 | return (self.request_type >> 7) & 0x01 342 | 343 | def get_type(self): 344 | return (self.request_type >> 5) & 0x03 345 | 346 | def get_recipient(self): 347 | return self.request_type & 0x1f 348 | 349 | # meaning of bits in wIndex changes whether we're talking about an 350 | # interface or an endpoint (see USB 2.0 spec section 9.3.4) 351 | def get_index(self): 352 | rec = self.get_recipient() 353 | if rec == 1: # interface 354 | return self.index 355 | elif rec == 2: # endpoint 356 | return self.index & 0x0f 357 | 358 | -------------------------------------------------------------------------------- /usb/USBEndpoint.py: -------------------------------------------------------------------------------- 1 | # USBEndpoint.py 2 | # 3 | # Contains class definition for USBEndpoint. 4 | 5 | class USBEndpoint: 6 | direction_out = 0x00 7 | direction_in = 0x01 8 | 9 | transfer_type_control = 0x00 10 | transfer_type_isochronous = 0x01 11 | transfer_type_bulk = 0x02 12 | transfer_type_interrupt = 0x03 13 | 14 | sync_type_none = 0x00 15 | sync_type_async = 0x01 16 | sync_type_adaptive = 0x02 17 | sync_type_synchronous = 0x03 18 | 19 | usage_type_data = 0x00 20 | usage_type_feedback = 0x01 21 | usage_type_implicit_feedback = 0x02 22 | 23 | def __init__(self, number, direction, transfer_type, sync_type, 24 | usage_type, max_packet_size, interval, handler): 25 | 26 | self.number = number 27 | self.direction = direction 28 | self.transfer_type = transfer_type 29 | self.sync_type = sync_type 30 | self.usage_type = usage_type 31 | self.max_packet_size = max_packet_size 32 | self.interval = interval 33 | self.handler = handler 34 | 35 | self.interface = None 36 | 37 | self.request_handlers = { 38 | 1 : self.handle_clear_feature_request 39 | } 40 | 41 | def handle_clear_feature_request(self, req): 42 | print("received CLEAR_FEATURE request for endpoint", self.number, 43 | "with value", req.value) 44 | self.interface.configuration.device.maxusb_app.send_on_endpoint(0, b'') 45 | 46 | def set_interface(self, interface): 47 | self.interface = interface 48 | 49 | # see Table 9-13 of USB 2.0 spec (pdf page 297) 50 | def get_descriptor(self): 51 | address = (self.number & 0x0f) | (self.direction << 7) 52 | attributes = (self.transfer_type & 0x03) \ 53 | | ((self.sync_type & 0x03) << 2) \ 54 | | ((self.usage_type & 0x03) << 4) 55 | 56 | d = bytearray([ 57 | 7, # length of descriptor in bytes 58 | 5, # descriptor type 5 == endpoint 59 | address, 60 | attributes, 61 | (self.max_packet_size >> 8) & 0xff, 62 | self.max_packet_size & 0xff, 63 | self.interval 64 | ]) 65 | 66 | return d 67 | 68 | -------------------------------------------------------------------------------- /usb/USBFtdi.py: -------------------------------------------------------------------------------- 1 | # USBFtdi.py 2 | # 3 | # Contains class definitions to implement a USB FTDI chip. 4 | 5 | from USB import * 6 | from USBDevice import * 7 | from USBConfiguration import * 8 | from USBInterface import * 9 | from USBEndpoint import * 10 | from USBVendor import * 11 | 12 | from util import * 13 | 14 | class USBFtdiVendor(USBVendor): 15 | name = "USB FTDI vendor" 16 | 17 | def setup_request_handlers(self): 18 | self.request_handlers = { 19 | 0 : self.handle_reset_request, 20 | 1 : self.handle_modem_ctrl_request, 21 | 2 : self.handle_set_flow_ctrl_request, 22 | 3 : self.handle_set_baud_rate_request, 23 | 4 : self.handle_set_data_request, 24 | 5 : self.handle_get_status_request, 25 | 6 : self.handle_set_event_char_request, 26 | 7 : self.handle_set_error_char_request, 27 | 9 : self.handle_set_latency_timer_request, 28 | 10 : self.handle_get_latency_timer_request 29 | } 30 | 31 | def handle_reset_request(self, req): 32 | if self.verbose > 0: 33 | print(self.name, "received reset request") 34 | 35 | self.device.maxusb_app.send_on_endpoint(0, b'') 36 | 37 | def handle_modem_ctrl_request(self, req): 38 | if self.verbose > 0: 39 | print(self.name, "received modem_ctrl request") 40 | 41 | dtr = req.value & 0x0001 42 | rts = (req.value & 0x0002) >> 1 43 | dtren = (req.value & 0x0100) >> 8 44 | rtsen = (req.value & 0x0200) >> 9 45 | 46 | if dtren: 47 | print("DTR is enabled, value", dtr) 48 | if rtsen: 49 | print("RTS is enabled, value", rts) 50 | 51 | self.device.maxusb_app.send_on_endpoint(0, b'') 52 | 53 | def handle_set_flow_ctrl_request(self, req): 54 | if self.verbose > 0: 55 | print(self.name, "received set_flow_ctrl request") 56 | 57 | if req.value == 0x000: 58 | print("SET_FLOW_CTRL to no handshaking") 59 | if req.value & 0x0001: 60 | print("SET_FLOW_CTRL for RTS/CTS handshaking") 61 | if req.value & 0x0002: 62 | print("SET_FLOW_CTRL for DTR/DSR handshaking") 63 | if req.value & 0x0004: 64 | print("SET_FLOW_CTRL for XON/XOFF handshaking") 65 | 66 | self.device.maxusb_app.send_on_endpoint(0, b'') 67 | 68 | def handle_set_baud_rate_request(self, req): 69 | if self.verbose > 0: 70 | print(self.name, "received set_baud_rate request") 71 | 72 | dtr = req.value & 0x0001 73 | print("baud rate set to", dtr) 74 | 75 | self.device.maxusb_app.send_on_endpoint(0, b'') 76 | 77 | def handle_set_data_request(self, req): 78 | if self.verbose > 0: 79 | print(self.name, "received set_data request") 80 | 81 | self.device.maxusb_app.send_on_endpoint(0, b'') 82 | 83 | def handle_get_status_request(self, req): 84 | if self.verbose > 0: 85 | print(self.name, "received get_status request") 86 | 87 | self.device.maxusb_app.send_on_endpoint(0, b'') 88 | 89 | def handle_set_event_char_request(self, req): 90 | if self.verbose > 0: 91 | print(self.name, "received set_event_char request") 92 | 93 | self.device.maxusb_app.send_on_endpoint(0, b'') 94 | 95 | def handle_set_error_char_request(self, req): 96 | if self.verbose > 0: 97 | print(self.name, "received set_error_char request") 98 | 99 | self.device.maxusb_app.send_on_endpoint(0, b'') 100 | 101 | def handle_set_latency_timer_request(self, req): 102 | if self.verbose > 0: 103 | print(self.name, "received set_latency_timer request") 104 | 105 | self.device.maxusb_app.send_on_endpoint(0, b'') 106 | 107 | def handle_get_latency_timer_request(self, req): 108 | if self.verbose > 0: 109 | print(self.name, "received get_latency_timer request") 110 | 111 | # bullshit value 112 | self.device.maxusb_app.send_on_endpoint(0, b'\x01') 113 | 114 | 115 | class USBFtdiInterface(USBInterface): 116 | name = "USB FTDI interface" 117 | 118 | def __init__(self, verbose=0): 119 | descriptors = { } 120 | 121 | endpoints = [ 122 | USBEndpoint( 123 | 1, # endpoint number 124 | USBEndpoint.direction_out, 125 | USBEndpoint.transfer_type_bulk, 126 | USBEndpoint.sync_type_none, 127 | USBEndpoint.usage_type_data, 128 | 16384, # max packet size 129 | 0, # polling interval, see USB 2.0 spec Table 9-13 130 | self.handle_data_available # handler function 131 | ), 132 | USBEndpoint( 133 | 3, # endpoint number 134 | USBEndpoint.direction_in, 135 | USBEndpoint.transfer_type_bulk, 136 | USBEndpoint.sync_type_none, 137 | USBEndpoint.usage_type_data, 138 | 16384, # max packet size 139 | 0, # polling interval, see USB 2.0 spec Table 9-13 140 | None # handler function 141 | ) 142 | ] 143 | 144 | # TODO: un-hardcode string index (last arg before "verbose") 145 | USBInterface.__init__( 146 | self, 147 | 0, # interface number 148 | 0, # alternate setting 149 | 0xff, # interface class: vendor-specific 150 | 0xff, # subclass: vendor-specific 151 | 0xff, # protocol: vendor-specific 152 | 0, # string index 153 | verbose, 154 | endpoints, 155 | descriptors 156 | ) 157 | 158 | def handle_data_available(self, data): 159 | s = data[1:] 160 | if self.verbose > 0: 161 | print(self.name, "received string", s) 162 | 163 | s = s.replace(b'\r', b'\r\n') 164 | 165 | reply = b'\x01\x00' + s 166 | 167 | self.configuration.device.maxusb_app.send_on_endpoint(3, reply) 168 | 169 | 170 | class USBFtdiDevice(USBDevice): 171 | name = "USB FTDI device" 172 | 173 | def __init__(self, maxusb_app, verbose=0): 174 | interface = USBFtdiInterface(verbose=verbose) 175 | 176 | config = USBConfiguration( 177 | 1, # index 178 | "FTDI config", # string desc 179 | [ interface ] # interfaces 180 | ) 181 | 182 | USBDevice.__init__( 183 | self, 184 | maxusb_app, 185 | 0, # device class 186 | 0, # device subclass 187 | 0, # protocol release number 188 | 64, # max packet size for endpoint 0 189 | 0x0403, # vendor id: FTDI 190 | 0x6001, # product id: FT232 USB-Serial (UART) IC 191 | 0x0001, # device revision 192 | "GoodFET", # manufacturer string 193 | "FTDI Emulator", # product string 194 | "S/N3420E", # serial number string 195 | [ config ], 196 | verbose=verbose 197 | ) 198 | 199 | self.device_vendor = USBFtdiVendor() 200 | self.device_vendor.set_device(self) 201 | 202 | -------------------------------------------------------------------------------- /usb/USBInterface.py: -------------------------------------------------------------------------------- 1 | # USBInterface.py 2 | # 3 | # Contains class definition for USBInterface. 4 | 5 | from USB import * 6 | 7 | class USBInterface: 8 | name = "generic USB interface" 9 | 10 | def __init__(self, interface_number, interface_alternate, interface_class, 11 | interface_subclass, interface_protocol, interface_string_index, 12 | verbose=0, endpoints=[], descriptors={}): 13 | 14 | self.number = interface_number 15 | self.alternate = interface_alternate 16 | self.iclass = interface_class 17 | self.subclass = interface_subclass 18 | self.protocol = interface_protocol 19 | self.string_index = interface_string_index 20 | 21 | self.endpoints = endpoints 22 | self.descriptors = descriptors 23 | 24 | self.verbose = verbose 25 | 26 | self.descriptors[USB.desc_type_interface] = self.get_descriptor 27 | 28 | self.request_handlers = { 29 | 6 : self.handle_get_descriptor_request, 30 | 11 : self.handle_set_interface_request 31 | } 32 | 33 | self.configuration = None 34 | 35 | for e in self.endpoints: 36 | e.set_interface(self) 37 | 38 | self.device_class = None 39 | self.device_vendor = None 40 | 41 | def set_configuration(self, config): 42 | self.configuration = config 43 | 44 | # USB 2.0 specification, section 9.4.3 (p 281 of pdf) 45 | # HACK: blatant copypasta from USBDevice pains me deeply 46 | def handle_get_descriptor_request(self, req): 47 | dtype = (req.value >> 8) & 0xff 48 | dindex = req.value & 0xff 49 | lang = req.index 50 | n = req.length 51 | 52 | response = None 53 | 54 | if self.verbose > 2: 55 | print(self.name, ("received GET_DESCRIPTOR req %d, index %d, " \ 56 | + "language 0x%04x, length %d") \ 57 | % (dtype, dindex, lang, n)) 58 | 59 | # TODO: handle KeyError 60 | response = self.descriptors[dtype] 61 | if callable(response): 62 | response = response(dindex) 63 | 64 | if response: 65 | n = min(n, len(response)) 66 | self.configuration.device.maxusb_app.send_on_endpoint(0, response[:n]) 67 | 68 | if self.verbose > 5: 69 | print(self.name, "sent", n, "bytes in response") 70 | 71 | def handle_set_interface_request(self, req): 72 | self.configuration.device.maxusb_app.stall_ep0() 73 | 74 | # Table 9-12 of USB 2.0 spec (pdf page 296) 75 | def get_descriptor(self): 76 | 77 | d = bytearray([ 78 | 9, # length of descriptor in bytes 79 | 4, # descriptor type 4 == interface 80 | self.number, 81 | self.alternate, 82 | len(self.endpoints), 83 | self.iclass, 84 | self.subclass, 85 | self.protocol, 86 | self.string_index 87 | ]) 88 | 89 | if self.iclass: 90 | iclass_desc_num = USB.interface_class_to_descriptor_type(self.iclass) 91 | if iclass_desc_num: 92 | d += self.descriptors[iclass_desc_num] 93 | 94 | for e in self.endpoints: 95 | d += e.get_descriptor() 96 | 97 | return d 98 | 99 | -------------------------------------------------------------------------------- /usb/USBKeyboard.py: -------------------------------------------------------------------------------- 1 | # USBKeyboard.py 2 | # 3 | # Contains class definitions to implement a USB keyboard. 4 | 5 | from USB import * 6 | from USBDevice import * 7 | from USBConfiguration import * 8 | from USBInterface import * 9 | from USBEndpoint import * 10 | 11 | class USBKeyboardInterface(USBInterface): 12 | name = "USB keyboard interface" 13 | 14 | hid_descriptor = b'\x09\x21\x10\x01\x00\x01\x22\x2b\x00' 15 | report_descriptor = b'\x05\x01\x09\x06\xA1\x01\x05\x07\x19\xE0\x29\xE7\x15\x00\x25\x01\x75\x01\x95\x08\x81\x02\x95\x01\x75\x08\x81\x01\x19\x00\x29\x65\x15\x00\x25\x65\x75\x08\x95\x01\x81\x00\xC0' 16 | 17 | def __init__(self, verbose=0): 18 | descriptors = { 19 | USB.desc_type_hid : self.hid_descriptor, 20 | USB.desc_type_report : self.report_descriptor 21 | } 22 | 23 | endpoint = USBEndpoint( 24 | 3, # endpoint number 25 | USBEndpoint.direction_in, 26 | USBEndpoint.transfer_type_interrupt, 27 | USBEndpoint.sync_type_none, 28 | USBEndpoint.usage_type_data, 29 | 16384, # max packet size 30 | 10, # polling interval, see USB 2.0 spec Table 9-13 31 | self.handle_buffer_available # handler function 32 | ) 33 | 34 | # TODO: un-hardcode string index (last arg before "verbose") 35 | USBInterface.__init__( 36 | self, 37 | 0, # interface number 38 | 0, # alternate setting 39 | 3, # interface class 40 | 0, # subclass 41 | 0, # protocol 42 | 0, # string index 43 | verbose, 44 | [ endpoint ], 45 | descriptors 46 | ) 47 | 48 | # "ls" 49 | empty_preamble = [ 0x00 ] * 10 50 | text = [ 0x0f, 0x00, 0x16, 0x00, 0x28, 0x00 ] 51 | 52 | self.keys = [ chr(x) for x in empty_preamble + text ] 53 | 54 | def handle_buffer_available(self): 55 | if not self.keys: 56 | return 57 | 58 | letter = self.keys.pop(0) 59 | self.type_letter(letter) 60 | 61 | def type_letter(self, letter, modifiers=0): 62 | data = bytes([ 0, 0, ord(letter) ]) 63 | 64 | if self.verbose > 2: 65 | print(self.name, "sending keypress 0x%02x" % ord(letter)) 66 | 67 | self.configuration.device.maxusb_app.send_on_endpoint(3, data) 68 | 69 | 70 | class USBKeyboardDevice(USBDevice): 71 | name = "USB keyboard device" 72 | 73 | def __init__(self, maxusb_app, verbose=0): 74 | config = USBConfiguration( 75 | 1, # index 76 | "Emulated Keyboard", # string desc 77 | [ USBKeyboardInterface() ] # interfaces 78 | ) 79 | 80 | USBDevice.__init__( 81 | self, 82 | maxusb_app, 83 | 0, # device class 84 | 0, # device subclass 85 | 0, # protocol release number 86 | 64, # max packet size for endpoint 0 87 | 0x610b, # vendor id 88 | 0x4653, # product id 89 | 0x3412, # device revision 90 | "Maxim", # manufacturer string 91 | "MAX3420E Enum Code", # product string 92 | "S/N3420E", # serial number string 93 | [ config ], 94 | verbose=verbose 95 | ) 96 | 97 | -------------------------------------------------------------------------------- /usb/USBMassStorage.py: -------------------------------------------------------------------------------- 1 | # USBMassStorage.py 2 | # 3 | # Contains class definitions to implement a USB mass storage device. 4 | 5 | from mmap import mmap 6 | import os 7 | 8 | from USB import * 9 | from USBDevice import * 10 | from USBConfiguration import * 11 | from USBInterface import * 12 | from USBEndpoint import * 13 | from USBClass import * 14 | 15 | from util import * 16 | 17 | class USBMassStorageClass(USBClass): 18 | name = "USB mass storage class" 19 | 20 | def setup_request_handlers(self): 21 | self.request_handlers = { 22 | 0xFF : self.handle_bulk_only_mass_storage_reset_request, 23 | 0xFE : self.handle_get_max_lun_request 24 | } 25 | 26 | def handle_bulk_only_mass_storage_reset_request(self, req): 27 | self.interface.configuration.device.maxusb_app.send_on_endpoint(0, b'') 28 | 29 | def handle_get_max_lun_request(self, req): 30 | self.interface.configuration.device.maxusb_app.send_on_endpoint(0, b'\x00') 31 | 32 | 33 | class USBMassStorageInterface(USBInterface): 34 | name = "USB mass storage interface" 35 | 36 | def __init__(self, disk_image, verbose=0): 37 | self.disk_image = disk_image 38 | descriptors = { } 39 | 40 | endpoints = [ 41 | USBEndpoint( 42 | 1, # endpoint number 43 | USBEndpoint.direction_out, 44 | USBEndpoint.transfer_type_bulk, 45 | USBEndpoint.sync_type_none, 46 | USBEndpoint.usage_type_data, 47 | 16384, # max packet size 48 | 0, # polling interval, see USB 2.0 spec Table 9-13 49 | self.handle_data_available # handler function 50 | ), 51 | USBEndpoint( 52 | 3, # endpoint number 53 | USBEndpoint.direction_in, 54 | USBEndpoint.transfer_type_bulk, 55 | USBEndpoint.sync_type_none, 56 | USBEndpoint.usage_type_data, 57 | 16384, # max packet size 58 | 0, # polling interval, see USB 2.0 spec Table 9-13 59 | None # handler function 60 | ) 61 | ] 62 | 63 | # TODO: un-hardcode string index (last arg before "verbose") 64 | USBInterface.__init__( 65 | self, 66 | 0, # interface number 67 | 0, # alternate setting 68 | 8, # interface class: Mass Storage 69 | 6, # subclass: SCSI transparent command set 70 | 0x50, # protocol: bulk-only (BBB) transport 71 | 0, # string index 72 | verbose, 73 | endpoints, 74 | descriptors 75 | ) 76 | 77 | self.device_class = USBMassStorageClass() 78 | self.device_class.set_interface(self) 79 | 80 | self.is_write_in_progress = False 81 | self.write_cbw = None 82 | self.write_base_lba = 0 83 | self.write_length = 0 84 | self.write_data = b'' 85 | 86 | def handle_data_available(self, data): 87 | print(self.name, "handling", len(data), "bytes of SCSI data") 88 | 89 | cbw = CommandBlockWrapper(data) 90 | opcode = cbw.cb[0] 91 | 92 | status = 0 # default to success 93 | response = None # with no response data 94 | 95 | if self.is_write_in_progress: 96 | if self.verbose > 0: 97 | print(self.name, "got", len(data), "bytes of SCSI write data") 98 | 99 | self.write_data += data 100 | 101 | if len(self.write_data) < self.write_length: 102 | # more yet to read, don't send the CSW 103 | return 104 | 105 | self.disk_image.put_sector_data(self.write_base_lba, self.write_data) 106 | cbw = self.write_cbw 107 | 108 | self.is_write_in_progress = False 109 | self.write_data = b'' 110 | 111 | elif opcode == 0x00: # Test Unit Ready: just return OK status 112 | if self.verbose > 0: 113 | print(self.name, "got SCSI Test Unit Ready") 114 | 115 | elif opcode == 0x03: # Request Sense 116 | if self.verbose > 0: 117 | print(self.name, "got SCSI Request Sense, data", 118 | bytes_as_hex(cbw.cb[1:])) 119 | 120 | response = b'\x70\x00\xFF\x00\x00\x00\x00\x0A\x00\x00\x00\x00\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00' 121 | 122 | elif opcode == 0x12: # Inquiry 123 | if self.verbose > 0: 124 | print(self.name, "got SCSI Inquiry, data", 125 | bytes_as_hex(cbw.cb[1:])) 126 | 127 | response = bytes([ 128 | 0x00, # 00 for Direct, 1F for "no floppy" 129 | 0x00, # make 0x80 for removable media, 0x00 for fixed 130 | 0x00, # Version 131 | 0x01, # Response Data Format 132 | 0x14, # Additional length. 133 | 0x00, 0x00, 0x00 134 | ]) 135 | 136 | response += b'GoodFET ' # vendor 137 | response += b'GoodFET ' # product id 138 | response += b' ' # product revision 139 | response += b'0.01' 140 | 141 | # pad up to data_transfer_length bytes 142 | #diff = cbw.data_transfer_length - len(response) 143 | #response += bytes([0] * diff) 144 | 145 | elif opcode == 0x1a or opcode == 0x5a: # Mode Sense (6 or 10) 146 | page = cbw.cb[2] & 0x3f 147 | 148 | if self.verbose > 0: 149 | print(self.name, "got SCSI Mode Sense, page code 0x%02x" % page) 150 | 151 | response = b'\x07\x00\x00\x00\x00\x00\x00\x1c' 152 | if page != 0x3f: 153 | print(self.name, "unkonwn page, returning empty page") 154 | response = b'\x07\x00\x00\x00\x00\x00\x00\x00' 155 | 156 | elif opcode == 0x1e: # Prevent/Allow Removal: feign success 157 | if self.verbose > 0: 158 | print(self.name, "got SCSI Prevent/Allow Removal") 159 | 160 | #elif opcode == 0x1a or opcode == 0x5a: # Mode Sense (6 or 10) 161 | # TODO 162 | 163 | elif opcode == 0x23: # Read Format Capacity 164 | if self.verbose > 0: 165 | print(self.name, "got SCSI Read Format Capacity") 166 | 167 | response = bytes([ 168 | 0x00, 0x00, 0x00, 0x08, # capacity list length 169 | 0x00, 0x00, 0x10, 0x00, # number of sectors (0x1000 = 10MB) 170 | 0x10, 0x00, # reserved/descriptor code 171 | 0x02, 0x00, # 512-byte sectors 172 | ]) 173 | 174 | elif opcode == 0x25: # Read Capacity 175 | if self.verbose > 0: 176 | print(self.name, "got SCSI Read Capacity, data", 177 | bytes_as_hex(cbw.cb[1:])) 178 | 179 | lastlba = self.disk_image.get_sector_count() 180 | 181 | response = bytes([ 182 | (lastlba >> 24) & 0xff, 183 | (lastlba >> 16) & 0xff, 184 | (lastlba >> 8) & 0xff, 185 | (lastlba ) & 0xff, 186 | 0x00, 0x00, 0x02, 0x00, # 512-byte blocks 187 | ]) 188 | 189 | elif opcode == 0x28: # Read (10) 190 | base_lba = cbw.cb[2] << 24 \ 191 | | cbw.cb[3] << 16 \ 192 | | cbw.cb[4] << 8 \ 193 | | cbw.cb[5] 194 | 195 | num_blocks = cbw.cb[7] << 8 \ 196 | | cbw.cb[8] 197 | 198 | if self.verbose > 0: 199 | print(self.name, "got SCSI Read (10), lba", base_lba, "+", 200 | num_blocks, "block(s)") 201 | 202 | 203 | # Note that here we send the data directly rather than putting 204 | # something in 'response' and letting the end of the switch send 205 | for block_num in range(num_blocks): 206 | data = self.disk_image.get_sector_data(base_lba + block_num) 207 | self.configuration.device.maxusb_app.send_on_endpoint(3, data) 208 | 209 | elif opcode == 0x2a: # Write (10) 210 | if self.verbose > 0: 211 | print(self.name, "got SCSI Write (10), data", 212 | bytes_as_hex(cbw.cb[1:])) 213 | 214 | base_lba = cbw.cb[1] << 24 \ 215 | | cbw.cb[2] << 16 \ 216 | | cbw.cb[3] << 8 \ 217 | | cbw.cb[4] 218 | 219 | num_blocks = cbw.cb[7] << 8 \ 220 | | cbw.cb[8] 221 | 222 | if self.verbose > 0: 223 | print(self.name, "got SCSI Write (10), lba", base_lba, "+", 224 | num_blocks, "block(s)") 225 | 226 | # save for later 227 | self.write_cbw = cbw 228 | self.write_base_lba = base_lba 229 | self.write_length = num_blocks * self.disk_image.block_size 230 | self.is_write_in_progress = True 231 | 232 | # because we need to snarf up the data from wire before we reply 233 | # with the CSW 234 | return 235 | 236 | elif opcode == 0x35: # Synchronize Cache (10): blindly OK 237 | if self.verbose > 0: 238 | print(self.name, "got Synchronize Cache (10)") 239 | 240 | else: 241 | print(self.name, "received unsupported SCSI opcode 0x%x" % opcode) 242 | status = 0x02 # command failed 243 | if cbw.data_transfer_length > 0: 244 | response = bytes([0] * cbw.data_transfer_length) 245 | 246 | if response: 247 | if self.verbose > 2: 248 | print(self.name, "responding with", len(response), "bytes:", 249 | bytes_as_hex(response)) 250 | 251 | self.configuration.device.maxusb_app.send_on_endpoint(3, response) 252 | 253 | csw = bytes([ 254 | ord('U'), ord('S'), ord('B'), ord('S'), 255 | cbw.tag[0], cbw.tag[1], cbw.tag[2], cbw.tag[3], 256 | 0x00, 0x00, 0x00, 0x00, 257 | status 258 | ]) 259 | 260 | if self.verbose > 3: 261 | print(self.name, "responding with status =", status) 262 | 263 | self.configuration.device.maxusb_app.send_on_endpoint(3, csw) 264 | 265 | 266 | class DiskImage: 267 | def __init__(self, filename, block_size): 268 | self.filename = filename 269 | self.block_size = block_size 270 | 271 | statinfo = os.stat(self.filename) 272 | self.size = statinfo.st_size 273 | 274 | self.file = open(self.filename, 'r+b') 275 | self.image = mmap(self.file.fileno(), 0) 276 | 277 | def close(self): 278 | self.image.flush() 279 | self.image.close() 280 | 281 | def get_sector_count(self): 282 | return int(self.size / self.block_size) - 1 283 | 284 | def get_sector_data(self, address): 285 | block_start = address * self.block_size 286 | block_end = (address + 1) * self.block_size # slices are NON-inclusive 287 | 288 | return self.image[block_start:block_end] 289 | 290 | def put_sector_data(self, address, data): 291 | block_start = address * self.block_size 292 | block_end = (address + 1) * self.block_size # slices are NON-inclusive 293 | 294 | self.image[block_start:block_end] = data[:self.block_size] 295 | self.image.flush() 296 | 297 | 298 | class CommandBlockWrapper: 299 | def __init__(self, bytestring): 300 | self.signature = bytestring[0:4] 301 | self.tag = bytestring[4:8] 302 | self.data_transfer_length = bytestring[8] \ 303 | | bytestring[9] << 8 \ 304 | | bytestring[10] << 16 \ 305 | | bytestring[11] << 24 306 | self.flags = int(bytestring[12]) 307 | self.lun = int(bytestring[13] & 0x0f) 308 | self.cb_length = int(bytestring[14] & 0x1f) 309 | #self.cb = bytestring[15:15+self.cb_length] 310 | self.cb = bytestring[15:] 311 | 312 | def __str__(self): 313 | s = "sig: " + bytes_as_hex(self.signature) + "\n" 314 | s += "tag: " + bytes_as_hex(self.tag) + "\n" 315 | s += "data transfer len: " + str(self.data_transfer_length) + "\n" 316 | s += "flags: " + str(self.flags) + "\n" 317 | s += "lun: " + str(self.lun) + "\n" 318 | s += "command block len: " + str(self.cb_length) + "\n" 319 | s += "command block: " + bytes_as_hex(self.cb) + "\n" 320 | 321 | return s 322 | 323 | 324 | class USBMassStorageDevice(USBDevice): 325 | name = "USB mass storage device" 326 | 327 | def __init__(self, maxusb_app, disk_image_filename, verbose=0): 328 | self.disk_image = DiskImage(disk_image_filename, 512) 329 | 330 | interface = USBMassStorageInterface(self.disk_image, verbose=verbose) 331 | 332 | config = USBConfiguration( 333 | 1, # index 334 | "Maxim umass config", # string desc 335 | [ interface ] # interfaces 336 | ) 337 | 338 | USBDevice.__init__( 339 | self, 340 | maxusb_app, 341 | 0, # device class 342 | 0, # device subclass 343 | 0, # protocol release number 344 | 64, # max packet size for endpoint 0 345 | 0x8107, # vendor id: Sandisk 346 | 0x5051, # product id: SDCZ2 Cruzer Mini Flash Drive (thin) 347 | 0x0003, # device revision 348 | "Maxim", # manufacturer string 349 | "MAX3420E Enum Code", # product string 350 | "S/N3420E", # serial number string 351 | [ config ], 352 | verbose=verbose 353 | ) 354 | 355 | def disconnect(self): 356 | self.disk_image.close() 357 | USBDevice.disconnect(self) 358 | 359 | -------------------------------------------------------------------------------- /usb/USBSerial.py: -------------------------------------------------------------------------------- 1 | # USBFtdi.py 2 | 3 | # Contains class definitions to implement a simple USB Serial chip, 4 | # such as the one in the HP48G+ and HP50G graphing calculators. See 5 | # usb-serial.txt in the Linux documentation for more info. 6 | 7 | from USB import * 8 | from USBDevice import * 9 | from USBConfiguration import * 10 | from USBInterface import * 11 | from USBEndpoint import * 12 | from USBVendor import * 13 | 14 | from util import * 15 | 16 | class USBSerialVendor(USBVendor): 17 | name = "USB Serial vendor" 18 | 19 | def setup_request_handlers(self): 20 | self.request_handlers = { 21 | # There are no vendor requests! 22 | # 0 : self.handle_reset_request, 23 | # 1 : self.handle_modem_ctrl_request, 24 | # 2 : self.handle_set_flow_ctrl_request, 25 | # 3 : self.handle_set_baud_rate_request, 26 | # 4 : self.handle_set_data_request, 27 | # 5 : self.handle_get_status_request, 28 | # 6 : self.handle_set_event_char_request, 29 | # 7 : self.handle_set_error_char_request, 30 | # 9 : self.handle_set_latency_timer_request, 31 | # 10 : self.handle_get_latency_timer_request 32 | } 33 | 34 | 35 | class USBSerialInterface(USBInterface): 36 | name = "USB Serial interface" 37 | 38 | def __init__(self, verbose=0): 39 | descriptors = { } 40 | 41 | endpoints = [ 42 | USBEndpoint( 43 | 1, # endpoint number 44 | USBEndpoint.direction_out, 45 | USBEndpoint.transfer_type_bulk, 46 | USBEndpoint.sync_type_none, 47 | USBEndpoint.usage_type_data, 48 | 16384, # max packet size 49 | 0, # polling interval, see USB 2.0 spec Table 9-13 50 | self.handle_data_available # handler function 51 | ), 52 | USBEndpoint( 53 | 3, # endpoint number 54 | USBEndpoint.direction_in, 55 | USBEndpoint.transfer_type_bulk, 56 | USBEndpoint.sync_type_none, 57 | USBEndpoint.usage_type_data, 58 | 16384, # max packet size 59 | 0, # polling interval, see USB 2.0 spec Table 9-13 60 | None # handler function 61 | ) 62 | ] 63 | 64 | # TODO: un-hardcode string index (last arg before "verbose") 65 | USBInterface.__init__( 66 | self, 67 | 0, # interface number 68 | 0, # alternate setting 69 | 0xff, # interface class: vendor-specific 70 | 0xff, # subclass: vendor-specific 71 | 0xff, # protocol: vendor-specific 72 | 0, # string index 73 | verbose, 74 | endpoints, 75 | descriptors 76 | ) 77 | 78 | def handle_data_available(self, data): 79 | s=data; 80 | if self.verbose > 0: 81 | print(self.name, "received string", s) 82 | 83 | s = s.replace(b'\r', b'\r\n') 84 | 85 | reply = s 86 | 87 | self.configuration.device.maxusb_app.send_on_endpoint(3, reply) 88 | 89 | 90 | class USBSerialDevice(USBDevice): 91 | name = "USB Serial device" 92 | 93 | def __init__(self, maxusb_app, verbose=0): 94 | interface = USBSerialInterface(verbose=verbose) 95 | 96 | config = USBConfiguration( 97 | 1, # index 98 | "Serial config", # string desc 99 | [ interface ] # interfaces 100 | ) 101 | 102 | USBDevice.__init__( 103 | self, 104 | maxusb_app, 105 | 0, # device class 106 | 0, # device subclass 107 | 0, # protocol release number 108 | 64, # max packet size for endpoint 0 109 | 0x03f0, # vendor id: HP 110 | 0x0121, # product id: HP50G 111 | 0x0001, # device revision 112 | "GoodFET", # manufacturer string 113 | "HP4X Emulator", # product string 114 | "12345", # serial number string 115 | [ config ], 116 | verbose=verbose 117 | ) 118 | 119 | self.device_vendor = USBSerialVendor() 120 | self.device_vendor.set_device(self) 121 | 122 | -------------------------------------------------------------------------------- /usb/USBVendor.py: -------------------------------------------------------------------------------- 1 | # USBVendor.py 2 | # 3 | # Contains class definition for USBVendor, intended as a base class (in the OO 4 | # sense) for implementing device vendors. 5 | 6 | class USBVendor: 7 | name = "generic USB device vendor" 8 | 9 | # maps bRequest to handler function 10 | request_handlers = { } 11 | 12 | def __init__(self, verbose=0): 13 | self.device = None 14 | self.verbose = verbose 15 | 16 | self.setup_request_handlers() 17 | 18 | def set_device(self, device): 19 | self.device = device 20 | 21 | def setup_request_handlers(self): 22 | """To be overridden for subclasses to modify self.request_handlers""" 23 | pass 24 | 25 | -------------------------------------------------------------------------------- /usb/util.py: -------------------------------------------------------------------------------- 1 | # util.py 2 | # 3 | # Random helpful functions. 4 | 5 | def bytes_as_hex(b, delim=" "): 6 | return delim.join(["%02x" % x for x in b]) 7 | 8 | --------------------------------------------------------------------------------