diff --git a/README.rst b/README.rst index 2cfe31f..33777bf 100644 --- a/README.rst +++ b/README.rst @@ -1,61 +1,50 @@ -Umap2 +nü-map ===== -Umap2 is the second revision of NCC Group's +nü-map (or nümap/numap) is the second revision of NCC Group's python based USB host security assessment tool. This revision will have all the features that were supported in the first revision: -- *umap2emulate* - USB device emulation -- *umap2scan* - USB host scanning for device support -- *umap2detect* - USB host OS detection (no implemented yet) -- *umap2fuzz* - USB host fuzzing +- *numap-emulate* - USB device emulation +- *numap-scan* - USB host scanning for device support +- *numap-detect* - USB host OS detection (no implemented yet) +- *numap-fuzz* - USB host fuzzing In this revision there will be some additional features: - USB host fuzzing uses kitty as fuzzing engine -- Umap2 not only contains executable scripts, +- nümap not only contains executable scripts, but is also installed as a package and may be used as a library -Umap2 is developed by NCC Group and Cisco SAS team. +nümap was developed by NCC Group and Cisco SAS team. +The numap modernization is developed by the FaceDancer team, incluing +@ktemkin and Great Scott Gadgets, LLC. Most of the credit still goes to the original authors. -Warning: umap2 is still an experimental, -alpha stage tool. -The APIs, executable names, etc. are likely to be changed -in the near future. -Use at your own risk. -Support -------- - -- IRC: `#umap2 on Freenode `_ Installation ------------ Since this is a very early version, -Umap2 is not yet available from pypi, +nümap is not yet available from pypi, instead, use pip to install it directly from github: :: - $ pip install git+https://github.com/nccgroup/umap2.git#egg=umap2 - -Python Versions ---------------- + $ pip install git+https://github.com/usb-tools/nu-map.git#egg=numap -Umap2 is python2/3 compatible for most parts. -However, the fuzzer script (**umap2kitty**) runs only on python2. "Soft" Dependencies ------------------- -Umap2's dependencies are listed in **setup.py** and will be installed with umap2, +nümap's dependencies are listed in **setup.py** and will be installed with numap, however, there are couple of things that you might want to do to add support for some devices: + Mass Storage ~~~~~~~~~~~~ @@ -73,11 +62,11 @@ Hardware -------- - `Facedancer `_ - is the recommended hardware for Umap2. - Umap2 was developed based on it, and you'll get the most support with it. + is the recommended hardware for nümap. + nümap was developed based on it, and you'll get the most support with it. - `Raspdancer ` is supported on RPi - **GadgetFS** is partially supported. - This support is very experimental (even more than the rest of Umap2) + This support is very experimental (even more than the rest of nümap) and limited. - BeagleboneBlack starting from Linux kernel 4.4.9 with a patched gadgetfs @@ -96,52 +85,52 @@ Usage Device Emulation ~~~~~~~~~~~~~~~~ -Umap2's basic functionallity is emulating a USB device. +nümap's basic functionallity is emulating a USB device. You can emulate one of the existing devices -(use **umap2list** to see the available devices): +(use **numap-list** to see the available devices): :: - $ umap2emulate -P fd:/dev/ttyUSB0 -C mass_storage + $ numap-emulate -P fd:/dev/ttyUSB0 -C mass_storage or emulate your own device: :: - $ umap2emulate -P fd:/dev/ttyUSB0 -C ~/my_mass_storage.py + $ numap-emulate -P fd:/dev/ttyUSB0 -C ~/my_mass_storage.py A detailed guide to add your device will be added soon, -in the meantime, you can take a look at umap2 devices -under *umap2/dev/* +in the meantime, you can take a look at numap devices +under *numap/dev/* Device Support Scanning ~~~~~~~~~~~~~~~~~~~~~~~ -Umap2 can attempt to detect what types of USB devices +nümap can attempt to detect what types of USB devices are supported by the host. -It is done by emulating each device that is implemented in Umap2 +It is done by emulating each device that is implemented in nümap for a short period of time, and checking whether a device-specific message was sent. :: - $ umap2scan -P fd:/dev/ttyUSB0 + $ numap-scan -P fd:/dev/ttyUSB0 Vendor Specific Device Support Scanning ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ In addition for scanning support of various device classes, -Umap2 can scan the host for support of vendor specific devices. +nümap can scan the host for support of vendor specific devices. Vendor specific scanning helps identifying the vendor specific drivers that are available on the host. -This can be done using the current Umap2 VID-PID DB (still working on it), +This can be done using the current nümap VID-PID DB (still working on it), or another file in the same format: :: - $ umap2vsscan -P fd:/dev/ttyUSB0 -d $UMAP2_DIR/data/vid_pid_db.py + $ numap-vsscan -P fd:/dev/ttyUSB0 -d $UMAP2_DIR/data/vid_pid_db.py Or by scanning a specific vid-pid range - in this example - @@ -150,17 +139,17 @@ and PID from 0x0000 to 0xffff: :: - $ umap2vsscan -P fd:/dev/ttyUSB0 -s 1001-1004:0000-ffff + $ numap-vsscan -P fd:/dev/ttyUSB0 -s 1001-1004:0000-ffff Any patches/additions to the vid_pid_db.py file are very welcome! Fuzzing ~~~~~~~ -A detailed guide for fuzzing using Umap2 can be found in -`docs/fuzzing.rst `_ +A detailed guide for fuzzing using nümap can be found in +`docs/fuzzing.rst `_ -Fuzzing with Umap2 is composed of three steps, +Fuzzing with nümap is composed of three steps, which might be unified into a single script in the future. 1. Find out what is the order of messages @@ -169,45 +158,34 @@ which might be unified into a single script in the future. :: - $ umap2stages -P fd:/dev/ttyUSB0 -C keyboard -s keyboard.stages + $ numap-stages -P fd:/dev/ttyUSB0 -C keyboard -s keyboard.stages 2. Start the kitty fuzzer in a separate shell, and provide it with the stages generated in step 1. :: - $ umap2kitty -s keyboard.stages + $ numap-kitty -s keyboard.stages -3. Start the umap2 keyboard emulation in fuzz mode +3. Start the numap keyboard emulation in fuzz mode :: - $ umap2fuzz -P fd:/dev/ttyUSB0 -C keyboard + $ numap-fuzz -P fd:/dev/ttyUSB0 -C keyboard After stage 3 is performed, the fuzzing session will begin. Note About MTP fuzzing ++++++++++++++++++++++ -While umap2 may be used to emulate and discover MTP devices +While numap may be used to emulate and discover MTP devices (see "Soft dependencies" section of this README), it does not fuzz the MTP layer at this point. In order to fuzz the MTP layer, you can use the fuzzer embedded in the MTP library. -We plan to support MTP fuzzing directly from umap2 in future releases. +We plan to support MTP fuzzing directly from numap in future releases. Host OS Detection ~~~~~~~~~~~~~~~~~ TBD - -Toubleshooting --------------- - -If you have issues with Umap2, try the -`troubleshooting section `_ - -Raspdancer --------------- -You need SPI-Py and GPIO python libraries. -Use phy `rd` diff --git a/data/vid_pid_db.py b/data/vid_pid_db.py index de16d9a..3bd9316 100644 --- a/data/vid_pid_db.py +++ b/data/vid_pid_db.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from umap2.apps.vsscan import DBEntry, OS +from numap.apps.vsscan import DBEntry, OS import sys db = [ @@ -6185,8 +6185,8 @@ history = set() for db_entry in db: if (db_entry.vid, db_entry.pid) in history: - print 'Duplicate found: 0x%04x, 0x%04x' % (db_entry.vid, db_entry.pid) + print('Duplicate found: 0x%04x, 0x%04x' % (db_entry.vid, db_entry.pid)) sys.exit(1) else: history.add((db_entry.vid, db_entry.pid)) - print 'DB OK!' + print('DB OK!') diff --git a/data/vid_pid_db_from_usb_ids.py b/data/vid_pid_db_from_usb_ids.py index 6f3b8db..8f05a0c 100644 --- a/data/vid_pid_db_from_usb_ids.py +++ b/data/vid_pid_db_from_usb_ids.py @@ -5,7 +5,7 @@ It is much more extensive than vid_pid_db.py, and contains no information about the drivers. ''' -from umap2.apps.vsscan import DBEntry, OS +from numap.apps.vsscan import DBEntry, OS import sys db = [ @@ -15492,8 +15492,8 @@ history = set() for db_entry in db: if (db_entry.vid, db_entry.pid) in history: - print 'Duplicate found: 0x%04x, 0x%04x' % (db_entry.vid, db_entry.pid) + print('Duplicate found: 0x%04x, 0x%04x' % (db_entry.vid, db_entry.pid)) sys.exit(1) else: history.add((db_entry.vid, db_entry.pid)) - print 'DB OK!' + print('DB OK!') diff --git a/docs/fuzzing.rst b/docs/fuzzing.rst index 458b0ec..8c156ed 100644 --- a/docs/fuzzing.rst +++ b/docs/fuzzing.rst @@ -1,7 +1,7 @@ Fuzzing USB Hosts ================= -This document describes the full process of fuzzing USB hosts with Umap2. +This document describes the full process of fuzzing USB hosts with nümap. Fuzzing a host consists of three basic steps. While the fuzzer does not require you to write any code, @@ -18,11 +18,11 @@ Step 1 - Select Device ~~~~~~~~~~~~~~~~~~~~~~ First, you need to detrmine which driver/subsystem/class you want to fuzz. -You can find out what is supported on the host by running ``umap2scan``: +You can find out what is supported on the host by running ``numapscan``: :: - $ umap2scan -P + $ numapscan -P Step 2 - Record Valid Flow ~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -32,11 +32,11 @@ and decided on a device that you want to emulate in your fuzzing, you need to record the basic communication flow of the host with this device. This step is essential for effective fuzzing, as different hosts communicate with the same device in different ways. -The tool to do that is ``umap2stages``: +The tool to do that is ``numapstages``: :: - $ umap2stages -P -C -s + $ numapstages -P -C -s Step 3 - Start Fuzzing ~~~~~~~~~~~~~~~~~~~~~~ @@ -48,24 +48,24 @@ First, you need to start the kitty-based fuzzer backend in a separate shell: :: - $ umap2kitty -s + $ numapkitty -s There are some command line options available in kitty, you can see them in the next section. -Now you need to start the umap2 stack. +Now you need to start the numap stack. Once it is up, it will signal the fuzzer backend and the fuzzing proces will begin :: - $ umap2fuzz -P -C + $ numapfuzz -P -C Monitor Progress ~~~~~~~~~~~~~~~~ -The output from ``umap2kitty`` and ``umap2fuzz`` is not meant as a UI. +The output from ``numapkitty`` and ``numapfuzz`` is not meant as a UI. If you want to watch the fuzzing session, use Kitty's web UI, which is available either from the browser at http://localhost:26001. Or using ``kitty-web-client`` which is a command line tool to retrieve @@ -78,13 +78,13 @@ reports about failed tests. Fuzzing Options --------------- -By passing command line parameters to ``umap2kitty`` you can achieve a better fuzzing session. +By passing command line parameters to ``numapkitty`` you can achieve a better fuzzing session. Here are some examples of such parameters. To see all of the fuzzer options, run :: - $ umap2kitty -s -k "--help" + $ numapkitty -s -k "--help" These are the options to the kitty fuzzer object, not the options to the runner. @@ -111,21 +111,21 @@ Fuzzing session file allows you to store the results of a run. It has three main 1. Continue a fuzzing session that was stopped for some reason: :: - $ umap2kitty -s -k "-f mysessionfile" + $ numapkitty -s -k "-f mysessionfile" # closed terminal by mistake :( # # Continue from the same place :) - $ umap2kitty -s -k "-f mysessionfile" + $ numapkitty -s -k "-f mysessionfile" 2. Review the failures from finished session (If for some reason the reports were not retreived). 3. **Most important** - retest failures. This allows you to retest crashes and make sure that the crash is reproducible, or debug it, or test on a new version. :: # first run - failures on tests 1,7,10 - $ umap2kitty -s -k "-f mysessionfile" + $ numapkitty -s -k "-f mysessionfile" # re-run only failures (tests 1,7,10) - $ umap2kitty -s -k "-r mysessionfile" + $ numapkitty -s -k "-r mysessionfile" Running Only a Subset of the tests @@ -137,6 +137,6 @@ For example - run tests 1,2,3,45,7,9 and everything above 100 :: - $ umap2kitty -s -k "-t 1-5,7,9,100-" + $ numapkitty -s -k "-t 1-5,7,9,100-" diff --git a/gadget/README.rst b/gadget/README.rst index a830819..08ade9a 100644 --- a/gadget/README.rst +++ b/gadget/README.rst @@ -1,17 +1,17 @@ -Using Umap2 with GadgetFS +Using nümap with GadgetFS ========================= Preface ------- -Umap and Umap2 were developed based on the Facedancer - +Umap and nümap were developed based on the Facedancer - it had some advantages over other solutions, and allowed python emulation of USB devices. It also enabled any PC to act as a USB device, by only using a small hardware device that speaks over a very common channel with the PC (UART over USB). -However, with Umap2, we wanted to decouple the device emulation (e.g. Umap2) +However, with nümap, we wanted to decouple the device emulation (e.g. nümap) from the physical interface that it speaks over. Support for GadgetFS is our first attempt to support a USB device hardware other than Facedancer. @@ -22,8 +22,8 @@ allowing the implementation of USB devices on Linux based machines GadgetFS is a kernel module that provides user space applications a File-System based interface to the USB gadget subsystem. -This is a guide on how to run Umap2 on a GadgetFS-enabled device, -it details the limitations and usage for Umap2 on such a device. +This is a guide on how to run nümap on a GadgetFS-enabled device, +it details the limitations and usage for nümap on such a device. Limitations ----------- @@ -34,11 +34,11 @@ Limitations and use the branch **am33x-v4.7**. Read the installation instructions below for more information. - Setup request frame data is not supported at the moment, - this affects couple of devices that will not be emulated properly by Umap2. + this affects couple of devices that will not be emulated properly by nümap. - In some cases, a disconnection in the gadget FS stack is not handled properly. This causes some devices to malfunction in certain cases. - The GadgetFS kernel module requires some modifications (provided here) -- You need to run umap2 as root +- You need to run numap as root Installation ------------ @@ -72,13 +72,13 @@ we provide 2 versions of the patched inode.c (gadgetfs module source): At this point, you should have: -- umap2 installed. +- numap installed. - the kernel module is at: /root/gadgetfs.ko -Running Umap2 +Running nümap ------------- -Before you run Umap2, you need to unload each module that uses the USB gadget +Before you run nümap, you need to unload each module that uses the USB gadget subsystem, and load the modified gadgetfs module. You can use the following script to do that: @@ -89,8 +89,8 @@ or for Raspberry Pi Zero W : $ $UMAP2_HOME/gadget/start_gadgetfs_RaspiZeroW.sh Once the new module is loaded, -you can run Umap2 as described in the README.rst in the root of the repository, +you can run nümap as described in the README.rst in the root of the repository, But specify ``-P gadgetfs`` in the command line -to use gadgetfs as the physical layer of Umap2. +to use gadgetfs as the physical layer of nümap. **HAPPY HACKING :)** diff --git a/gadget/inode.c-v4.4.9 b/gadget/inode.c-v4.4.9 index 1d523aa..9b43ad6 100644 --- a/gadget/inode.c-v4.4.9 +++ b/gadget/inode.c-v4.4.9 @@ -65,7 +65,7 @@ * selective clearing of endpoint halts, to implement SET_INTERFACE. */ -#define DRIVER_DESC "USB Gadget filesystem - Umap2 patches" +#define DRIVER_DESC "USB Gadget filesystem - numap patches" #define DRIVER_VERSION "24 Aug 2004" static const char driver_desc [] = DRIVER_DESC; diff --git a/gadget/inode.c-v4.6_and_up b/gadget/inode.c-v4.6_and_up index 5045b2f..95acf2c 100644 --- a/gadget/inode.c-v4.6_and_up +++ b/gadget/inode.c-v4.6_and_up @@ -65,7 +65,7 @@ * selective clearing of endpoint halts, to implement SET_INTERFACE. */ -#define DRIVER_DESC "USB Gadget filesystemi - Umap2 patches" +#define DRIVER_DESC "USB Gadget filesystemi - numap patches" #define DRIVER_VERSION "24 Aug 2004" static const char driver_desc [] = DRIVER_DESC; diff --git a/gadget/start_gadgetfs.sh b/gadget/start_gadgetfs.sh index 2d79a04..1b6a215 100755 --- a/gadget/start_gadgetfs.sh +++ b/gadget/start_gadgetfs.sh @@ -48,7 +48,7 @@ if [ -f $MODULE_PATH ]; then echo "- Mounting gadgetfs to /dev/gadget" mount -t gadgetfs none /dev/gadget - echo "-- System is ready for Umap2 --" + echo "-- System is ready for numap --" else echo "$MODULE_PATH was not found, not performing changes" fi diff --git a/umap2/__init__.py b/numap/__init__.py similarity index 52% rename from umap2/__init__.py rename to numap/__init__.py index 8cabd9a..d86e979 100644 --- a/umap2/__init__.py +++ b/numap/__init__.py @@ -1,7 +1,7 @@ # # Add debug levels/functions # -from umap2.utils.ulogger import prepare_logging +from numap.utils.ulogger import prepare_logging prepare_logging() diff --git a/numap/apps/__init__.py b/numap/apps/__init__.py new file mode 100644 index 0000000..910342b --- /dev/null +++ b/numap/apps/__init__.py @@ -0,0 +1,3 @@ +''' +nümap applications +''' diff --git a/umap2/apps/base.py b/numap/apps/base.py similarity index 72% rename from umap2/apps/base.py rename to numap/apps/base.py index 85c5712..e1a1029 100644 --- a/umap2/apps/base.py +++ b/numap/apps/base.py @@ -1,19 +1,18 @@ ''' -Umap2 applications should subclass the Umap2App. +nümap applications should subclass the NumapApp. ''' import sys import os import importlib import logging import docopt -from serial import Serial, PARITY_NONE -from umap2.phy.facedancer.max342x_phy import Max342xPhy -from umap2.phy.gadgetfs.gadgetfs_phy import GadgetFsPhy -from umap2.utils.ulogger import set_default_handler_level +# TODO: replace FaceDancerPhy with just FaceDancerApp +from facedancer import FacedancerUSBApp +from numap.utils.ulogger import set_default_handler_level -class Umap2App(object): +class NumapApp(object): def __init__(self, docstring=None): if docstring is not None: @@ -43,11 +42,11 @@ def get_logger(self): levels = { 0: logging.INFO, 1: logging.DEBUG, - # verbose is added by umap2.__init__ module + # verbose is added by numap.__init__ module 2: logging.VERBOSE, } verbose = self.options.get('--verbose', 0) - logger = logging.getLogger('umap2') + logger = logging.getLogger('numap') if verbose in levels: set_default_handler_level(levels[verbose]) else: @@ -57,35 +56,14 @@ def get_logger(self): return logger def load_phy(self, phy_string): - self.logger.info('Loading physical interface: %s' % phy_string) - phy_arr = phy_string.split(':') - phy_type = phy_arr[0] - if phy_type == 'fd': - self.logger.debug('Physical interface is facedancer') - dev_name = phy_arr[1] - s = Serial(dev_name, 115200, parity=PARITY_NONE, timeout=2) - # fd = Facedancer(s) - phy = Max342xPhy(self, s) - return phy - elif phy_type == 'rd': - try: - from umap2.phy.raspdancer.raspdancer_phy import RaspdancerPhy - self.logger.debug('Physical interface is raspdancer') - phy = RaspdancerPhy(self) - return phy - except ImportError: - raise Exception('Raspdancer support misses spi module and/or gpio module.') - elif phy_type == 'gadgetfs': - self.logger.debug('Physical interface is GadgetFs') - phy = GadgetFsPhy(self) - return phy - raise Exception('Phy type not supported: %s' % phy_type) + # TODO: support options; bring GadgetFS into FaceDancer2? + return FacedancerUSBApp() def load_device(self, dev_name, phy): if dev_name in self.umap_classes: self.logger.info('Loading USB device %s' % dev_name) module_name = self.umap_class_dict[dev_name][0] - module = importlib.import_module('umap2.dev.%s' % module_name) + module = importlib.import_module('numap.dev.%s' % module_name) else: self.logger.info('Loading custom USB device from file: %s' % dev_name) dirpath, filename = os.path.split(dev_name) diff --git a/umap2/apps/detect_os.py b/numap/apps/detect_os.py similarity index 76% rename from umap2/apps/detect_os.py rename to numap/apps/detect_os.py index b49cdfa..abd7828 100644 --- a/umap2/apps/detect_os.py +++ b/numap/apps/detect_os.py @@ -3,7 +3,7 @@ Not implemented yet. Usage: - umap2detect -P=PHY_INFO [-q] [-v ...] + numapdetect [-P=PHY_INFO] [-q] [-v ...] Options: -P --phy PHY_INFO physical layer info, see list below @@ -15,19 +15,19 @@ gadgetfs use gadgetfs (requires mounting of gadgetfs beforehand) Example: - umap2detect -P fd:/dev/ttyUSB0 -q + numapdetect -P fd:/dev/ttyUSB0 -q ''' -from umap2.apps.base import Umap2App +from numap.apps.base import NumapApp -class Umap2DetectOSApp(Umap2App): +class NumapDetectOSApp(NumapApp): def run(self): self.logger.error('OS detection is not implemented yet') def main(): - app = Umap2DetectOSApp(__doc__) + app = NumapDetectOSApp(__doc__) app.run() diff --git a/umap2/apps/emulate.py b/numap/apps/emulate.py similarity index 76% rename from umap2/apps/emulate.py rename to numap/apps/emulate.py index afcf5f0..bf74f87 100644 --- a/umap2/apps/emulate.py +++ b/numap/apps/emulate.py @@ -3,10 +3,10 @@ Emulate a USB device Usage: - umap2emulate -P=PHY_INFO -C=DEVICE_CLASS [-q] [--vid=VID] [--pid=PID] [-v ...] + numapemulate -C=DEVICE_CLASS [-P=PHY_INFO] [-q] [--vid=VID] [--pid=PID] [-v ...] Options: - -P --phy PHY_INFO physical layer info, see list below + -P --phy PHY_INFO physical layer info, see list below [default: auto] -C --class DEVICE_CLASS class of the device or path to python file with device class -v --verbose verbosity level -q --quiet quiet mode. only print warning/error messages @@ -16,19 +16,22 @@ Physical layer: fd: use facedancer connected to given serial port gadgetfs use gadgetfs (requires mounting of gadgetfs beforehand) + auto automatically detect how we should connect Examples: emulate keyboard: - umap2emulate -P fd:/dev/ttyUSB1 -C keyboard + numapemulate -P fd:/dev/ttyUSB1 -C keyboard emulate your own device: - umap2emulate -P fd:/dev/ttyUSB1 -C my_usb_device.py + numapemulate -P fd:/dev/ttyUSB1 -C my_usb_device.py ''' import traceback -from umap2.apps.base import Umap2App +from numap.apps.base import NumapApp -class Umap2EmulationApp(Umap2App): +class NumapEmulationApp(NumapApp): + + verbose = 0 def run(self): self.fuzzer = self.get_fuzzer() @@ -49,7 +52,7 @@ def get_fuzzer(self): def main(): - app = Umap2EmulationApp(__doc__) + app = NumapEmulationApp(__doc__) app.run() diff --git a/umap2/apps/fuzz.py b/numap/apps/fuzz.py similarity index 91% rename from umap2/apps/fuzz.py rename to numap/apps/fuzz.py index 0574987..193eae0 100644 --- a/umap2/apps/fuzz.py +++ b/numap/apps/fuzz.py @@ -3,7 +3,7 @@ Emulate a USB device to be used for fuzzing Usage: - umap2fuzz -P=PHY_INFO -C=DEVICE_CLASS [-q] [--vid=VID] [--pid=PID] [-i=FUZZER_IP] [-p FUZZER_PORT] [-v ...] + numapfuzz-C=DEVICE_CLASS [-P=PHY_INFO] [-q] [--vid=VID] [--pid=PID] [-i=FUZZER_IP] [-p FUZZER_PORT] [-v ...] Options: -P --phy PHY_INFO physical layer info, see list below @@ -21,18 +21,18 @@ Examples: emulate disk-on-key: - umap2fuzz -P fd:/dev/ttyUSB1 -C mass_storage + numapfuzz -P fd:/dev/ttyUSB1 -C mass_storage ''' import os import time from kitty.remote.rpc import RpcClient -from umap2.apps.emulate import Umap2EmulationApp +from numap.apps.emulate import NumapEmulationApp -class Umap2FuzzApp(Umap2EmulationApp): +class NumapFuzzApp(NumapEmulationApp): def __init__(self, options): - super(Umap2FuzzApp, self).__init__(options) + super(NumapFuzzApp, self).__init__(options) self.count = 0 def get_fuzzer(self): @@ -106,7 +106,7 @@ def get_mutation(self, stage, data=None): def main(): - app = Umap2FuzzApp(__doc__) + app = NumapFuzzApp(__doc__) app.run() diff --git a/umap2/apps/list_classes.py b/numap/apps/list_classes.py similarity index 63% rename from umap2/apps/list_classes.py rename to numap/apps/list_classes.py index cb37d31..97bb7f7 100644 --- a/umap2/apps/list_classes.py +++ b/numap/apps/list_classes.py @@ -3,22 +3,22 @@ List default classes supported in umap Usage: - umap2list [-v] + numaplist [-v] Options: -v, --verbose show more information ''' -from umap2.apps.base import Umap2App +from numap.apps.base import NumapApp -class Umap2ListClassesApp(Umap2App): +class NumapListClassesApp(NumapApp): def run(self): ks = self.umap_classes verbose = self.options.get('--verbose', False) if verbose: - print '%-20s %s' % ('Device', 'Description') - print '-------------------- ----------------------------------------------------' + print('%-20s %s' % ('Device', 'Description')) + print('-------------------- ----------------------------------------------------') for k in ks: if verbose: print('%-20s %s' % (k, self.umap_class_dict[k][1])) @@ -27,7 +27,7 @@ def run(self): def main(): - app = Umap2ListClassesApp(__doc__) + app = NumapListClassesApp(__doc__) app.run() diff --git a/umap2/apps/makestages.py b/numap/apps/makestages.py similarity index 79% rename from umap2/apps/makestages.py rename to numap/apps/makestages.py index 0645b55..b01ac70 100644 --- a/umap2/apps/makestages.py +++ b/numap/apps/makestages.py @@ -3,7 +3,7 @@ Prepare stages for USB fuzzing Usage: - umap2stages -P=PHY_INFO -C=DEVICE_CLASS -s=FILE [-q] [--vid=VID] [--pid=PID] [-v ...] + numapstages -C=DEVICE_CLASS [-P=PHY_INFO] -s=FILE [-q] [--vid=VID] [--pid=PID] [-v ...] Options: -P --phy PHY_INFO physical layer info, see list below @@ -19,11 +19,11 @@ gadgetfs use gadgetfs (requires mounting of gadgetfs beforehand) ''' import time -from umap2.apps.emulate import Umap2EmulationApp -from umap2.fuzz.helpers import StageLogger, set_stage_logger +from numap.apps.emulate import NumapEmulationApp +from numap.fuzz.helpers import StageLogger, set_stage_logger -class Umap2MakeStagesApp(Umap2EmulationApp): +class NumapMakeStagesApp(NumapEmulationApp): def load_device(self, dev_name, phy): self.start_time = time.time() @@ -31,7 +31,7 @@ def load_device(self, dev_name, phy): stage_logger = StageLogger(self.stage_file_name) stage_logger.start() set_stage_logger(stage_logger) - return super(Umap2MakeStagesApp, self).load_device(dev_name, phy) + return super(NumapMakeStagesApp, self).load_device(dev_name, phy) def should_stop_phy(self): stop_phy = False @@ -43,7 +43,7 @@ def should_stop_phy(self): def main(): - app = Umap2MakeStagesApp(__doc__) + app = NumapMakeStagesApp(__doc__) app.run() diff --git a/umap2/apps/scan.py b/numap/apps/scan.py similarity index 91% rename from umap2/apps/scan.py rename to numap/apps/scan.py index 564256c..99a9852 100644 --- a/umap2/apps/scan.py +++ b/numap/apps/scan.py @@ -2,7 +2,7 @@ Scan device support in USB host Usage: - umap2scan -P=PHY_INFO [-q] [-v ...] + numapscan [-P=PHY_INFO] [-q] [-v ...] Options: -P --phy PHY_INFO physical layer info, see list below @@ -14,17 +14,17 @@ gadgetfs use gadgetfs (requires mounting of gadgetfs beforehand) Example: - umap2scan -P fd:/dev/ttyUSB0 -q + numapscan -P fd:/dev/ttyUSB0 -q ''' import time import traceback -from umap2.apps.base import Umap2App +from numap.apps.base import NumapApp -class Umap2ScanApp(Umap2App): +class NumapScanApp(NumapApp): def __init__(self, options): - super(Umap2ScanApp, self).__init__(options) + super(NumapScanApp, self).__init__(options) self.current_usb_function_supported = False self.start_time = 0 @@ -76,7 +76,7 @@ def should_stop_phy(self): def main(): - app = Umap2ScanApp(__doc__) + app = NumapScanApp(__doc__) app.run() diff --git a/umap2/apps/vsscan.py b/numap/apps/vsscan.py similarity index 95% rename from umap2/apps/vsscan.py rename to numap/apps/vsscan.py index a25664e..7346ca0 100644 --- a/umap2/apps/vsscan.py +++ b/numap/apps/vsscan.py @@ -2,7 +2,7 @@ Scan USB host for vendor specific device support Usage: - umap2vsscan -P=PHY_INFO [-q] [-d=DB_FILE] [-s=VID:PID] [-t=TIMEOUT] [-z|-b=DELAY] [-r=RESUME_FILE] [-o=OS] [-e] [-v ...] + numapvsscan [-P=PHY_INFO] [-q] [-d=DB_FILE] [-s=VID:PID] [-t=TIMEOUT] [-z|-b=DELAY] [-r=RESUME_FILE] [-o=OS] [-e] [-v ...] Options: -P --phy PHY_INFO physical layer info, see list below @@ -23,7 +23,7 @@ DB_FILE: a python file with a db member which is a list of DBEntry() objects. - a sample can be found at: umap2/data/vid_pid_db.py + a sample can be found at: numap/data/vid_pid_db.py OS: Linux, Windows, OSX, QNX @@ -33,9 +33,9 @@ Examples: scan using a db file with 5 seconds timeout and 2 seconds delay between tries - $ umap2vsscan -P fd:/dev/ttyUSB0 -d vid_pid_db.py -t 5 -b 2 + $ numapvsscan -P fd:/dev/ttyUSB0 -d vid_pid_db.py -t 5 -b 2 scan using facedancer a specific vid:pid with 5 seconds timeout - $ umap2vsscan -P fd:/dev/ttyUSB0 -s 2058:1005 -t 5 + $ numapvsscan -P fd:/dev/ttyUSB0 -s 2058:1005 -t 5 ''' import time import traceback @@ -44,8 +44,8 @@ import sys import six from six.moves import cPickle -from umap2.apps.base import Umap2App -from umap2.dev.vendor_specific import USBVendorSpecificDevice +from numap.apps.base import NumapApp +from numap.dev.vendor_specific import USBVendorSpecificDevice class OS(object): @@ -105,10 +105,10 @@ def __init__(self): self.current = 0 -class Umap2VSScanApp(Umap2App): +class NumapVSScanApp(NumapApp): def __init__(self, options): - super(Umap2VSScanApp, self).__init__(options) + super(NumapVSScanApp, self).__init__(options) self.current_usb_function_supported = False self.scan_session = _ScanSession() self.start_time = 0 @@ -149,7 +149,7 @@ def load_db_from_file(self, db_file): if dirpath in sys.path: sys.path.remove(dirpath) sys.path.insert(0, dirpath) - module = __import__(modulename, globals(), locals(), [], -1) + module = __import__(modulename, globals(), locals(), [], 0) self.scan_session.db = module.db self.logger.always('loaded %d entries' % len(self.scan_session.db)) @@ -300,7 +300,7 @@ def should_stop_phy(self): def main(): - app = Umap2VSScanApp(__doc__) + app = NumapVSScanApp(__doc__) app.run() diff --git a/umap2/core/__init__.py b/numap/core/__init__.py similarity index 100% rename from umap2/core/__init__.py rename to numap/core/__init__.py diff --git a/numap/core/phy.py b/numap/core/phy.py new file mode 100644 index 0000000..01e58b3 --- /dev/null +++ b/numap/core/phy.py @@ -0,0 +1,35 @@ +""" +Temporary compatibility shim for using FaceDancer2 to replace the umap PHY architecture. +""" + +import logging + +try: + from facedancer import FacedancerUSBApp +except ImportError: + pass +else: + + class FaceDancerPhy: + """ PHY definition for using the FaceDancer2 generic USB-emulation backend. Mostly a pass-through. """ + + name = "FaceDancer2" + + def __init__(self, arguments): + """ Initializes a new FaceDancerPhy. + + Parameters: + arguments -- The argument string passed in from the command line. + """ + + # Generate our logging backend. + self.logger = logging.getLogger('numap') + + # And create our FD2 connection. + # TODO: support specifying exact types / parameters? + self.backend = FacedancerUSBApp() + + + def __getattr__(self, name): + """ This compatibilty shim mostly works by passing things through to the FaceDancer backend. """ + return getattr(self.backend, name) diff --git a/umap2/core/usb.py b/numap/core/usb.py similarity index 100% rename from umap2/core/usb.py rename to numap/core/usb.py diff --git a/umap2/core/usb_base.py b/numap/core/usb_base.py similarity index 97% rename from umap2/core/usb_base.py rename to numap/core/usb_base.py index 9fd69a2..aa80c18 100644 --- a/umap2/core/usb_base.py +++ b/numap/core/usb_base.py @@ -13,14 +13,14 @@ class USBBaseActor(object): def __init__(self, app, phy): ''' - :param app: Umap2 application + :param app: nümap application :param phy: Physical connection ''' self.phy = phy self.app = app self.session_data = {} self.str_dict = {} - self.logger = logging.getLogger('umap2') + self.logger = logging.getLogger('numap') def get_mutation(self, stage, data=None): ''' diff --git a/umap2/core/usb_bos.py b/numap/core/usb_bos.py similarity index 86% rename from umap2/core/usb_bos.py rename to numap/core/usb_bos.py index 2ff4cdb..45c4dbb 100644 --- a/umap2/core/usb_bos.py +++ b/numap/core/usb_bos.py @@ -4,16 +4,16 @@ It holds multiple device capabilities ''' import struct -from umap2.core.usb import DescriptorType -from umap2.core.usb_base import USBBaseActor -from umap2.fuzz.helpers import mutable +from numap.core.usb import DescriptorType +from numap.core.usb_base import USBBaseActor +from numap.fuzz.helpers import mutable class USBBinaryObjectStore(USBBaseActor): def __init__(self, app, phy, capabilities): ''' - :param app: Umap2 application + :param app: nümap application :param phy: Physical connection ''' super(USBBinaryObjectStore, self).__init__(app, phy) diff --git a/umap2/core/usb_class.py b/numap/core/usb_class.py similarity index 94% rename from umap2/core/usb_class.py rename to numap/core/usb_class.py index b854f2d..3b3c7d1 100644 --- a/umap2/core/usb_class.py +++ b/numap/core/usb_class.py @@ -3,7 +3,7 @@ # Contains class definition for USBClass, intended as a base class (in the OO # sense) for implementing device classes (in the USB sense), eg, HID devices, # mass storage devices. -from umap2.core.usb_base import USBBaseActor +from numap.core.usb_base import USBBaseActor class USBClass(USBBaseActor): @@ -33,7 +33,7 @@ class USBClass(USBBaseActor): def __init__(self, app, phy): ''' - :param app: Umap2 application + :param app: nümap application :param phy: Physical connection ''' super(USBClass, self).__init__(app, phy) diff --git a/numap/core/usb_configuration.py b/numap/core/usb_configuration.py new file mode 100644 index 0000000..3a5170a --- /dev/null +++ b/numap/core/usb_configuration.py @@ -0,0 +1,40 @@ +''' +USB Configuration class. +Each instance represents a single USB configuration. +In most cases it should not be subclassed. +''' +import struct +from numap.core.usb_base import USBBaseActor +from numap.core.usb import DescriptorType +from numap.fuzz.helpers import mutable + +from facedancer.USBConfiguration import USBConfiguration as BaseUSBConfiguration + +class USBConfiguration(USBBaseActor, BaseUSBConfiguration): + + name = 'Configuration' + + # Those attributes can be ORed + # At least one should be selected + ATTR_BASE = 0x80 + ATTR_SELF_POWERED = ATTR_BASE | 0x40 + ATTR_REMOTE_WAKEUP = ATTR_BASE | 0x20 + + def __init__( + self, app, phy, + index, string, interfaces, + attributes=ATTR_SELF_POWERED, + max_power=0x32, + ): + ''' + :param app: nümap application + :param phy: Physical connection + :param index: configuration index (starts from 1) + :param string: configuration string + :param interfaces: list of interfaces for this configuration + :param attributes: configuratioin attributes. one or more of USBConfiguration.ATTR_* (default: ATTR_SELF_POWERED) + :param max_power: maximum power consumption of this configuration (default: 0x32) + ''' + + USBBaseActor.__init__(self, app, phy) + BaseUSBConfiguration.__init__(self, index, string, interfaces, attributes, max_power) diff --git a/umap2/core/usb_cs_endpoint.py b/numap/core/usb_cs_endpoint.py similarity index 89% rename from umap2/core/usb_cs_endpoint.py rename to numap/core/usb_cs_endpoint.py index 9c27d77..51d63b5 100644 --- a/umap2/core/usb_cs_endpoint.py +++ b/numap/core/usb_cs_endpoint.py @@ -2,9 +2,9 @@ Class-Specific endpoint (used in USB Audio) ''' import struct -from umap2.core.usb import DescriptorType -from umap2.core.usb_base import USBBaseActor -from umap2.fuzz.helpers import mutable +from numap.core.usb import DescriptorType +from numap.core.usb_base import USBBaseActor +from numap.fuzz.helpers import mutable class USBCSEndpoint(USBBaseActor): @@ -14,7 +14,7 @@ class USBCSEndpoint(USBBaseActor): def __init__(self, name, app, phy, cs_config): ''' :param name: Name of the endpoint - :param app: Umap2 application + :param app: nümap application :param phy: Physical connection :param cs_config: Containing class specific config ''' diff --git a/umap2/core/usb_cs_interface.py b/numap/core/usb_cs_interface.py similarity index 89% rename from umap2/core/usb_cs_interface.py rename to numap/core/usb_cs_interface.py index 003e985..2f9e84d 100644 --- a/umap2/core/usb_cs_interface.py +++ b/numap/core/usb_cs_interface.py @@ -2,8 +2,8 @@ # # Contains class definition for USBCSInterface. import struct -from umap2.core.usb import DescriptorType -from umap2.core.usb_base import USBBaseActor +from numap.core.usb import DescriptorType +from numap.core.usb_base import USBBaseActor class USBCSInterface(USBBaseActor): @@ -11,7 +11,7 @@ class USBCSInterface(USBBaseActor): def __init__(self, name, app, phy, cs_config): ''' - :param app: umap2 application + :param app: numap application :param phy: physical connection :param cs_config: class specific configuration ''' @@ -47,7 +47,7 @@ def handle_get_descriptor_request(self, req): if response: n = min(n, len(response)) self.phy.send_on_endpoint(0, response[:n]) - self.verbose('sent %d bytes in response' % (n)) + self.log_verbose('sent %d bytes in response' % (n)) def handle_set_interface_request(self, req): self.phy.stall_ep0() @@ -60,5 +60,5 @@ def default_handler(self, req): def get_descriptor(self, usb_type='fullspeed', valid=False): descriptor_type = DescriptorType.cs_interface length = len(self.cs_config) + 2 - response = struct.pack('BB', length & 0xff, descriptor_type) + self.cs_config + response = struct.pack('BB', length & 0xff, descriptor_type) + self.cs_config.encode("utf-8") return response diff --git a/umap2/core/usb_device.py b/numap/core/usb_device.py similarity index 72% rename from umap2/core/usb_device.py rename to numap/core/usb_device.py index 6bc1bef..522c4ba 100644 --- a/umap2/core/usb_device.py +++ b/numap/core/usb_device.py @@ -2,14 +2,17 @@ # # Contains class definitions for USBDevice and USBDeviceRequest. +# TODO: replace with FaceDancer + import traceback import struct -from umap2.core.usb import DescriptorType, State, Request -from umap2.core.usb_base import USBBaseActor -from umap2.fuzz.helpers import mutable +from numap.core.usb import DescriptorType, State, Request +from numap.core.usb_base import USBBaseActor +from numap.fuzz.helpers import mutable +from facedancer.USBDevice import USBDevice as BaseUSBDevice -class USBDevice(USBBaseActor): +class USBDevice(USBBaseActor, BaseUSBDevice): name = 'Device' def __init__( @@ -19,7 +22,7 @@ def __init__( serial_number_string, configurations=None, descriptors=None, usb_class=None, usb_vendor=None, bos=None): ''' - :param app: umap2 application + :param app: numap application :param phy: physical connection :param device_class: device class :param device_subclass: device subclass @@ -37,11 +40,17 @@ def __init__( :param usb_vendor: USB device vendor (default: None) :param bos: USBBinaryStoreObject instance (default: None) ''' - super(USBDevice, self).__init__(app, phy) + if configurations is None: configurations = [] if descriptors is None: descriptors = {} + + USBBaseActor.__init__(self, app, phy) + BaseUSBDevice.__init__(self, phy, usb_class, device_subclass, protocol_rel_num, max_packet_size_ep0, + vendor_id, product_id, device_rev, manufacturer_string, product_string, serial_number_string, configurations, + descriptors) + self.supported_device_class_trigger = False self.supported_device_class_count = 0 @@ -82,14 +91,18 @@ def __init__( self.bos = bos for c in self.configurations: - csi = self.get_string_id(c.get_string()) - c.set_string_index(csi) + + if c.configuration_string: + csi = self.get_string_id(c.configuration_string) + c.configuration_string_index = 0 + c.set_device(self) + # this is fool-proof against weird drivers - if self.usb_class is None: - self.usb_class = c.usb_class - if self.usb_vendor is None: - self.usb_vendor = c.usb_vendor + #if self.usb_class is None: + # self.usb_class = c.usb_class + #if self.usb_vendor is None: + # self.usb_vendor = c.usb_vendor if self.usb_vendor: self.usb_vendor.device = self @@ -100,8 +113,6 @@ def __init__( self.ready = False self.address = 0 - - self.setup_request_handlers() self.endpoints = {} def get_string_id(self, s): @@ -136,13 +147,9 @@ def connect(self): self.state = State.powered def disconnect(self): - if self.phy.is_connected(): - self.phy.disconnect() + self.phy.disconnect() self.state = State.detached - def run(self): - self.phy.run() - def ack_status_stage(self): self.phy.ack_status_stage() @@ -193,65 +200,6 @@ def get_device_qualifier_descriptor(self, n): d = struct.pack('B', len(d) + 1) + d return d - def handle_request(self, buf): - req = USBDeviceRequest(buf) - self.debug('Received request: %s' % req) - - # figure out the intended recipient - req_type = req.get_type() - recipient_type = req.get_recipient() - recipient = None - handler_entity = None - - if req_type == Request.type_standard: # for standard requests we lookup the recipient by index - index = req.get_index() - if recipient_type == Request.recipient_device: - recipient = self - elif recipient_type == Request.recipient_interface: - index = index & 0xff - if index < len(self.configuration.interfaces): - recipient = self.configuration.interfaces[index] - else: - self.warning('Failed to get interface recipient at index: %d' % index) - elif recipient_type == Request.recipient_endpoint: - recipient = self.endpoints.get(index, None) - if recipient is None: - self.warning('Failed to get endpoint recipient at index: %d' % index) - elif recipient_type == Request.recipient_other: - recipient = self.configuration.interfaces[0] # HACK for Hub class - handler_entity = recipient - - elif req_type == Request.type_class: # for class requests we take the usb_class handler from the configuration - handler_entity = self.usb_class - elif req_type == Request.type_vendor: # for vendor requests we take the usb_vendor handler from the configuration - handler_entity = self.usb_vendor - - if not handler_entity: - self.warning('invalid handler entity, stalling') - self.phy.stall_ep0() - return - - # if handler_entity == 9: # HACK: for hub class - # handler_entity = recipient - - self.debug('req: %s' % req) - handler = handler_entity.request_handlers.get(req.request, handler_entity.default_handler) - - if not handler: - self.error('request not handled: %s' % req) - self.error('handler entity type: %s' % (type(handler_entity))) - self.error('handler entity: %s' % (handler_entity)) - self.error('handler_entity.request_handlers: %s' % (handler_entity.request_handlers)) - for k in sorted(handler_entity.request_handlers.keys()): - self.error('0x%02x: %s' % (k, handler_entity.request_handlers[k])) - self.error('invalid handler, stalling') - self.phy.stall_ep0() - try: - handler(req) - except: - traceback.print_exc() - raise - def default_handler(self, req): """ Called when there is no handler for the request @@ -278,55 +226,7 @@ def handle_buffer_available(self, ep_num): raise # standard request handlers - ##################################################### - - # USB 2.0 specification, section 9.4.5 (p 282 of pdf) - def handle_get_status_request(self, req): - self.verbose('Received GET_STATUS request') - # self-powered and remote-wakeup (USB 2.0 Spec section 9.4.5) - # response = b'\x03\x00' - response = b'\x01\x00' - self.phy.send_on_endpoint(0, response) - - # USB 2.0 specification, section 9.4.1 (p 280 of pdf) - def handle_clear_feature_request(self, req): - self.verbose('Received CLEAR_FEATURE request with type 0x%02x and value 0x%02x' % (req.request_type, req.value)) - # self.phy.send_on_endpoint(0, b'') - - # USB 2.0 specification, section 9.4.9 (p 286 of pdf) - def handle_set_feature_request(self, req): - self.verbose('Received SET_FEATURE request') - response = b'' - self.phy.send_on_endpoint(0, response) - - # USB 2.0 specification, section 9.4.6 (p 284 of pdf) - def handle_set_address_request(self, req): - self.address = req.value - self.state = State.address - self.ack_status_stage() - - self.verbose('Received SET_ADDRESS request for address', self.address) - - # USB 2.0 specification, section 9.4.3 (p 281 of pdf) - def handle_get_descriptor_request(self, req): - dtype = (req.value >> 8) & 0xff - dindex = req.value & 0xff - n = req.length - - response = None - self.verbose('Received GET_DESCRIPTOR request') - - response = self.descriptors.get(dtype, None) - if callable(response): - response = response(dindex) - - if response: - response = response[:n] - self.phy.send_on_endpoint(0, response) - self.verbose('Sent %d bytes in response' % n) - else: - self.phy.stall_ep0() # # No need to mutate this one, will mutate @@ -420,13 +320,14 @@ def handle_set_descriptor_request(self, req): # USB 2.0 specification, section 9.4.2 (p 281 of pdf) def handle_get_configuration_request(self, req): - if self.verbose > 0: - self.debug('Received GET_CONFIGURATION request') + self.debug('Received GET_CONFIGURATION request') self.phy.send_on_endpoint(0, b'\x01') # HACK - once configuration supported # USB 2.0 specification, section 9.4.7 (p 285 of pdf) def handle_set_configuration_request(self, req): self.debug('Received SET_CONFIGURATION request') + self.debug(req) + self.debug(self.configurations) self.supported_device_class_trigger = True # configs are one-based @@ -491,16 +392,21 @@ class USBDeviceRequest(object): Request.recipient_other: 'other', } - def __init__(self, raw_bytes): - '''Expects raw 8-byte setup data request packet''' - ( - self.request_type, - self.request, - self.value, - self.index, - self.length - ) = struct.unpack('II', lastlba, length) @@ -286,7 +285,7 @@ def handle_read_capacity_10(self, cbw): @mutable('scsi_read_capacity_16_response') def handle_read_capacity_16(self, cbw): # .. todo: is the length correct? - self.debug('SCSI Read Capacity(16), data: %s' % hexlify(cbw.cb[1:])) + self.debug('SCSI Read Capacity(16), data: %s' % cbw.cb[1:].hex()) lastlba = self.disk_image.get_sector_count() length = self.disk_image.block_size response = struct.pack('>BBQIBB', 0x9e, 0x10, lastlba, length, 0x00, 0x00) @@ -302,7 +301,7 @@ def handle_prevent_allow_medium_removal(self, cbw): @mutable('scsi_write_10_response') def handle_write_10(self, cbw): - self.debug('SCSI Write (10), data: %s' % hexlify(cbw.cb[1:])) + self.debug('SCSI Write (10), data: %s' % cbw.cb[1:].hex()) base_lba = struct.unpack('>I', cbw.cb[2:6])[0] num_blocks = struct.unpack('>H', cbw.cb[7:9])[0] @@ -336,7 +335,7 @@ def handle_verify_10(self, cbw): raise NotImplementedError('yet...') def _build_page0_report(self, page, data): - report = struct.pack('BB', page, len(data)) + report = struct.pack(b'BB', page, len(data)) report += data return report @@ -390,7 +389,7 @@ def handle_scsi_mode_sense(self, mode_type, page, subpage, alloc_len, ctrl, with if report is None: # default behaviour, taken from previous implementation # this should probably be changed ... - report = '\x07\x00\x00\x00\x00\x00\x00\x00' + report = b'\x07\x00\x00\x00\x00\x00\x00\x00' if with_header: self.debug('SCSI mode sense (%d) - adding header' % (mode_type)) report = self._report_header(mode_type, len(report)) + report @@ -440,13 +439,13 @@ def __init__(self, bytestring): self.opcode = self.cb[0] def __str__(self): - s = 'sig: %s\n' % hexlify(self.signature) - s += 'tag: %s\n' % hexlify(self.tag) + s = 'sig: %s\n' % self.signature.hex() + s += 'tag: %s\n' % self.tag.hex() s += 'data transfer len: %s\n' % self.data_transfer_length s += 'flags: %s\n' % self.flags s += 'lun: %s\n' % self.lun s += 'command block len: %s\n' % self.cb_length - s += 'command block: %s\n' % hexlify(self.cb) + s += 'command block: %s\n' % self.cb.hex() return s @@ -529,7 +528,7 @@ def __init__( device_rev=rev, manufacturer_string='PNY', product_string='USB 2.0 FD', - serial_number_string='4731020ef1914da9', + serial_number_string=b'4731020ef1914da9', configurations=[ USBConfiguration( app=app, diff --git a/umap2/dev/mtp.py b/numap/dev/mtp.py similarity index 92% rename from umap2/dev/mtp.py rename to numap/dev/mtp.py index 45bcb65..5f9cb1c 100644 --- a/umap2/dev/mtp.py +++ b/numap/dev/mtp.py @@ -2,13 +2,13 @@ # # Contains class definitions to implement a USB keyboard. import struct -from umap2.core.usb_device import USBDevice -from umap2.core.usb_configuration import USBConfiguration -from umap2.core.usb_interface import USBInterface -from umap2.core.usb_endpoint import USBEndpoint -from umap2.core.usb_vendor import USBVendor -from umap2.core.usb_class import USBClass -from umap2.fuzz.helpers import mutable +from numap.core.usb_device import USBDevice +from numap.core.usb_configuration import USBConfiguration +from numap.core.usb_interface import USBInterface +from numap.core.usb_endpoint import USBEndpoint +from numap.core.usb_vendor import USBVendor +from numap.core.usb_class import USBClass +from numap.fuzz.helpers import mutable try: from mtpdevice.mtp_device import MtpDevice, MtpDeviceInfo from mtpdevice.mtp_object import MtpObject @@ -102,7 +102,7 @@ def __init__(self, app, phy): manufacturer='UMAP2', model='Role', device_version='1.2', - serial_number='3031323334353637', + serial_number=b'3031323334353637', ) properties = [ MtpDeviceProperty(MtpDevicePropertyCode.MTP_DeviceFriendlyName, 0, MStr('UmapMtpDevice'), MStr('')), @@ -150,7 +150,7 @@ def pad(data, pad_len=8): reserved = pad(b'\x00', 7) properties = b'' for prop in self.property_sections: - properties += struct.pack('BB', prop[0], prop[1]) + prop[2] + prop[3] + prop[4] + properties += struct.pack(b'BB', prop[0], prop[1]) + prop[2] + prop[3] + prop[4] payload = struct.pack(' [-d ] [-c ] [-k ] + numapkitty -s [-d ] [-c ] [-k ] Options: -c --count stage count (e.g. how many times a stage might repeat @@ -20,10 +20,10 @@ from kitty.model import GraphModel from kitty.model import Template, Meta, String, UInt32 -from templates import audio, cdc, enum, generic, hid, hub, mass_storage -from templates import smart_card +from numap.fuzz.templates import audio, cdc, enum, generic, hid, hub, mass_storage +from numap.fuzz.templates import smart_card -from controller import UmapController +from numap.fuzz.controller import UmapController def enumerate_templates(module): @@ -51,7 +51,7 @@ def get_stages(stage_file): ''' Get a dictionary (stage:count) from a stage file - :param stage_file: filename with stage list (generated by umap2stages) + :param stage_file: filename with stage list (generated by numapstages) :return: dictionary of stage:count ''' with open(stage_file, 'r') as f: @@ -161,7 +161,7 @@ def get_fuzzer(options=None): '--disconnect-delays': '0.0,0.0' } local_options.update(options) - fuzzer = ClientFuzzer(name='Umap2', option_line=local_options['--kitty-options']) + fuzzer = ClientFuzzer(name='numap', option_line=local_options['--kitty-options']) fuzzer.set_interface(WebInterface()) target = ClientTarget(name='USBTarget') diff --git a/umap2/fuzz/helpers.py b/numap/fuzz/helpers.py similarity index 94% rename from umap2/fuzz/helpers.py rename to numap/fuzz/helpers.py index 8eed012..ad26fb2 100644 --- a/umap2/fuzz/helpers.py +++ b/numap/fuzz/helpers.py @@ -45,9 +45,10 @@ def log_stage(stage): def mutable(stage, silent=False): def wrap_f(func): func_self = None + if inspect.ismethod(func): - func_self = func.im_self - func = func.im_func + func_self = func.__self__ + func = func.__func__ def wrapper(*args, **kwargs): if func_self is None: @@ -79,7 +80,7 @@ def wrapper(*args, **kwargs): self.logger.error(''.join(traceback.format_stack())) raise e if response is not None: - info('Response: %s' % binascii.hexlify(response)) + info('Response: %s' % response.hex()) return response return wrapper return wrap_f diff --git a/umap2/fuzz/templates/__init__.py b/numap/fuzz/templates/__init__.py similarity index 100% rename from umap2/fuzz/templates/__init__.py rename to numap/fuzz/templates/__init__.py diff --git a/umap2/fuzz/templates/audio.py b/numap/fuzz/templates/audio.py similarity index 92% rename from umap2/fuzz/templates/audio.py rename to numap/fuzz/templates/audio.py index c8502a6..10b327a 100644 --- a/umap2/fuzz/templates/audio.py +++ b/numap/fuzz/templates/audio.py @@ -1,13 +1,14 @@ ''' Audio device templates ''' -from umap2.core.usb import DescriptorType +from numap.core.usb import DescriptorType from kitty.model import UInt8, LE16, RandomBytes, BitField, Static from kitty.model import Template, Repeat, List, Container, ForEach, OneOf from kitty.model import ElementCount, SizeInBytes from kitty.model import ENC_INT_LE -from hid import GenerateHidReport -from generic import Descriptor, SizedPt, DynamicInt, SubDescriptor +from numap.fuzz.templates.hid import GenerateHidReport +from numap.fuzz.templates.generic import Descriptor, SizedPt, DynamicInt, SubDescriptor +import binascii class _AC_DescriptorSubTypes: # AC Interface Descriptor Subtype @@ -87,7 +88,7 @@ class _AS_DescriptorSubTypes: # AS Interface Descriptor Subtype UInt8(name='bUnitID', value=0x00), UInt8(name='bSourceID', value=0x00), SizedPt(name='bmaControls', - fields=RandomBytes(name='bmaControlsX', value='\x00', min_length=0, step=17, max_length=249)), + fields=RandomBytes(name='bmaControlsX', value=b'\x00', min_length=0, step=17, max_length=249)), UInt8(name='iFeature', value=0x00) ]) @@ -138,7 +139,7 @@ class _AS_DescriptorSubTypes: # AS Interface Descriptor Subtype audio_report_descriptor = Template( name='audio_report_descriptor', fields=GenerateHidReport( - '050C0901A1011500250109E909EA75019502810209E209008106050B092095018142050C09009503810226FF000900750895038102090095049102C0'.decode('hex') + binascii.a2b_hex('050C0901A1011500250109E909EA75019502810209E209008106050B092095018142050C09009503810226FF000900750895038102090095049102C0') ) ) @@ -177,7 +178,7 @@ def size_in_words(x): LE16(name='bcdADC', value=0x100), SizeInBytes(name='wTotalLength', sized_field='Class-Specific AC interfaces', length=16, encoder=ENC_INT_LE), SizeInBytes(name='bInCollection', sized_field='baInterfaceNr', length=8), - RandomBytes(name='baInterfaceNr', value='\x01', min_length=0, max_length=250), + RandomBytes(name='baInterfaceNr', value=b'\x01', min_length=0, max_length=250), ] ), SubDescriptor( @@ -213,7 +214,7 @@ def size_in_words(x): UInt8(name='bDesciptorSubType', value=0x04), UInt8(name='bUnitID', value=3), SizeInBytes(name='bNrInPins', sized_field='baSourceID', length=8), - RandomBytes(name='baSourceID', value='\x01', min_length=0, max_length=250), + RandomBytes(name='baSourceID', value=b'\x01', min_length=0, max_length=250), UInt8(name='bNrChannels', value=8), LE16(name='wChannelConfig', value=0xaaaa), UInt8(name='iChannelNames', value=4), @@ -228,7 +229,7 @@ def size_in_words(x): UInt8(name='bDesciptorSubType', value=0x05), UInt8(name='bUnitID', value=4), SizeInBytes(name='bNrInPins', sized_field='baSourceID', length=8), - RandomBytes(name='baSourceID', value='\x01', min_length=0, max_length=250), + RandomBytes(name='baSourceID', value=b'\x01', min_length=0, max_length=250), UInt8(name='iSelector', value=1), ] ), @@ -240,7 +241,7 @@ def size_in_words(x): UInt8(name='bUnitID', value=5), UInt8(name='bSourceID', value=2), SizeInBytes(name='bControlSize', sized_field='bmaControls', length=8), - RandomBytes(name='bmaControls', value='\x01', min_length=0, max_length=250), + RandomBytes(name='bmaControls', value=b'\x01', min_length=0, max_length=250), UInt8(name='iFeature', value=1), ] ), @@ -254,7 +255,7 @@ def size_in_words(x): fields=[ UInt8(name='bDesciptorSubType', value=0x15), ForEach('bDesciptorSubType', fields=[ - RandomBytes(name='junk', value='\x01', min_length=0, max_length=250), + RandomBytes(name='junk', value=b'\x01', min_length=0, max_length=250), ]) ] ), diff --git a/umap2/fuzz/templates/cdc.py b/numap/fuzz/templates/cdc.py similarity index 95% rename from umap2/fuzz/templates/cdc.py rename to numap/fuzz/templates/cdc.py index d455e16..b75a238 100644 --- a/umap2/fuzz/templates/cdc.py +++ b/numap/fuzz/templates/cdc.py @@ -1,14 +1,14 @@ ''' CDC Device tempaltes ''' -from umap2.dev.cdc import FunctionalDescriptor, CommunicationClassSubclassCodes -from umap2.core.usb_class import USBClass -from umap2.core.usb import DescriptorType +from numap.dev.cdc import FunctionalDescriptor, CommunicationClassSubclassCodes +from numap.core.usb_class import USBClass +from numap.core.usb import DescriptorType from kitty.model import UInt8, LE16, RandomBytes, BitField, Static from kitty.model import Template, Repeat, List, Container, ForEach, OneOf from kitty.model import ElementCount from kitty.model import MutableField -from generic import SubDescriptor +from numap.fuzz.templates.generic import SubDescriptor cdc_control_interface_descriptor = Template( @@ -81,7 +81,7 @@ fields=[ UInt8(name='bDesciptorSubType', value=0x15), ForEach('bDesciptorSubType', fields=[ - RandomBytes(name='junk', value='\x01', min_length=0, max_length=250), + RandomBytes(name='junk', value=b'\x01', min_length=0, max_length=250), ]) ] ), diff --git a/umap2/fuzz/templates/enum.py b/numap/fuzz/templates/enum.py similarity index 95% rename from umap2/fuzz/templates/enum.py rename to numap/fuzz/templates/enum.py index 56fde91..98f4701 100644 --- a/umap2/fuzz/templates/enum.py +++ b/numap/fuzz/templates/enum.py @@ -1,7 +1,7 @@ ''' Tempaltes related to generic enumeration stage ''' -from umap2.core.usb import DescriptorType +from numap.core.usb import DescriptorType # fields from kitty.model import LE16, UInt8, BitField, String, RandomBytes # containers @@ -9,8 +9,8 @@ # dynamic fields from kitty.model import ElementCount, SizeInBytes # encoders -from kitty.model import StrEncodeEncoder, ENC_INT_LE -from generic import Descriptor, SubDescriptor +from kitty.model import StrEncoder, ENC_INT_LE +from numap.fuzz.templates.generic import Descriptor, SubDescriptor # Device descriptor @@ -125,7 +125,7 @@ name='string_descriptor', descriptor_type=DescriptorType.string, fields=[ - String(name='bString', value='hello_kitty', encoder=StrEncodeEncoder('utf_16_le'), max_size=254 / 2) + String(name='bString', value='hello_kitty'.encode('utf-16'), encoder=StrEncoder(), max_size=254 / 2) ]) @@ -133,7 +133,7 @@ name='string_descriptor_zero', descriptor_type=DescriptorType.string, fields=[ - RandomBytes(name='lang_id', min_length=0, max_length=253, step=3, value='\x04\x09') + RandomBytes(name='lang_id', min_length=0, max_length=253, step=3, value=b'\x04\x09') ]) hub_descriptor = Descriptor( diff --git a/umap2/fuzz/templates/generic.py b/numap/fuzz/templates/generic.py similarity index 100% rename from umap2/fuzz/templates/generic.py rename to numap/fuzz/templates/generic.py diff --git a/umap2/fuzz/templates/hid.py b/numap/fuzz/templates/hid.py similarity index 93% rename from umap2/fuzz/templates/hid.py rename to numap/fuzz/templates/hid.py index 21c041c..346d1ec 100644 --- a/umap2/fuzz/templates/hid.py +++ b/numap/fuzz/templates/hid.py @@ -17,14 +17,15 @@ ''' Legos to generate USB HID reports ''' -from umap2.core.usb import DescriptorType +from numap.core.usb import DescriptorType from kitty.model import Template, Container, OneOf, TakeFrom from kitty.model import MutableField from kitty.model import UInt8, LE16, BitField, Static from kitty.model import ENC_INT_LE from kitty.core import KittyException from random import Random -from generic import DynamicInt, Descriptor +from numap.fuzz.templates.generic import DynamicInt, Descriptor +import binascii opcodes = { @@ -124,7 +125,7 @@ def GenerateHidReport(report_str, name=None): index = 0 namer = NameGen() while index < len(report_str): - opcode = ord(report_str[index]) + opcode = report_str[index] num_args = opcode & 3 if index + num_args >= len(report_str): raise KittyException('Not enough bytes in hid report for last opcode') @@ -134,7 +135,7 @@ def GenerateHidReport(report_str, name=None): fields.append(UInt8(opcode, name=cur_name)) else: args = report_str[index:index + num_args] - value = sum(ord(args[i]) << (i * 8) for i in range(len(args))) # little endian... + value = sum(args[i] << (i * 8) for i in range(len(args))) # little endian... fields.append(Container( name=cur_name, fields=[ @@ -179,6 +180,6 @@ def GenerateHidReport(report_str, name=None): hid_report_descriptor = Template( name='hid_report_descriptor', fields=GenerateHidReport( - '05010906A101050719E029E7150025017501950881029501750881011900296515002565750895018100C0'.decode('hex') + binascii.a2b_hex('05010906A101050719E029E7150025017501950881029501750881011900296515002565750895018100C0') ) ) diff --git a/umap2/fuzz/templates/hub.py b/numap/fuzz/templates/hub.py similarity index 77% rename from umap2/fuzz/templates/hub.py rename to numap/fuzz/templates/hub.py index fb6f30c..9a9aead 100644 --- a/umap2/fuzz/templates/hub.py +++ b/numap/fuzz/templates/hub.py @@ -1,10 +1,10 @@ ''' Hub templates ''' -from umap2.core.usb import DescriptorType +from numap.core.usb import DescriptorType from kitty.model import UInt8, LE16, RandomBytes from kitty.model import Size -from generic import Descriptor +from numap.fuzz.templates.generic import Descriptor # hub_descriptor @@ -21,8 +21,8 @@ num_bytes = self.num_ports // 7 if self.num_ports % 7 != 0: num_bytes += 1 - d += '\x00' * num_bytes - d += '\xff' * num_bytes + d += b'\x00' * num_bytes + d += b'\xff' * num_bytes d = struct.pack('B', len(d) + 1) + d return d ''' @@ -35,5 +35,5 @@ LE16(name='wHubCharacteristics', value=0x0000), UInt8(name='bPwrOn2PwrGood', value=0x00), UInt8(name='bHubContrCurrent', value=0x02), - RandomBytes(name='DeviceRemovable', value='\x00', min_length=0, max_length=250), + RandomBytes(name='DeviceRemovable', value=b'\x00', min_length=0, max_length=250), ]) diff --git a/umap2/fuzz/templates/mass_storage.py b/numap/fuzz/templates/mass_storage.py similarity index 95% rename from umap2/fuzz/templates/mass_storage.py rename to numap/fuzz/templates/mass_storage.py index 5e474ee..d3e3ad5 100644 --- a/umap2/fuzz/templates/mass_storage.py +++ b/numap/fuzz/templates/mass_storage.py @@ -4,7 +4,7 @@ from kitty.model import Template, Pad from kitty.model import String, UInt8, BE32, BE16, RandomBytes from kitty.model import SizeInBytes -from generic import SizedPt +from numap.fuzz.templates.generic import SizedPt # TODO: scsi_test_unit_ready_response (nothing to fuzz! no data returned, besides the csw) # TODO: scsi_send_diagnostic_response @@ -80,7 +80,7 @@ SizeInBytes(name='bLength', sized_field='scsi_mode_sense_6_response', length=8, fuzzable=True), UInt8(name='MediumType', value=0x00), UInt8(name='Device_Specific_Param', value=0x00), - SizedPt(name='Mode_Parameter_Container', fields=RandomBytes(name='Mode_Parameter', min_length=0, max_length=4, value='\x1c')) + SizedPt(name='Mode_Parameter_Container', fields=RandomBytes(name='Mode_Parameter', min_length=0, max_length=4, value=b'\x1c')) ]) @@ -91,7 +91,7 @@ SizeInBytes(name='bLength', sized_field='scsi_mode_sense_10_response', length=8, fuzzable=True), UInt8(name='MediumType', value=0x00), UInt8(name='Device_Specific_Param', value=0x00), - SizedPt(name='Mode_Parameter_Container', fields=RandomBytes(name='Mode_Parameter', min_length=0, max_length=4, value='\x1c')) + SizedPt(name='Mode_Parameter_Container', fields=RandomBytes(name='Mode_Parameter', min_length=0, max_length=4, value=b'\x1c')) ]) diff --git a/umap2/fuzz/templates/smart_card.py b/numap/fuzz/templates/smart_card.py similarity index 88% rename from umap2/fuzz/templates/smart_card.py rename to numap/fuzz/templates/smart_card.py index 8223a3f..878ed92 100644 --- a/umap2/fuzz/templates/smart_card.py +++ b/numap/fuzz/templates/smart_card.py @@ -6,7 +6,7 @@ from kitty.model import SizeInBytes from kitty.model import ENC_INT_LE from kitty.model import Template, Container -from generic import DynamicInt +from numap.fuzz.templates.generic import DynamicInt class R2PParameters(Template): @@ -30,7 +30,7 @@ def __init__(self, name, status, error, proto, ab_data, fuzzable=True): status=0x00, error=0x80, proto=0, - ab_data=RandomBytes(name='data', value='\x11\x00\x00\x0a\x00', min_length=0, max_length=150), + ab_data=RandomBytes(name='data', value=b'\x11\x00\x00\x0a\x00', min_length=0, max_length=150), ) @@ -39,7 +39,7 @@ def __init__(self, name, status, error, proto, ab_data, fuzzable=True): status=0x00, error=0x80, proto=0, - ab_data=RandomBytes(name='data', value='\x11\x00\x00\x0a\x00', min_length=0, max_length=150), + ab_data=RandomBytes(name='data', value=b'\x11\x00\x00\x0a\x00', min_length=0, max_length=150), ) smartcard_SetParameters_response = R2PParameters( @@ -47,7 +47,7 @@ def __init__(self, name, status, error, proto, ab_data, fuzzable=True): status=0x00, error=0x80, proto=0, - ab_data=RandomBytes(name='data', value='\x11\x00\x00\x0a\x00', min_length=0, max_length=150), + ab_data=RandomBytes(name='data', value=b'\x11\x00\x00\x0a\x00', min_length=0, max_length=150), ) @@ -71,7 +71,7 @@ def __init__(self, name, status, error, chain_param, ab_data, fuzzable=True): status=0x00, error=0x80, chain_param=0x00, - ab_data=RandomBytes(name='data', value='\x3b\x6e\x00\x00\x80\x31\x80\x66\xb0\x84\x12\x01\x6e\x01\x83\x00\x90\x00', min_length=0, max_length=150), + ab_data=RandomBytes(name='data', value=b'\x3b\x6e\x00\x00\x80\x31\x80\x66\xb0\x84\x12\x01\x6e\x01\x83\x00\x90\x00', min_length=0, max_length=150), ) smartcard_XfrBlock_response = R2PDataBlock( @@ -79,7 +79,7 @@ def __init__(self, name, status, error, chain_param, ab_data, fuzzable=True): status=0x00, error=0x80, chain_param=0x00, - ab_data=RandomBytes(name='data', value='\x6a\x82', min_length=0, max_length=150), + ab_data=RandomBytes(name='data', value=b'\x6a\x82', min_length=0, max_length=150), ) diff --git a/umap2/phy/__init__.py b/numap/utils/__init__.py similarity index 100% rename from umap2/phy/__init__.py rename to numap/utils/__init__.py diff --git a/umap2/utils/dev_generator.py b/numap/utils/dev_generator.py similarity index 93% rename from umap2/utils/dev_generator.py rename to numap/utils/dev_generator.py index 1d46c28..d1bfb3a 100644 --- a/umap2/utils/dev_generator.py +++ b/numap/utils/dev_generator.py @@ -1,9 +1,9 @@ #!/usr/bin/env python ''' -Generate a umap2 USB device python code from device and configuration descriptors +Generate a numap USB device python code from device and configuration descriptors Usage: - umap2mkdevice ... + numapmkdevice ... Arguments: DEVICE_DESCRIPTOR device descriptor (hex) @@ -11,21 +11,20 @@ Example(write in one line ...): - umap2mkdevice 120100020000004012831283000001020001 \\ + numapmkdevice 120100020000004012831283000001020001 \\ 09022e00010100c0fa0904000004ff00000007058203000401070504020002000705860200020007058802000200 ''' from docopt import docopt -from binascii import unhexlify, hexlify import struct -from umap2.core.usb import DescriptorType +from numap.core.usb import DescriptorType def get_device_descriptor(opts): - return unhexlify(opts['']) + return bytes.fromhex(opts['']) def get_configuration_descriptors(opts): - return [unhexlify(desc) for desc in opts['']] + return [bytes.fromhex(desc) for desc in opts['']] def add_indentation(s, count=1): @@ -92,12 +91,12 @@ def to_code(self): fres = '' for dep in self.deps: pre_code = '''# This script was auto generated from descriptors -from umap2.core.usb_device import USBDevice -from umap2.core.usb_configuration import USBConfiguration -from umap2.core.usb_interface import USBInterface -from umap2.core.usb_endpoint import USBEndpoint -from umap2.core.usb_cs_interface import USBCSInterface -from umap2.core.usb_cs_endpoint import USBCSEndpoint +from numap.core.usb_device import USBDevice +from numap.core.usb_configuration import USBConfiguration +from numap.core.usb_interface import USBInterface +from numap.core.usb_endpoint import USBEndpoint +from numap.core.usb_cs_interface import USBCSInterface +from numap.core.usb_cs_endpoint import USBCSEndpoint class USBMyDevice(USBDevice): @@ -134,7 +133,6 @@ def get_text(self): def parse_pfn(desc_type): def parser_wrapper(pfn): def wrapper(self, desc): - # print 'Parsing desc %02x: %s' % (desc_type, hexlify(desc)) self.select_node(desc_type) node = DescriptorNode(desc_type) node.text = pfn(self, desc, node) @@ -201,7 +199,7 @@ def select_node(self, desc_type): def parse_cs_endpoint_desc(self, desc, node): return build_init('CSEndpoint', [ ('name', "'CSEndpoint'"), - ('cs_config', "'%s'" % (desc[2:])) + ('cs_config', "b'%s'" % (desc[2:].hex())) ]) @parse_pfn(DescriptorType.endpoint) @@ -237,7 +235,7 @@ def parse_endpoint_desc(self, desc, node): @parse_pfn(DescriptorType.cs_interface) def parse_cs_interface_desc(self, desc, node): return build_init('CSInterface', [ - ("cs_config", "'%s'" % (desc[2:])), + ("cs_config", "b'%s'" % (desc[2:].hex())), ("name", "'CSInterface'") ]) @@ -341,9 +339,9 @@ def parse_config_desc(self, desc_buff): raise Exception('Invalid configuration descriptor') desc_len, desc_type = struct.unpack('BB', desc_buff[:2]) if desc_len > len(desc_buff): - raise Exception('Invalid descriptor length: %02x %s' % (desc_len, hexlify(desc_buff))) + raise Exception('Invalid descriptor length: %02x %s' % (desc_len, desc_buff.hex())) if desc_type not in self.parsers: - raise Exception('Invalid descriptor type: %02x %s' % (desc_len, hexlify(desc_buff))) + raise Exception('Invalid descriptor type: %02x %s' % (desc_len, desc_buff.hex())) current_desc = desc_buff[:desc_len] desc_buff = desc_buff[desc_len:] self.parsers[desc_type](current_desc) diff --git a/umap2/utils/ulogger.py b/numap/utils/ulogger.py similarity index 77% rename from umap2/utils/ulogger.py rename to numap/utils/ulogger.py index dcc114c..b3c1564 100644 --- a/umap2/utils/ulogger.py +++ b/numap/utils/ulogger.py @@ -1,13 +1,13 @@ import logging stdio_handler = None -umap2_logger = None +numap_logger = None def prepare_logging(): - global umap2_logger + global numap_logger global stdio_handler - if umap2_logger is None: + if numap_logger is None: def add_debug_level(num, name): def fn(self, message, *args, **kwargs): if self.isEnabledFor(num): @@ -24,10 +24,10 @@ def fn(self, message, *args, **kwargs): stdio_handler.setLevel(logging.INFO) formatter = logging.Formatter(FORMAT) stdio_handler.setFormatter(formatter) - umap2_logger = logging.getLogger('umap2') - umap2_logger.addHandler(stdio_handler) - umap2_logger.setLevel(logging.VERBOSE) - return umap2_logger + numap_logger = logging.getLogger('numap') + numap_logger.addHandler(stdio_handler) + numap_logger.setLevel(logging.VERBOSE) + return numap_logger def set_default_handler_level(level): diff --git a/setup.py b/setup.py index 5da2c8f..167b634 100644 --- a/setup.py +++ b/setup.py @@ -8,31 +8,31 @@ def read(fname): DESCRIPTION = read('README.rst') setup( - name='umap2', - version='2.0.1', + name='numap', + version='2.0.2', description='USB Host Security Assessment Tool - Revision 2', long_description=DESCRIPTION, author='NCCGroup & Cisco SAS team', author_email='', - url='https://github.com/nccgroup/umap2', + url='https://github.com/nccgroup/numap', packages=find_packages(), install_requires=[ + 'six', 'docopt', 'kittyfuzzer>=0.6.9', - 'pyserial', - 'six', + 'facedancer' ], keywords='security,usb,fuzzing,kitty', entry_points={ 'console_scripts': [ - 'umap2detect=umap2.apps.detect_os:main', - 'umap2emulate=umap2.apps.emulate:main', - 'umap2fuzz=umap2.apps.fuzz:main', - 'umap2list=umap2.apps.list_classes:main', - 'umap2kitty=umap2.fuzz.fuzz_engine:main', - 'umap2scan=umap2.apps.scan:main', - 'umap2vsscan=umap2.apps.vsscan:main', - 'umap2stages=umap2.apps.makestages:main', + 'numap-detect=numap.apps.detect_os:main', + 'numap-emulate=numap.apps.emulate:main', + 'numap-fuzz=numap.apps.fuzz:main', + 'numap-list=numap.apps.list_classes:main', + 'numap-kitty=numap.fuzz.fuzz_engine:main', + 'numap-scan=numap.apps.scan:main', + 'numap-vsscan=numap.apps.vsscan:main', + 'numap-stages=numap.apps.makestages:main', ] }, package_data={} diff --git a/tests/common.py b/tests/common.py index ea585e2..418e185 100644 --- a/tests/common.py +++ b/tests/common.py @@ -9,7 +9,7 @@ def get_test_logger(): global test_logger if test_logger is None: # logger = logging.getLogger('unit_test_logs') - logger = logging.getLogger('umap2') + logger = logging.getLogger('numap') formatter = logging.Formatter( '[%(asctime)s] [%(levelname)s] -> %(message)s' ) diff --git a/tests/infra_app.py b/tests/infra_app.py index f43d227..7077733 100644 --- a/tests/infra_app.py +++ b/tests/infra_app.py @@ -1,10 +1,10 @@ import logging -from umap2.apps.base import Umap2App -from umap2.utils.ulogger import set_default_handler_level +from numap.apps.base import NumapApp +from numap.utils.ulogger import set_default_handler_level from infra_phy import TestPhy -class TestApp(Umap2App): +class TestApp(NumapApp): def __init__(self, docstring=None, event_handler=None): super(TestApp, self).__init__(docstring) diff --git a/tests/infra_phy.py b/tests/infra_phy.py index 63730a3..db551b5 100644 --- a/tests/infra_phy.py +++ b/tests/infra_phy.py @@ -1,7 +1,7 @@ ''' -Physical layer for testing umap2 core and devices +Physical layer for testing numap core and devices ''' -from umap2.phy.iphy import PhyInterface +from numap.phy.iphy import PhyInterface from infra_event_handler import TestEvent @@ -25,7 +25,7 @@ class TestPhy(PhyInterface): ''' def __init__(self, app): ''' - :type app: :class:`~umap2.app.base.Umap2App` + :type app: :class:`~numap.app.base.NumapApp` :param app: application instance ''' super(TestPhy, self).__init__(app, 'Test') diff --git a/tests/test_devices.py b/tests/test_devices.py index 2facf52..1a3a39a 100644 --- a/tests/test_devices.py +++ b/tests/test_devices.py @@ -8,7 +8,7 @@ from infra_event_handler import EventHandler from infra_app import TestApp from infra_phy import SendDataEvent, StallEp0Event -from umap2.dev.cdc import USBCDCClass +from numap.dev.cdc import USBCDCClass DIR_OUT = 0x00 DIR_IN = 0x80 diff --git a/umap2/apps/__init__.py b/umap2/apps/__init__.py deleted file mode 100644 index 3ddab14..0000000 --- a/umap2/apps/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -''' -Umap2 applications -''' diff --git a/umap2/core/usb_configuration.py b/umap2/core/usb_configuration.py deleted file mode 100644 index 687ab15..0000000 --- a/umap2/core/usb_configuration.py +++ /dev/null @@ -1,142 +0,0 @@ -''' -USB Configuration class. -Each instance represents a single USB configuration. -In most cases it should not be subclassed. -''' -import struct -from umap2.core.usb_base import USBBaseActor -from umap2.core.usb import DescriptorType -from umap2.fuzz.helpers import mutable - - -class USBConfiguration(USBBaseActor): - - name = 'Configuration' - - # Those attributes can be ORed - # At least one should be selected - ATTR_BASE = 0x80 - ATTR_SELF_POWERED = ATTR_BASE | 0x40 - ATTR_REMOTE_WAKEUP = ATTR_BASE | 0x20 - - def __init__( - self, app, phy, - index, string, interfaces, - attributes=ATTR_SELF_POWERED, - max_power=0x32, - ): - ''' - :param app: Umap2 application - :param phy: Physical connection - :param index: configuration index (starts from 1) - :param string: configuration string - :param interfaces: list of interfaces for this configuration - :param attributes: configuratioin attributes. one or more of USBConfiguration.ATTR_* (default: ATTR_SELF_POWERED) - :param max_power: maximum power consumption of this configuration (default: 0x32) - ''' - super(USBConfiguration, self).__init__(app, phy) - self._index = index - self._string = string - self._string_index = 0 - self.interfaces = interfaces - self._attributes = attributes - self._max_power = max_power - self._device = None - self.usb_class = None - self.usb_vendor = None - for i in self.interfaces: - i.set_configuration(self) - # this is fool-proof against weird drivers - if i.usb_class is not None: - self.usb_class = i.usb_class - if i.usb_vendor is not None: - self.usb_vendor = i.usb_vendor - - def set_device(self, device): - ''' - :param device: usb device - ''' - self._device = device - - def set_string_index(self, string_index): - ''' - :param string_index: configuration string index - ''' - self._string_index = string_index - - def get_string(self): - ''' - :return: the configuration string - ''' - return self._string - - def get_string_by_id(self, str_id): - ''' - :param str_id: string id - :return: string with id of str_id - ''' - s = super(USBConfiguration, self).get_string_by_id(str_id) - if not s: - for iface in self.interfaces: - s = iface.get_string_by_id(str_id) - if s: - break - return s - - @mutable('configuration_descriptor') - def get_descriptor(self, usb_type='fullspeed', valid=False): - ''' - Get the configuration descriptor. - The configuration descriptor is composed of one or more - interface descriptors. - - :return: a string of the entire configuration descriptor - ''' - interface_descriptors = b'' - for i in self.interfaces: - interface_descriptors += i.get_descriptor(usb_type, valid) - bLength = 9 # always 9 - bDescriptorType = DescriptorType.configuration - wTotalLength = len(interface_descriptors) + 9 - bNumInterfaces = len(self.interfaces) - d = struct.pack( - ' 0: - data = self.read(n) - else: - data = b'' - - if len(data) != n: - raise ValueError('Facedancer expected %d bytes but received only %d' % (n, len(data))) - cmd = FacedancerCommand(app, verb, data) - self.logger.verbose('Facedancer Rx command: %s' % cmd) - return cmd - - def write(self, b): - '''Write raw bytes.''' - self.logger.verbose('Facedancer Tx: %s' % hexlify(b)) - self.serialport.write(b) - - def writecmd(self, c): - '''Write a single command.''' - self.write(c.as_bytestring()) - self.logger.verbose('Facedancer Tx command: %s' % c) - - -class FacedancerCommand(object): - def __init__(self, app=None, verb=None, data=None): - self.app = app - self.verb = verb - self.data = data - - def __str__(self): - s = 'app 0x%02x, verb 0x%02x, len %d' % (self.app, self.verb, len(self.data)) - - if len(self.data) > 0: - s += ', data %s' % hexlify(self.data) - - return s - - def long_string(self): - s = 'app: %s\nverb: %s\nlen: %s' % (self.app, self.verb, len(self.data)) - - if len(self.data) > 0: - try: - s += '\n' + self.data.decode('utf-8') - except UnicodeDecodeError: - s += '\n' + hexlify(self.data) - - return s - - def as_bytestring(self): - b = struct.pack(' 64: - self.write_bytes(fifo_reg, data[:64]) - self.write_register(bc_reg, 64, ack=True) - - data = data[64:] - - self.write_bytes(fifo_reg, data) - self.write_register(bc_reg, len(data), ack=True) - - self.verbose('wrote %s to endpoint %#x' % (hexlify(data), ep_num)) - - # HACK: but given the limitations of the MAX chips, it seems necessary - def read_from_endpoint(self, ep_num): - if ep_num != 1: - return b'' - byte_count = self.read_register(Regs.ep1_out_byte_count) - if byte_count == 0: - return b'' - data = self.read_bytes(Regs.ep1_out_fifo, byte_count) - self.verbose('read %s from endpoint %#x' % (hexlify(data), ep_num)) - return data - - def stall_ep0(self): - self.verbose('stalling endpoint 0') - self.write_register(Regs.ep_stalls, 0x23) - - def run(self): - self.service_irqs() - - def service_irqs(self): - while not self.stop: - irq = self.read_register(Regs.endpoint_irq) - - self.verbose('read endpoint irq: 0x%02x' % irq) - - if irq & ~( - PINCTL.in0_buffer_avail | - PINCTL.in2_buffer_avail | - PINCTL.in3_buffer_avail - ): - self.debug('notable irq: 0x%02x' % irq) - - if irq & PINCTL.setup_data_avail: - self.clear_irq_bit(Regs.endpoint_irq, PINCTL.setup_data_avail) - - b = self.read_bytes(Regs.setup_data_fifo, 8) - if (irq & PINCTL.out0_data_avail) and (ord(b[0]) & 0x80 == 0x00): - data_bytes_len = struct.unpack(' 0: - # Turns out, that at this point we cannot - # - self.debug(req) - self.debug('expecting additional data on control ep - %#x bytes' % (req.length)) - data = os.read(self.control_fd, req.length) - if len(data) != req.length: - self.error('EP0 data have wrong length') - else: - req.data = data - self.connected_device.handle_request(setup_data) - self.configured = self.connected_device.configuration is not None - - def _setup_endpoints(self): - conf = self.connected_device.configuration - for iface in conf.interfaces: - for ep in iface.endpoints: - self._setup_endpoint(ep) - - def _setup_endpoint(self, ep): - if self._ep_already_opened(ep): - self._update_ep(ep) - else: - self._open_endpoint_fd(ep) - buff = struct.pack('I', GFS_CMD_INIT_EP) - descs = ep.get_descriptor(usb_type='fullspeed', valid=True) - if self._is_high_speed(): - descs += ep.get_descriptor(usb_type='highspeed', valid=True) - descs = filter_descriptors(descs, DescriptorType.endpoint) - buff += descs - os.write(ep.fd, buff) - if ep.direction == USBEndpoint.direction_out: - t = OutEpThread(self, ep) - else: - t = InEpThread(self, ep) - self.in_ep_threads.append(t) - self.ep_threads[ep.address] = t - t.start() - - def _ep_already_opened(self, ep): - return ep.address in self.ep_threads - - def _update_ep(self, ep): - self.ep_threads[ep.address].ep = ep - - def _open_endpoint_fd(self, ep): - ''' - :param ep: USBEndpoint object - - :raises Exception: if no endpoint file found, or failed to open - - .. todo:: detect transfer-type specific endpoint files - ''' - num = ep.number - s_dir = 'out' if ep.direction == USBEndpoint.direction_out else 'in' - filename = 'ep%d%s' % (num, s_dir) - path = os.path.join(self.gadgetfs_dir, filename) - fd = os.open(path, os.O_RDWR | os.O_NONBLOCK) - self.debug('Opened endpoint %d' % (num)) - self.debug('ep: %d dir: %s file: %s fd: %d' % (num, s_dir, filename, fd)) - ep.fd = fd - - def _handle_ep0_suspend(self, event): - self.debug('EP0 event type SUSPEND(%#x)' % (GFS_EV_SUSPEND)) - - def ack_status_stage(self): - os.read(self.control_fd, 0) - self._setup_endpoints() - - -class EndpointThread(threading.Thread): - '''Thread for endpoint I/O''' - - def __init__(self, phy, ep): - super(EndpointThread, self).__init__() - self.phy = phy - self.ep = ep - self.stop_evt = threading.Event() - - def run(self): - self.phy.debug('Starting thread for EP %#x' % (self.ep.address)) - first = True - while not self.stop_evt.isSet(): - try: - self.io_op() - except OSError as err: - # bad fd, transport socket closed - if err.errno in [9, 108] and first: - first = False - continue - self.phy.error('Error in EP%d handling thread: %s' % (self.ep.number, err)) - - -class InEpThread(EndpointThread): - - def __init__(self, phy, ep): - super(InEpThread, self).__init__(phy, ep) - self.queue = Queue() - - def send(self, data): - self.queue.put(data) - - def handling_write(self): - return not self.queue.empty() - - def io_op(self): - ''' - Fetch data from send queue and write to endpoint - ''' - try: - data = self.queue.get(True, 0.1) - os.write(self.ep.fd, data) - except Empty: - pass - - -class OutEpThread(EndpointThread): - - def __init__(self, phy, ep): - super(OutEpThread, self).__init__(phy, ep) - self.read_size = self.ep._get_max_packet_size('highspeed') - - def io_op(self): - ''' - read data from endpoint fd and let the endpoint handle it - ''' - self.phy.debug('About to read from EP%d' % (self.ep.number)) - buff = os.read(self.ep.fd, self.read_size) - self.phy.debug('Done reading from EP%d' % (self.ep.number)) - self.phy.connected_device.handle_data_available(self.ep.number, buff) - diff --git a/umap2/phy/iphy.py b/umap2/phy/iphy.py deleted file mode 100644 index 8487659..0000000 --- a/umap2/phy/iphy.py +++ /dev/null @@ -1,91 +0,0 @@ -''' -Physical interface API -''' -import logging - - -class PhyInterface(object): - ''' - This class specifies the API that a physical interface should conform to - ''' - - def __init__(self, app, name): - ''' - :type app: :class:`~umap2.app.base.Umap2App` - :param app: application instance - ''' - self.app = app - self.name = name - self.logger = logging.getLogger('umap2') - self.stop = False - self.connected_device = None - - def connect(self, usb_device): - ''' - Connect a USB device - - :type usb_device: :class:`~umap2.core.usb_class.USBClass` - :param usb_device: USB device class - ''' - self.connected_device = usb_device - - def disconnect(self): - ''' - Disconnect a device. - Once this function returns, the phy doesn't have a reference to the - device. - - :return: the disconnected device (if was connected) - ''' - if self.connected_device: - self.info('Disconnected device %s' % self.connected_device.name) - else: - self.info('Disconnect called when already disconnected') - dev = self.connected_device - self.connected_device = None - return dev - - def is_connected(self): - return self.connected_device is not None - - def send_on_endpoint(self, ep_num, data): - ''' - Send data on a specific endpoint - - :param ep_num: number of endpoint - :param data: data to send - ''' - raise NotImplementedError('should be implemented in subclass') - - def stall_ep0(self): - ''' - Stalls control endpoint (0) - ''' - raise NotImplementedError('should be implemented in subclass') - - def run(self): - ''' - Handle USB requests - ''' - raise NotImplementedError('should be implemented in subclass') - - def verbose(self, msg, *args, **kwargs): - self.logger.verbose('[%s] %s' % (self.name, msg), *args, **kwargs) - - def debug(self, msg, *args, **kwargs): - self.logger.debug('[%s] %s' % (self.name, msg), *args, **kwargs) - - def info(self, msg, *args, **kwargs): - self.logger.info('[%s] %s' % (self.name, msg), *args, **kwargs) - - def warning(self, msg, *args, **kwargs): - self.logger.warning('[%s] %s' % (self.name, msg), *args, **kwargs) - - def error(self, msg, *args, **kwargs): - self.logger.error('[%s] %s' % (self.name, msg), *args, **kwargs) - - def critical(self, msg, *args, **kwargs): - self.logger.critical('[%s] %s' % (self.name, msg), *args, **kwargs) - - def always(self, msg, *args, **kwargs): - self.logger.always('[%s] %s' % (self.name, msg), *args, **kwargs) diff --git a/umap2/phy/raspdancer/__init__.py b/umap2/phy/raspdancer/__init__.py deleted file mode 100644 index 0898828..0000000 --- a/umap2/phy/raspdancer/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -''' -Communication and control with the Raspdancer -''' - diff --git a/umap2/phy/raspdancer/raspdancer.py b/umap2/phy/raspdancer/raspdancer.py deleted file mode 100644 index d7633f6..0000000 --- a/umap2/phy/raspdancer/raspdancer.py +++ /dev/null @@ -1,34 +0,0 @@ -# (C) 2013 Philippe Teuwen -# Modified by Sebastian Haap - -import spi -import RPi.GPIO as GPIO -import struct -from binascii import hexlify -import logging -from time import sleep - - -class Raspdancer: - - def __init__(self): - GPIO.setmode(GPIO.BOARD) - # pin15=GPIO22 is linked to MAX3420 -RST - GPIO.setup(15, GPIO.OUT) - GPIO.output(15,GPIO.LOW) - GPIO.output(15,GPIO.HIGH) - spi.openSPI(speed=26000000) - - def __del__(self): - spi.closeSPI() - GPIO.output(15,GPIO.LOW) - GPIO.output(15,GPIO.HIGH) - GPIO.cleanup() - - def transfer(self, data=[]): - if isinstance(data,str): - data = [ord(x) for x in data] - data = tuple(data) - data = spi.transfer(data) - dataout = "".join([chr(x) for x in data]) - return dataout diff --git a/umap2/phy/raspdancer/raspdancer_phy.py b/umap2/phy/raspdancer/raspdancer_phy.py deleted file mode 100644 index f02b4db..0000000 --- a/umap2/phy/raspdancer/raspdancer_phy.py +++ /dev/null @@ -1,215 +0,0 @@ -''' -Raspdancer (with MAX342x chip) USB physical interface - -This code is based on the MAXUSBApp and FacedancerApp implementation -in GootFET by Travis Goodspeed: https://github.com/travisgoodspeed/goodfet -''' -import struct -from binascii import hexlify -from umap2.phy.iphy import PhyInterface -from umap2.phy.raspdancer.raspdancer import Raspdancer - - -class Regs: - ''' - Enumeration of MAX342x registers - ''' - ep0_fifo = 0x00 - ep1_out_fifo = 0x01 - ep2_in_fifo = 0x02 - ep3_in_fifo = 0x03 - setup_data_fifo = 0x04 - ep0_byte_count = 0x05 - ep1_out_byte_count = 0x06 - ep2_in_byte_count = 0x07 - ep3_in_byte_count = 0x08 - ep_stalls = 0x09 - clr_togs = 0x0a - endpoint_irq = 0x0b - endpoint_interrupt_enable = 0x0c - usb_irq = 0x0d - usb_interrupt_enable = 0x0e - usb_control = 0x0f - cpu_control = 0x10 - pin_control = 0x11 - revision = 0x12 - function_address = 0x13 - io_pins = 0x14 - - -class USBCTL: - ''' - USBCTL (r15) register bits - ''' - hoscsten = 0x80 - vbgate = 0x40 - chipres = 0x20 - pwrdown = 0x10 - connect = 0x08 - sigrwu = 0x04 - - -class PINCTL: - ''' - PINCTL (r17) register bits - ''' - setup_data_avail = 0x20 # SUDAVIRQ - in3_buffer_avail = 0x10 # IN3BAVIRQ - in2_buffer_avail = 0x08 # IN2BAVIRQ - out1_data_avail = 0x04 # OUT1DAVIRQ - out0_data_avail = 0x02 # OUT0DAVIRQ - in0_buffer_avail = 0x01 # IN0BAVIRQ - - -class RaspdancerPhy(PhyInterface): - - # bitmask values for reg_pin_control = 0x11 - interrupt_level = 0x08 - full_duplex = 0x10 - - def __init__(self, app): - super(RaspdancerPhy, self).__init__(app, 'RaspdancerPhy') - self.device = Raspdancer() - self.info('Initialized commands') - self.retries = False - self.reply_buffer = "" - rev = self.read_register(Regs.revision) - self.info('MAX F/W revision: %s' % rev) - # first, we need to read - self.write_register(Regs.pin_control, self.full_duplex | self.interrupt_level) - - def read_register(self, reg_num, ack=False): - self.verbose('Reading register 0x%02x' % reg_num) - mask = 0 if not ack else 1 - data = struct.pack(' 64: - self.write_bytes(fifo_reg, data[:64]) - self.write_register(bc_reg, 64, ack=True) - - data = data[64:] - - self.write_bytes(fifo_reg, data) - self.write_register(bc_reg, len(data), ack=True) - - self.verbose('wrote %s to endpoint %#x' % (hexlify(data), ep_num)) - - # HACK: but given the limitations of the MAX chips, it seems necessary - def read_from_endpoint(self, ep_num): - if ep_num != 1: - return b'' - byte_count = self.read_register(Regs.ep1_out_byte_count) - if byte_count == 0: - return b'' - data = self.read_bytes(Regs.ep1_out_fifo, byte_count) - self.verbose('read %s from endpoint %#x' % (hexlify(data), ep_num)) - return data - - def stall_ep0(self): - self.verbose('stalling endpoint 0') - self.write_register(Regs.ep_stalls, 0x23) - - def run(self): - self.service_irqs() - - def service_irqs(self): - while not self.stop: - irq = self.read_register(Regs.endpoint_irq) - - self.verbose('read endpoint irq: 0x%02x' % irq) - - if irq & ~( - PINCTL.in0_buffer_avail | - PINCTL.in2_buffer_avail | - PINCTL.in3_buffer_avail - ): - self.debug('notable irq: 0x%02x' % irq) - - if irq & PINCTL.setup_data_avail: - self.clear_irq_bit(Regs.endpoint_irq, PINCTL.setup_data_avail) - - b = self.read_bytes(Regs.setup_data_fifo, 8) - if (irq & PINCTL.out0_data_avail) and (ord(b[0]) & 0x80 == 0x00): - data_bytes_len = struct.unpack('