├── .gitignore ├── .idea ├── boofuzz-modbus.iml ├── misc.xml ├── modules.xml ├── vcs.xml └── workspace.xml ├── LICENSE ├── README.md ├── __init__.py ├── __init__.pyc ├── boofuzz ├── .gitignore ├── .travis.yml ├── AUTHORS.txt ├── CHANGELOG.rst ├── CONTRIBUTING.rst ├── CONTRIBUTORS.txt ├── INSTALL.rst ├── LICENSE.txt ├── MANIFEST.in ├── README.rst ├── _static │ └── boo.png ├── boofuzz.pdf ├── boofuzz │ ├── __init__.py │ ├── blocks │ │ ├── __init__.py │ │ ├── block.py │ │ ├── checksum.py │ │ ├── repeat.py │ │ ├── request.py │ │ └── size.py │ ├── constants.py │ ├── doc │ │ ├── block.md │ │ ├── checksum.md │ │ ├── connections.md │ │ ├── primitives.md │ │ ├── request.md │ │ ├── size.md │ │ ├── socket_connection.md │ │ └── target.md │ ├── event_hook.py │ ├── fuzz_logger.py │ ├── fuzz_logger_csv.py │ ├── fuzz_logger_file.py │ ├── fuzz_logger_text.py │ ├── fuzzers.py │ ├── helpers.py │ ├── ifuzz_logger.py │ ├── ifuzz_logger_backend.py │ ├── ifuzzable.py │ ├── instrumentation.py │ ├── ip_constants.py │ ├── iserial_like.py │ ├── itarget_connection.py │ ├── legos │ │ ├── __init__.py │ │ ├── ber.py │ │ ├── dcerpc.py │ │ ├── misc.py │ │ └── xdr.py │ ├── pedrpc.py │ ├── pgraph │ │ ├── __init__.py │ │ ├── cluster.py │ │ ├── edge.py │ │ ├── graph.py │ │ └── node.py │ ├── primitives │ │ ├── __init__.py │ │ ├── base_primitive.py │ │ ├── bit_field.py │ │ ├── byte.py │ │ ├── delim.py │ │ ├── dword.py │ │ ├── from_file.py │ │ ├── group.py │ │ ├── qword.py │ │ ├── random_data.py │ │ ├── static.py │ │ ├── string.py │ │ └── word.py │ ├── serial_connection.py │ ├── serial_connection_low_level.py │ ├── sessions.py │ ├── sex.py │ ├── socket_connection.py │ ├── utils │ │ ├── __init__.py │ │ ├── crash_binning.py │ │ ├── dcerpc.py │ │ └── scada.py │ └── web │ │ ├── __init__.py │ │ ├── app.py │ │ ├── static │ │ └── css │ │ │ └── boofuzz.css │ │ └── templates │ │ ├── base.html │ │ ├── index.html │ │ └── view_crash.html ├── conftest.py ├── docs │ ├── Makefile │ ├── _static │ │ └── boo.png │ ├── conf.py │ ├── index.rst │ ├── make.bat │ ├── source │ │ ├── IFuzzable.rst │ │ ├── Session.rst │ │ ├── Size.rst │ │ ├── Target.rst │ │ ├── boofuzz.event_hook.rst │ │ ├── boofuzz.helpers.rst │ │ ├── boofuzz.instrumentation.rst │ │ ├── boofuzz.ip_constants.rst │ │ ├── boofuzz.pedrpc.rst │ │ ├── boofuzz.sex.rst │ │ ├── boofuzz.utils.crash_binning.rst │ │ └── boofuzz.utils.dcerpc.rst │ └── user │ │ ├── connections.rst │ │ ├── contributing.rst │ │ ├── install.rst │ │ ├── logging.rst │ │ ├── quickstart.rst │ │ └── static-protocol-definition.rst ├── examples │ ├── README.md │ ├── ftp-simple.py │ ├── ftp-with-procmon.py │ ├── fuzz_http_server.py │ ├── fuzz_trend_control_manager_20901.py │ ├── fuzz_trend_server_protect_5168.py │ ├── fuzz_trillian_jabber.py │ └── mdns.py ├── network_monitor.py ├── process_monitor.py ├── process_monitor_unix.py ├── requests │ ├── ___REQUESTS___.html │ ├── hp.py │ ├── http.py │ ├── http_get.py │ ├── http_header.py │ ├── http_post.py │ ├── jabber.py │ ├── ldap.py │ ├── mcafee.py │ ├── ndmp.py │ ├── rendezvous.py │ ├── stun.py │ ├── trend.py │ └── xbox.py ├── setup.cfg ├── setup.py ├── tox.ini ├── unit_test.py ├── unit_tests │ ├── __init__.py │ ├── bit_field_original_value.feature │ ├── block_original_value.feature │ ├── checksum.feature │ ├── checksum_original_value.feature │ ├── helpers_ip_str_to_bytes.feature │ ├── helpers_udp_checksum.feature │ ├── legos.py │ ├── primitives.py │ ├── request_original_value.feature │ ├── size_original_value.feature │ ├── string_original_value.feature │ ├── test_bit_field_original_value.py │ ├── test_block_original_value.py │ ├── test_blocks.py │ ├── test_checksum.py │ ├── test_checksum_original_value.py │ ├── test_fuzz_logger.py │ ├── test_fuzz_logger_csv.py │ ├── test_fuzz_logger_text.py │ ├── test_helpers_ip_str_to_bytes.py │ ├── test_helpers_udp_checksum.py │ ├── test_request_original_value.py │ ├── test_serial_connection_generic.py │ ├── test_size_original_value.py │ ├── test_socket_connection.py │ └── test_string_original_value.py ├── utils │ ├── crashbin_explorer.py │ ├── ida_fuzz_library_extender.py │ ├── pcap_cleaner.py │ ├── pdml_parser.py │ └── print_session.py └── vmcontrol.py ├── fuzz_config.py ├── fuzz_ethernetip.py ├── fuzz_modbus.py ├── fuzz_opc.py ├── fuzz_profinet.py ├── fuzzer.py ├── log_analysis.py ├── logging_config.ini ├── misc ├── README.md ├── modbus.log ├── modbus_read_client.py ├── modbus_rtu_read.py ├── modbus_serial_read.py ├── modbus_server.py └── modbus_write_client.py ├── modbus.py ├── modbus_fuzzer.log └── root.log /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | 3 | # C extensions 4 | *.so 5 | 6 | # Packages 7 | *.egg 8 | *.egg-info 9 | dist 10 | build 11 | eggs 12 | parts 13 | bin 14 | var 15 | sdist 16 | develop-eggs 17 | .installed.cfg 18 | lib 19 | lib64 20 | 21 | # Installer logs 22 | pip-log.txt 23 | 24 | # Unit test / coverage reports 25 | .coverage 26 | .tox 27 | nosetests.xml 28 | 29 | # Translations 30 | *.mo 31 | 32 | # Mr Developer 33 | .mr.developer.cfg 34 | .project 35 | .pydevproject 36 | 37 | *~ 38 | 39 | -------------------------------------------------------------------------------- /.idea/boofuzz-modbus.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 12 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # boofuzz-modbus 2 | modbus fuzzer based on boofuzz framework.!! Cool 3 | 4 | Recently i use fuzzer for my papers, modbus fuzzer for boofuzz 5 | I only write read_coil_memory packet protocol model. 6 | 7 | # first version 8 | 9 | ## packet protocol list 10 | 11 | In modbus protocol specification.list some type of modbus (after list function_code) 12 | 13 | 1. [OK] read_coil(x01) 14 | 2. [OK] read_discrete_inputs(0x02) 15 | 3. read_hoding_registers(0x03) 16 | 4. read_input_register(0x04) 17 | 5. write_single_coil(0x05) 18 | 6. write_single_register(0x06) 19 | 7. read_exception_status(serial line only)(0x07) 20 | 8. diagnostics (Serial Line only)(0x08) 21 | 9. get_comm_event_counter(Serial Line only)(0x0b) 22 | 10. get_comm_event_log(Serial Line only)(0x0c) 23 | 11. write_multiple_coils(0x0f) 24 | 12. write_multiple_registers(0x10) 25 | 13. report_server_id(Serial Line only)(0x11) 26 | 14. read_file_record(0x14) 27 | 15. write_file_record(0x15) 28 | 16. mask_write_register(0x16) 29 | 17. read_write_multiple_registers(0x17) 30 | 18. read_fifo_queue(0x18) 31 | 19. Encapsulated_interface_transport(0x2b) 32 | ...... 33 | 34 | # usage 35 | 36 | modify target ip address : 127.0.0.1:502 37 | 38 | default web ui : http://127.0.0.1:26000 39 | 40 | in cmd: python modbus.py 41 | 42 | ``` 43 | [2018-04-01 06:00:58,785] Test Case: 180 44 | [2018-04-01 06:00:58,785] Info: primitive name: "modbus_read_coil_memorys", type: Repeat, default value: 45 | [2018-04-01 06:00:58,785] Info: Test case 181 of 181 for this node. 180 of 181 overall. 46 | [2018-04-01 06:00:58,785] Test Step: Fuzzing Node 'modbus_read_coil_memory' 47 | [2018-04-01 06:00:58,786] Transmitting 212 bytes: 00 00 00 00 00 06 ff 01 00 00 00 00 01 00 00 00 00 01 00 00 00 00 01 00 00 00 00 01 00 00 00 00 01 00 00 00 00 01 00 00 00 00 01 00 00 00 00 01 00 00 00 00 01 00 00 00 00 01 00 00 00 00 01 00 00 00 00 01 00 00 00 00 01 00 00 00 00 01 00 00 00 00 01 00 00 00 00 01 00 00 00 00 01 00 00 00 00 01 00 00 00 00 01 00 00 00 00 01 00 00 00 00 01 00 00 00 00 01 00 00 00 00 01 00 00 00 00 01 00 00 00 00 01 00 00 00 00 01 00 00 00 00 01 00 00 00 00 01 00 00 00 00 01 00 00 00 00 01 00 00 00 00 01 00 00 00 00 01 00 00 00 00 01 00 00 00 00 01 00 00 00 00 01 00 00 00 00 01 00 00 00 00 01 00 00 00 00 01 00 00 00 00 01 00 00 00 00 01 00 00 00 00 b'\x00\x00\x00\x00\x00\x06\xff\x01\x00\x00\x00\x00\x01\x00\x00\x00\x00\x01\x00\x00\x00\x00\x01\x00\x00\x00\x00\x01\x00\x00\x00\x00\x01\x00\x00\x00\x00\x01\x00\x00\x00\x00\x01\x00\x00\x00\x00\x01\x00\x00\x00\x00\x01\x00\x00\x00\x00\x01\x00\x00\x00\x00\x01\x00\x00\x00\x00\x01\x00\x00\x00\x00\x01\x00\x00\x00\x00\x01\x00\x00\x00\x00\x01\x00\x00\x00\x00\x01\x00\x00\x00\x00\x01\x00\x00\x00\x00\x01\x00\x00\x00\x00\x01\x00\x00\x00\x00\x01\x00\x00\x00\x00\x01\x00\x00\x00\x00\x01\x00\x00\x00\x00\x01\x00\x00\x00\x00\x01\x00\x00\x00\x00\x01\x00\x00\x00\x00\x01\x00\x00\x00\x00\x01\x00\x00\x00\x00\x01\x00\x00\x00\x00\x01\x00\x00\x00\x00\x01\x00\x00\x00\x00\x01\x00\x00\x00\x00\x01\x00\x00\x00\x00\x01\x00\x00\x00\x00\x01\x00\x00\x00\x00\x01\x00\x00\x00\x00\x01\x00\x00\x00\x00\x01\x00\x00\x00\x00\x01\x00\x00\x00\x00\x01\x00\x00\x00\x00\x01\x00\x00\x00\x00' 48 | ``` 49 | 50 | enjoy your fuzzing ! 51 | if you have some problem ,contact gmail:youngtala@gmail.com -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | # coding = utf-8 2 | from fuzz_modbus import * 3 | 4 | -------------------------------------------------------------------------------- /__init__.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/y1t2r4/boofuzz-modbus/bfeb48345b56797b48079e0620e7b06b27085789/__init__.pyc -------------------------------------------------------------------------------- /boofuzz/.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | archived_fuzzies/*.session 3 | docs/_build/ 4 | build/ 5 | dist/ 6 | *.egg-info/ 7 | .idea/ 8 | testing/ 9 | .cache 10 | .tox 11 | -------------------------------------------------------------------------------- /boofuzz/.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | 3 | sudo: required 4 | 5 | before_install: sudo sed -i -e 's/^Defaults\tsecure_path.*$//' /etc/sudoers 6 | 7 | install: pip install tox 8 | 9 | script: 10 | - tox 11 | 12 | deploy: 13 | provider: pypi 14 | user: jtpereyda 15 | password: 16 | secure: GD7YRd2CmeSheZ4A85jVFp/aOLGD75M9WjK/Xc7d99OMBp19porIPbkYi+n+X/CQjl4lzUDVGNfvVZKXGVrY+QPxclfAHHIgb7Tc4nXFOWgaXrvw3LWsSfDreANacC105RGC3siGBkAWnMQgO/Q6N3LDHgy7A7XOa8t6sxedxeu1j0cJa0PmrQnvQy6+G+EpxxgrDphy5vwsIwEwRXRD6+4ekFKG81s7aWL1gvGcic/8JSnhc+jpNkbYrcf3edLT8NyMQlTnoplAYNrCMhPaFkNkstFlJvq2m84WlYUiqkTHOyPla07qaJaGPDt89LqqRISX2SNm2BjG5SqJ+6IkloS3Re83kzWL0kSXr9g4sqeCtsvvOhatEeRWEaOzCEE6pK5feLnagZKUL1ZXPu6ywl+yxxK5jcJE2PklvdMCL2KjdlF4CtMp3yg9a6X6VuPunzoXxVUn0cpj7xqenhMz2nDw1s7ZqFrCQ8ed8Pp+TfbmwZfnZ7GSdvywA4CaapOTLOfP4tWcV9GWiLOo0BjIFMac1tJCsHTjuQeRiUO47hpKGHf8+I7qltzlBJJNf/FGUoSqZjzy4o3zbuzfQHQbo2ueI8aS2C5VjjunbivBjp54eoIiu9rPbZl3uHmMWR74Stch0S2Amfj7d0ovZlaljkWWOf4R0tkuxDCtg+vnsy8= 17 | distributions: sdist bdist_wheel 18 | skip_cleanup: true 19 | on: 20 | tags: true 21 | repo: jtpereyda/boofuzz 22 | branch: master 23 | -------------------------------------------------------------------------------- /boofuzz/AUTHORS.txt: -------------------------------------------------------------------------------- 1 | Pedram Amini 2 | http://pedram.openrce.org 3 | http://www.openrce.org 4 | 5 | Aaron Portnoy 6 | http://dvlabs.tippingpoint.com/team/aportnoy 7 | 8 | Ryan Sears 9 | http://fitblip.github.com/ -------------------------------------------------------------------------------- /boofuzz/CONTRIBUTING.rst: -------------------------------------------------------------------------------- 1 | ============ 2 | Contributing 3 | ============ 4 | 5 | Issues and Bugs 6 | =============== 7 | If you have a bug report or idea for improvement, please create an issue on GitHub, or a pull request with the fix. 8 | 9 | Code Reviews 10 | ============ 11 | All pull requests are subject to professional code review. If you do not want your code reviewed, do not submit it. 12 | 13 | Contributors 14 | ============ 15 | 16 | See installation instructions for details on installing boofuzz from source with developer options. 17 | 18 | Pull Request Checklist 19 | ---------------------- 20 | 21 | 1. Verify tests pass: :: 22 | 23 | tox 24 | 25 | 2. If you have PyCharm, use it to see if your changes introduce any new static analysis warnings. 26 | 27 | 3. Modify CHANGELOG.rst to say what you changed. 28 | 29 | 4. If adding a new module, re-run sphinx-apidoc: :: 30 | 31 | sphinx-apidoc --separate -o source ..\boofuzz 32 | 33 | Maintainers 34 | =========== 35 | 36 | Review Checklist 37 | ---------------- 38 | On every pull request: 39 | 40 | 1. Verify changes are sensible and in line with project goals. 41 | 2. Verify tests pass (continuous integration is OK for this). 42 | 3. Use PyCharm to check static analysis if changes are significant or non-trivial. 43 | 4. Verify CHANGELOG.rst is updated. 44 | 5. Merge in. 45 | 46 | 47 | Release Checklist 48 | ----------------- 49 | Releases are deployed from Travis based on git tags. 50 | 51 | Prep 52 | ++++ 53 | 54 | 1. Create release branch. 55 | 56 | 2. Increment version number from last release according to PEP 0440 and roughly according to the Semantic Versioning guidelines. 57 | 58 | 3. Modify CHANGELOG file for publication if needed. 59 | 60 | 4. Merge release branch. 61 | 62 | Release 63 | +++++++ 64 | 65 | 1. Create release tag in Github. 66 | 67 | 2. Verify Travis deployment succeeds. 68 | -------------------------------------------------------------------------------- /boofuzz/CONTRIBUTORS.txt: -------------------------------------------------------------------------------- 1 | # alphabetical order. 2 | 3 | Charles Timko 4 | - Semantic fix (problems from removing ^M) 5 | 6 | Charlie Miller 7 | - General ideas, beta testing and morale support. 8 | 9 | Chris Bisnett 10 | - Python with statement blocks 11 | 12 | Gabriel Campana 13 | - Bunch of bug fixes (including finally fixing the memory leak issue). 14 | - Various feature enhancements including Ctrl-C support on Unix and the addition of an instrumentation class. 15 | - Various feature enhancements to primitives. 16 | 17 | Gerald Eisenhaur 18 | - General ideas, beta testing and morale support. 19 | - Numerous bug reports. 20 | - SSL support to fuzz session. 21 | 22 | Jared DeMott 23 | - General ideas, beta testing and morale support. 24 | 25 | Nibbler 26 | - Lots of general improvements 27 | - Bugfixes 28 | - Layer 2 fuzzing capabilities 29 | - Morale support (architecture direction) 30 | 31 | Nikolai Rusakov 32 | - Add file-fuzzing capabilities 33 | 34 | nnp 35 | - Unix process monitor agent. 36 | 37 | Peter Silberman 38 | - General ideas, beta testing and morale support. 39 | - Upcoming file fuzzing capability. 40 | - Bug reports. 41 | 42 | Markus Piéton 43 | - Serious bug fix footwork. 44 | -------------------------------------------------------------------------------- /boofuzz/INSTALL.rst: -------------------------------------------------------------------------------- 1 | Installing boofuzz 2 | ================== 3 | 4 | Prerequisites 5 | ------------- 6 | 7 | Boofuzz requires Python. Recommended installation requires ``pip``. 8 | 9 | Ubuntu: ``sudo apt-get install python-pip`` 10 | 11 | Windows: See this `help site`_ but make sure to get Python 2.x instead 12 | of 3.x (pip is included). 13 | 14 | Install 15 | ------- 16 | :: 17 | 18 | pip install boofuzz 19 | 20 | From Source 21 | ----------- 22 | 23 | 1. Download source code: `https://github.com/jtpereyda/boofuzz`_ 24 | 2. Install. Run ``pip`` from within the boofuzz directory: 25 | 26 | - Ubuntu: ``sudo pip install .`` 27 | - Windows: ``pip install .`` 28 | 29 | Tips: 30 | 31 | - Use the ``-e`` option for developer mode, which allows changes to be 32 | seen automatically without reinstalling: 33 | 34 | :: 35 | 36 | `sudo pip install -e .` 37 | 38 | - To install developer tools (unit test dependencies, test runners, etc.) as well: 39 | 40 | :: 41 | 42 | `sudo pip install -e .[dev]` 43 | 44 | - If you’re behind a proxy: 45 | 46 | :: 47 | 48 | `set HTTPS_PROXY=http://your.proxy.com:port` 49 | 50 | - On Linux, also use ``sudo``\ ’s ``-E`` option: 51 | 52 | ``sudo -E pip install -e .`` 53 | 54 | Extras 55 | ------ 56 | 57 | process\_monitor.py (Windows only) 58 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 59 | 60 | The process monitor is a tool for detecting crashes and restarting an 61 | application on Windows (process\_monitor\_unix.py is provided for Unix). 62 | 63 | The process monitor is included with boofuzz, but requires additional 64 | libraries to run. While boofuzz typically runs on a different machine 65 | than the target, the process monitor must run on the target machine 66 | itself. 67 | 68 | If you want to use process\_monitor.py, follow these additional steps: 69 | 70 | 1. Download and install pydbg. 71 | 72 | 1. Make sure to install and run pydbg using a 32-bit Python interpreter, not 64-bit! 73 | 2. The OpenRCE repository doesn’t have a setup.py. Use Fitblip’s 74 | `fork`_. 75 | 3. ``C:\Users\IEUser\Downloads\pydbg-master>pip install ./pydbg-master`` 76 | 77 | 2. Download and install `pydasm`_. 78 | 79 | 1. ``C:\Users\IEUser\Downloads\libdasm-master\libdasm-master\pydasm>python setup.py build_ext``\ \*\* 80 | 2. ``C:\Users\IEUser\Downloads\libdasm-master\libdasm-master\pydasm>python setup.py install`` 81 | 82 | 3. Verify that process\_monitor.py runs: 83 | 84 | :: 85 | 86 | C:\Users\IEUser\Downloads\boofuzz>python process_monitor.py -h 87 | usage: procmon [-h] [--debug] [--quiet] [-f STR] [-c FILENAME] [-i PID] 88 | [-l LEVEL] [-p NAME] [-P PORT] 89 | 90 | optional arguments: 91 | -h, --help show this help message and exit 92 | --debug toggle debug output 93 | --quiet suppress all output 94 | -f STR, --foo STR the notorious foo option 95 | -c FILENAME, --crash_bin FILENAME 96 | filename to serialize crash bin class to 97 | -i PID, --ignore_pid PID 98 | PID to ignore when searching for target process 99 | -l LEVEL, --log_level LEVEL 100 | log level: default 1, increase for more verbosity 101 | -p NAME, --proc_name NAME 102 | process name to search for and attach to 103 | -P PORT, --port PORT TCP port to bind this agent to 104 | 105 | \*\* Building pydasm on Windows requires the `Visual C++ Compiler for 106 | Python 2.7`_. 107 | 108 | Deprecated: network\_monitor.py 109 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 110 | 111 | The network monitor was Sulley’s primary tool for recording test data, 112 | and has been replaced with boofuzz’s logging mechanisms. 113 | However, some people still prefer the PCAP approach. 114 | 115 | .. _help site: http://www.howtogeek.com/197947/how-to-install-python-on-windows/ 116 | .. _releases page: https://github.com/jtpereyda/boofuzz/releases 117 | .. _`https://github.com/jtpereyda/boofuzz`: https://github.com/jtpereyda/boofuzz 118 | .. _fork: https://github.com/Fitblip/pydbg 119 | .. _pydasm: https://github.com/jtpereyda/libdasm 120 | .. _Visual C++ Compiler for Python 2.7: http://aka.ms/vcpython27 121 | -------------------------------------------------------------------------------- /boofuzz/MANIFEST.in: -------------------------------------------------------------------------------- 1 | include *.py 2 | include *.rst 3 | include *.txt 4 | recursive-include boofuzz *.md 5 | recursive-include boofuzz *.py 6 | recursive-include examples *.py 7 | recursive-include examples *.md 8 | recursive-include requests *.html 9 | recursive-include requests *.py 10 | recursive-include unit_tests *.feature 11 | recursive-include unit_tests *.py 12 | recursive-include utils *.py 13 | include tox.ini 14 | 15 | recursive-include docs *.bat 16 | recursive-include docs *.py 17 | recursive-include docs *.rst 18 | recursive-include docs Makefile 19 | 20 | recursive-include _static *.png 21 | recursive-include docs *.png 22 | -------------------------------------------------------------------------------- /boofuzz/README.rst: -------------------------------------------------------------------------------- 1 | boofuzz: Network Protocol Fuzzing for Humans 2 | ============================================ 3 | 4 | .. image:: https://travis-ci.org/jtpereyda/boofuzz.svg?branch=master 5 | :target: https://travis-ci.org/jtpereyda/boofuzz 6 | .. image:: https://readthedocs.org/projects/boofuzz/badge/?version=latest 7 | :target: http://boofuzz.readthedocs.io/en/latest/?badge=latest 8 | :alt: Documentation Status 9 | .. image:: https://img.shields.io/pypi/v/boofuzz.svg 10 | :target: https://pypi.python.org/pypi/boofuzz 11 | .. image:: https://badges.gitter.im/jtpereyda/boofuzz.svg 12 | :alt: Join the chat at https://gitter.im/jtpereyda/boofuzz 13 | :target: https://gitter.im/jtpereyda/boofuzz?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge 14 | 15 | Boofuzz is a fork of and the successor to the venerable `Sulley`_ fuzzing 16 | framework. Besides numerous bug fixes, boofuzz aims for extensibility. 17 | The goal: fuzz everything. 18 | 19 | Why? 20 | ---- 21 | 22 | Sulley has been the preeminent open source fuzzer for some time, but has 23 | fallen out of maintenance. 24 | 25 | Features 26 | -------- 27 | 28 | Like Sulley, boofuzz incorporates all the critical elements of a fuzzer: 29 | 30 | - Easy and quick data generation. 31 | - Instrumentation – AKA failure detection. 32 | - Target reset after failure. 33 | - Recording of test data. 34 | 35 | Unlike Sulley, boofuzz also features: 36 | 37 | - Online `documentation`_. 38 | - Support for arbitrary communications mediums. 39 | - Built-in support for serial fuzzing, ethernet- and IP-layer, UDP broadcast. 40 | - Better recording of test data -- consistent, thorough, clear. 41 | - Test result CSV export. 42 | - *Extensible* instrumentation/failure detection. 43 | - Much easier install experience! 44 | - Far fewer bugs. 45 | 46 | Sulley is affectionately named after the giant teal and purple creature 47 | from Monsters Inc. due to his fuzziness. Boofuzz is likewise named after 48 | the only creature known to have scared Sulley himself: Boo! 49 | 50 | .. figure:: _static/boo.png 51 | :alt: Boo from Monsters Inc 52 | 53 | Boo from Monsters Inc 54 | 55 | Installation 56 | ------------ 57 | :: 58 | 59 | pip install boofuzz 60 | 61 | 62 | Boofuzz installs as a Python library used to build fuzzer scripts. See 63 | `INSTALL.rst`_ for advanced and detailed instructions. 64 | 65 | 66 | Documentation 67 | ------------- 68 | 69 | Documentation is available at http://boofuzz.readthedocs.io/, including nifty quickstart guides. 70 | 71 | Contributions 72 | ------------- 73 | 74 | Pull requests are welcome, as boofuzz is actively maintained (at the 75 | time of this writing ;)). See `CONTRIBUTING.rst`_. 76 | 77 | Community 78 | --------- 79 | 80 | For questions that take the form of “How do I… with boofuzz?” or “I got 81 | this error with boofuzz, why?”, consider posting your question on Stack 82 | Overflow. Make sure to use the ``fuzzing`` tag. 83 | 84 | If you’ve found a bug, or have an idea/suggestion/request, file an issue 85 | here on GitHub. 86 | 87 | For other questions, check out boofuzz on `gitter`_ or `Google Groups`_. 88 | 89 | For updates, follow `@fuzztheplanet`_ on Twitter. 90 | 91 | .. _Sulley: https://github.com/OpenRCE/sulley 92 | .. _Google Groups: https://groups.google.com/d/forum/boofuzz 93 | .. _gitter: https://gitter.im/jtpereyda/boofuzz 94 | .. _@fuzztheplanet: https://twitter.com/fuzztheplanet 95 | .. _documentation: http://boofuzz.readthedocs.io/ 96 | .. _INSTALL.rst: INSTALL.rst 97 | .. _CONTRIBUTING.rst: CONTRIBUTING.rst 98 | -------------------------------------------------------------------------------- /boofuzz/_static/boo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/y1t2r4/boofuzz-modbus/bfeb48345b56797b48079e0620e7b06b27085789/boofuzz/_static/boo.png -------------------------------------------------------------------------------- /boofuzz/boofuzz.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/y1t2r4/boofuzz-modbus/bfeb48345b56797b48079e0620e7b06b27085789/boofuzz/boofuzz.pdf -------------------------------------------------------------------------------- /boofuzz/boofuzz/blocks/__init__.py: -------------------------------------------------------------------------------- 1 | # Import blocks at this level for backwards compatibility. 2 | # blocks/ used to be blocks.py 3 | from .block import Block 4 | from .checksum import Checksum 5 | from .repeat import Repeat 6 | from .request import Request 7 | from .size import Size 8 | 9 | REQUESTS = {} 10 | CURRENT = None 11 | -------------------------------------------------------------------------------- /boofuzz/boofuzz/constants.py: -------------------------------------------------------------------------------- 1 | BIG_ENDIAN = ">" 2 | LITTLE_ENDIAN = "<" 3 | -------------------------------------------------------------------------------- /boofuzz/boofuzz/doc/block.md: -------------------------------------------------------------------------------- 1 | Block Class 2 | =========== 3 | Blocks are added to Requests. 4 | Blocks can contain Sulley Primitives, including other Blocks. -------------------------------------------------------------------------------- /boofuzz/boofuzz/doc/checksum.md: -------------------------------------------------------------------------------- 1 | Checksum Class 2 | ============== 3 | Sulley's Checksum class creates a block that calculates the checksum of another 4 | block. 5 | 6 | Algorithms 7 | ---------- 8 | Checksum contains only the Sulley block logic for a checksum, not specific 9 | algorithm implementations. 10 | Algorithms are implemented with a library or a free function. 11 | 12 | Recursion 13 | --------- 14 | To enable a checksum to be calculated over its parent block, it is necessary 15 | to account for recursion. 16 | This is done with a recursion flag. 17 | When Checksum renders itself for the sake of calculations, it will set a 18 | recursion flag on itself. 19 | Then, when the parent block again renders the Checksum, Checksum will check its 20 | own recursion flag and return its default value. 21 | 22 | Note: To avoid recursion problems with Size, it is important that Checksum's 23 | length method not call render on itself. 24 | 25 | UDP 26 | --- 27 | UDP is special in that it is computed over a pseudo-header, including selected 28 | fields from IPv4 and the entire UDP header and payload. 29 | The IPv4 fields are: 30 | 31 | * IPv4 Source Address 32 | * IPv4 Destination Address 33 | * Protocol (should always be UDP) 34 | * Length of UDP header+payload 35 | 36 | Note that these fields do not themselves need to be individually fuzzed, since 37 | fuzzing any of them would result in a bad checksum -- and a bad checksum can be 38 | checked by fuzzing the checksum field itself. 39 | 40 | Designs considered: 41 | 42 | 1. Passing a reference to the IPv4 Sulley Block and navigating to its children. 43 | 2. Passing source and destination addresses directly to the Checksum 44 | constructor. 45 | 3. Passing references to the source and destination Sulley Blocks into the 46 | Checksum constructor. 47 | 48 | Option 1 was rejected as being too involved. 49 | It would need to know the structure of the IPv4 Sulley Block, which would 50 | result in complexity and possibly duplicated information. 51 | 52 | Option 2 was rejected as inferior to 1 and 3. 53 | With option 2, when the IPv4 source or destination address is being fuzzed, 54 | the UDP checksum will automatically start failing. 55 | This could draw attention away from the IPv4 src/dst fields for the sake of 56 | fuzzing. 57 | 58 | With option 3, when the IPv4 src/dst fields are being fuzzed, the UDP checksum 59 | will still pass. 60 | Furthermore, taking a reference to two Sulley Blocks is relatively easy. -------------------------------------------------------------------------------- /boofuzz/boofuzz/doc/connections.md: -------------------------------------------------------------------------------- 1 | ITargetConnection 2 | ================= 3 | ITargetConnection defines the interface used by the Target classes when sending 4 | and receiving data. This represents the network layer or medium directly below 5 | the protocol under test. 6 | 7 | Design Considerations 8 | --------------------- 9 | Design goals: 10 | 11 | 1. Flexibility with mediums. 12 | 2. Low-layer; avoid interactions with rest of framework. 13 | * Normal logging is left to higher layers. 14 | 3. Facilitate thorough, auditable logs. 15 | * The send method returns the number of bytes actually transmitted, since 16 | some mediums have maximum transmission unit (MTU) limits. The Sulley code 17 | using a connection should check this value and log the number of bytes 18 | transmitted; this enables thorough auditability of data actually sent. 19 | -------------------------------------------------------------------------------- /boofuzz/boofuzz/doc/primitives.md: -------------------------------------------------------------------------------- 1 | Sulley Primitives 2 | ================= 3 | This document describes the standing design of the Sulley Primitives system. 4 | 5 | A Sulley Primitive has an interface partially defined by class BasePrimitive. 6 | The interface consists importantly of: 7 | 8 | * Mutation method. 9 | * Rendering method. 10 | * Num-mutations method to get total mutation count. 11 | * Reset method to reset the primitive to non-mutated state. 12 | * A name property. 13 | * Support for reading length. 14 | 15 | Most of these interface elements are shared by Blocks and Requests. 16 | 17 | Room For Improvement 18 | -------------------- 19 | * Elimination of public variables. 20 | * Explicit and cohesive interface. 21 | -------------------------------------------------------------------------------- /boofuzz/boofuzz/doc/request.md: -------------------------------------------------------------------------------- 1 | Request Class 2 | ============= 3 | 4 | Building 5 | -------- 6 | The Request class has push and pop methods for creating it. 7 | Whenever a new Block is opened/started, it is pushed onto the "block stack". 8 | Note that this is different than the "stack". 9 | When a Block is closed, it is popped. 10 | 11 | When the Request is assembled in the fuzz definition, its stack should be empty. 12 | To accomplish this, all blocks should be closed. -------------------------------------------------------------------------------- /boofuzz/boofuzz/doc/size.md: -------------------------------------------------------------------------------- 1 | Size Class 2 | ========== 3 | The Size class creates a block that calculates the size of another block. 4 | 5 | Calculation 6 | ----------- 7 | To calculate the size of its target block, Size simply calls `len()` on the 8 | target (all Sulley Primitives must support `__len__()`). 9 | 10 | Design Considerations 11 | --------------------- 12 | Size was originally calculated by rendering the target block, or using 13 | callbacks to wait for it to get rendered. 14 | This resulted in dependency issues if a block contained both Size and Checksum 15 | primitives, or in blocks that referenced each other. 16 | Checksum naturally depends on Size's value, but if Size depends on Checksum's 17 | value, we have a recursion problem. 18 | 19 | The current design is motivated by the fact that, in reality, Size does not 20 | depend on Checksum's value. Depending on the length method rather than 21 | rendering more closely matches reality. -------------------------------------------------------------------------------- /boofuzz/boofuzz/doc/socket_connection.md: -------------------------------------------------------------------------------- 1 | SocketConnection Class 2 | ====================== 3 | The SocketConnection is used by the Target class to encapsulate socket 4 | connection details. It implements ITargetConnection. 5 | 6 | Multiple protocols may be used; see constructor. 7 | 8 | Future 9 | ------ 10 | The low-level socket protocols have maximum transmission unit (MTU) limits 11 | based on the standard ethernet frame. Availability of jumbo frames could 12 | enable some interesting tests. -------------------------------------------------------------------------------- /boofuzz/boofuzz/doc/target.md: -------------------------------------------------------------------------------- 1 | Targets 2 | ======= 3 | The Target class defines the interface for Sulley targets, and doubles as 4 | a Socket implementation of the Target interface. 5 | 6 | Target Interface 7 | ---------------- 8 | Sulley uses the Target interface to send and receive data. The Target 9 | implementation(s) should handle logging. This enables consistent send/receive 10 | logging within and between tests. 11 | 12 | Target Class 13 | ------------ 14 | The Target class is also the Socket Target implementation, which the user's 15 | Sulley scripts use to define the target under test. 16 | 17 | The user passes host, port, etc. to Target, which passes them to its aggregate 18 | connection object. 19 | 20 | Future 21 | ------ 22 | Having the interface and a specific implementation in one doesn't match up with 23 | our current design strategy, and is a historical holdover. Furthermore, the 24 | socket arguments and argument docstrings are duplicated in Target; Target 25 | receives them and passes them to its aggregate class. One redesign plan is: 26 | 27 | * Remove SerialTarget; have Target take an ITargetConnection in its constructor. 28 | * This brings us down to a single Target class which doesn't reproduce its 29 | connection's implementation details (like constructor arguments). 30 | * The user would now create a Target Connection instead of a Target. 31 | -------------------------------------------------------------------------------- /boofuzz/boofuzz/event_hook.py: -------------------------------------------------------------------------------- 1 | class EventHook(object): 2 | """ 3 | An EventHook that registers events using +=and -=. 4 | 5 | Based on spassig's solution here: http://stackoverflow.com/a/1094423/461834 6 | """ 7 | def __init__(self): 8 | self.__handlers = [] 9 | 10 | def __iadd__(self, handler): 11 | self.__handlers.append(handler) 12 | return self 13 | 14 | def __isub__(self, handler): 15 | self.__handlers.remove(handler) 16 | return self 17 | 18 | def __len__(self): 19 | return len(self.__handlers) 20 | 21 | def __iter__(self): 22 | return iter(self.__handlers) 23 | 24 | def fire(self, *args, **kwargs): 25 | """ 26 | Call each event handler in sequence. 27 | 28 | @param args: Forwarded to event handler. 29 | @param kwargs: Forwarded to event handler. 30 | 31 | @return: None 32 | """ 33 | for handler in self.__handlers: 34 | handler(*args, **kwargs) 35 | -------------------------------------------------------------------------------- /boofuzz/boofuzz/fuzz_logger.py: -------------------------------------------------------------------------------- 1 | import ifuzz_logger 2 | 3 | 4 | class FuzzLogger(ifuzz_logger.IFuzzLogger): 5 | """ 6 | Implementation for IFuzzLogger. 7 | 8 | FuzzLogger takes logged data and directs it to the appropriate backends. 9 | It aggregates an arbitrary number of logger backends, and functions like a 10 | multiplexer. 11 | 12 | FuzzLogger also maintains failure and error data. 13 | """ 14 | 15 | def __init__(self, fuzz_loggers=None): 16 | if fuzz_loggers is None: 17 | fuzz_loggers = [] 18 | self._fuzz_loggers = fuzz_loggers 19 | 20 | self._cur_test_case_id = '' 21 | self.failed_test_cases = {} 22 | self.error_test_cases = {} 23 | self.passed_test_cases = {} 24 | self.all_test_cases = [] 25 | 26 | def open_test_step(self, description): 27 | for fuzz_logger in self._fuzz_loggers: 28 | fuzz_logger.open_test_step(description=description) 29 | 30 | def log_error(self, description): 31 | if self._cur_test_case_id not in self.error_test_cases: 32 | self.error_test_cases[self._cur_test_case_id] = [] 33 | self.error_test_cases[self._cur_test_case_id].append(description) 34 | for fuzz_logger in self._fuzz_loggers: 35 | fuzz_logger.log_error(description=description) 36 | 37 | def log_fail(self, description=""): 38 | if self._cur_test_case_id not in self.failed_test_cases: 39 | self.failed_test_cases[self._cur_test_case_id] = [] 40 | self.failed_test_cases[self._cur_test_case_id].append(description) 41 | for fuzz_logger in self._fuzz_loggers: 42 | fuzz_logger.log_fail(description=description) 43 | 44 | def log_info(self, description): 45 | for fuzz_logger in self._fuzz_loggers: 46 | fuzz_logger.log_info(description=description) 47 | 48 | def log_recv(self, data): 49 | for fuzz_logger in self._fuzz_loggers: 50 | fuzz_logger.log_recv(data=data) 51 | 52 | def log_pass(self, description=""): 53 | if self._cur_test_case_id not in self.passed_test_cases: 54 | self.passed_test_cases[self._cur_test_case_id] = [] 55 | self.passed_test_cases[self._cur_test_case_id].append(description) 56 | for fuzz_logger in self._fuzz_loggers: 57 | fuzz_logger.log_pass(description=description) 58 | 59 | def log_check(self, description): 60 | for fuzz_logger in self._fuzz_loggers: 61 | fuzz_logger.log_check(description=description) 62 | 63 | def open_test_case(self, test_case_id): 64 | self._cur_test_case_id = test_case_id 65 | self.all_test_cases.append(test_case_id) 66 | for fuzz_logger in self._fuzz_loggers: 67 | fuzz_logger.open_test_case(test_case_id=test_case_id) 68 | 69 | def log_send(self, data): 70 | for fuzz_logger in self._fuzz_loggers: 71 | fuzz_logger.log_send(data=data) 72 | 73 | def failure_summary(self): 74 | """Return test summary string based on fuzz logger results. 75 | 76 | :return: Test summary string, may be multi-line. 77 | """ 78 | summary = "Test Summary: {0} tests ran.\n".format(len(self.all_test_cases)) 79 | summary += "PASSED: {0} test cases.\n".format(len(self.passed_test_cases)) 80 | 81 | if len(self.failed_test_cases) > 0: 82 | summary += "FAILED: {0} test cases:\n".format(len(self.failed_test_cases)) 83 | summary += "{0}\n".format('\n'.join(map(str, self.failed_test_cases.iterkeys()))) 84 | 85 | if len(self.error_test_cases) > 0: 86 | summary += "Errors on {0} test cases:\n".format(len(self.error_test_cases)) 87 | summary += "{0}".format('\n'.join(map(str, self.error_test_cases.iterkeys()))) 88 | 89 | return summary 90 | -------------------------------------------------------------------------------- /boofuzz/boofuzz/fuzz_logger_csv.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | import sys 3 | import datetime 4 | import csv 5 | 6 | from . import helpers 7 | from . import ifuzz_logger_backend 8 | 9 | 10 | def hex_to_hexstr(input_bytes): 11 | """ 12 | Render input_bytes as ASCII-encoded hex bytes, followed by a best effort 13 | utf-8 rendering. 14 | 15 | :param input_bytes: Arbitrary bytes. 16 | 17 | :return: Printable string. 18 | """ 19 | return helpers.hex_str(input_bytes) 20 | 21 | 22 | DEFAULT_HEX_TO_STR = hex_to_hexstr 23 | 24 | 25 | def get_time_stamp(): 26 | s = datetime.datetime.utcnow().isoformat() 27 | return s 28 | 29 | 30 | class FuzzLoggerCsv(ifuzz_logger_backend.IFuzzLoggerBackend): 31 | """ 32 | This class formats FuzzLogger data for pcap file. It can be 33 | configured to output to a named file. 34 | """ 35 | 36 | def __init__(self, file_handle=sys.stdout, bytes_to_str=DEFAULT_HEX_TO_STR): 37 | """ 38 | :type file_handle: io.FileIO 39 | :param file_handle: Open file handle for logging. Defaults to sys.stdout. 40 | 41 | :type bytes_to_str: function 42 | :param bytes_to_str: Function that converts sent/received bytes data to string for logging. 43 | """ 44 | self._file_handle = file_handle 45 | self._format_raw_bytes = bytes_to_str 46 | self._csv_handle = csv.writer(self._file_handle) 47 | 48 | def open_test_step(self, description): 49 | self._print_log_msg(["open step", "", "", description]) 50 | 51 | def log_check(self, description): 52 | self._print_log_msg(["check", "", "", description]) 53 | 54 | def log_error(self, description): 55 | self._print_log_msg(["error", "", "", description]) 56 | 57 | def log_recv(self, data): 58 | self._print_log_msg(["recv", len(data), self._format_raw_bytes(data), data]) 59 | 60 | def log_send(self, data): 61 | self._print_log_msg(["send", len(data), self._format_raw_bytes(data), data]) 62 | 63 | def log_info(self, description): 64 | self._print_log_msg(["info", "", "", description]) 65 | 66 | def open_test_case(self, test_case_id): 67 | self._print_log_msg(["open test case", "", "", "Test case " + str(test_case_id)]) 68 | 69 | def log_fail(self, description=""): 70 | self._print_log_msg(["fail", "", "", description]) 71 | 72 | def log_pass(self, description=""): 73 | self._print_log_msg(["pass", "", "", description]) 74 | 75 | def _print_log_msg(self, msg): 76 | time_stamp = get_time_stamp() 77 | self._csv_handle.writerow([time_stamp] + msg) 78 | -------------------------------------------------------------------------------- /boofuzz/boofuzz/fuzz_logger_file.py: -------------------------------------------------------------------------------- 1 | import ifuzz_logger 2 | import os 3 | import errno 4 | 5 | 6 | class FuzzLoggerFile(ifuzz_logger.IFuzzLogger): 7 | """ 8 | Deprecated: Use FuzzLogger with FuzzLoggerText for typical fuzz logging. 9 | 10 | IFuzzLogger that saves sent and received data to files within a directory. 11 | 12 | File format is: -(rx|tx)-.txt 13 | """ 14 | 15 | def log_error(self, description): 16 | raise Exception("FuzzLoggerFile does not support log_error()!") 17 | 18 | def open_test_step(self, description): 19 | raise Exception("FuzzLoggerFile does not support open_test_step()!") 20 | 21 | def log_fail(self, description=""): 22 | raise Exception("FuzzLoggerFile does not support log_fail()!") 23 | 24 | def log_info(self, description): 25 | raise Exception("FuzzLoggerFile does not support log_info()!") 26 | 27 | def log_pass(self, description=""): 28 | raise Exception("FuzzLoggerFile does not support log_pass()!") 29 | 30 | def log_check(self, description): 31 | raise Exception("FuzzLoggerFile does not support log_check()!") 32 | 33 | def __init__(self, path): 34 | """ 35 | :param path: Directory in which to save fuzz data. 36 | """ 37 | self._path = path 38 | self._current_id = '' 39 | self._rx_count = 0 40 | self._tx_count = 0 41 | 42 | # mkdir -p self._path 43 | try: 44 | os.makedirs(self._path) 45 | except OSError as exc: 46 | if exc.errno == errno.EEXIST and os.path.isdir(path): 47 | pass 48 | else: 49 | raise 50 | 51 | def open_test_case(self, test_case_id): 52 | """ 53 | Open a test case - i.e., a fuzzing mutation. 54 | 55 | :param test_case_id: Test case name/number. Should be unique. 56 | 57 | :return: None 58 | """ 59 | self._current_id = str(test_case_id) 60 | self._rx_count = 0 61 | self._tx_count = 0 62 | 63 | def log_send(self, data): 64 | """ 65 | Records data as about to be sent to the target. 66 | 67 | :param data: Transmitted data 68 | :type data: bytes 69 | 70 | :return: None 71 | :rtype: None 72 | """ 73 | self._tx_count += 1 74 | 75 | filename = "{0}-tx-{1}.txt".format(self._current_id, self._tx_count) 76 | full_name = os.path.join(self._path, filename) 77 | 78 | # Write data in binary mode to avoid newline conversion 79 | with open(full_name, "wb") as file_handle: 80 | file_handle.write(data) 81 | 82 | def log_recv(self, data): 83 | """ 84 | Records data as having been received from the target. 85 | 86 | :param data: Received data. 87 | :type data: bytes 88 | 89 | :return: None 90 | :rtype: None 91 | """ 92 | self._rx_count += 1 93 | 94 | filename = "{0}-rx-{1}.txt".format(self._current_id, self._tx_count) 95 | full_name = os.path.join(self._path, filename) 96 | 97 | # Write data in binary mode to avoid newline conversion 98 | with open(full_name, "wb") as file_handle: 99 | file_handle.write(data) 100 | -------------------------------------------------------------------------------- /boofuzz/boofuzz/fuzzers.py: -------------------------------------------------------------------------------- 1 | from .sex import MustImplementException 2 | 3 | 4 | class Fuzzer(object): 5 | blocks = [] 6 | 7 | def __init__(self): 8 | pass 9 | 10 | def send(self): 11 | raise MustImplementException("You must implement a send() function in your fuzzer!") 12 | 13 | def __repr__(self): 14 | return "" 15 | 16 | 17 | class BlockBasedFuzzer(Fuzzer): 18 | def __init__(self): 19 | super(BlockBasedFuzzer, self).__init__() 20 | 21 | def __repr__(self): 22 | return "" 23 | 24 | 25 | class DumbFileFuzzer(Fuzzer): 26 | def __init__(self): 27 | super(DumbFileFuzzer, self).__init__() 28 | 29 | def __repr__(self): 30 | return "" 31 | -------------------------------------------------------------------------------- /boofuzz/boofuzz/ifuzz_logger_backend.py: -------------------------------------------------------------------------------- 1 | import ifuzz_logger 2 | 3 | # IFuzzLoggerBackend is identical to IFuzzLogger at this point, so just copy: 4 | IFuzzLoggerBackend = ifuzz_logger.IFuzzLogger 5 | """ 6 | Interface for backends for FuzzLogger. Currently identical to IFuzzLogger. 7 | """ 8 | -------------------------------------------------------------------------------- /boofuzz/boofuzz/ifuzzable.py: -------------------------------------------------------------------------------- 1 | import abc 2 | 3 | 4 | class DocStringInheritor(type): 5 | """ 6 | A variation on 7 | http://groups.google.com/group/comp.lang.python/msg/26f7b4fcb4d66c95 8 | by Paul McGuire 9 | """ 10 | def __new__(meta, name, bases, clsdict): 11 | if not('__doc__' in clsdict and clsdict['__doc__']): 12 | for mro_cls in (mro_cls for base in bases for mro_cls in base.mro()): 13 | doc=mro_cls.__doc__ 14 | if doc: 15 | clsdict['__doc__']=doc 16 | break 17 | for attr, attribute in clsdict.items(): 18 | if not attribute.__doc__: 19 | for mro_cls in (mro_cls for base in bases for mro_cls in base.mro() 20 | if hasattr(mro_cls, attr)): 21 | doc=getattr(getattr(mro_cls,attr),'__doc__') 22 | if doc: 23 | if isinstance(attribute, property): 24 | clsdict[attr] = property(attribute.fget, attribute.fset, 25 | attribute.fdel, doc) 26 | else: 27 | attribute.__doc__ = doc 28 | break 29 | return type.__new__(meta, name, bases, clsdict) 30 | 31 | 32 | class IFuzzable(object): 33 | """Describes a fuzzable message element or message. 34 | 35 | Design Notes: 36 | - mutate and reset pretty much form an iterator. Future design goal is 37 | to eliminate them and add a generator function in their place. 38 | """ 39 | __metaclass__ = DocStringInheritor 40 | 41 | @abc.abstractproperty 42 | def fuzzable(self): 43 | """If False, this element should not be mutated in normal fuzzing.""" 44 | return 45 | 46 | @abc.abstractproperty 47 | def mutant_index(self): 48 | """Index of current mutation. 0 => normal value. 1 => first mutation. 49 | """ 50 | return 51 | 52 | @abc.abstractproperty 53 | def original_value(self): 54 | """Original, non-mutated value of element.""" 55 | return 56 | 57 | @abc.abstractproperty 58 | def name(self): 59 | """Element name, should be specific for each instance.""" 60 | return 61 | 62 | @abc.abstractmethod 63 | def mutate(self): 64 | """Mutate this element. Returns True each time and False on completion. 65 | 66 | Use reset() after completing mutations to bring back to original state. 67 | 68 | Mutated values available through render(). 69 | 70 | Returns: 71 | bool: True if there are mutations left, False otherwise. 72 | """ 73 | return 74 | 75 | @abc.abstractmethod 76 | def num_mutations(self): 77 | """Return the total number of mutations for this element. 78 | 79 | Returns: 80 | int: Number of mutated forms this primitive can take 81 | """ 82 | return 83 | 84 | @abc.abstractmethod 85 | def render(self): 86 | """Return rendered value. Equal to original value after reset(). 87 | """ 88 | return 89 | 90 | @abc.abstractmethod 91 | def reset(self): 92 | """Reset element to pre-mutation state.""" 93 | return 94 | 95 | @abc.abstractmethod 96 | def __repr__(self): 97 | return 98 | 99 | @abc.abstractmethod 100 | def __len__(self): 101 | """Length of field. May vary if mutate() changes the length. 102 | 103 | Returns: 104 | int: Length of element (length of mutated element if mutated). 105 | """ 106 | return 107 | 108 | @abc.abstractmethod 109 | def __nonzero__(self): 110 | """Make sure instances evaluate to True even if __len__ is zero. 111 | 112 | Design Note: Exists in case some wise guy uses `if my_element:` to 113 | check for null value. 114 | 115 | Returns: 116 | bool: True 117 | """ 118 | return 119 | -------------------------------------------------------------------------------- /boofuzz/boofuzz/instrumentation.py: -------------------------------------------------------------------------------- 1 | class External: 2 | """ 3 | External instrumentation class 4 | Monitor a target which doesn't support a debugger, allowing external 5 | commands to be called 6 | """ 7 | 8 | def __init__(self, pre=None, post=None, start=None, stop=None): 9 | """ 10 | @type pre: def 11 | @param pre: Callback called before each test case 12 | @type post: def 13 | @param post: Callback called after each test case for instrumentation. Must return True if the target is still 14 | active, False otherwise. 15 | @type start: def 16 | @param start: Callback called to start the target 17 | @type stop: def 18 | @param stop: Callback called to stop the target 19 | """ 20 | 21 | self.pre = pre 22 | self.post = post 23 | self.start = start 24 | self.stop = stop 25 | self.__dbg_flag = False 26 | 27 | # noinspection PyMethodMayBeStatic 28 | def alive(self): 29 | """ 30 | Check if this script is alive. Always True. 31 | """ 32 | 33 | return True 34 | 35 | def debug(self, msg): 36 | """ 37 | Print a debug mesage. 38 | """ 39 | 40 | if self.__dbg_flag: 41 | print "EXT-INSTR> %s" % msg 42 | 43 | # noinspection PyUnusedLocal 44 | def pre_send(self, test_number): 45 | """ 46 | This routine is called before the fuzzer transmits a test case and ensure the target is alive. 47 | 48 | @type test_number: Integer 49 | @param test_number: Test number. 50 | """ 51 | 52 | if self.pre: 53 | self.pre() 54 | 55 | def post_send(self): 56 | """ 57 | This routine is called after the fuzzer transmits a test case and returns the status of the target. 58 | 59 | @rtype: Boolean 60 | @return: Return True if the target is still active, False otherwise. 61 | """ 62 | 63 | if self.post: 64 | return self.post() 65 | else: 66 | return True 67 | 68 | def start_target(self): 69 | """ 70 | Start up the target. Called when post_send failed. 71 | Returns success of failure of the action 72 | If no method defined, false is returned 73 | """ 74 | 75 | if self.start: 76 | return self.start() 77 | else: 78 | return False 79 | 80 | def stop_target(self): 81 | """ 82 | Stop the target. 83 | """ 84 | 85 | if self.stop: 86 | self.stop() 87 | 88 | # noinspection PyMethodMayBeStatic 89 | def get_crash_synopsis(self): 90 | """ 91 | Return the last recorded crash synopsis. 92 | 93 | @rtype: String 94 | @return: Synopsis of last recorded crash. 95 | """ 96 | 97 | return 'External instrumentation detects a crash...\n' 98 | -------------------------------------------------------------------------------- /boofuzz/boofuzz/ip_constants.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file contains constants for the IPv4 protocol. 3 | """ 4 | IPV4_PROTOCOL_UDP = 0x11 5 | #: Theoretical maximum length of a UDP packet, based on constraints in the UDP 6 | #: packet format. 7 | #: WARNING! a UDP packet cannot actually be this long in the context of IPv4! 8 | UDP_MAX_LENGTH_THEORETICAL = 65535 9 | #: Theoretical maximum length of a UDP payload based on constraints in the 10 | #: UDP and IPv4 packet formats. 11 | #: WARNING! Some systems may set a payload limit smaller than this. 12 | UDP_MAX_PAYLOAD_IPV4_THEORETICAL = 65507 13 | -------------------------------------------------------------------------------- /boofuzz/boofuzz/iserial_like.py: -------------------------------------------------------------------------------- 1 | import abc 2 | 3 | 4 | class ISerialLike(object): 5 | """ 6 | A serial-like interface, based on the pySerial module, 7 | the notable difference being that open() must always be called after the object is first created. 8 | 9 | Facilitates dependency injection in modules that use pySerial. 10 | """ 11 | __metaclass__ = abc.ABCMeta 12 | 13 | @abc.abstractmethod 14 | def close(self): 15 | """ 16 | Close connection to the target. 17 | 18 | :return: None 19 | """ 20 | raise NotImplementedError 21 | 22 | @abc.abstractmethod 23 | def open(self): 24 | """ 25 | Opens connection to the target. Make sure to call close! 26 | 27 | :return: None 28 | """ 29 | raise NotImplementedError 30 | 31 | @abc.abstractmethod 32 | def recv(self, max_bytes): 33 | """ 34 | Receive up to max_bytes data from the target. 35 | 36 | :param max_bytes: Maximum number of bytes to receive. 37 | :type max_bytes: int 38 | 39 | :return: Received data. 40 | """ 41 | raise NotImplementedError 42 | 43 | @abc.abstractmethod 44 | def send(self, data): 45 | """ 46 | Send data to the target. Only valid after calling open! 47 | 48 | :param data: Data to send. 49 | 50 | :return: Number of bytes actually sent. 51 | """ 52 | raise NotImplementedError 53 | -------------------------------------------------------------------------------- /boofuzz/boofuzz/itarget_connection.py: -------------------------------------------------------------------------------- 1 | import abc 2 | 3 | 4 | class ITargetConnection(object): 5 | """ 6 | Interface for connections to fuzzing targets. 7 | Target connections may be opened and closed multiple times. You must open before using send/recv and close 8 | afterwards. 9 | """ 10 | __metaclass__ = abc.ABCMeta 11 | 12 | @abc.abstractmethod 13 | def close(self): 14 | """ 15 | Close connection. 16 | 17 | :return: None 18 | """ 19 | raise NotImplementedError 20 | 21 | @abc.abstractmethod 22 | def open(self): 23 | """ 24 | Opens connection to the target. Make sure to call close! 25 | 26 | :return: None 27 | """ 28 | raise NotImplementedError 29 | 30 | @abc.abstractmethod 31 | def recv(self, max_bytes): 32 | """ 33 | Receive up to max_bytes data. 34 | 35 | :param max_bytes: Maximum number of bytes to receive. 36 | :type max_bytes: int 37 | 38 | :return: Received data. bytes('') if no data is received. 39 | """ 40 | raise NotImplementedError 41 | 42 | @abc.abstractmethod 43 | def send(self, data): 44 | """ 45 | Send data to the target. 46 | 47 | :param data: Data to send. 48 | 49 | :rtype int 50 | :return: Number of bytes actually sent. 51 | """ 52 | raise NotImplementedError 53 | -------------------------------------------------------------------------------- /boofuzz/boofuzz/legos/__init__.py: -------------------------------------------------------------------------------- 1 | import ber 2 | import dcerpc 3 | import misc 4 | import xdr 5 | 6 | # all defined legos must be added to this bin. 7 | BIN = { 8 | "ber_string": ber.String, 9 | "ber_integer": ber.Integer, 10 | "dns_hostname": misc.DNSHostname, 11 | "ndr_conformant_array": dcerpc.NdrConformantArray, 12 | "ndr_wstring": dcerpc.NdrWString, 13 | "ndr_string": dcerpc.NdrString, 14 | "tag": misc.Tag, 15 | "xdr_string": xdr.String 16 | } 17 | -------------------------------------------------------------------------------- /boofuzz/boofuzz/legos/ber.py: -------------------------------------------------------------------------------- 1 | # ASN.1 / BER TYPES (http://luca.ntop.org/Teaching/Appunti/asn1.html) 2 | 3 | from __future__ import absolute_import 4 | from .. import blocks, primitives, sex 5 | from ..constants import BIG_ENDIAN 6 | 7 | 8 | class String(blocks.Block): 9 | """ 10 | [0x04][0x84][dword length][string] 11 | 12 | Where: 13 | 14 | 0x04 = string 15 | 0x84 = length is 4 bytes 16 | """ 17 | 18 | def __init__(self, name, request, value, options=None): 19 | if not options: 20 | options = {} 21 | 22 | super(String, self).__init__(name, request) 23 | 24 | self.value = value 25 | self.options = options 26 | self.prefix = options.get("prefix", "\x04") 27 | 28 | if not self.value: 29 | raise sex.SullyRuntimeError("MISSING LEGO.ber_string DEFAULT VALUE") 30 | 31 | str_block = blocks.Block(name + "_STR", request) 32 | str_block.push(primitives.String(self.value)) 33 | 34 | self.push(blocks.Size(name + "_STR", request, endian=BIG_ENDIAN, fuzzable=True)) 35 | self.push(str_block) 36 | 37 | def render(self): 38 | # let the parent do the initial render. 39 | blocks.Block.render(self) 40 | 41 | # TODO: What is this I don't even 42 | self._rendered = self.prefix + "\x84" + self._rendered 43 | 44 | return self._rendered 45 | 46 | 47 | class Integer(blocks.Block): 48 | """ 49 | [0x02][0x04][dword] 50 | 51 | Where: 52 | 53 | 0x02 = integer 54 | 0x04 = integer length is 4 bytes 55 | """ 56 | 57 | def __init__(self, name, request, value, options=None): 58 | if not options: 59 | options = {} 60 | 61 | super(Integer).__init__(name, request) 62 | 63 | self.value = value 64 | self.options = options 65 | 66 | if not self.value: 67 | raise sex.SullyRuntimeError("MISSING LEGO.ber_integer DEFAULT VALUE") 68 | 69 | self.push(primitives.DWord(self.value, endian=BIG_ENDIAN)) 70 | 71 | def render(self): 72 | # let the parent do the initial render. 73 | blocks.Block.render(self) 74 | 75 | self._rendered = "\x02\x04" + self._rendered 76 | return self._rendered 77 | -------------------------------------------------------------------------------- /boofuzz/boofuzz/legos/misc.py: -------------------------------------------------------------------------------- 1 | # Misc Types 2 | 3 | from __future__ import absolute_import 4 | from .. import blocks, primitives, sex 5 | 6 | 7 | class DNSHostname(blocks.Block): 8 | def __init__(self, name, request, value, options=None): 9 | if not options: 10 | options = {} 11 | 12 | super(DNSHostname).__init__(name, request) 13 | 14 | self.value = value 15 | self.options = options 16 | 17 | if not self.value: 18 | raise sex.SullyRuntimeError("MISSING LEGO.tag DEFAULT VALUE") 19 | 20 | self.push(primitives.String(self.value)) 21 | 22 | def render(self): 23 | """ 24 | We overload and extend the render routine in order to properly insert substring lengths. 25 | """ 26 | 27 | # let the parent do the initial render. 28 | blocks.Block.render(self) 29 | 30 | new_str = "" 31 | 32 | # replace dots (.) with the substring length. 33 | for part in self._rendered.split("."): 34 | new_str += str(len(part)) + part 35 | 36 | # be sure to null terminate too. 37 | self._rendered = new_str + "\x00" 38 | 39 | return self._rendered 40 | 41 | 42 | class Tag(blocks.Block): 43 | def __init__(self, name, request, value, options=None): 44 | if not options: 45 | options = {} 46 | 47 | super(Tag, self).__init__(name, request) 48 | 49 | self.value = value 50 | self.options = options 51 | 52 | if not self.value: 53 | raise sex.SullyRuntimeError("MISSING LEGO.tag DEFAULT VALUE") 54 | 55 | # 56 | # [delim][string][delim] 57 | 58 | self.push(primitives.Delim("<")) 59 | self.push(primitives.String(self.value)) 60 | self.push(primitives.Delim(">")) 61 | -------------------------------------------------------------------------------- /boofuzz/boofuzz/legos/xdr.py: -------------------------------------------------------------------------------- 1 | # XDR TYPES (http://www.freesoft.org/CIE/RFC/1832/index.htm) 2 | 3 | from __future__ import absolute_import 4 | 5 | import struct 6 | 7 | from .. import blocks, primitives, sex 8 | from ..helpers import calculate_four_byte_padding 9 | 10 | 11 | class String(blocks.Block): 12 | """ 13 | Note: this is not for fuzzing the XDR protocol but rather just representing an XDR string for fuzzing the actual 14 | client. 15 | """ 16 | 17 | def __init__(self, name, request, value, options=None): 18 | if not options: 19 | options = {} 20 | 21 | super(String).__init__(name, request) 22 | 23 | self.value = value 24 | self.options = options 25 | 26 | if not self.value: 27 | raise sex.SullyRuntimeError("MISSING LEGO.xdr_string DEFAULT VALUE") 28 | 29 | self.push(primitives.String(self.value)) 30 | 31 | def render(self): 32 | """ 33 | We overload and extend the render routine in order to properly pad and prefix the string. 34 | 35 | [dword length][array][pad] 36 | """ 37 | 38 | # let the parent do the initial render. 39 | blocks.Block.render(self) 40 | 41 | # encode the empty string correctly: 42 | if self._rendered == "": 43 | self._rendered = "\x00\x00\x00\x00" 44 | else: 45 | size_header = struct.pack(">L", len(self._rendered)) 46 | self._rendered = size_header + self._rendered + calculate_four_byte_padding(self._rendered) 47 | 48 | return self._rendered 49 | -------------------------------------------------------------------------------- /boofuzz/boofuzz/pgraph/__init__.py: -------------------------------------------------------------------------------- 1 | # 2 | # pGRAPH 3 | # Copyright (C) 2006 Pedram Amini 4 | # 5 | # This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public 6 | # License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later 7 | # version. 8 | # 9 | # This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied 10 | # warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 11 | # 12 | # You should have received a copy of the GNU General Public License along with this program; if not, write to the Free 13 | # Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 14 | # 15 | 16 | from cluster import * 17 | from edge import * 18 | from graph import * 19 | from node import * -------------------------------------------------------------------------------- /boofuzz/boofuzz/pgraph/cluster.py: -------------------------------------------------------------------------------- 1 | # 2 | # pGRAPH 3 | # Copyright (C) 2006 Pedram Amini 4 | # 5 | # This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public 6 | # License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later 7 | # version. 8 | # 9 | # This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied 10 | # warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 11 | # 12 | # You should have received a copy of the GNU General Public License along with this program; if not, write to the Free 13 | # Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 14 | # 15 | 16 | 17 | class Cluster(object): 18 | 19 | id = None 20 | nodes = [] 21 | 22 | def __init__(self, cluster_id=None): 23 | """ 24 | Class constructor. 25 | """ 26 | 27 | self.id = cluster_id 28 | self.nodes = [] 29 | 30 | def add_node(self, node): 31 | """ 32 | Add a node to the cluster. 33 | 34 | @type node: pGRAPH Node 35 | @param node: Node to add to cluster 36 | """ 37 | 38 | self.nodes.append(node) 39 | 40 | return self 41 | 42 | def del_node(self, node_id): 43 | """ 44 | Remove a node from the cluster. 45 | 46 | @type node_id: pGRAPH Node 47 | @param node_id: Node to remove from cluster 48 | """ 49 | 50 | for node in self.nodes: 51 | if node.id == node_id: 52 | self.nodes.remove(node) 53 | break 54 | 55 | return self 56 | 57 | def find_node(self, attribute, value): 58 | """ 59 | Find and return the node with the specified attribute / value pair. 60 | 61 | @type attribute: str 62 | @param attribute: Attribute name we are looking for 63 | @type value: Mixed 64 | @param value: Value of attribute we are looking for 65 | 66 | @rtype: Mixed 67 | @return: Node, if attribute / value pair is matched. None otherwise. 68 | """ 69 | 70 | for node in self.nodes: 71 | if hasattr(node, attribute): 72 | if getattr(node, attribute) == value: 73 | return node 74 | 75 | return None 76 | 77 | def render(self): 78 | pass -------------------------------------------------------------------------------- /boofuzz/boofuzz/primitives/__init__.py: -------------------------------------------------------------------------------- 1 | # Import blocks at this level for API backwards compatibility. 2 | from .base_primitive import BasePrimitive 3 | from .bit_field import BitField 4 | from .byte import Byte 5 | from .delim import Delim 6 | from .dword import DWord 7 | from .from_file import FromFile 8 | from .group import Group 9 | from .qword import QWord 10 | from .random_data import RandomData 11 | from .static import Static 12 | from .string import String 13 | from .word import Word 14 | -------------------------------------------------------------------------------- /boofuzz/boofuzz/primitives/base_primitive.py: -------------------------------------------------------------------------------- 1 | import abc 2 | 3 | from ..ifuzzable import IFuzzable 4 | 5 | 6 | class BasePrimitive(IFuzzable): 7 | """ 8 | The primitive base class implements common functionality shared across most primitives. 9 | """ 10 | 11 | @abc.abstractproperty 12 | def name(self): 13 | pass 14 | 15 | @property 16 | def mutant_index(self): 17 | return self._mutant_index 18 | 19 | @property 20 | def fuzzable(self): 21 | return self._fuzzable 22 | 23 | @property 24 | def original_value(self): 25 | return self._render(self._original_value) 26 | 27 | def __init__(self): 28 | self._fuzzable = True # flag controlling whether or not the given primitive is to be fuzzed. 29 | self._mutant_index = 0 # current mutation index into the fuzz library. 30 | self._original_value = None # original value of primitive. 31 | self._original_value_rendered = None # original value as rendered 32 | 33 | self._fuzz_complete = False # this flag is raised when the mutations are exhausted. 34 | self._fuzz_library = [] # library of static fuzz heuristics to cycle through. 35 | self._rendered = "" # rendered value of primitive. 36 | self._value = None # current value of primitive. 37 | 38 | def mutate(self): 39 | fuzz_complete = False 40 | # if we've ran out of mutations, raise the completion flag. 41 | if self._mutant_index == self.num_mutations(): 42 | self._fuzz_complete = True 43 | fuzz_complete = True 44 | 45 | # if fuzzing was disabled or complete, and mutate() is called, ensure the original value is restored. 46 | if not self._fuzzable or fuzz_complete: 47 | self._value = self._original_value 48 | return False 49 | 50 | # update the current value from the fuzz library. 51 | self._value = self._fuzz_library[self._mutant_index] 52 | 53 | # increment the mutation count. 54 | self._mutant_index += 1 55 | 56 | return True 57 | 58 | def num_mutations(self): 59 | return len(self._fuzz_library) 60 | 61 | def render(self): 62 | """ 63 | Nothing fancy on render, simply return the value. 64 | """ 65 | 66 | self._rendered = self._render(self._value) 67 | return self._rendered 68 | 69 | def _render(self, value): 70 | """ 71 | Render an arbitrary value. 72 | 73 | Args: 74 | value: Value to render. 75 | 76 | Returns: 77 | bytes: Rendered value 78 | """ 79 | return value 80 | 81 | def reset(self): 82 | self._fuzz_complete = False 83 | self._mutant_index = 0 84 | self._value = self._original_value 85 | 86 | def __repr__(self): 87 | return '<%s %s>' % (self.__class__.__name__, repr(self._value)) 88 | 89 | def __len__(self): 90 | return len(self._value) 91 | 92 | def __nonzero__(self): 93 | """ 94 | Make sure instances evaluate to True even if __len__ is zero. 95 | 96 | :return: True 97 | """ 98 | return True 99 | -------------------------------------------------------------------------------- /boofuzz/boofuzz/primitives/byte.py: -------------------------------------------------------------------------------- 1 | import struct 2 | 3 | from .bit_field import BitField 4 | 5 | 6 | class Byte(BitField): 7 | def __init__(self, value, *args, **kwargs): 8 | # Inject the one parameter we care to pass in (width) 9 | width = 8 10 | max_num = None 11 | 12 | super(Byte, self).__init__(value, width, max_num, *args, **kwargs) 13 | 14 | if type(self._value) not in [int, long, list, tuple]: 15 | self._value = struct.unpack(self.endian + "B", self._value)[0] 16 | -------------------------------------------------------------------------------- /boofuzz/boofuzz/primitives/delim.py: -------------------------------------------------------------------------------- 1 | from .base_primitive import BasePrimitive 2 | 3 | 4 | class Delim(BasePrimitive): 5 | def __init__(self, value=None, fuzzable=True, name=None): 6 | """ 7 | Represent a delimiter such as :,\r,\n, ,=,>,< etc... Mutations include repetition, substitution and exclusion. 8 | 9 | @type value: chr 10 | @param value: Original value 11 | @type fuzzable: bool 12 | @param fuzzable: (Optional, def=True) Enable/disable fuzzing of this primitive 13 | @type name: str 14 | @param name: (Optional, def=None) Specifying a name gives you direct access to a primitive 15 | """ 16 | 17 | super(Delim, self).__init__() 18 | 19 | self._fuzzable = fuzzable 20 | self._name = name 21 | self._value = self._original_value = value 22 | 23 | if self._value: 24 | self._fuzz_library.append(self._value * 2) 25 | self._fuzz_library.append(self._value * 5) 26 | self._fuzz_library.append(self._value * 10) 27 | self._fuzz_library.append(self._value * 25) 28 | self._fuzz_library.append(self._value * 100) 29 | self._fuzz_library.append(self._value * 500) 30 | self._fuzz_library.append(self._value * 1000) 31 | 32 | self._fuzz_library.append("") 33 | if self._value == " ": 34 | self._fuzz_library.append("\t") 35 | self._fuzz_library.append("\t" * 2) 36 | self._fuzz_library.append("\t" * 100) 37 | 38 | self._fuzz_library.append(" ") 39 | self._fuzz_library.append("\t") 40 | self._fuzz_library.append("\t " * 100) 41 | self._fuzz_library.append("\t\r\n" * 100) 42 | self._fuzz_library.append("!") 43 | self._fuzz_library.append("@") 44 | self._fuzz_library.append("#") 45 | self._fuzz_library.append("$") 46 | self._fuzz_library.append("%") 47 | self._fuzz_library.append("^") 48 | self._fuzz_library.append("&") 49 | self._fuzz_library.append("*") 50 | self._fuzz_library.append("(") 51 | self._fuzz_library.append(")") 52 | self._fuzz_library.append("-") 53 | self._fuzz_library.append("_") 54 | self._fuzz_library.append("+") 55 | self._fuzz_library.append("=") 56 | self._fuzz_library.append(":") 57 | self._fuzz_library.append(": " * 100) 58 | self._fuzz_library.append(":7" * 100) 59 | self._fuzz_library.append(";") 60 | self._fuzz_library.append("'") 61 | self._fuzz_library.append("\"") 62 | self._fuzz_library.append("/") 63 | self._fuzz_library.append("\\") 64 | self._fuzz_library.append("?") 65 | self._fuzz_library.append("<") 66 | self._fuzz_library.append(">") 67 | self._fuzz_library.append(".") 68 | self._fuzz_library.append(",") 69 | self._fuzz_library.append("\r") 70 | self._fuzz_library.append("\n") 71 | self._fuzz_library.append("\r\n" * 64) 72 | self._fuzz_library.append("\r\n" * 128) 73 | self._fuzz_library.append("\r\n" * 512) 74 | 75 | @property 76 | def name(self): 77 | return self._name 78 | -------------------------------------------------------------------------------- /boofuzz/boofuzz/primitives/dword.py: -------------------------------------------------------------------------------- 1 | import struct 2 | 3 | from boofuzz.primitives.bit_field import BitField 4 | 5 | 6 | class DWord(BitField): 7 | def __init__(self, value, *args, **kwargs): 8 | # Inject our width argument 9 | width = 32 10 | max_num = None 11 | 12 | super(DWord, self).__init__(value, width, max_num, *args, **kwargs) 13 | 14 | if type(self._value) not in [int, long, list, tuple]: 15 | self._value = struct.unpack(self.endian + "L", self._value)[0] 16 | -------------------------------------------------------------------------------- /boofuzz/boofuzz/primitives/from_file.py: -------------------------------------------------------------------------------- 1 | import random 2 | import glob 3 | 4 | from .base_primitive import BasePrimitive 5 | 6 | 7 | class FromFile(BasePrimitive): 8 | 9 | def __init__(self, value, encoding="ascii", fuzzable=True, max_len=0, name=None, filename=None): 10 | """ 11 | Cycles through a list of "bad" values from a file(s). Takes filename and open the file(s) to read 12 | the values to use in fuzzing process. filename may contain glob characters. 13 | 14 | @type value: str 15 | @param value: Default string value 16 | @type encoding: str 17 | @param encoding: (Optional, def="ascii") String encoding, ex: utf_16_le for Microsoft Unicode. 18 | @type fuzzable: bool 19 | @param fuzzable: (Optional, def=True) Enable/disable fuzzing of this primitive 20 | @type max_len: int 21 | @param max_len: (Optional, def=0) Maximum string length 22 | @type name: str 23 | @param name: (Optional, def=None) Specifying a name gives you direct access to a primitive 24 | @type filename: str 25 | @param filename: Filename pattern to load all fuzz value 26 | """ 27 | 28 | super(FromFile, self).__init__() 29 | 30 | self._value = self._original_value = value 31 | self.encoding = encoding 32 | self._fuzzable = fuzzable 33 | self._name = name 34 | self._filename = filename 35 | self._fuzz_library = [] 36 | list_of_files = glob.glob(self._filename) 37 | for fname in list_of_files: 38 | with open(fname,"r") as _file_handle: 39 | self._fuzz_library.extend(_file_handle.readlines()) 40 | 41 | # TODO: Make this more clear 42 | if max_len > 0: 43 | # If any of our strings are over max_len 44 | if any(len(s) > max_len for s in self._fuzz_library): 45 | # Pull out only the ones that aren't 46 | self._fuzz_library = list(set([s for s in self._fuzz_library if len(s) <= max_len])) 47 | 48 | @property 49 | def name(self): 50 | return self._name -------------------------------------------------------------------------------- /boofuzz/boofuzz/primitives/group.py: -------------------------------------------------------------------------------- 1 | from .base_primitive import BasePrimitive 2 | 3 | 4 | class Group(BasePrimitive): 5 | def __init__(self, name, values): 6 | """ 7 | This primitive represents a list of static values, stepping through each one on mutation. You can tie a block 8 | to a group primitive to specify that the block should cycle through all possible mutations for *each* value 9 | within the group. The group primitive is useful for example for representing a list of valid opcodes. 10 | 11 | @type name: str 12 | @param name: Name of group 13 | @type values: list or str 14 | @param values: List of possible raw values this group can take. 15 | """ 16 | 17 | super(Group, self).__init__() 18 | 19 | self._name = name 20 | self.values = values 21 | 22 | assert len(self.values) > 0, "You can't have an empty value list for your group!" 23 | 24 | self._value = self._original_value = self.values[0] 25 | 26 | for val in self.values: 27 | assert isinstance(val, basestring), "Value list may only contain strings or raw data" 28 | 29 | @property 30 | def name(self): 31 | return self._name 32 | 33 | def mutate(self): 34 | """ 35 | Move to the next item in the values list. 36 | 37 | @rtype: bool 38 | @return: False 39 | """ 40 | # TODO: See if num_mutations() can be done away with (me thinks yes). 41 | if self._mutant_index == self.num_mutations(): 42 | self._fuzz_complete = True 43 | 44 | # if fuzzing was disabled or complete, and mutate() is called, ensure the original value is restored. 45 | if not self._fuzzable or self._fuzz_complete: 46 | self._value = self._original_value 47 | return False 48 | 49 | # step through the value list. 50 | # TODO: break this into a get_value() function, so we can keep mutate as close to standard as possible. 51 | self._value = self.values[self._mutant_index] 52 | 53 | # increment the mutation count. 54 | self._mutant_index += 1 55 | 56 | return True 57 | 58 | def num_mutations(self): 59 | """ 60 | Number of values in this primitive. 61 | 62 | @rtype: int 63 | @return: Number of values in this primitive. 64 | """ 65 | 66 | return len(self.values) 67 | -------------------------------------------------------------------------------- /boofuzz/boofuzz/primitives/qword.py: -------------------------------------------------------------------------------- 1 | import struct 2 | 3 | from boofuzz.primitives.bit_field import BitField 4 | 5 | 6 | class QWord(BitField): 7 | def __init__(self, value, *args, **kwargs): 8 | width = 64 9 | max_num = None 10 | 11 | super(QWord, self).__init__(value, width, max_num, *args, **kwargs) 12 | 13 | if type(self._value) not in [int, long, list, tuple]: 14 | self._value = struct.unpack(self.endian + "Q", self._value)[0] 15 | -------------------------------------------------------------------------------- /boofuzz/boofuzz/primitives/random_data.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | from .base_primitive import BasePrimitive 4 | 5 | 6 | class RandomData(BasePrimitive): 7 | def __init__(self, value, min_length, max_length, max_mutations=25, fuzzable=True, step=None, name=None): 8 | """ 9 | Generate a random chunk of data while maintaining a copy of the original. A random length range 10 | can be specified. 11 | 12 | For a static length, set min/max length to be the same. 13 | 14 | @type value: str 15 | @param value: Original value 16 | @type min_length: int 17 | @param min_length: Minimum length of random block 18 | @type max_length: int 19 | @param max_length: Maximum length of random block 20 | @type max_mutations: int 21 | @param max_mutations: (Optional, def=25) Number of mutations to make before reverting to default 22 | @type fuzzable: bool 23 | @param fuzzable: (Optional, def=True) Enable/disable fuzzing of this primitive 24 | @type step: int 25 | @param step: (Optional, def=None) If not null, step count between min and max reps, otherwise random 26 | @type name: str 27 | @param name: (Optional, def=None) Specifying a name gives you direct access to a primitive 28 | """ 29 | 30 | super(RandomData, self).__init__() 31 | 32 | self._value = self._original_value = str(value) 33 | self.min_length = min_length 34 | self.max_length = max_length 35 | self.max_mutations = max_mutations 36 | self._fuzzable = fuzzable 37 | self.step = step 38 | self._name = name 39 | if self.step: 40 | self.max_mutations = (self.max_length - self.min_length) / self.step + 1 41 | 42 | @property 43 | def name(self): 44 | return self._name 45 | 46 | def mutate(self): 47 | """ 48 | Mutate the primitive value returning False on completion. 49 | 50 | @rtype: bool 51 | @return: True on success, False otherwise. 52 | """ 53 | 54 | # if we've ran out of mutations, raise the completion flag. 55 | if self._mutant_index == self.num_mutations(): 56 | self._fuzz_complete = True 57 | 58 | # if fuzzing was disabled or complete, and mutate() is called, ensure the original value is restored. 59 | if not self._fuzzable or self._fuzz_complete: 60 | self._value = self._original_value 61 | return False 62 | 63 | # select a random length for this string. 64 | if not self.step: 65 | length = random.randint(self.min_length, self.max_length) 66 | # select a length function of the mutant index and the step. 67 | else: 68 | length = self.min_length + self._mutant_index * self.step 69 | 70 | # reset the value and generate a random string of the determined length. 71 | self._value = "" 72 | for i in xrange(length): 73 | self._value += chr(random.randint(0, 255)) 74 | 75 | # increment the mutation count. 76 | self._mutant_index += 1 77 | 78 | return True 79 | 80 | def num_mutations(self): 81 | """ 82 | Calculate and return the total number of mutations for this individual primitive. 83 | 84 | @rtype: int 85 | @return: Number of mutated forms this primitive can take 86 | """ 87 | 88 | return self.max_mutations 89 | -------------------------------------------------------------------------------- /boofuzz/boofuzz/primitives/static.py: -------------------------------------------------------------------------------- 1 | from .base_primitive import BasePrimitive 2 | 3 | 4 | class Static(BasePrimitive): 5 | def __init__(self, value, name=None): 6 | """ 7 | Primitive that contains static content. 8 | 9 | @type value: str 10 | @param value: Raw static data 11 | @type name: str 12 | @param name: (Optional, def=None) Specifying a name gives you direct access to a primitive 13 | """ 14 | 15 | super(Static, self).__init__() 16 | 17 | self._fuzz_complete = True 18 | self._fuzzable = False 19 | self._value = self._original_value = value 20 | self._name = name 21 | 22 | @property 23 | def name(self): 24 | return self._name 25 | 26 | def mutate(self): 27 | """ 28 | Always return false, don't fuzz 29 | """ 30 | return False 31 | 32 | def num_mutations(self): 33 | """ 34 | We have no mutations 35 | """ 36 | return 0 37 | -------------------------------------------------------------------------------- /boofuzz/boofuzz/primitives/word.py: -------------------------------------------------------------------------------- 1 | import struct 2 | 3 | from boofuzz.primitives.bit_field import BitField 4 | 5 | 6 | class Word(BitField): 7 | def __init__(self, value, *args, **kwargs): 8 | # Inject our width argument 9 | width = 16 10 | max_num = None 11 | 12 | super(Word, self).__init__(value, width, max_num, *args, **kwargs) 13 | 14 | 15 | if type(self._value) not in [int, long, list, tuple]: 16 | self._value = struct.unpack(self.endian + "H", self._value)[0] 17 | -------------------------------------------------------------------------------- /boofuzz/boofuzz/serial_connection_low_level.py: -------------------------------------------------------------------------------- 1 | import iserial_like 2 | import serial 3 | 4 | 5 | class SerialConnectionLowLevel(iserial_like.ISerialLike): 6 | """Internal wrapper for a serial object; backend for SerialConnection. 7 | 8 | Separated from SerialConnection to allow for effective unit testing. 9 | 10 | Implements serial_like.ISerialLike. 11 | """ 12 | 13 | def __init__(self, port, baudrate, timeout=None): 14 | """ 15 | @type port: int | str 16 | @param port: Serial port name or number. 17 | @type baudrate: int 18 | @param baudrate: Baud rate for port. 19 | @type timeout: float 20 | @param timeout: Serial port timeout. See pySerial docs. May be updated after creation. 21 | """ 22 | self._device = None 23 | self.port = port 24 | self.baudrate = baudrate 25 | self.timeout = timeout 26 | 27 | def close(self): 28 | """ 29 | Close connection to the target. 30 | 31 | :return: None 32 | """ 33 | self._device.close() 34 | 35 | def open(self): 36 | """ 37 | Opens connection to the target. Make sure to call close! 38 | 39 | :return: None 40 | """ 41 | self._device = serial.Serial(port=self.port, baudrate=self.baudrate) 42 | 43 | def recv(self, max_bytes): 44 | """ 45 | Receive up to max_bytes data from the target. 46 | 47 | :param max_bytes: Maximum number of bytes to receive. 48 | :type max_bytes: int 49 | 50 | :return: Received data. 51 | """ 52 | self._device.timeout = self.timeout 53 | return self._device.read(size=max_bytes) 54 | 55 | def send(self, data): 56 | """ 57 | Send data to the target. Only valid after calling open! 58 | 59 | :param data: Data to send. 60 | 61 | :return: Number of bytes actually sent. 62 | """ 63 | return self._device.write(data) 64 | -------------------------------------------------------------------------------- /boofuzz/boofuzz/sex.py: -------------------------------------------------------------------------------- 1 | import attr 2 | # Sulley EXception Class 3 | 4 | 5 | class BoofuzzError(Exception): 6 | pass 7 | 8 | 9 | class BoofuzzRestartFailedError(BoofuzzError): 10 | pass 11 | 12 | 13 | class BoofuzzTargetConnectionFailedError(BoofuzzError): 14 | pass 15 | 16 | 17 | class BoofuzzTargetConnectionReset(BoofuzzError): 18 | pass 19 | 20 | 21 | @attr.s 22 | class BoofuzzTargetConnectionAborted(BoofuzzError): 23 | """ 24 | Raised on `errno.ECONNABORTED`. 25 | """ 26 | socket_errno = attr.ib() 27 | socket_errmsg = attr.ib() 28 | 29 | 30 | class SullyRuntimeError(Exception): 31 | pass 32 | 33 | 34 | class SizerNotUtilizedError(Exception): 35 | pass 36 | 37 | 38 | class MustImplementException(Exception): 39 | pass 40 | -------------------------------------------------------------------------------- /boofuzz/boofuzz/utils/__init__.py: -------------------------------------------------------------------------------- 1 | from . import crash_binning 2 | from . import dcerpc 3 | from . import scada 4 | -------------------------------------------------------------------------------- /boofuzz/boofuzz/utils/dcerpc.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | import math 3 | import struct 4 | from .. import helpers 5 | 6 | 7 | def bind(uuid, version): 8 | """ 9 | Generate the data necessary to bind to the specified interface. 10 | """ 11 | 12 | major, minor = version.split(".") 13 | 14 | major = struct.pack("') 25 | def view_crash(crash_id): 26 | return render_template("view_crash.html", crashinfo=app.session.procmon_results[crash_id]) 27 | 28 | 29 | @app.route("/") 30 | def index(): 31 | crashes = [] 32 | procmon_result_keys = app.session.procmon_results.keys() 33 | procmon_result_keys.sort() 34 | 35 | for key in procmon_result_keys: 36 | val = app.session.procmon_results[key] 37 | status_bytes = " " 38 | 39 | if key in app.session.netmon_results: 40 | status_bytes = commify(app.session.netmon_results[key]) 41 | 42 | crash = { 43 | "key": key, 44 | "value": val.split("\n")[0], 45 | "status_bytes": status_bytes 46 | } 47 | crashes.append(crash) 48 | 49 | # which node (request) are we currently fuzzing. 50 | if app.session.fuzz_node.name: 51 | current_name = app.session.fuzz_node.name 52 | else: 53 | current_name = "[N/A]" 54 | 55 | # render sweet progress bars. 56 | mutant_index = float(app.session.fuzz_node.mutant_index) 57 | num_mutations = float(app.session.fuzz_node.num_mutations()) 58 | 59 | try: 60 | progress_current = mutant_index / num_mutations 61 | except ZeroDivisionError: 62 | progress_current = 0 63 | num_bars = int(progress_current * 50) 64 | progress_current_bar = "[" + "=" * num_bars + " " * (50 - num_bars) + "]" 65 | progress_current = "%.3f%%" % (progress_current * 100) 66 | 67 | total_mutant_index = float(app.session.total_mutant_index) 68 | total_num_mutations = float(app.session.total_num_mutations) 69 | 70 | try: 71 | progress_total = total_mutant_index / total_num_mutations 72 | except ZeroDivisionError: 73 | progress_total = 0 74 | num_bars = int(progress_total * 50) 75 | progress_total_bar = "[" + "=" * num_bars + " " * (50 - num_bars) + "]" 76 | progress_total = "%.3f%%" % (progress_total * 100) 77 | 78 | state = { 79 | "session": app.session, 80 | "current_mutant_index": commify(app.session.fuzz_node.mutant_index), 81 | "current_name": current_name, 82 | "current_num_mutations": commify(app.session.fuzz_node.num_mutations()), 83 | "progress_current": progress_current, 84 | "progress_current_bar": progress_current_bar, 85 | "progress_total": progress_total, 86 | "progress_total_bar": progress_total_bar, 87 | "total_mutant_index": commify(app.session.total_mutant_index), 88 | "total_num_mutations": commify(app.session.total_num_mutations), 89 | } 90 | 91 | return render_template('index.html', state=state, crashes=crashes) -------------------------------------------------------------------------------- /boofuzz/boofuzz/web/static/css/boofuzz.css: -------------------------------------------------------------------------------- 1 | a:link {color: #FF8200; text-decoration: none;} 2 | a:visited {color: #FF8200; text-decoration: none;} 3 | a:hover {color: #C5C5C5; text-decoration: none;} 4 | 5 | body { 6 | background-color: #000000; 7 | font-family: Arial, Helvetica, sans-serif; 8 | font-size: 12px; 9 | color: #FFFFFF; 10 | } 11 | 12 | td { 13 | font-family: Arial, Helvetica, sans-serif; 14 | font-size: 12px; 15 | color: #A0B0B0; 16 | } 17 | 18 | .fixed { 19 | font-family: Courier New, sans-serif; 20 | font-size: 12px; 21 | color: #A0B0B0; 22 | } 23 | 24 | .input { 25 | font-family: Arial, Helvetica, sans-serif; 26 | font-size: 11px; 27 | color: #FFFFFF; 28 | background-color: #333333; 29 | border: thin none; 30 | height: 20px; 31 | } 32 | 33 | .running { 34 | color: green; 35 | } 36 | 37 | .paused { 38 | color: red; 39 | } 40 | 41 | .main-wrapper { 42 | margin-left: auto; 43 | margin-right: auto; 44 | display: block; 45 | width: 750px; 46 | } -------------------------------------------------------------------------------- /boofuzz/boofuzz/web/templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | boofuzz Fuzz Control 7 | 8 | 9 | 10 | {% block body %} {% endblock %} 11 | 12 | -------------------------------------------------------------------------------- /boofuzz/boofuzz/web/templates/index.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block body %} 3 | 4 |
5 | 6 | 7 | 101 | 102 |
8 | 9 | 10 | 13 | 20 | 21 | 22 | 68 | 69 | 70 | 75 | 76 |
11 |
ICS Fuzz Control
12 |
14 | {% if state.session.is_paused %} 15 |
PAUSED
16 | {% else %} 17 |
RUNNING
18 | {% endif %} 19 |
23 | 24 | 25 | 28 | 31 | 34 | 37 | 40 | 43 | 44 | 45 | 50 | 53 | 56 | 59 | 62 | 65 | 66 |
26 | Total: 27 | 29 | {{ state.total_mutant_index }} 30 | 32 | of 33 | 35 | {{ state.total_num_mutations }} 36 | 38 | {{ state.progress_total_bar | safe }} 39 | 41 | {{ state.progress_total }} 42 |
46 | 47 | {{ state.current_name }}: 48 | 49 | 51 | {{ state.current_mutant_index }} 52 | 54 | of 55 | 57 | {{ state.current_num_mutations }} 58 | 60 | {{ state.progress_current_bar | safe }} 61 | 63 | {{ state.progress_current }} 64 |
67 |
71 |
72 | 73 |
74 |
77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | {% for crash in crashes %} 85 | 86 | 91 | 94 | 97 | 98 | {% endfor %} 99 |
Test Case #Crash SynopsisCaptured Bytes
87 | 88 | {{crash.key}} 89 | 90 | 92 | {{crash.value}} 93 | 95 | {{crash.status_bytes}} 96 |
100 |
103 |
104 | {% endblock %} -------------------------------------------------------------------------------- /boofuzz/boofuzz/web/templates/view_crash.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block body %} 3 |
4 | 5 | 6 | 20 | 21 |
7 | 8 | 9 | 12 | 13 | 14 | 17 | 18 |
10 |
Crash Viewer
11 |
15 |
{{ crashinfo.decode('utf-8', errors='replace') }}
16 |
19 |
22 |
23 | {% endblock %} 24 | -------------------------------------------------------------------------------- /boofuzz/conftest.py: -------------------------------------------------------------------------------- 1 | # pytest is required as an extras_require: 2 | # noinspection PyPackageRequirements 3 | import pytest 4 | 5 | 6 | @pytest.fixture 7 | def context(): 8 | class Context(object): 9 | pass 10 | 11 | return Context() 12 | -------------------------------------------------------------------------------- /boofuzz/docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | SPHINXPROJ = boofuzz 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -------------------------------------------------------------------------------- /boofuzz/docs/_static/boo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/y1t2r4/boofuzz-modbus/bfeb48345b56797b48079e0620e7b06b27085789/boofuzz/docs/_static/boo.png -------------------------------------------------------------------------------- /boofuzz/docs/index.rst: -------------------------------------------------------------------------------- 1 | boofuzz: Network Protocol Fuzzing for Humans 2 | ============================================ 3 | 4 | .. image:: https://travis-ci.org/jtpereyda/boofuzz.svg?branch=master 5 | :target: https://travis-ci.org/jtpereyda/boofuzz 6 | .. image:: https://readthedocs.org/projects/boofuzz/badge/?version=latest 7 | :target: http://boofuzz.readthedocs.io/en/latest/?badge=latest 8 | :alt: Documentation Status 9 | .. image:: https://img.shields.io/pypi/v/boofuzz.svg 10 | :target: https://pypi.python.org/pypi/boofuzz 11 | .. image:: https://badges.gitter.im/jtpereyda/boofuzz.svg 12 | :alt: Join the chat at https://gitter.im/jtpereyda/boofuzz 13 | :target: https://gitter.im/jtpereyda/boofuzz?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge 14 | 15 | Boofuzz is a fork of and the successor to the venerable `Sulley`_ fuzzing 16 | framework. Besides numerous bug fixes, boofuzz aims for extensibility. 17 | The goal: fuzz everything. 18 | 19 | Why? 20 | ---- 21 | 22 | Sulley has been the preeminent open source fuzzer for some time, but has 23 | fallen out of maintenance. 24 | 25 | Features 26 | -------- 27 | 28 | Like Sulley, boofuzz incorporates all the critical elements of a fuzzer: 29 | 30 | - Easy and quick data generation. 31 | - Instrumentation – AKA failure detection. 32 | - Target reset after failure. 33 | - Recording of test data. 34 | 35 | Unlike Sulley, boofuzz also features: 36 | 37 | - Much easier install experience! 38 | - Support for arbitrary communications mediums. 39 | - Built-in support for serial fuzzing, ethernet- and IP-layer, UDP broadcast. 40 | - Better recording of test data -- consistent, thorough, clear. 41 | - Test result CSV export. 42 | - *Extensible* instrumentation/failure detection. 43 | - Far fewer bugs. 44 | 45 | Sulley is affectionately named after the giant teal and purple creature 46 | from Monsters Inc. due to his fuzziness. Boofuzz is likewise named after 47 | the only creature known to have scared Sulley himself: Boo! 48 | 49 | .. figure:: _static/boo.png 50 | :alt: Boo from Monsters Inc 51 | 52 | Boo from Monsters Inc 53 | 54 | Installation 55 | ------------ 56 | :: 57 | 58 | pip install boofuzz 59 | 60 | 61 | Boofuzz installs as a Python library used to build fuzzer scripts. See 62 | :ref:`install` for advanced and detailed instructions. 63 | 64 | 65 | User Guide 66 | ---------- 67 | 68 | 69 | .. toctree:: 70 | :maxdepth: 2 71 | 72 | user/install 73 | user/quickstart 74 | 75 | Public Protocol Libraries 76 | ------------------------- 77 | 78 | The following protocol libraries are free and open source, but the implementations are not at all close to full protocol 79 | coverage: 80 | 81 | - `boofuzz-ftp`_ 82 | - `boofuzz-http`_ 83 | 84 | If you have an open source boofuzz protocol suite to share, please :ref:`let us know `! 85 | 86 | API Documentation 87 | ----------------- 88 | 89 | .. toctree:: 90 | :maxdepth: 2 91 | 92 | source/Session 93 | source/Target 94 | user/connections 95 | user/logging 96 | user/static-protocol-definition 97 | 98 | Contributions 99 | ------------- 100 | 101 | Pull requests are welcome, as boofuzz is actively maintained (at the 102 | time of this writing ;)). See :ref:`contributing`. 103 | 104 | .. _community: 105 | 106 | Community 107 | --------- 108 | 109 | For questions that take the form of “How do I… with boofuzz?” or “I got 110 | this error with boofuzz, why?”, consider posting your question on Stack 111 | Overflow. Make sure to use the ``fuzzing`` tag. 112 | 113 | If you’ve found a bug, or have an idea/suggestion/request, file an issue 114 | here on GitHub. 115 | 116 | For other questions, check out boofuzz on `gitter`_ or `Google Groups`_. 117 | 118 | For updates, follow `@fuzztheplanet`_ on Twitter. 119 | 120 | .. _Sulley: https://github.com/OpenRCE/sulley 121 | .. _Google Groups: https://groups.google.com/d/forum/boofuzz 122 | .. _gitter: https://gitter.im/jtpereyda/boofuzz 123 | .. _@fuzztheplanet: https://twitter.com/fuzztheplanet 124 | .. _boofuzz-ftp: https://github.com/jtpereyda/boofuzz-ftp 125 | .. _boofuzz-http: https://github.com/jtpereyda/boofuzz-http 126 | 127 | 128 | Indices and tables 129 | ================== 130 | 131 | * :ref:`genindex` 132 | * :ref:`modindex` 133 | * :ref:`search` 134 | -------------------------------------------------------------------------------- /boofuzz/docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | set SPHINXPROJ=boofuzz 13 | 14 | if "%1" == "" goto help 15 | 16 | %SPHINXBUILD% >NUL 2>NUL 17 | if errorlevel 9009 ( 18 | echo. 19 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 20 | echo.installed, then set the SPHINXBUILD environment variable to point 21 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 22 | echo.may add the Sphinx directory to PATH. 23 | echo. 24 | echo.If you don't have Sphinx installed, grab it from 25 | echo.http://sphinx-doc.org/ 26 | exit /b 1 27 | ) 28 | 29 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 30 | goto end 31 | 32 | :help 33 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 34 | 35 | :end 36 | popd 37 | -------------------------------------------------------------------------------- /boofuzz/docs/source/IFuzzable.rst: -------------------------------------------------------------------------------- 1 | IFuzzable 2 | ========= 3 | 4 | .. automodule:: boofuzz.ifuzzable 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /boofuzz/docs/source/Session.rst: -------------------------------------------------------------------------------- 1 | Session 2 | ======= 3 | 4 | .. autoclass:: boofuzz.Session 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /boofuzz/docs/source/Size.rst: -------------------------------------------------------------------------------- 1 | Size 2 | ==== 3 | 4 | .. autoclass:: boofuzz.Size 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /boofuzz/docs/source/Target.rst: -------------------------------------------------------------------------------- 1 | Target 2 | ====== 3 | 4 | .. autoclass:: boofuzz.Target 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /boofuzz/docs/source/boofuzz.event_hook.rst: -------------------------------------------------------------------------------- 1 | boofuzz.event_hook module 2 | ========================= 3 | 4 | .. automodule:: boofuzz.event_hook 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /boofuzz/docs/source/boofuzz.helpers.rst: -------------------------------------------------------------------------------- 1 | boofuzz.helpers module 2 | ====================== 3 | 4 | .. automodule:: boofuzz.helpers 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /boofuzz/docs/source/boofuzz.instrumentation.rst: -------------------------------------------------------------------------------- 1 | boofuzz.instrumentation module 2 | ============================== 3 | 4 | .. automodule:: boofuzz.instrumentation 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /boofuzz/docs/source/boofuzz.ip_constants.rst: -------------------------------------------------------------------------------- 1 | boofuzz.ip_constants module 2 | =========================== 3 | 4 | .. automodule:: boofuzz.ip_constants 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /boofuzz/docs/source/boofuzz.pedrpc.rst: -------------------------------------------------------------------------------- 1 | boofuzz.pedrpc module 2 | ===================== 3 | 4 | .. automodule:: boofuzz.pedrpc 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /boofuzz/docs/source/boofuzz.sex.rst: -------------------------------------------------------------------------------- 1 | boofuzz.sex module 2 | ================== 3 | 4 | .. automodule:: boofuzz.sex 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /boofuzz/docs/source/boofuzz.utils.crash_binning.rst: -------------------------------------------------------------------------------- 1 | boofuzz.utils.crash_binning module 2 | ================================== 3 | 4 | .. automodule:: boofuzz.utils.crash_binning 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /boofuzz/docs/source/boofuzz.utils.dcerpc.rst: -------------------------------------------------------------------------------- 1 | boofuzz.utils.dcerpc module 2 | =========================== 3 | 4 | .. automodule:: boofuzz.utils.dcerpc 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /boofuzz/docs/user/connections.rst: -------------------------------------------------------------------------------- 1 | .. _connections: 2 | 3 | Connections 4 | =========== 5 | 6 | Connection objects implement :class:`ITargetConnection `. Available options include 7 | :class:`SocketConnection ` and :class:`SerialConnection `. 8 | 9 | .. autoclass:: boofuzz.ITargetConnection 10 | :members: 11 | :undoc-members: 12 | :show-inheritance: 13 | 14 | .. autoclass:: boofuzz.SocketConnection 15 | :members: 16 | :undoc-members: 17 | :show-inheritance: 18 | 19 | .. autoclass:: boofuzz.SerialConnection 20 | :members: 21 | :undoc-members: 22 | :show-inheritance: 23 | -------------------------------------------------------------------------------- /boofuzz/docs/user/contributing.rst: -------------------------------------------------------------------------------- 1 | .. _contributing: 2 | .. include:: ../../CONTRIBUTING.rst -------------------------------------------------------------------------------- /boofuzz/docs/user/install.rst: -------------------------------------------------------------------------------- 1 | .. _install: 2 | .. include:: ../../INSTALL.rst -------------------------------------------------------------------------------- /boofuzz/docs/user/logging.rst: -------------------------------------------------------------------------------- 1 | .. _logging: 2 | 3 | ======= 4 | Logging 5 | ======= 6 | 7 | Boofuzz provides flexible logging. All logging classes implement :class:`IFuzzLogger `. 8 | Built-in logging classes are detailed below. 9 | 10 | Logging Interface 11 | ================= 12 | .. automodule:: boofuzz.ifuzz_logger 13 | :members: 14 | :undoc-members: 15 | :show-inheritance: 16 | 17 | .. automodule:: boofuzz.ifuzz_logger_backend 18 | :members: 19 | :undoc-members: 20 | :show-inheritance: 21 | 22 | Text Logging 23 | ============ 24 | .. automodule:: boofuzz.fuzz_logger_text 25 | :members: 26 | :undoc-members: 27 | :show-inheritance: 28 | 29 | CSV Logging 30 | =========== 31 | .. automodule:: boofuzz.fuzz_logger_csv 32 | :members: 33 | :undoc-members: 34 | :show-inheritance: 35 | 36 | File Logging 37 | ============ 38 | .. automodule:: boofuzz.fuzz_logger_file 39 | :members: 40 | :undoc-members: 41 | :show-inheritance: 42 | 43 | FuzzLogger Object 44 | ================= 45 | .. automodule:: boofuzz.fuzz_logger 46 | :members: 47 | :undoc-members: 48 | :show-inheritance: 49 | -------------------------------------------------------------------------------- /boofuzz/docs/user/quickstart.rst: -------------------------------------------------------------------------------- 1 | .. _quickstart: 2 | 3 | Quickstart 4 | ========== 5 | 6 | The :class:`Session ` object is the center of your fuzz... session. When you create it, 7 | you'll pass it a :class:`Target ` object, which will itself receive a :ref:`Connection ` object. For example: :: 8 | 9 | session = Session( 10 | target=Target( 11 | connection=SocketConnection("127.0.0.1", 8021, proto='tcp'))) 12 | 13 | Connection objects implement :class:`ITargetConnection `. Available options include 14 | :class:`SocketConnection ` and :class:`SerialConnection `. 15 | 16 | With a Session object ready, you next need to define the messages in your protocol. Once you've read the requisite 17 | RFC, tutorial, etc., you should be confident enough in the format to define your protocol using the various 18 | :ref:`static protocol definition functions`. 19 | 20 | Each message starts with an :meth:`s_initialize ` function. 21 | 22 | Here are several message definitions from the FTP protocol: :: 23 | 24 | s_initialize("user") 25 | s_string("USER") 26 | s_delim(" ") 27 | s_string("anonymous") 28 | s_static("\r\n") 29 | 30 | s_initialize("pass") 31 | s_string("PASS") 32 | s_delim(" ") 33 | s_string("james") 34 | s_static("\r\n") 35 | 36 | s_initialize("stor") 37 | s_string("STOR") 38 | s_delim(" ") 39 | s_string("AAAA") 40 | s_static("\r\n") 41 | 42 | s_initialize("retr") 43 | s_string("RETR") 44 | s_delim(" ") 45 | s_string("AAAA") 46 | s_static("\r\n") 47 | 48 | Once you've defined your message(s), you will connect them into a graph using the Session object you just created.:: 49 | 50 | session.connect(s_get("user")) 51 | session.connect(s_get("user"), s_get("pass")) 52 | session.connect(s_get("pass"), s_get("stor")) 53 | session.connect(s_get("pass"), s_get("retr")) 54 | 55 | After that, you are ready to fuzz: :: 56 | 57 | session.fuzz() 58 | 59 | Note that at this point you have only a very basic fuzzer. Making it kick butt is up to you. 60 | 61 | To do cool stuff like checking responses, you'll want to use :meth:`Session.post_send `. 62 | You may also want be interested in :ref:`custom-blocks`. 63 | 64 | Remember boofuzz is all Python, so everything is there for your customization. 65 | If you are doing crazy cool stuff, check out the :ref:`community info ` and consider contributing back! 66 | 67 | Happy fuzzing, and Godspeed! -------------------------------------------------------------------------------- /boofuzz/docs/user/static-protocol-definition.rst: -------------------------------------------------------------------------------- 1 | .. _static-primitives: 2 | 3 | Static Protocol Definition 4 | ========================== 5 | 6 | Static functions are used in boofuzz to assemble messages for a protocol definition. They may be obsoleted in future 7 | releases by a less static approach to message construction. For now, you can see the :ref:`Quickstart ` 8 | guide for an intro. 9 | 10 | Requests are messages, Blocks are chunks within a message, and Primitives are the elements (bytes, strings, numbers, 11 | checksums, etc.) that make up a Block/Request. 12 | 13 | Request Manipulation 14 | -------------------- 15 | 16 | .. autofunction:: boofuzz.s_initialize 17 | .. autofunction:: boofuzz.s_get 18 | .. autofunction:: boofuzz.s_mutate 19 | .. autofunction:: boofuzz.s_num_mutations 20 | .. autofunction:: boofuzz.s_render 21 | .. autofunction:: boofuzz.s_switch 22 | 23 | Block Manipulation 24 | ------------------ 25 | .. autofunction:: boofuzz.s_block 26 | .. autofunction:: boofuzz.s_block_start 27 | .. autofunction:: boofuzz.s_block_end 28 | .. autofunction:: boofuzz.s_checksum 29 | .. autofunction:: boofuzz.s_repeat 30 | .. autofunction:: boofuzz.s_size 31 | .. autofunction:: boofuzz.s_update 32 | 33 | Primitive Definition 34 | -------------------- 35 | 36 | .. autofunction:: boofuzz.s_binary 37 | .. autofunction:: boofuzz.s_delim 38 | .. autofunction:: boofuzz.s_group 39 | .. autofunction:: boofuzz.s_lego 40 | .. autofunction:: boofuzz.s_random 41 | .. autofunction:: boofuzz.s_static 42 | .. autofunction:: boofuzz.s_string 43 | .. autofunction:: boofuzz.s_from_file 44 | .. autofunction:: boofuzz.s_bit_field 45 | .. autofunction:: boofuzz.s_byte 46 | .. autofunction:: boofuzz.s_word 47 | .. autofunction:: boofuzz.s_dword 48 | .. autofunction:: boofuzz.s_qword 49 | 50 | .. _custom-blocks: 51 | 52 | Making Your Own Block/Primitive 53 | ------------------------------- 54 | 55 | Now I know what you're thinking: "With that many sweet primitives and blocks available, what else could I ever 56 | conceivably need? And yet, I am urged by joy to contribute my own sweet blocks!" 57 | 58 | To make your own block/primitive: 59 | 60 | 1. Create an object that implements :class:`IFuzzable `. 61 | 2. Create an accompanying static primitive function. See boofuzz's `__init__.py` file for examples. 62 | 3. ??? 63 | 4. Profit! 64 | 65 | If your block depends on references to other blocks, the way a checksum or length field depends on other parts of the 66 | message, see the :class:`Size ` source code for an example of how to avoid recursion issues. Or otherwise 67 | be careful. :) -------------------------------------------------------------------------------- /boofuzz/examples/README.md: -------------------------------------------------------------------------------- 1 | Examples 2 | ======== 3 | Most of these examples are leftover from Sulley and may not be working. 4 | The ftp- examples, however, are maintained and designed for boofuzz. -------------------------------------------------------------------------------- /boofuzz/examples/ftp-simple.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Designed for use with boofuzz v0.0.8 3 | from boofuzz import * 4 | 5 | 6 | def main(): 7 | """ 8 | This example is a very simple FTP fuzzer. It uses no process monitory 9 | (procmon) and assumes that the FTP server is already running. 10 | """ 11 | session = Session( 12 | target=Target( 13 | connection=SocketConnection("127.0.0.1", 21, proto='tcp'))) 14 | 15 | s_initialize("user") 16 | s_string("USER") 17 | s_delim(" ") 18 | s_string("anonymous") 19 | s_static("\r\n") 20 | 21 | s_initialize("pass") 22 | s_string("PASS") 23 | s_delim(" ") 24 | s_string("james") 25 | s_static("\r\n") 26 | 27 | s_initialize("stor") 28 | s_string("STOR") 29 | s_delim(" ") 30 | s_string("AAAA") 31 | s_static("\r\n") 32 | 33 | s_initialize("retr") 34 | s_string("RETR") 35 | s_delim(" ") 36 | s_string("AAAA") 37 | s_static("\r\n") 38 | 39 | session.connect(s_get("user")) 40 | session.connect(s_get("user"), s_get("pass")) 41 | session.connect(s_get("pass"), s_get("stor")) 42 | session.connect(s_get("pass"), s_get("retr")) 43 | 44 | session.fuzz() 45 | 46 | 47 | if __name__ == "__main__": 48 | main() 49 | -------------------------------------------------------------------------------- /boofuzz/examples/ftp-with-procmon.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Designed for use with boofuzz v0.0.8 3 | from boofuzz import * 4 | 5 | 6 | def main(): 7 | """ 8 | This example is a very simple FTP fuzzer using a process monitor (procmon). 9 | It assumes that the procmon is already running. The script will connect to 10 | the procmon and tell the procmon to start the target application 11 | (see start_cmd). 12 | 13 | The ftpd.py in `start_cmd` is a simple FTP server using pyftpdlib. You can 14 | substitute any FTP server. 15 | """ 16 | target_ip = "127.0.0.1" 17 | start_cmd = ['python', 'C:\\ftpd\\ftpd.py'] 18 | session = Session( 19 | target=Target( 20 | connection=SocketConnection(target_ip, 21, proto='tcp'), 21 | procmon=pedrpc.Client(target_ip, 26002), 22 | procmon_options={"start_commands": [start_cmd]} 23 | ), 24 | sleep_time=1, 25 | ) 26 | 27 | s_initialize("user") 28 | s_string("USER") 29 | s_delim(" ") 30 | s_string("anonymous") 31 | s_static("\r\n") 32 | 33 | s_initialize("pass") 34 | s_string("PASS") 35 | s_delim(" ") 36 | s_string("james") 37 | s_static("\r\n") 38 | 39 | s_initialize("stor") 40 | s_string("STOR") 41 | s_delim(" ") 42 | s_string("AAAA") 43 | s_static("\r\n") 44 | 45 | s_initialize("retr") 46 | s_string("RETR") 47 | s_delim(" ") 48 | s_string("AAAA") 49 | s_static("\r\n") 50 | 51 | session.connect(s_get("user")) 52 | session.connect(s_get("user"), s_get("pass")) 53 | session.connect(s_get("pass"), s_get("stor")) 54 | session.connect(s_get("pass"), s_get("retr")) 55 | 56 | session.fuzz() 57 | 58 | 59 | if __name__ == "__main__": 60 | main() 61 | -------------------------------------------------------------------------------- /boofuzz/examples/fuzz_http_server.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path.insert(0, '../') 3 | 4 | from boofuzz.primitives import String, Static, Delim 5 | 6 | 7 | class Group(object): 8 | blocks = [] 9 | 10 | def __init__(self, name, definition=None): 11 | self.name = name 12 | if definition: 13 | self.definition = definition 14 | 15 | def add_definition(self, definition): 16 | assert isinstance(definition, (list, tuple)), "Definition must be a list or a tuple!" 17 | self.definition = definition 18 | 19 | def render(self): 20 | return "".join([x.value for x in self.definition]) 21 | 22 | def exhaust(self): 23 | for item in self.definition: 24 | while item.mutate(): 25 | current_value = item.value 26 | self.log_send(current_value) 27 | recv_data = self.send_buffer(current_value) 28 | self.log_recv(recv_data) 29 | 30 | def __repr__(self): 31 | return '<%s [%s items]>' % (self.__class__.__name__, len(self.definition)) 32 | 33 | # noinspection PyMethodMayBeStatic 34 | def send_buffer(self, current_value): 35 | return "Sent %s!" % current_value 36 | 37 | def log_send(self, current_value): 38 | pass 39 | 40 | def log_recv(self, recv_data): 41 | pass 42 | 43 | 44 | s_static = Static 45 | s_delim = Delim 46 | s_string = String 47 | 48 | CloseHeader = Group( 49 | "HTTP Close Header", 50 | definition=[ 51 | # GET / HTTP/1.1\r\n 52 | s_static("GET / HTTP/1.1\r\n"), 53 | # Connection: close 54 | s_static("Connection"), s_delim(":"), s_delim(" "), s_string("close"), 55 | s_static("\r\n\r\n") 56 | ] 57 | ) 58 | 59 | OpenHeader = Group( 60 | "HTTP Open Header", 61 | definition=[ 62 | # GET / HTTP/1.1\r\n 63 | Static("GET / HTTP/1.1\r\n"), 64 | # Connection: close 65 | Static("Connection"), Delim(":"), Delim(" "), String("open"), 66 | Static("\r\n\r\n") 67 | ] 68 | ) 69 | 70 | # CloseHeader = Group("HTTP Close Header") 71 | # CloseHeader.add_definition([ 72 | # # GET / HTTP/1.1\r\n 73 | # s_static("GET / HTTP/1.1\r\n"), 74 | # # Connection: close 75 | # s_static("Connection"), s_delim(":"), s_delim(" "), s_string("close"), 76 | # s_static("\r\n\r\n") 77 | # ]) -------------------------------------------------------------------------------- /boofuzz/examples/fuzz_trend_control_manager_20901.py: -------------------------------------------------------------------------------- 1 | #!c:\\python\\python.exe 2 | 3 | # 4 | # pedram amini 5 | # 6 | # this was a really half assed fuzz. someone should take it further, see my notes in the requests file for more info. 7 | # 8 | 9 | from boofuzz import * 10 | # noinspection PyUnresolvedReferences 11 | from requests import trend 12 | 13 | 14 | sess = sessions.Session(session_filename="audits/trend_server_protect_20901.session", sleep_time=.25, log_level=10) 15 | sess.add_target(sessions.Target("192.168.181.2", 20901)) 16 | 17 | sess.connect(s_get("20901")) 18 | sess.fuzz() 19 | -------------------------------------------------------------------------------- /boofuzz/examples/fuzz_trend_server_protect_5168.py: -------------------------------------------------------------------------------- 1 | #!c:\\python\\python.exe 2 | 3 | # 4 | # pedram amini 5 | # 6 | # on vmware: 7 | # cd shared\boofuzz\branches\pedram 8 | # process_monitor.py -c audits\trend_server_protect_5168.crashbin -p SpntSvc.exe 9 | # network_monitor.py -d 1 -f "src or dst port 5168" -p audits\trend_server_protect_5168 10 | # 11 | # on localhost: 12 | # vmcontrol.py -r "c:\Progra~1\VMware\VMware~1\vmrun.exe" \ 13 | # -x "v:\vmfarm\images\windows\2000\win_2000_pro-clones\TrendM~1\win_2000_pro.vmx" \ 14 | # --snapshot "boofuzz ready and waiting" 15 | # 16 | # this key gets written which fucks trend service even on reboot. 17 | # HKEY_LOCAL_MACHINE\SOFTWARE\TrendMicro\ServerProtect\CurrentVersion\Engine 18 | # 19 | # uncomment the req/num to do a single test case. 20 | # 21 | 22 | from boofuzz import utils, s_get, s_mutate, s_render, sessions, pedrpc 23 | # noinspection PyUnresolvedReferences 24 | from requests import trend 25 | 26 | req = num = None 27 | #req = "5168: op-3" 28 | #num = "\x04" 29 | 30 | 31 | def rpc_bind(sock): 32 | bind = utils.dcerpc.bind("25288888-bd5b-11d1-9d53-0080c83a5c2c", "1.0") 33 | sock.send(bind) 34 | 35 | utils.dcerpc.bind_ack(sock.recv(1000)) 36 | 37 | 38 | def do_single(req, num): 39 | import socket 40 | 41 | # connect to the server. 42 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 43 | s.connect(("192.168.181.133", 5168)) 44 | 45 | # send rpc bind. 46 | rpc_bind(s) 47 | 48 | request = s_get(req) 49 | 50 | while 1: 51 | if request.names["subs"].value == num: 52 | break 53 | 54 | s_mutate() 55 | 56 | print "xmitting single test case" 57 | s.send(s_render()) 58 | print "done." 59 | 60 | 61 | def do_fuzz(): 62 | sess = sessions.Session(session_filename="audits/trend_server_protect_5168.session") 63 | target = sessions.Target("192.168.181.133", 5168) 64 | 65 | target.netmon = pedrpc.Client("192.168.181.133", 26001) 66 | target.procmon = pedrpc.Client("192.168.181.133", 26002) 67 | target.vmcontrol = pedrpc.Client("127.0.0.1", 26003) 68 | 69 | target.procmon_options = \ 70 | { 71 | "proc_name": "SpntSvc.exe", 72 | "stop_commands": ['net stop "trend serverprotect"'], 73 | "start_commands": ['net start "trend serverprotect"'], 74 | } 75 | 76 | # start up the target. 77 | target.vmcontrol.restart_target() 78 | 79 | print "virtual machine up and running" 80 | 81 | sess.add_target(target) 82 | sess.pre_send = rpc_bind 83 | sess.connect(s_get("5168: op-1")) 84 | sess.connect(s_get("5168: op-2")) 85 | sess.connect(s_get("5168: op-3")) 86 | sess.connect(s_get("5168: op-5")) 87 | sess.connect(s_get("5168: op-a")) 88 | sess.connect(s_get("5168: op-1f")) 89 | sess.fuzz() 90 | 91 | print "done fuzzing. web interface still running." 92 | 93 | if not req or not num: 94 | do_fuzz() 95 | else: 96 | do_single(req, num) -------------------------------------------------------------------------------- /boofuzz/examples/fuzz_trillian_jabber.py: -------------------------------------------------------------------------------- 1 | #!c:\\python\\python.exe 2 | 3 | # 4 | # pedram amini 5 | # 6 | # on vmware: 7 | # cd shared\boofuzz\branches\pedram 8 | # network_monitor.py -d 1 -f "src or dst port 5298" -p audits\trillian_jabber 9 | # process_monitor.py -c audits\trillian_jabber.crashbin -p trillian.exe 10 | # 11 | # on localhost: 12 | # vmcontrol.py -r "c:\Progra~1\VMware\VMware~1\vmrun.exe" \ 13 | # -x "v:\vmfarm\images\windows\xp\win_xp_pro-clones\allsor~1\win_xp_pro.vmx" \ 14 | # --snapshot "boofuzz ready and waiting" 15 | # 16 | # note: 17 | # you MUST register the IP address of the fuzzer as a valid MDNS "presence" host. to do so, simply install and 18 | # launch trillian on the fuzz box with rendezvous enabled. otherwise the target will drop the connection. 19 | # 20 | 21 | from boofuzz import sessions, \ 22 | pedrpc, \ 23 | s_get 24 | 25 | # noinspection PyUnresolvedReferences 26 | from requests import jabber 27 | 28 | 29 | def init_message(sock): 30 | init = '\n' 31 | init += '' 32 | 33 | sock.send(init) 34 | sock.recv(1024) 35 | 36 | sess = sessions.Session(session_filename="audits/trillian.session") 37 | target = sessions.Target("152.67.137.126", 5298) 38 | target.netmon = pedrpc.Client("152.67.137.126", 26001) 39 | target.procmon = pedrpc.Client("152.67.137.126", 26002) 40 | target.vmcontrol = pedrpc.Client("127.0.0.1", 26003) 41 | target.procmon_options = {"proc_name": "trillian.exe"} 42 | 43 | # start up the target. 44 | target.vmcontrol.restart_target() 45 | print "virtual machine up and running" 46 | 47 | sess.add_target(target) 48 | sess.pre_send = init_message 49 | sess.connect(sess.root, s_get("chat message")) 50 | sess.fuzz() 51 | -------------------------------------------------------------------------------- /boofuzz/examples/mdns.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # A partial MDNS fuzzer. Could be made to be a DNS fuzzer trivially 4 | # Charlie Miller 5 | 6 | from boofuzz import s_word, \ 7 | s_initialize, \ 8 | sessions, \ 9 | s_block_start, \ 10 | s_size, \ 11 | s_block_end, \ 12 | s_string, \ 13 | s_repeat, \ 14 | s_group, \ 15 | s_dword, \ 16 | s_binary, \ 17 | s_get 18 | 19 | 20 | def insert_questions(sess, node, edge, sock): 21 | node.names['Questions'].value = 1 + node.names['queries'].current_reps 22 | node.names['Authority'].value = 1 + node.names['auth_nameservers'].current_reps 23 | 24 | s_initialize("query") 25 | s_word(0, name="TransactionID") 26 | s_word(0, name="Flags") 27 | s_word(1, name="Questions", endian='>') 28 | s_word(0, name="Answer", endian='>') 29 | s_word(1, name="Authority", endian='>') 30 | s_word(0, name="Additional", endian='>') 31 | 32 | # ######## Queries ################ 33 | if s_block_start("query"): 34 | if s_block_start("name_chunk"): 35 | s_size("string", length=1) 36 | if s_block_start("string"): 37 | s_string("A" * 10) 38 | s_block_end() 39 | s_block_end() 40 | s_repeat("name_chunk", min_reps=2, max_reps=4, step=1, fuzzable=True, name="aName") 41 | 42 | s_group("end", values=["\x00", "\xc0\xb0"]) # very limited pointer fuzzing 43 | s_word(0xc, name="Type", endian='>') 44 | s_word(0x8001, name="Class", endian='>') 45 | s_block_end() 46 | s_repeat("query", 0, 1000, 40, name="queries") 47 | 48 | 49 | ######## Authorities ############ 50 | if s_block_start("auth_nameserver"): 51 | if s_block_start("name_chunk_auth"): 52 | s_size("string_auth", length=1) 53 | if s_block_start("string_auth"): 54 | s_string("A" * 10) 55 | s_block_end() 56 | s_block_end() 57 | s_repeat("name_chunk_auth", min_reps=2, max_reps=4, step=1, fuzzable=True, name="aName_auth") 58 | s_group("end_auth", values=["\x00", "\xc0\xb0"]) # very limited pointer fuzzing 59 | 60 | s_word(0xc, name="Type_auth", endian='>') 61 | s_word(0x8001, name="Class_auth", endian='>') 62 | s_dword(0x78, name="TTL_auth", endian='>') 63 | s_size("data_length", length=2, endian='>') 64 | if s_block_start("data_length"): 65 | s_binary("00 00 00 00 00 16 c0 b0") # This should be fuzzed according to the type, but I'm too lazy atm 66 | s_block_end() 67 | s_block_end() 68 | s_repeat("auth_nameserver", 0, 1000, 40, name="auth_nameservers") 69 | 70 | s_word(0) 71 | 72 | sess = sessions.Session(proto="udp") 73 | target = sessions.Target("224.0.0.251", 5353) 74 | sess.add_target(target) 75 | sess.connect(s_get("query"), callback=insert_questions) 76 | 77 | sess.fuzz() 78 | 79 | -------------------------------------------------------------------------------- /boofuzz/requests/___REQUESTS___.html: -------------------------------------------------------------------------------- 1 | 29 | 30 |

About

31 | This file contains the descriptions for stored request categories and lists individual types. Maintain alphabetical order. 32 | 33 |

HTTP

34 | 35 |

Jabber

36 | 37 |

LDAP

38 | 39 |

NDMP

40 | 41 |

Veritas NDMP_CONECT_CLIENT_AUTH

42 | Regular auth types include 0 (none), 1 (plain-text) and 2 (MD5). According to NDMP_CONFIG_GET_SERVER_INFO response Veritas defines types 4, 5, and 190. This fuzz requests will generate valid NDMP fragment and regular headers then fuzz the body. See the request to toggle between random data fuzzing (1k to 50k of pure random data) and valid XDR string fuzzing. Nothing was found when running this against version 11d-hotfix-10. 43 | 44 |

Veritas Proprietary Message Types

45 | The NDMP spec defines a number of message types enumerated at the top of requests\ndmp.py, the spec also alots space for proprietary usage. Sniffing a back up job and then further reversing beremote.exe revealed a number of defined custom message types. This fuzz request will generate valid NDMP fragment and regular headers then fuzz the body which contains purely random data chosen of random lengths between 1k and 50k. 46 | 47 |

Rendezvous

48 | 49 |

STUN

50 | 51 |

Trend Micro

52 | 53 |

20901

54 | This fuzz request targets Trend Micro Control Manager (DcsProcessor.exe) TCP port 20901. For more information about the audit target see: 55 | 56 | 57 | 58 | The CRC field in this protocol is calculated in some non-standard fashion so the check must be NOP-ed out in the target binary. The protocol is also obfuscated using a basic XOR routine. I wrote an appropriate block encoder named trend_xor_encode(), a decoder is defined as well. This fuzz found nothing so far! I need to uncover more protocol details. See also: Pedram's pwned notebook page 3, 4. 59 | 60 |

5168: op-[0x1, 0x2, 0x3, 0x5, 0xa, 0x1f]

61 | These fuzz requests (one for each Trend opnum, read on for more) targets the RPC endpoint on Trend Micro Server Protect (SpNTsvc.exe) TCP port 5168. There is only a single RPC opnum exposed however Trend defines it's own op/sub-op codes exposing a plethora of code coverage through this interface. The RPC endpoints is defined as: 62 |
63 |     // opcode: 0x00, address: 0x65741030
64 |     // uuid: 25288888-bd5b-11d1-9d53-0080c83a5c2c
65 |     // version: 1.0
66 | 
67 |     error_status_t rpc_opnum_0 (
68 |     [in] handle_t arg_1,                          // not sent on wire
69 |     [in] long trend_req_num,
70 |     [in][size_is(arg_4)] byte overflow_str[],
71 |     [in] long arg_4,
72 |     [out][size_is(arg_6)] byte arg_5[],           // not sent on wire
73 |     [in] long arg_6
74 |     );
75 | 
76 | This fuzz uncovered a bunch of DoS and code exec bugs. The obvious code exec bugs were documented and released to the vendor. See also: Pedram's pwned notebook page 1, 2 and the following advisories: 77 | 81 | 82 |

Xbox

-------------------------------------------------------------------------------- /boofuzz/requests/hp.py: -------------------------------------------------------------------------------- 1 | from boofuzz import * 2 | 3 | from struct import * 4 | 5 | 6 | def unicode_ftw(val): 7 | """ 8 | Simple unicode slicer 9 | """ 10 | 11 | val_list = [] 12 | for char in val: 13 | val_list.append("\x00") 14 | val_list.append(pack('B', ord(char))) 15 | 16 | ret = "" 17 | for char in val_list: 18 | ret += char 19 | 20 | return ret 21 | 22 | 23 | s_initialize("omni") 24 | """ 25 | Hewlett Packard OpenView Data Protector OmniInet.exe 26 | """ 27 | 28 | if s_block_start("packet_1"): 29 | s_size("packet_2", endian=">", length=4) 30 | s_block_end() 31 | 32 | if s_block_start("packet_2"): 33 | 34 | # unicode byte order marker 35 | s_static("\xfe\xff") 36 | 37 | # unicode magic 38 | if s_block_start("unicode_magic", encoder=unicode_ftw): 39 | s_int(267, output_format="ascii") 40 | s_block_end() 41 | s_static("\x00\x00") 42 | 43 | # random 2 bytes 44 | s_string("AA", size=2) 45 | 46 | # unicode value to pass calls to wtoi() 47 | if s_block_start("unicode_100_1", encoder=unicode_ftw): 48 | s_int(100, output_format="ascii") 49 | s_block_end() 50 | s_static("\x00\x00") 51 | 52 | # random 2 bytes 53 | s_string("BB", size=2) 54 | 55 | # unicode value to pass calls to wtoi() 56 | if s_block_start("unicode_100_2", encoder=unicode_ftw): 57 | s_int(100, output_format="ascii") 58 | s_block_end() 59 | s_static("\x00\x00") 60 | 61 | # random 2 bytes 62 | s_string("CC", size=2) 63 | 64 | # unicode value to pass calls to wtoi() 65 | if s_block_start("unicode_100_3", encoder=unicode_ftw): 66 | s_int(100, output_format="ascii") 67 | s_block_end() 68 | s_static("\x00\x00") 69 | 70 | # random buffer 71 | s_string("D" * 32, size=32) 72 | 73 | # barhost cookie 74 | s_dword(0x7cde7bab, endian="<", fuzzable=False) 75 | 76 | # random buffer 77 | s_string("FOO") 78 | 79 | s_block_end() 80 | -------------------------------------------------------------------------------- /boofuzz/requests/http.py: -------------------------------------------------------------------------------- 1 | from boofuzz import * 2 | 3 | # Old http.py request primitives, http_* does all of these and many more (AFAIK) 4 | 5 | # List of all blocks defined here (for easy copy/paste) 6 | """ 7 | sess.connect(s_get("HTTP VERBS")) 8 | sess.connect(s_get("HTTP VERBS BASIC")) 9 | sess.connect(s_get("HTTP VERBS POST")) 10 | sess.connect(s_get("HTTP HEADERS")) 11 | sess.connect(s_get("HTTP COOKIE")) 12 | """ 13 | 14 | s_initialize("HTTP VERBS") 15 | s_group("verbs", values=["GET", "HEAD", "POST", "OPTIONS", "TRACE", "PUT", "DELETE", "PROPFIND"]) 16 | if s_block_start("body", group="verbs"): 17 | s_delim(" ") 18 | s_delim("/") 19 | s_string("index.html") 20 | s_delim(" ") 21 | s_string("HTTP") 22 | s_delim("/") 23 | s_string("1") 24 | s_delim(".") 25 | s_string("1") 26 | s_static("\r\n\r\n") 27 | s_block_end() 28 | 29 | s_initialize("HTTP VERBS BASIC") 30 | s_group("verbs", values=["GET", "HEAD"]) 31 | if s_block_start("body", group="verbs"): 32 | s_delim(" ") 33 | s_delim("/") 34 | s_string("index.html") 35 | s_delim(" ") 36 | s_string("HTTP") 37 | s_delim("/") 38 | s_string("1") 39 | s_delim(".") 40 | s_string("1") 41 | s_static("\r\n\r\n") 42 | s_block_end() 43 | 44 | s_initialize("HTTP VERBS POST") 45 | s_static("POST / HTTP/1.1\r\n") 46 | s_static("Content-Type: ") 47 | s_string("application/x-www-form-urlencoded") 48 | s_static("\r\n") 49 | s_static("Content-Length: ") 50 | s_size("post blob", output_format="ascii", signed=True, fuzzable=True) 51 | s_static("\r\n\r\n") 52 | 53 | if s_block_start("post blob"): 54 | s_string("A" * 100 + "=" + "B" * 100) 55 | s_block_end() 56 | 57 | s_initialize("HTTP HEADERS") 58 | s_static("GET / HTTP/1.1\r\n") 59 | 60 | # let's fuzz random headers with malformed delimiters. 61 | s_string("Host") 62 | s_delim(":") 63 | s_delim(" ") 64 | s_string("localhost") 65 | s_delim("\r\n") 66 | 67 | # let's fuzz the value portion of some popular headers. 68 | s_static("User-Agent: ") 69 | s_string("Mozilla/5.0 (Windows; U)") 70 | s_static("\r\n") 71 | 72 | s_static("Accept-Language: ") 73 | s_string("en-us") 74 | s_delim(",") 75 | s_string("en;q=0.5") 76 | s_static("\r\n") 77 | 78 | s_static("Keep-Alive: ") 79 | s_string("300") 80 | s_static("\r\n") 81 | 82 | s_static("Connection: ") 83 | s_string("keep-alive") 84 | s_static("\r\n") 85 | 86 | s_static("Referer: ") 87 | s_string("http://dvlabs.tippingpoint.com") 88 | s_static("\r\n") 89 | s_static("\r\n") 90 | 91 | s_initialize("HTTP COOKIE") 92 | s_static("GET / HTTP/1.1\r\n") 93 | 94 | if s_block_start("cookie"): 95 | s_static("Cookie: ") 96 | s_string("auth") 97 | s_delim("=") 98 | s_string("1234567890") 99 | s_static("\r\n") 100 | s_block_end() 101 | 102 | s_repeat("cookie", max_reps=5000, step=500) 103 | s_static("\r\n") -------------------------------------------------------------------------------- /boofuzz/requests/http_get.py: -------------------------------------------------------------------------------- 1 | from boofuzz import * 2 | 3 | # All HTTP requests that I could think of/find 4 | 5 | # List of all blocks defined here (for easy copy/paste) 6 | """ 7 | sess.connect(s_get("HTTP VERBS")) 8 | sess.connect(s_get("HTTP METHOD")) 9 | sess.connect(s_get("HTTP REQ")) 10 | """ 11 | 12 | 13 | # Fuzz all the publicly avalible methods known for HTTP Servers 14 | 15 | s_initialize("HTTP VERBS") 16 | s_group("verbs", values=["GET", "HEAD", "POST", "OPTIONS", "TRACE", "PUT", "DELETE", "PROPFIND", "CONNECT", "PROPPATCH", 17 | "MKCOL", "COPY", "MOVE", "LOCK", "UNLOCK", "VERSION-CONTROL", "REPORT", "CHECKOUT", "CHECKIN", 18 | "UNCHECKOUT", "MKWORKSPACE", "UPDATE", "LABEL", "MERGE", "BASELINE-CONTROL", "MKACTIVITY", 19 | "ORDERPATCH", "ACL", "PATCH", "SEARCH", "CAT"]) 20 | if s_block_start("body", group="verbs"): 21 | s_delim(" ") 22 | s_delim("/") 23 | s_string("index.html") 24 | s_delim(" ") 25 | s_string("HTTP") 26 | s_delim("/") 27 | s_int(1, output_format="ascii") 28 | s_delim(".") 29 | s_int(1, output_format="ascii") 30 | s_static("\r\n\r\n") 31 | s_block_end() 32 | 33 | 34 | # Fuzz the HTTP Method itself 35 | 36 | s_initialize("HTTP METHOD") 37 | s_string("FUZZ") 38 | s_static(" /index.html HTTP/1.1") 39 | s_static("\r\n\r\n") 40 | 41 | 42 | # Fuzz this standard multi-header HTTP request 43 | # GET / HTTP/1.1 44 | # Host: www.google.com 45 | # Connection: keep-alive 46 | # User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.83 Safari/537.1 47 | # Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 48 | # Accept-Encoding: gzip,deflate,sdch 49 | # Accept-Language: en-US,en;q=0.8 50 | # Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3 51 | 52 | s_initialize("HTTP REQ") 53 | s_static("GET / HTTP/1.1\r\n") 54 | # Host: www.google.com 55 | s_static("Host") 56 | s_delim(":") 57 | s_delim(" ") 58 | s_string("www.google.com") 59 | s_static("\r\n") 60 | # Connection: keep-alive 61 | s_static("Connection") 62 | s_delim(":") 63 | s_delim(" ") 64 | s_string("Keep-Alive") 65 | # User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.83 Safari/537.1 66 | s_static("User-Agent") 67 | s_delim(":") 68 | s_delim(" ") 69 | s_string("Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.83 Safari/537.1") 70 | s_static("\r\n") 71 | # Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 72 | s_static("Accept") 73 | s_delim(":") 74 | s_delim(" ") 75 | s_string("text") 76 | s_delim("/") 77 | s_string("html") 78 | s_delim(",") 79 | s_string("application") 80 | s_delim("/") 81 | s_string("xhtml") 82 | s_delim("+") 83 | s_string("xml") 84 | s_delim(",") 85 | s_string("application") 86 | s_delim("/") 87 | s_string("xml") 88 | s_delim(";") 89 | s_string("q") 90 | s_delim("=") 91 | s_int(0, output_format="ascii") 92 | s_delim(".") 93 | s_int(9, output_format="ascii") 94 | s_delim(",") 95 | s_string("*") 96 | s_delim("/") 97 | s_string("*") 98 | s_delim(";") 99 | s_string("q") 100 | s_delim("=") 101 | s_int(0, output_format="ascii") 102 | s_delim(".") 103 | s_int(8, output_format="ascii") 104 | s_static("\r\n") 105 | # Accept-Encoding: gzip,deflate,sdch 106 | s_static("Accept-Encoding") 107 | s_delim(":") 108 | s_delim(" ") 109 | s_string("gzip") 110 | s_delim(",") 111 | s_string("deflate") 112 | s_delim(",") 113 | s_string("sdch") 114 | s_static("\r\n") 115 | # Accept-Language: en-US,en;q=0.8 116 | s_static("Accept-Language") 117 | s_delim(":") 118 | s_delim(" ") 119 | s_string("en-US") 120 | s_delim(",") 121 | s_string("en") 122 | s_delim(";") 123 | s_string("q") 124 | s_delim("=") 125 | s_string("0.8") 126 | s_static("\r\n") 127 | # Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3 128 | s_static("Accept-Charset") 129 | s_delim(":") 130 | s_delim(" ") 131 | s_string("ISO") 132 | s_delim("-") 133 | s_int(8859, output_format="ascii") 134 | s_delim("-") 135 | s_int(1, output_format="ascii") 136 | s_delim(",") 137 | s_string("utf-8") 138 | s_delim(";") 139 | s_string("q") 140 | s_delim("=") 141 | s_int(0, output_format="ascii") 142 | s_delim(".") 143 | s_int(7, output_format="ascii") 144 | s_delim(",") 145 | s_string("*") 146 | s_delim(";") 147 | s_string("q") 148 | s_delim("=") 149 | s_int(0, output_format="ascii") 150 | s_delim(".") 151 | s_int(3, output_format="ascii") 152 | s_static("\r\n\r\n") 153 | -------------------------------------------------------------------------------- /boofuzz/requests/http_post.py: -------------------------------------------------------------------------------- 1 | from boofuzz import * 2 | 3 | # All POST mimetypes that I could think of/find 4 | 5 | # List of all blocks defined here (for easy copy/paste) 6 | """ 7 | sess.connect(s_get("HTTP VERBS POST")) 8 | sess.connect(s_get("HTTP VERBS POST ALL")) 9 | sess.connect(s_get("HTTP VERBS POST REQ")) 10 | """ 11 | 12 | 13 | # Fuzz POST requests with most MIMETypes known 14 | 15 | s_initialize("HTTP VERBS POST ALL") 16 | s_static("POST / HTTP/1.1\r\n") 17 | s_static("Content-Type: ") 18 | s_group("mimetypes", values=[ 19 | "audio/basic", 20 | "audio/x-mpeg", 21 | "drawing/x-dwf", 22 | "graphics/x-inventor", 23 | "image/x-portable-bitmap", 24 | "message/external-body", 25 | "message/http", 26 | "message/news", 27 | "message/partial", 28 | "message/rfc822", 29 | "multipart/alternative", 30 | "multipart/appledouble", 31 | "multipart/digest", 32 | "multipart/form-data", 33 | "multipart/header-set", 34 | "multipart/mixed", 35 | "multipart/parallel", 36 | "multipart/related", 37 | "multipart/report", 38 | "multipart/voice-message", 39 | "multipart/x-mixed-replace", 40 | "text/css", 41 | "text/enriched", 42 | "text/html", 43 | "text/javascript", 44 | "text/plain", 45 | "text/richtext", 46 | "text/sgml", 47 | "text/tab-separated-values", 48 | "text/vbscript", 49 | "video/x-msvideo", 50 | "video/x-sgi-movie", 51 | "workbook/formulaone", 52 | "x-conference/x-cooltalk", 53 | "x-form/x-openscape", 54 | "x-music/x-midi", 55 | "x-script/x-wfxclient", 56 | "x-world/x-3dmf" 57 | ]) 58 | 59 | if s_block_start("mime", group="mimetypes"): 60 | s_static("\r\n") 61 | s_static("Content-Length: ") 62 | s_size("post blob", output_format="ascii", signed=True, fuzzable=True) 63 | s_static("\r\n\r\n") 64 | s_block_end() 65 | 66 | if s_block_start("post blob"): 67 | s_string("A" * 100 + "=" + "B" * 100) 68 | s_block_end() 69 | s_static("\r\n\r\n") 70 | 71 | 72 | # Basic fuzz of post payloads 73 | 74 | s_initialize("HTTP VERBS POST") 75 | s_static("POST / HTTP/1.1\r\n") 76 | s_static("Content-Type: ") 77 | s_string("application/x-www-form-urlencoded") 78 | s_static("\r\n") 79 | s_static("Content-Length: ") 80 | s_size("post blob", output_format="ascii", signed=True, fuzzable=True) 81 | s_static("\r\n") 82 | if s_block_start("post blob"): 83 | s_string("A" * 100 + "=" + "B" * 100) 84 | s_block_end() 85 | s_static("\r\n\r\n") 86 | 87 | 88 | # Fuzz POST request MIMETypes 89 | 90 | s_initialize("HTTP VERBS POST REQ") 91 | s_static("POST / HTTP/1.1\r\n") 92 | s_static("Content-Type: ") 93 | s_string("application") 94 | s_delim("/") 95 | s_string("x") 96 | s_delim("-") 97 | s_string("www") 98 | s_delim("-") 99 | s_string("form") 100 | s_delim("-") 101 | s_string("urlencoded") 102 | s_static("\r\n") 103 | s_static("Content-Length: ") 104 | s_size("post blob", output_format="ascii", signed=True, fuzzable=True) 105 | s_static("\r\n") 106 | if s_block_start("post blob"): 107 | s_string("A" * 100 + "=" + "B" * 100) 108 | s_block_end() 109 | s_static("\r\n\r\n") -------------------------------------------------------------------------------- /boofuzz/requests/jabber.py: -------------------------------------------------------------------------------- 1 | from boofuzz import * 2 | 3 | 4 | s_initialize("chat init") 5 | 6 | """ 7 | 8 | 9 | """ 10 | 11 | # i'll fuzz these bitches later. 12 | # TODO: still need to figure out how to incorporate dynamic IPs 13 | s_static('') 14 | s_static('') 15 | 16 | s_initialize("chat message") 17 | s_static('\n') 18 | s_static('\n') 19 | s_static('\n') 20 | s_static('\n') 21 | s_static('\n') 22 | s_static('\n') 23 | s_static('\n') 24 | 25 | # s_static('\n') 26 | s_delim("<") 27 | s_string("message") 28 | s_delim(" ") 29 | s_string("to") 30 | s_delim("=") 31 | s_delim('"') 32 | s_string("TSR@GIZMO") 33 | s_delim('"') 34 | s_static(' type="chat"') 35 | s_delim(">") 36 | s_delim("\n") 37 | 38 | # s_static('hello from python!\n') 39 | s_static("") 40 | s_string("hello from python!") 41 | s_static("\n") 42 | 43 | # s_static(' 44 | # 45 | # 46 | # hello from python 47 | # 48 | # 49 | # \n 50 | # ') 51 | s_static('') 52 | s_static("<") 53 | s_string("font") 54 | s_static(' face="') 55 | s_string("Helvetica") 56 | s_string('" ABSZ="') 57 | s_word(12, output_format="ascii", signed=True) 58 | s_static('" color="') 59 | s_string("#000000") 60 | s_static('">') 61 | s_string("hello from python") 62 | s_static('\n') 63 | 64 | s_static('\n') 65 | s_static('\n') 66 | s_static('\n') 67 | -------------------------------------------------------------------------------- /boofuzz/requests/ldap.py: -------------------------------------------------------------------------------- 1 | from boofuzz import * 2 | 3 | """ 4 | Application number Application 5 | 0 BindRequest 6 | 1 BindResponse 7 | 2 UnbindRequest 8 | 3 SearchRequest 9 | 4 SearchResponse 10 | 5 ModifyRequest 11 | 6 ModifyResponse 12 | 7 AddRequest 13 | 8 AddResponse 14 | 9 DelRequest 15 | 10 DelResponse 16 | 11 ModifyRDNRequest 17 | 12 ModifyRDNResponse 18 | 13 CompareRequest 19 | 14 CompareResponse 20 | 15 AbandonRequest 21 | """ 22 | 23 | s_initialize("anonymous bind") 24 | 25 | # all ldap messages start with this. 26 | s_static("\x30") 27 | 28 | # length of entire envelope. 29 | s_static("\x84") 30 | s_sizer("envelope", endian=">") 31 | 32 | if s_block_start("envelope"): 33 | s_static("\x02\x01\x01") # message id (always one) 34 | s_static("\x60") # bind request (0) 35 | 36 | s_static("\x84") 37 | s_sizer("bind request", endian=">") 38 | 39 | if s_block_start("bind request"): 40 | s_static("\x02\x01\x03") # version 41 | 42 | s_lego("ber_string", "anonymous") 43 | s_lego("ber_string", "foobar", options={"prefix": "\x80"}) # 0x80 is "simple" authentication 44 | s_block_end() 45 | s_block_end() 46 | 47 | s_initialize("search request") 48 | 49 | # all ldap messages start with this. 50 | s_static("\x30") 51 | 52 | # length of entire envelope. 53 | s_static("\x84") 54 | s_sizer("envelope", endian=">", fuzzable=True) 55 | 56 | if s_block_start("envelope"): 57 | s_static("\x02\x01\x02") # message id (always one) 58 | s_static("\x63") # search request (3) 59 | 60 | s_static("\x84") 61 | s_sizer("searchRequest", endian=">", fuzzable=True) 62 | 63 | if s_block_start("searchRequest"): 64 | s_static("\x04\x00") # static empty string ... why? 65 | s_static("\x0a\x01\x00") # scope: baseOjbect (0) 66 | s_static("\x0a\x01\x00") # deref: never (0) 67 | s_lego("ber_integer", 1000) # size limit 68 | s_lego("ber_integer", 30) # time limit 69 | s_static("\x01\x01\x00") # typesonly: false 70 | s_lego("ber_string", "objectClass", options={"prefix": "\x87"}) 71 | s_static("\x30") 72 | 73 | s_static("\x84") 74 | s_sizer("attributes", endian=">") 75 | 76 | if s_block_start("attributes"): 77 | s_lego("ber_string", "1.1") 78 | s_block_end("attributes") 79 | 80 | s_block_end("searchRequest") 81 | s_block_end("envelope") -------------------------------------------------------------------------------- /boofuzz/requests/mcafee.py: -------------------------------------------------------------------------------- 1 | from boofuzz import * 2 | 3 | from struct import * 4 | 5 | 6 | # stupid one byte XOR 7 | def mcafee_epo_xor(buf, poly=0xAA): 8 | new_buf = "" 9 | 10 | for char in buf: 11 | new_buf += chr(ord(char) ^ poly) 12 | 13 | return new_buf 14 | 15 | 16 | s_initialize("mcafee_epo_framework_tcp") 17 | """ 18 | McAfee FrameworkService.exe TCP port 8081 19 | """ 20 | 21 | s_static("POST", name="post_verb") 22 | s_delim(" ") 23 | s_group("paths", ["/spipe/pkg", "/spipe/file", "default.htm"]) 24 | s_delim("?") 25 | s_string("URL") 26 | s_delim("=") 27 | s_string("TESTFILE") 28 | s_delim("\r\n") 29 | 30 | s_static("Content-Length:") 31 | s_delim(" ") 32 | s_size("payload", output_format="ascii") 33 | s_delim("\r\n\r\n") 34 | 35 | if s_block_start("payload"): 36 | s_string("TESTCONTENTS") 37 | s_delim("\r\n") 38 | s_block_end() 39 | 40 | s_initialize("mcafee_epo_framework_udp") 41 | """ 42 | McAfee FrameworkService.exe UDP port 8082 43 | """ 44 | 45 | s_static('Type=\"AgentWakeup\"', name="agent_wakeup") 46 | s_static('\"DataSize=\"') 47 | s_size("data", output_format="ascii") # must be over 234 48 | 49 | if s_block_start("data", encoder=mcafee_epo_xor): 50 | s_static("\x50\x4f", name="signature") 51 | s_group(values=[pack(' 78 | s_delim("<") 79 | s_string("s") 80 | s_delim(":") 81 | s_string("Envelope") 82 | s_static(" xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" ") 83 | s_static("s:") 84 | s_string("encodingStyle") 85 | s_delim("=") 86 | s_string("\"http://schemas.xmlsoap.org/soap/encoding/\"") 87 | s_delim(">") 88 | 89 | s_static("") 90 | s_static("") 91 | 92 | # 7 93 | s_static("") 94 | s_dword(7, output_format="ascii", signed=True) 95 | s_static("") 96 | 97 | # (upnp:class = "object.container.album.musicAlbum") 98 | s_static("(upnp:class = "") 99 | s_string("object.container.album.musicAlbum") 100 | s_static("")") 101 | 102 | # dc:title,upnp:artist 103 | s_static("") 104 | s_delim("dc") 105 | s_delim(":") 106 | s_string("title") 107 | s_delim(",") 108 | s_string("upnp") 109 | s_delim(":") 110 | s_string("artist") 111 | s_static("") 112 | 113 | # 0 114 | s_static("") 115 | s_dword(0, output_format="ascii", signed=True) 116 | s_static("") 117 | 118 | # 1000 119 | s_static("") 120 | s_dword(1000, output_format="ascii", signed=True) 121 | s_static("") 122 | 123 | s_static("+dc:title") 124 | s_static("") 125 | s_static("") 126 | 127 | # 128 | s_delim("<") 129 | s_delim("/") 130 | s_delim("s") 131 | s_delim(":") 132 | s_string("Envelope") 133 | s_delim(">") 134 | 135 | s_block_end() -------------------------------------------------------------------------------- /boofuzz/setup.cfg: -------------------------------------------------------------------------------- 1 | [easy_install] 2 | zip_ok = False 3 | 4 | [zest.releaser] 5 | python-file-with-version = boofuzz/__init__.py 6 | -------------------------------------------------------------------------------- /boofuzz/setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import ast 3 | import os 4 | import re 5 | 6 | from setuptools import setup, find_packages 7 | 8 | setup_dir = os.path.abspath(os.path.dirname(__file__)) 9 | 10 | 11 | def find_version(*path_elements): 12 | """Search a file for `__version__ = 'version number'` and return version. 13 | 14 | @param path_elements: Arguments specifying file to search. 15 | 16 | @return: Version number string. 17 | """ 18 | path = os.path.join(setup_dir, *path_elements) 19 | for line in open(path): 20 | for match in re.finditer('__version__\s*=\s(.*)$', line): 21 | return ast.literal_eval(match.group(1)) 22 | raise RuntimeError("version string not found in {0}".format(path)) 23 | 24 | 25 | setup( 26 | name='boofuzz', 27 | version=find_version("boofuzz", "__init__.py"), 28 | maintainer='Joshua Pereyda', 29 | maintainer_email='joshua.t.pereyda@gmail.com', 30 | url='https://github.com/jtpereyda/boofuzz', 31 | license='GPL', 32 | packages=find_packages(exclude=['unit_tests', 'requests', 'examples', 'utils', 'web', 'new_examples']), 33 | package_data={'boofuzz': ['web/templates/*', 'web/static/css/*']}, 34 | install_requires=[ 35 | 'future', 'pyserial', 'pydot', 'tornado==4.0.2', 36 | 'Flask==0.10.1', 'impacket', 'colorama', 'cement', 'attrs'], 37 | extras_require={ 38 | # This list is duplicated in tox.ini. Make sure to change both! 39 | 'dev': ['check-manifest', 'mock', 'pytest', 'pytest-bdd', 'netifaces', 'ipaddress'], 40 | }, 41 | classifiers=[ 42 | 'Development Status :: 4 - Beta', 43 | 'Environment :: Console', 44 | 'Intended Audience :: Developers', 45 | 'Intended Audience :: Science/Research', 46 | 'License :: OSI Approved :: MIT License', 47 | 'Natural Language :: English', 48 | 'Operating System :: OS Independent', 49 | 'Programming Language :: Python', 50 | 'Programming Language :: Python :: 2.7', 51 | 'Topic :: Security', 52 | 'Topic :: Software Development :: Testing :: Traffic Generation', 53 | ] 54 | ) 55 | -------------------------------------------------------------------------------- /boofuzz/tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py27-{unix,windows} 3 | 4 | [testenv] 5 | whitelist_externals=sudo 6 | platform= 7 | windows: win32 8 | unix: linux2|darwin 9 | # This list is duplicated in setup.py extras_require. Make sure to change both! 10 | # This can stop once tox supports installing package extras. 11 | deps = 12 | pytest 13 | pytest-bdd 14 | mock 15 | check-manifest 16 | netifaces 17 | ipaddress 18 | install_command = 19 | python -m pip install {opts} {packages} 20 | commands = 21 | windows: python -m pytest 22 | unix: sudo {envpython} -m pytest 23 | python -m check_manifest 24 | -------------------------------------------------------------------------------- /boofuzz/unit_test.py: -------------------------------------------------------------------------------- 1 | #!c:\\python\\python.exe 2 | 3 | import unit_tests 4 | 5 | unit_tests.test_blocks.run() 6 | unit_tests.legos.run() 7 | unit_tests.primitives.run() -------------------------------------------------------------------------------- /boofuzz/unit_tests/__init__.py: -------------------------------------------------------------------------------- 1 | import test_blocks 2 | import legos 3 | import primitives 4 | -------------------------------------------------------------------------------- /boofuzz/unit_tests/bit_field_original_value.feature: -------------------------------------------------------------------------------- 1 | Feature: BitField.original_value 2 | 3 | Scenario: Same as initial render 4 | Given A BitField 5 | When Calling original_value 6 | Then Result equals .render() 7 | 8 | Scenario: Same as initial render after mutation 9 | Given A BitField 10 | And Mutated once 11 | When Calling original_value 12 | Then Result equals .render() after .reset() 13 | 14 | Scenario: Same as initial render after 2 mutations 15 | Given A BitField 16 | And Mutated twice 17 | When Calling original_value 18 | Then Result equals .render() after .reset() 19 | 20 | Scenario: Same as initial render after 3 mutations 21 | Given A BitField 22 | And Mutated thrice 23 | When Calling original_value 24 | Then Result equals .render() after .reset() 25 | -------------------------------------------------------------------------------- /boofuzz/unit_tests/block_original_value.feature: -------------------------------------------------------------------------------- 1 | Feature: Block.original_value 2 | 3 | Scenario: Same as initial render 4 | Given A Block with contents 5 | When Calling original_value 6 | Then Result equals .render() 7 | 8 | Scenario: Same as initial render after mutation 9 | Given A Block with contents 10 | And Mutated once 11 | When Calling original_value 12 | Then Result equals .render() after .reset() 13 | 14 | Scenario: Same as initial render after 2 mutations 15 | Given A Block with contents 16 | And Mutated twice 17 | When Calling original_value 18 | Then Result equals .render() after .reset() 19 | 20 | Scenario: Same as initial render after 3 mutations 21 | Given A Block with contents 22 | And Mutated thrice 23 | When Calling original_value 24 | Then Result equals .render() after .reset() 25 | -------------------------------------------------------------------------------- /boofuzz/unit_tests/checksum.feature: -------------------------------------------------------------------------------- 1 | Feature: Checksum primitive 2 | 3 | Scenario: Normal Checksum computation 4 | 5 | Scenario: Checksum compute mutated src address 6 | 7 | 8 | Scenario: Checksum compute mutated dst address 9 | 10 | Scenario: Checksum compute mutated src address after checksum 11 | 12 | Scenario: Checksum compute mutated dst address after checksum 13 | 14 | Scenario: Checksum compute with address mutated to zero bytes 15 | 16 | Scenario: Checksum compute with address mutated to large value -------------------------------------------------------------------------------- /boofuzz/unit_tests/checksum_original_value.feature: -------------------------------------------------------------------------------- 1 | Feature: Checksum.original_value 2 | 3 | Scenario: Same as initial render 4 | Given A Checksum 5 | When Calling original_value 6 | Then Result equals .render() 7 | 8 | Scenario: Same as initial render after mutation 9 | Given A Checksum 10 | And Mutated once 11 | When Calling original_value 12 | Then Result equals .render() after .reset() 13 | 14 | Scenario: Same as initial render after 2 mutations 15 | Given A Checksum 16 | And Mutated twice 17 | When Calling original_value 18 | Then Result equals .render() after .reset() 19 | 20 | Scenario: Same as initial render after 3 mutations 21 | Given A Checksum 22 | And Mutated thrice 23 | When Calling original_value 24 | Then Result equals .render() after .reset() 25 | 26 | Scenario: Same as initial render after target block mutation 27 | Given A Checksum 28 | And Target block mutated once 29 | When Calling original_value 30 | Then Result equals .render() after target block reset() 31 | 32 | Scenario: Same as initial render after 2 target block mutations 33 | Given A Checksum 34 | And Target block mutated twice 35 | When Calling original_value 36 | Then Result equals .render() after target block reset() 37 | 38 | Scenario: Same as initial render after 3 target block mutations 39 | Given A Checksum 40 | And Target block mutated thrice 41 | When Calling original_value 42 | Then Result equals .render() after target block reset() 43 | 44 | Scenario: UDP Checksum same as initial render after mutating ipv4_src_block_name 45 | Given A UDP Checksum 46 | And ipv4_src_block_name block mutated twice 47 | When Calling original_value 48 | Then Result equals .render() after ipv4_src_block_name reset() 49 | 50 | Scenario: UDP Checksum same as initial render after mutating ipv4_dst_block_name 51 | Given A UDP Checksum 52 | And ipv4_dst_block_name block mutated twice 53 | When Calling original_value 54 | Then Result equals .render() after ipv4_dst_block_name reset() 55 | -------------------------------------------------------------------------------- /boofuzz/unit_tests/helpers_ip_str_to_bytes.feature: -------------------------------------------------------------------------------- 1 | Feature: helpers.ip_str_to_bytes 2 | 3 | Scenario: Valid IP addresses 4 | Given Various IP addresses and expected values 5 | When Calling ip_str_to_bytes 6 | Then The result is as expected 7 | 8 | Scenario: Invalid IP addresses 9 | Given Various invalid IP addresses 10 | When Calling ip_str_to_bytes 11 | Then A ValueError exception is raised -------------------------------------------------------------------------------- /boofuzz/unit_tests/helpers_udp_checksum.feature: -------------------------------------------------------------------------------- 1 | Feature: helpers.udp_checksum() 2 | 3 | Scenario: Empty message 4 | Given Empty msg 5 | And src_addr 192.168.0.12 6 | And dst_addr 255.255.255.255 7 | When Calling udp_checksum 8 | Then The result is 0x3F3A 9 | 10 | Scenario: Msg 1 byte 11 | Given msg b'\xA1' 12 | And src_addr 192.168.0.12 13 | And dst_addr 255.255.255.255 14 | When Calling udp_checksum 15 | Then The result is 0x9E38 16 | 17 | Scenario: Msg 2 bytes 18 | Given msg b'\x00\x00' 19 | And src_addr 192.168.0.12 20 | And dst_addr 255.255.255.255 21 | When Calling udp_checksum 22 | Then The result is 0x3F38 23 | 24 | Scenario: Msg 3 bytes 25 | Given msg b'\xF1\xFF\xFF' 26 | And src_addr 192.168.0.12 27 | And dst_addr 255.255.255.255 28 | When Calling udp_checksum 29 | Then The result is 0x4E36 30 | 31 | Scenario: Msg 60 bytes 32 | Given msg with 60 bytes 33 | And src_addr 192.168.0.12 34 | And dst_addr 255.255.255.255 35 | When Calling udp_checksum 36 | Then The result is 0x3EE9 37 | 38 | Scenario: Max length - 1 39 | Given Maximum length - 1 msg 40 | And src_addr 192.168.0.12 41 | And dst_addr 255.255.255.255 42 | When Calling udp_checksum 43 | Then The result is 0xFF3B 44 | 45 | Scenario: Max length 46 | Given Maximum length msg 47 | And src_addr 192.168.0.12 48 | And dst_addr 255.255.255.255 49 | When Calling udp_checksum 50 | Then The result is 0x7F3A 51 | 52 | Scenario: Max length + 1 53 | Given Maximum length + 1 msg 54 | And src_addr 192.168.0.12 55 | And dst_addr 255.255.255.255 56 | When Calling udp_checksum 57 | Then The result is 0x7F3A # Same as Max length; see docstring -------------------------------------------------------------------------------- /boofuzz/unit_tests/legos.py: -------------------------------------------------------------------------------- 1 | from boofuzz import * 2 | 3 | 4 | def run(): 5 | tag() 6 | ndr_string() 7 | # ber() 8 | 9 | # clear out the requests. 10 | blocks.REQUESTS = {} 11 | blocks.CURRENT = None 12 | 13 | 14 | def tag(): 15 | s_initialize("UNIT TEST TAG 1") 16 | s_lego("tag", value="pedram") 17 | 18 | req = s_get("UNIT TEST TAG 1") 19 | 20 | print "LEGO MUTATION COUNTS:" 21 | print "\ttag: %d" % req.num_mutations() 22 | 23 | 24 | def ndr_string(): 25 | s_initialize("UNIT TEST NDR 1") 26 | s_lego("ndr_string", value="pedram") 27 | 28 | req = s_get("UNIT TEST NDR 1") 29 | # TODO: unfinished! 30 | # print req.render() 31 | 32 | 33 | def ber(): 34 | s_initialize("UNIT TEST BER 1") 35 | s_lego("ber_string", value="pedram") 36 | req = s_get("UNIT TEST BER 1") 37 | assert (s_render() == "\x04\x84\x00\x00\x00\x06\x70\x65\x64\x72\x61\x6d") 38 | s_mutate() 39 | assert (s_render() == "\x04\x84\x00\x00\x00\x00\x70\x65\x64\x72\x61\x6d") 40 | 41 | s_initialize("UNIT TEST BER 2") 42 | s_lego("ber_integer", value=0xdeadbeef) 43 | req = s_get("UNIT TEST BER 2") 44 | assert (s_render() == "\x02\x04\xde\xad\xbe\xef") 45 | s_mutate() 46 | assert (s_render() == "\x02\x04\x00\x00\x00\x00") 47 | s_mutate() 48 | assert (s_render() == "\x02\x04\x00\x00\x00\x01") -------------------------------------------------------------------------------- /boofuzz/unit_tests/request_original_value.feature: -------------------------------------------------------------------------------- 1 | Feature: Request.original_value 2 | 3 | Scenario: same_as_initial_render_1_block 4 | Given A Request with one block 5 | When Calling original_value 6 | Then Result equals .render() 7 | 8 | Scenario: same_as_initial_render_many_blocks 9 | Given A Request with multiple blocks 10 | When Calling original_value 11 | Then Result equals .render() 12 | 13 | Scenario: same_as_initial_render_after_mutate 14 | Given A Request with multiple blocks 15 | And Request is mutated once 16 | When Calling original_value 17 | Then Result equals .render() after .reset() 18 | 19 | Scenario: same_as_initial_render_after_2_mutations 20 | Given A Request with multiple blocks 21 | And Request is mutated twice 22 | When Calling original_value 23 | Then Result equals .render() after .reset() 24 | 25 | Scenario: same_as_initial_render_after_3_mutations 26 | Given A Request with multiple blocks 27 | And Request is mutated thrice 28 | When Calling original_value 29 | Then Result equals .render() after .reset() 30 | -------------------------------------------------------------------------------- /boofuzz/unit_tests/size_original_value.feature: -------------------------------------------------------------------------------- 1 | Feature: Size.original_value 2 | 3 | Scenario: Same as initial render 4 | Given A Size 5 | When Calling original_value 6 | Then Result equals .render() 7 | 8 | Scenario: Same as initial render after mutation 9 | Given A Size 10 | And Mutated once 11 | When Calling original_value 12 | Then Result equals .render() after .reset() 13 | 14 | Scenario: Same as initial render after 2 mutations 15 | Given A Size 16 | And Mutated twice 17 | When Calling original_value 18 | Then Result equals .render() after .reset() 19 | 20 | Scenario: Same as initial render after 3 mutations 21 | Given A Size 22 | And Mutated thrice 23 | When Calling original_value 24 | Then Result equals .render() after .reset() 25 | 26 | Scenario: Same as initial render after target block mutation 27 | Given A Size whose target block will change size upon mutation 28 | And Target block mutated once 29 | When Calling original_value 30 | Then Result equals .render() after Request reset() 31 | 32 | Scenario: Same as initial render after 2 target block mutations 33 | Given A Size whose target block will change size upon mutation 34 | And Target block mutated twice 35 | When Calling original_value 36 | Then Result equals .render() after Request reset() 37 | 38 | Scenario: Same as initial render after 3 target block mutations 39 | Given A Size whose target block will change size upon mutation 40 | And Target block mutated thrice 41 | When Calling original_value 42 | Then Result equals .render() after Request reset() 43 | -------------------------------------------------------------------------------- /boofuzz/unit_tests/string_original_value.feature: -------------------------------------------------------------------------------- 1 | Feature: String.original_value 2 | 3 | Scenario: Same as initial render 4 | Given A String 5 | When Calling original_value 6 | Then Result equals .render() 7 | 8 | Scenario: Same as initial render after mutation 9 | Given A String 10 | And Mutated once 11 | When Calling original_value 12 | Then Result equals .render() after .reset() 13 | 14 | Scenario: Same as initial render after 2 mutations 15 | Given A String 16 | And Mutated twice 17 | When Calling original_value 18 | Then Result equals .render() after .reset() 19 | 20 | Scenario: Same as initial render after 3 mutations 21 | Given A String 22 | And Mutated thrice 23 | When Calling original_value 24 | Then Result equals .render() after .reset() 25 | -------------------------------------------------------------------------------- /boofuzz/unit_tests/test_bit_field_original_value.py: -------------------------------------------------------------------------------- 1 | from pytest_bdd import given, when, then, scenarios 2 | 3 | from boofuzz import BitField 4 | 5 | scenarios('bit_field_original_value.feature') 6 | 7 | 8 | @given('A BitField') 9 | def request_one_block(context): 10 | context.uut = BitField(100, width=8) 11 | 12 | 13 | @given('Mutated once') 14 | def mutate_once(context): 15 | context.uut.mutate() 16 | 17 | 18 | @given('Mutated twice') 19 | def mutate_twice(context): 20 | context.uut.mutate() 21 | context.uut.mutate() 22 | 23 | 24 | @given('Mutated thrice') 25 | def mutate_thrice(context): 26 | context.uut.mutate() 27 | context.uut.mutate() 28 | context.uut.mutate() 29 | 30 | 31 | @when('Calling original_value') 32 | def call_original_value(context): 33 | context.result = context.uut.original_value 34 | 35 | 36 | @then('Result equals .render()') 37 | def result_equals_render(context): 38 | assert context.result == context.uut.render() 39 | 40 | 41 | @then('Result equals .render() after .reset()') 42 | def result_equals_render_after_reset(context): 43 | context.uut.reset() 44 | assert context.result == context.uut.render() 45 | -------------------------------------------------------------------------------- /boofuzz/unit_tests/test_block_original_value.py: -------------------------------------------------------------------------------- 1 | from pytest_bdd import given, when, then, scenarios 2 | 3 | from boofuzz import Request, Block, Byte 4 | 5 | scenarios('block_original_value.feature') 6 | 7 | 8 | @given('A Block with contents') 9 | def request_one_block(context): 10 | request = Request(name="unit-test-request") 11 | 12 | block = Block(name="unit-test-block", request=request) 13 | request.push(block) 14 | 15 | byte1 = Byte(0x01, name="Byte block 1") 16 | request.push(byte1) 17 | 18 | request.pop() 19 | 20 | context.uut = block 21 | 22 | 23 | @given('Mutated once') 24 | def mutate_once(context): 25 | context.uut.mutate() 26 | 27 | 28 | @given('Mutated twice') 29 | def mutate_twice(context): 30 | context.uut.mutate() 31 | context.uut.mutate() 32 | 33 | 34 | @given('Mutated thrice') 35 | def mutate_thrice(context): 36 | context.uut.mutate() 37 | context.uut.mutate() 38 | context.uut.mutate() 39 | 40 | 41 | @when('Calling original_value') 42 | def call_original_value(context): 43 | context.uut.render() # Ensure UUT object state is updated 44 | context.result = context.uut.original_value 45 | 46 | 47 | @then('Result equals .render()') 48 | def result_equals_render(context): 49 | assert context.result == context.uut.render() 50 | 51 | 52 | @then('Result equals .render() after .reset()') 53 | def result_equals_render_after_reset(context): 54 | context.uut.reset() 55 | assert context.result == context.uut.render() 56 | -------------------------------------------------------------------------------- /boofuzz/unit_tests/test_checksum.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/y1t2r4/boofuzz-modbus/bfeb48345b56797b48079e0620e7b06b27085789/boofuzz/unit_tests/test_checksum.py -------------------------------------------------------------------------------- /boofuzz/unit_tests/test_checksum_original_value.py: -------------------------------------------------------------------------------- 1 | from pytest_bdd import given, when, then, scenarios 2 | 3 | from boofuzz import Checksum, Request, Block, Byte, DWord, QWord 4 | 5 | scenarios('checksum_original_value.feature') 6 | 7 | 8 | @given('A Checksum') 9 | def a_checksum(context): 10 | request = Request("unit-test-request") 11 | 12 | block = Block(name="unit-test-block", request=request) 13 | request.push(block) 14 | 15 | byte1 = Byte(0x01, name="Byte block 1") 16 | byte2 = Byte(0x02, name="Byte block 2") 17 | block.push(byte1) 18 | block.push(byte2) 19 | 20 | checksum = Checksum(block_name="unit-test-block", request=request, fuzzable=True, name="Checksum block") 21 | request.push(checksum) 22 | 23 | request.pop() 24 | 25 | context.uut = checksum 26 | context.block = block 27 | context.request = request 28 | 29 | 30 | @given('A UDP Checksum') 31 | def udp_checksum(context): 32 | request = Request("unit-test-request") 33 | 34 | block = Block(name="unit-test-block", request=request) 35 | request.push(block) 36 | 37 | ipv4_packet = QWord(0x01, name="IPv4 Packet") 38 | ipv4_src = DWord(0x12345678, name="IPv4 Src Block") 39 | ipv4_dst = DWord(0x23456789, name="IPv4 Dst Block") 40 | request.push(ipv4_packet) 41 | request.push(ipv4_src) 42 | request.push(ipv4_dst) 43 | 44 | checksum = Checksum(block_name="IPv4 Packet", 45 | ipv4_src_block_name="IPv4 Src Block", 46 | ipv4_dst_block_name="IPv4 Dst Block", 47 | request=request, 48 | fuzzable=True, 49 | algorithm='udp', 50 | name="Checksum block", ) 51 | request.push(checksum) 52 | 53 | request.pop() 54 | 55 | context.uut = checksum 56 | context.block = ipv4_packet 57 | context.request = request 58 | context.ipv4_src = ipv4_src 59 | context.ipv4_dst = ipv4_dst 60 | 61 | 62 | @given('Mutated once') 63 | def mutate_once(context): 64 | context.uut.mutate() 65 | 66 | 67 | @given('Mutated twice') 68 | def mutate_twice(context): 69 | context.uut.mutate() 70 | context.uut.mutate() 71 | 72 | 73 | @given('Mutated thrice') 74 | def mutate_thrice(context): 75 | context.uut.mutate() 76 | context.uut.mutate() 77 | context.uut.mutate() 78 | 79 | 80 | @given('Target block mutated once') 81 | def mutate_target_block_once(context): 82 | context.block.mutate() 83 | 84 | 85 | @given('Target block mutated twice') 86 | def mutate_target_block_twice(context): 87 | context.block.mutate() 88 | context.block.mutate() 89 | 90 | 91 | @given('Target block mutated thrice') 92 | def mutate_target_block_thrice(context): 93 | context.block.mutate() 94 | context.block.mutate() 95 | context.block.mutate() 96 | 97 | 98 | @given('ipv4_src_block_name block mutated twice') 99 | def mutate_ipv4_src_block_twice(context): 100 | context.ipv4_src.mutate() 101 | context.ipv4_src.mutate() 102 | 103 | 104 | @given('ipv4_dst_block_name block mutated twice') 105 | def mutate_ipv4_dst_block_twice(context): 106 | context.ipv4_dst.mutate() 107 | context.ipv4_dst.mutate() 108 | 109 | 110 | @when('Calling original_value') 111 | def call_original_value(context): 112 | context.uut.render() # Ensure UUT object state is updated 113 | context.result = context.uut.original_value 114 | 115 | 116 | @then('Result equals .render()') 117 | def result_equals_render(context): 118 | assert context.result == context.uut.render() 119 | 120 | 121 | @then('Result equals .render() after .reset()') 122 | def result_equals_render_after_reset(context): 123 | context.uut.reset() 124 | assert context.result == context.uut.render() 125 | 126 | 127 | @then('Result equals .render() after target block reset()') 128 | def result_equals_render_after_reset_target_block(context): 129 | context.block.reset() 130 | assert context.result == context.uut.render() 131 | 132 | 133 | @then('Result equals .render() after ipv4_src_block_name reset()') 134 | def result_equals_render_after_reset_ipv4_src(context): 135 | context.ipv4_src.reset() 136 | assert context.result == context.uut.render() 137 | 138 | 139 | @then('Result equals .render() after ipv4_dst_block_name reset()') 140 | def result_equals_render_after_reset_ipv4_dst(context): 141 | context.ipv4_dst.reset() 142 | assert context.result == context.uut.render() 143 | -------------------------------------------------------------------------------- /boofuzz/unit_tests/test_helpers_ip_str_to_bytes.py: -------------------------------------------------------------------------------- 1 | # pytest is required as an extras_require: 2 | # noinspection PyPackageRequirements 3 | import pytest 4 | from pytest_bdd import given, when, then, scenarios, scenario 5 | from boofuzz import helpers 6 | 7 | 8 | @pytest.mark.parametrize(['ip_str', 'ip_bytes'], 9 | [('127.0.0.1', b'\x7F\x00\x00\x01'), 10 | ('255.255.255.255', b'\xFF\xFF\xFF\xFF'), 11 | ('0.0.0.0', b'\x00\x00\x00\x00'), 12 | ('0.1.2.3', b'\x00\x01\x02\x03'), 13 | ('1.2.3', b'\x01\x02\x00\x03'), 14 | ('1.2.256', b'\x01\x02\x01\x00'), 15 | ('1.2.65535', b'\x01\x02\xFF\xFF'), 16 | ('1.16777215', b'\x01\xFF\xFF\xFF'), 17 | ('4294967294', b'\xFF\xFF\xFF\xFE')]) 18 | @scenario('helpers_ip_str_to_bytes.feature', 'Valid IP addresses') 19 | def test_valid_ip_addresses(ip_str, ip_bytes): 20 | pass 21 | 22 | 23 | @pytest.mark.parametrize('bad_ip_str', 24 | ['256.0.0.0', 25 | '0.256.0.0', 26 | '0.0.256.0', 27 | '0.0.0.256', 28 | '1.2.3.', 29 | '1.2.3.-1', 30 | '1.2..3', 31 | '1..2.3', 32 | '.1.2.3', 33 | '']) 34 | @scenario('helpers_ip_str_to_bytes.feature', 'Invalid IP addresses') 35 | def test_invalid_ip_addresses(bad_ip_str): 36 | _ = bad_ip_str 37 | pass 38 | 39 | 40 | scenarios('helpers_ip_str_to_bytes.feature') 41 | 42 | 43 | @given("Various IP addresses and expected values") 44 | def ips_and_values(context, ip_str, ip_bytes): 45 | context.ip = ip_str 46 | context.expected = ip_bytes 47 | 48 | 49 | @when("Calling ip_str_to_bytes") 50 | def call(context): 51 | try: 52 | context.result = helpers.ip_str_to_bytes(context.ip) 53 | except ValueError as e: 54 | context.exc_info = e 55 | 56 | 57 | @then("The result is as expected") 58 | def result_as_expected(context): 59 | assert context.result == context.expected 60 | 61 | 62 | @given("Various invalid IP addresses") 63 | def invalid_ips(context, bad_ip_str): 64 | context.ip = bad_ip_str 65 | 66 | 67 | @then("A ValueError exception is raised") 68 | def except_raised(context): 69 | assert isinstance(context.exc_info, ValueError) 70 | -------------------------------------------------------------------------------- /boofuzz/unit_tests/test_helpers_udp_checksum.py: -------------------------------------------------------------------------------- 1 | from __future__ import division 2 | import ast 3 | import math 4 | # pytest is required as an extras_require: 5 | # noinspection PyPackageRequirements 6 | import pytest 7 | from pytest_bdd import parsers 8 | from pytest_bdd import given, when, then, scenarios 9 | from boofuzz import helpers 10 | from boofuzz import ip_constants 11 | 12 | 13 | scenarios('helpers_udp_checksum.feature') 14 | 15 | 16 | @given('Empty msg') 17 | def msg_empty(context): 18 | context.msg = b'' 19 | 20 | 21 | @given(parsers.cfparse('msg {msg}')) 22 | def msg_1_byte(context, msg): 23 | context.msg = ast.literal_eval(msg) 24 | 25 | 26 | @given('msg with 60 bytes') 27 | def msg_60_bytes(context): 28 | # Use each bit at least once... 29 | all_16_bits = b'\x00\x01\x00\x02\x00\x04\x00\x08\x00\x10\x00\x20\x00\x40\x00\x80' + \ 30 | b'\x01\x00\x02\x00\x04\x00\x08\x00\x10\x00\x20\x00\x40\x00\x80\x00' 31 | # Then fill the remaining bytes. 32 | # Note that using all FF bytes causes a boring pattern, since FFFF + FFFF 33 | # with carry is just FFFF again. 0x8001 adds just a little bit at a time, 34 | # hopefully ensuring that all bytes are actually factored into the answer. 35 | filler = b'\x80\x01' * ((60 - len(all_16_bits)) // 2) 36 | context.msg = all_16_bits + filler 37 | 38 | assert len(context.msg) == 60 39 | 40 | 41 | @given('Maximum length - 1 msg') 42 | def msg_max_minus_1(context): 43 | context.msg = b'\x80\x01' * int(math.ceil(ip_constants.UDP_MAX_LENGTH_THEORETICAL / 2)) 44 | context.msg = context.msg[0:ip_constants.UDP_MAX_LENGTH_THEORETICAL - 1] 45 | assert len(context.msg) == ip_constants.UDP_MAX_LENGTH_THEORETICAL - 1 46 | 47 | 48 | @given('Maximum length msg') 49 | def msg_max(context): 50 | context.msg = b'\x80\x01' * int(math.ceil(ip_constants.UDP_MAX_LENGTH_THEORETICAL / 2)) 51 | context.msg = context.msg[0:ip_constants.UDP_MAX_LENGTH_THEORETICAL] 52 | assert len(context.msg) == ip_constants.UDP_MAX_LENGTH_THEORETICAL 53 | 54 | 55 | @given('Maximum length + 1 msg') 56 | def msg_max_plus_1(context): 57 | context.msg = b'\x80\x01' * int(math.ceil((ip_constants.UDP_MAX_LENGTH_THEORETICAL + 1) / 2)) 58 | context.msg = context.msg[0:ip_constants.UDP_MAX_LENGTH_THEORETICAL + 1] 59 | assert len(context.msg) == ip_constants.UDP_MAX_LENGTH_THEORETICAL + 1 60 | 61 | 62 | @given(parsers.cfparse('src_addr {text}')) 63 | def src_addr(context, text): 64 | context.src_addr = helpers.ip_str_to_bytes(text) 65 | 66 | 67 | @given(parsers.cfparse('dst_addr {text}')) 68 | def dst_addr(context, text): 69 | context.dst_addr = helpers.ip_str_to_bytes(text) 70 | 71 | 72 | @when('Calling udp_checksum') 73 | def call_udp_checksum(context): 74 | context.result = helpers.udp_checksum(context.msg, context.src_addr, context.dst_addr) 75 | 76 | 77 | @then(parsers.cfparse('The result is {result:d}')) 78 | def result_is(context, result): 79 | assert context.result == result 80 | 81 | 82 | if __name__ == '__main__': 83 | pytest.main() 84 | -------------------------------------------------------------------------------- /boofuzz/unit_tests/test_request_original_value.py: -------------------------------------------------------------------------------- 1 | from pytest_bdd import given, when, then, scenarios 2 | 3 | from boofuzz import Request 4 | from boofuzz import primitives 5 | 6 | scenarios('request_original_value.feature') 7 | 8 | 9 | @given('A Request with one block') 10 | def request_one_block(context): 11 | r = Request("unit-test-request") 12 | r.push(primitives.Byte(value=0, name="byte block")) 13 | context.uut = r 14 | 15 | 16 | @given('A Request with multiple blocks') 17 | def request_multiple_blocks(context): 18 | r = Request("unit-test-request") 19 | r.push(primitives.Byte(value=1, name="string block")) 20 | r.push(primitives.String(value="The perfection of art is to conceal art.", name="unit-test-byte")) 21 | context.uut = r 22 | 23 | 24 | @given('Request is mutated once') 25 | def mutate_once(context): 26 | context.uut.mutate() 27 | 28 | 29 | @given('Request is mutated twice') 30 | def mutate_twice(context): 31 | context.uut.mutate() 32 | context.uut.mutate() 33 | 34 | 35 | @given('Request is mutated thrice') 36 | def mutate_twice(context): 37 | context.uut.mutate() 38 | context.uut.mutate() 39 | context.uut.mutate() 40 | 41 | 42 | @when('Calling original_value') 43 | def call_original_value(context): 44 | context.uut.render() # Ensure UUT object state is updated 45 | context.result = context.uut.original_value 46 | 47 | 48 | @then('Result equals .render()') 49 | def result_equals_render(context): 50 | assert context.result == context.uut.render() 51 | 52 | 53 | @then('Result equals .render() after .reset()') 54 | def result_equals_render_after_reset(context): 55 | context.uut.reset() 56 | assert context.result == context.uut.render() 57 | -------------------------------------------------------------------------------- /boofuzz/unit_tests/test_size_original_value.py: -------------------------------------------------------------------------------- 1 | from pytest_bdd import given, when, then, scenarios 2 | 3 | from boofuzz import Size, Request, Block, Byte, BasePrimitive 4 | 5 | scenarios('size_original_value.feature') 6 | 7 | 8 | class SizeChangingBlock(BasePrimitive): 9 | @property 10 | def name(self): 11 | return self._name 12 | 13 | def __init__(self, value=b'\x01', name=None): 14 | """A block that changes size with each mutation. 15 | """ 16 | super(SizeChangingBlock, self).__init__() 17 | 18 | self._name = name 19 | self._value = self._original_value = value 20 | 21 | self._fuzz_library.append(b'\x01\x02') 22 | self._fuzz_library.append(b'\x01\x02'*1000) 23 | self._fuzz_library.append(b'\x01\x02'*5) 24 | 25 | 26 | @given('A Size') 27 | def request_one_block(context): 28 | request = Request("unit-test-request") 29 | 30 | block = Block(name="unit-test-block", request=Request) 31 | request.push(block) 32 | 33 | byte1 = Byte(0x01, name="Byte block 1") 34 | byte2 = Byte(0x02, name="Byte block 2") 35 | request.push(byte1) 36 | request.push(byte2) 37 | 38 | size = Size(block_name="unit-test-block", request=request, fuzzable=True, name="Size block") 39 | request.push(size) 40 | 41 | request.pop() 42 | 43 | context.uut = size 44 | 45 | 46 | @given('A Size whose target block will change size upon mutation') 47 | def request_one_block(context): 48 | request = Request("unit-test-request") 49 | 50 | block = Block(name="unit-test-block", request=Request) 51 | request.push(block) 52 | 53 | size_changing_block = SizeChangingBlock(name="size-changing-block") 54 | request.push(size_changing_block) 55 | 56 | request.pop() 57 | 58 | size = Size(block_name="size-changing-block", request=request, fuzzable=True, name="Size block") 59 | request.push(size) 60 | 61 | context.uut = size 62 | context.block = block 63 | context.request = request 64 | 65 | 66 | @given('Mutated once') 67 | def mutate_once(context): 68 | context.uut.mutate() 69 | 70 | 71 | @given('Mutated twice') 72 | def mutate_twice(context): 73 | context.uut.mutate() 74 | context.uut.mutate() 75 | 76 | 77 | @given('Mutated thrice') 78 | def mutate_thrice(context): 79 | context.uut.mutate() 80 | context.uut.mutate() 81 | context.uut.mutate() 82 | 83 | 84 | @given('Target block mutated once') 85 | def mutate_target_block_once(context): 86 | context.block.mutate() 87 | 88 | 89 | @given('Target block mutated twice') 90 | def mutate_target_block_twice(context): 91 | context.block.mutate() 92 | context.block.mutate() 93 | 94 | 95 | @given('Target block mutated thrice') 96 | def mutate_target_block_thrice(context): 97 | context.block.mutate() 98 | context.block.mutate() 99 | context.block.mutate() 100 | 101 | 102 | @when('Calling original_value') 103 | def call_original_value(context): 104 | context.uut.render() # Ensure UUT object state is updated 105 | context.result = context.uut.original_value 106 | 107 | 108 | @then('Result equals .render()') 109 | def result_equals_render(context): 110 | assert context.result == context.uut.render() 111 | 112 | 113 | @then('Result equals .render() after .reset()') 114 | def result_equals_render_after_reset(context): 115 | context.uut.reset() 116 | assert context.result == context.uut.render() 117 | 118 | 119 | @then('Result equals .render() after Request reset()') 120 | def result_equals_render_after_reset(context): 121 | context.request.reset() 122 | assert context.result == context.uut.render() 123 | -------------------------------------------------------------------------------- /boofuzz/unit_tests/test_string_original_value.py: -------------------------------------------------------------------------------- 1 | from pytest_bdd import given, when, then, scenarios 2 | 3 | from boofuzz import String 4 | 5 | scenarios('string_original_value.feature') 6 | 7 | 8 | @given('A String') 9 | def request_one_block(context): 10 | context.uut = String('unit-test-string') 11 | 12 | 13 | @given('Mutated once') 14 | def mutate_once(context): 15 | context.uut.mutate() 16 | 17 | 18 | @given('Mutated twice') 19 | def mutate_twice(context): 20 | context.uut.mutate() 21 | context.uut.mutate() 22 | 23 | 24 | @given('Mutated thrice') 25 | def mutate_thrice(context): 26 | context.uut.mutate() 27 | context.uut.mutate() 28 | context.uut.mutate() 29 | 30 | 31 | @when('Calling original_value') 32 | def call_original_value(context): 33 | context.result = context.uut.original_value 34 | 35 | 36 | @then('Result equals .render()') 37 | def result_equals_render(context): 38 | assert context.result == context.uut.render() 39 | 40 | 41 | @then('Result equals .render() after .reset()') 42 | def result_equals_render_after_reset(context): 43 | context.uut.reset() 44 | assert context.result == context.uut.render() 45 | -------------------------------------------------------------------------------- /boofuzz/utils/crashbin_explorer.py: -------------------------------------------------------------------------------- 1 | #!c:\\python\\python.exe 2 | 3 | import getopt 4 | import sys 5 | sys.path.append(r"../../../paimei") 6 | 7 | from boofuzz import utils 8 | import pgraph 9 | 10 | USAGE = "\nUSAGE: crashbin_explorer.py " \ 11 | "\n [-t|--test #] dump the crash synopsis for a specific test case number" \ 12 | "\n [-g|--graph name] generate a graph of all crash paths, save to 'name'.udg\n" 13 | 14 | # 15 | # parse command line options. 16 | # 17 | 18 | try: 19 | if len(sys.argv) < 2: 20 | raise Exception 21 | 22 | opts, args = getopt.getopt(sys.argv[2:], "t:g:", ["test=", "graph="]) 23 | except Exception: 24 | print USAGE 25 | sys.exit(1) 26 | 27 | test_number = graph_name = graph = None 28 | 29 | for opt, arg in opts: 30 | if opt in ("-t", "--test"): 31 | test_number = int(arg) 32 | if opt in ("-g", "--graph"): 33 | graph_name = arg 34 | 35 | try: 36 | crashbin = utils.crash_binning.CrashBinning() 37 | crashbin.import_file(sys.argv[1]) 38 | except Exception: 39 | print "unable to open crashbin: '%s'." % sys.argv[1] 40 | sys.exit(1) 41 | 42 | # 43 | # display the full crash dump of a specific test case 44 | # 45 | 46 | if test_number: 47 | for _, crashes in crashbin.bins.iteritems(): 48 | for crash in crashes: 49 | if test_number == crash.extra: 50 | print crashbin.crash_synopsis(crash) 51 | sys.exit(0) 52 | 53 | # 54 | # display an overview of all recorded crashes. 55 | # 56 | 57 | if graph_name: 58 | graph = pgraph.Graph() 59 | 60 | # noinspection PyRedeclaration 61 | for _, crashes in crashbin.bins.iteritems(): 62 | synopsis = crashbin.crash_synopsis(crashes[0]).split("\n")[0] 63 | 64 | for crash in crashes: 65 | if graph: 66 | crash_node = pgraph.Node(crashes[0].exception_address) 67 | crash_node.count = len(crashes) 68 | crash_node.label = "[%d] %s.%08x" % (crash_node.count, crashes[0].exception_module, crash_node.id) 69 | graph.add_node(crash_node) 70 | print "[%d] %s" % (len(crashes), synopsis) 71 | print "\t", 72 | last = crash_node.id 73 | for entry in crash.stack_unwind: 74 | address = long(entry.split(":")[1], 16) 75 | n = graph.find_node("id", address) 76 | 77 | if not n: 78 | n = pgraph.Node(address) 79 | n.count = 1 80 | n.label = "[%d] %s" % (n.count, entry) 81 | graph.add_node(n) 82 | else: 83 | n.count += 1 84 | n.label = "[%d] %s" % (n.count, entry) 85 | 86 | edge = pgraph.Edge(n.id, last) 87 | graph.add_edge(edge) 88 | last = n.id 89 | print "%d," % crash.extra, 90 | 91 | print "\n" 92 | 93 | if graph: 94 | fh = open("%s.udg" % graph_name, "w+") 95 | fh.write(graph.render_graph_udraw()) 96 | fh.close() -------------------------------------------------------------------------------- /boofuzz/utils/pcap_cleaner.py: -------------------------------------------------------------------------------- 1 | #!c:\\python\\python.exe 2 | 3 | import os 4 | import sys 5 | sys.path.append(r"..\..\..\paimei") 6 | 7 | from boofuzz import utils 8 | 9 | USAGE = "\nUSAGE: pcap_cleaner.py \n" 10 | 11 | if len(sys.argv) != 3: 12 | print USAGE 13 | sys.exit(1) 14 | 15 | 16 | # 17 | # generate a list of all test cases that triggered a crash. 18 | # 19 | 20 | try: 21 | crashbin = utils.crash_binning.CrashBinning() 22 | crashbin.import_file(sys.argv[1]) 23 | except Exception: 24 | print "unable to open crashbin: '%s'." % sys.argv[1] 25 | sys.exit(1) 26 | 27 | test_cases = [] 28 | for _, crashes in crashbin.bins.iteritems(): 29 | for crash in crashes: 30 | test_cases.append("%d.pcap" % crash.extra) 31 | 32 | # 33 | # step through the pcap directory and erase all files not pertaining to a crash. 34 | # 35 | 36 | for filename in os.listdir(sys.argv[2]): 37 | if filename not in test_cases: 38 | os.unlink("%s/%s" % (sys.argv[2], filename)) 39 | -------------------------------------------------------------------------------- /boofuzz/utils/pdml_parser.py: -------------------------------------------------------------------------------- 1 | #!c:\python\python.exe 2 | 3 | import sys 4 | 5 | from xml.sax import make_parser 6 | from xml.sax import ContentHandler 7 | from xml.sax.handler import feature_namespaces 8 | 9 | 10 | class ParsePDML(ContentHandler): 11 | def __init__(self): 12 | ContentHandler.__init__(self) 13 | self.current = None 14 | self.start_parsing = False 15 | self.sulley = "" 16 | 17 | def startElement(self, name, attributes): 18 | if name == "proto": 19 | self.current = attributes["name"] 20 | 21 | # if parsing flag is set, we're past tcp 22 | if self.start_parsing: 23 | 24 | if not name == "field": 25 | print "Found payload with name %s" % attributes["name"] 26 | elif name == "field": 27 | if "value" in attributes.keys(): 28 | val_string = self.get_string(attributes["value"]) 29 | 30 | if val_string: 31 | self.sulley += "s_string(\"%s\")\n" % val_string 32 | print self.sulley 33 | # print "\tFound value: %s" % val_string 34 | else: 35 | # not string 36 | pass 37 | else: 38 | raise Exception("WTFException") 39 | 40 | def characters(self, data): 41 | pass 42 | 43 | def endElement(self, name): 44 | # if we're closing a packet 45 | if name == "packet": 46 | self.start_parsing = False 47 | 48 | # if we're closing a proto tag 49 | if name == "proto": 50 | # and that proto is tcp, set parsing flag 51 | if self.current == "tcp": 52 | # print "Setting parsing flag to TRUE" 53 | self.start_parsing = True 54 | 55 | else: 56 | self.start_parsing = False 57 | 58 | # noinspection PyMethodMayBeStatic 59 | def get_string(self, parsed): 60 | 61 | parsed = parsed.replace("\t", "") 62 | parsed = parsed.replace("\r", "") 63 | parsed = parsed.replace("\n", "") 64 | parsed = parsed.replace(",", "") 65 | parsed = parsed.replace("0x", "") 66 | parsed = parsed.replace("\\x", "") 67 | 68 | value = "" 69 | while parsed: 70 | pair = parsed[:2] 71 | parsed = parsed[2:] 72 | 73 | hex_pair = int(pair, 16) 74 | if hex_pair > 0x7f: 75 | return False 76 | 77 | value += chr(hex_pair) 78 | 79 | value = value.replace("\t", "") 80 | value = value.replace("\r", "") 81 | value = value.replace("\n", "") 82 | value = value.replace(",", "") 83 | value = value.replace("0x", "") 84 | value = value.replace("\\x", "") 85 | 86 | return value 87 | 88 | # noinspection PyMethodMayBeStatic 89 | def error(self, exception): 90 | print "Oh shitz: ", exception 91 | sys.exit(1) 92 | 93 | 94 | if __name__ == '__main__': 95 | # create the parser object 96 | parser = make_parser() 97 | 98 | # dont care about xml namespace 99 | parser.setFeature(feature_namespaces, 0) 100 | 101 | # make the document handler 102 | handler = ParsePDML() 103 | 104 | # point parser to handler 105 | parser.setContentHandler(handler) 106 | 107 | # parse 108 | parser.parse(sys.argv[1]) 109 | 110 | 111 | -------------------------------------------------------------------------------- /boofuzz/utils/print_session.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | 3 | import sys 4 | import zlib 5 | import cPickle 6 | 7 | USAGE = "\nUSAGE: print_session.py \n" 8 | 9 | if len(sys.argv) != 2: 10 | print USAGE 11 | sys.exit(1) 12 | 13 | fh = open(sys.argv[1], "rb") 14 | data = cPickle.loads(zlib.decompress(fh.read())) 15 | fh.close() 16 | 17 | 18 | #print data 19 | for key in data.keys(): 20 | print key + " -> " + str(data[key]) 21 | 22 | -------------------------------------------------------------------------------- /fuzz_config.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ''' 5 | configure for modbus fuzzer 6 | 7 | session_filename=None, skip=0, sleep_time=0.0, restart_interval=0, web_port=26000, crash_threshold=3, 8 | restart_sleep_time=5, fuzz_data_logger=None, check_data_received_each_request=True, log_level=logging.INFO, 9 | logfile=None, logfile_level=logging.DEBUG, ignore_connection_reset=False, ignore_connection_aborted=False, 10 | target=None, ): 11 | 12 | ''' 13 | import logging 14 | 15 | skip = None 16 | sleep_time = 0 17 | restart_interval = 0 18 | web_port = 26000 19 | crash_threshold = 800 20 | restart_sleep_time = 5 21 | fuzz_data_logger = None 22 | check_data_received_each_request=True 23 | log_level=logging.INFO 24 | logfile=None 25 | logfile_level=logging.INFO 26 | ignore_connection_reset=False 27 | ignore_connection_aborted=False 28 | target=None 29 | 30 | 31 | #------------------- 32 | 33 | method = 1 34 | -------------------------------------------------------------------------------- /fuzz_ethernetip.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | -------------------------------------------------------------------------------- /fuzz_opc.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/y1t2r4/boofuzz-modbus/bfeb48345b56797b48079e0620e7b06b27085789/fuzz_opc.py -------------------------------------------------------------------------------- /fuzz_profinet.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/y1t2r4/boofuzz-modbus/bfeb48345b56797b48079e0620e7b06b27085789/fuzz_profinet.py -------------------------------------------------------------------------------- /fuzzer.py: -------------------------------------------------------------------------------- 1 | # coding = utf-8 2 | 3 | import fuzz_modbus 4 | from fuzz_modbus import * 5 | 6 | 7 | class Modbus_fuzzer(): 8 | 9 | def standard_fuzz(self): 10 | pass 11 | 12 | def shneidier_fuzz(self): 13 | pass 14 | 15 | class EthernetIP_fuzzer(): 16 | 17 | @staticmethod 18 | def standard_fuzz(self): 19 | pass 20 | 21 | 22 | if __name__ == '__main__': 23 | -------------------------------------------------------------------------------- /log_analysis.py: -------------------------------------------------------------------------------- 1 | # coding = utf-8 2 | import csv 3 | 4 | class analysislog(): 5 | 6 | def __init__(self, csvfile,type): 7 | pass 8 | 9 | def _do(self): 10 | pass 11 | 12 | def _run(self): 13 | pass 14 | 15 | 16 | -------------------------------------------------------------------------------- /logging_config.ini: -------------------------------------------------------------------------------- 1 | [loggers] 2 | keys=root 3 | 4 | [handlers] 5 | keys=cmdshow,fileshow 6 | 7 | [formatters] 8 | keys=formatter 9 | 10 | [logger_root] 11 | level=DEBUG 12 | handlers=cmdshow,fileshow 13 | 14 | [logger_modbus_logger] 15 | level=DEBUG 16 | handlers=cmdshow,fileshow 17 | qualname=1 18 | 19 | [handler_cmdshow] 20 | class=StreamHandler 21 | level=DEBUG 22 | formatter=formatter 23 | args=(sys.stderr,) 24 | 25 | [handler_fileshow] 26 | class= FileHandler 27 | level=DEBUG 28 | formatter=formatter 29 | args=('modbus_fuzzer.log', 'w') 30 | 31 | [formatter_formatter] 32 | format=%(asctime)s %(name)-12s %(levelname)-8s %(message)s -------------------------------------------------------------------------------- /misc/README.md: -------------------------------------------------------------------------------- 1 | #PyModbus 2 | ## Modbus Client and Server written in Python-2.7. 3 | * Modbus Read Client 4 | * Modbus Write Client 5 | * Modbus Server 6 | * Modbus RTU Read Client over TCP Port Server 7 | * Modbus RTU Read Client over Serial Port 8 | * Source code in Tar: https://github.com/pal100/PyModbus/tarball/master 9 | * Source code in Zip: https://github.com/pal100/PyModbus/zipball/master 10 | -------------------------------------------------------------------------------- /misc/modbus.log: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/y1t2r4/boofuzz-modbus/bfeb48345b56797b48079e0620e7b06b27085789/misc/modbus.log -------------------------------------------------------------------------------- /misc/modbus_read_client.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # version 2.7 3 | import socket 4 | from sys import argv,exit 5 | from array import array 6 | from time import sleep 7 | if len(argv) < 6 or argv[1] == '-h' or argv[1] == '--help' or argv[1] == '?': 8 | print "usage: python modbus_read_client.py Host Unit FC Address Length" 9 | exit() 10 | HOST = argv[1] # The remote host 11 | #HOST = '192.168.5.30' # The remote host 12 | PORT = 502 # The same port as used by the server 13 | UNIT = int(argv[2]) 14 | FC = int(argv[3]) 15 | ADD = int(argv[4]) 16 | LEN = int(argv[5]) 17 | lLEN = LEN & 0x00FF 18 | mLEN = LEN >> 8 19 | if (FC < 3): BYT = (lambda x: x/8 if (x%8==0) else x/8+1)(LEN) #Round off the no. of bytes 20 | else: BYT = LEN*2 21 | lADD = ADD & 0x00FF 22 | mADD = (ADD & 0xFF00)>>8 23 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 24 | s.connect((HOST, PORT)) 25 | cmd = array('B', [00,00,00,00,00,06,UNIT,FC,mADD,lADD,mLEN,lLEN]) 26 | while 1: 27 | s.send(cmd) 28 | buffer = array('B', [0]*(BYT+9)) 29 | s.recv_into(buffer) 30 | buf = buffer[9:(9+BYT)] 31 | print 'Received', buffer 32 | if (FC > 2): 33 | for j in range(BYT/2): 34 | #print 'data for Reg',(BYT/2)-1-j,'=', (buffer[(-j*2)-2]<<8)+buffer[(-j*2)-1] 35 | print 'data for Reg',j,'=', (buf[(j*2)]<<8)+buf[j*2+1] 36 | else: 37 | for j in range(BYT): 38 | #print 'data for Bytes',BYT-1-j,'=', buffer[-1-j] 39 | print 'data for Byte',j,'=', buf[j] 40 | sleep(1) 41 | 42 | 43 | -------------------------------------------------------------------------------- /misc/modbus_rtu_read.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # version 2.7 3 | # Read Modbus RTU over TCP Port Server 4 | import socket 5 | from sys import argv,exit 6 | from array import array 7 | from time import sleep 8 | if len(argv) < 7 or argv[1] == '-h' or argv[1] == '--help' or argv[1] == '?': 9 | print "usage: python modbus_rtu_read.py Host Port Unit FC Address Length" 10 | exit() 11 | def CRC(list): 12 | crc = 0xffff 13 | for l in list: 14 | c = l ^ crc 15 | for i in range (8): 16 | if (c & 0x0001) != 0: 17 | c = c >> 1 18 | c = c ^ 0xA001 19 | else: 20 | c = c >> 1 21 | crc = c 22 | return crc 23 | HOST = argv[1] 24 | PORT = int(argv[2]) 25 | UNIT = int(argv[3]) 26 | FC = int(argv[4]) 27 | ADD = int(argv[5]) 28 | LEN = int(argv[6]) 29 | lLEN = LEN & 0x00FF 30 | mLEN = LEN >> 8 31 | if (FC < 3): BYT = (lambda x: x/8 if (x%8==0) else x/8+1)(LEN) #Round off the no. of bytes 32 | else: BYT = LEN*2 33 | lADD = ADD & 0x00FF 34 | mADD = (ADD & 0xFF00)>>8 35 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 36 | s.connect((HOST, PORT)) 37 | cmd = array('B', [UNIT,FC,mADD,lADD,mLEN,lLEN]) 38 | crc = CRC(cmd) 39 | lcrc = crc & 0x00FF 40 | mcrc = (crc & 0xFF00)>>8 41 | cmd = array('B', [UNIT,FC,mADD,lADD,mLEN,lLEN,lcrc,mcrc]) 42 | while 1: 43 | s.send(cmd) 44 | buffer = array('B', [0]*(BYT+7)) 45 | s.recv_into(buffer) 46 | buf = buffer[3:(3+BYT)] 47 | print 'Received', buffer 48 | if (FC > 2): 49 | for j in range(BYT/2): 50 | #print 'data for Reg',(BYT/2)-1-j,'=', (buffer[(-j*2)-2]<<8)+buffer[(-j*2)-1] 51 | print 'data for Reg',j,'=', (buf[(j*2)]<<8)+buf[j*2+1] 52 | else: 53 | for j in range(BYT): 54 | #print 'data for Bytes',BYT-1-j,'=', buffer[-1-j] 55 | print 'data for Byte',j,'=', buf[j] 56 | sleep(2) 57 | -------------------------------------------------------------------------------- /misc/modbus_serial_read.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # version 2.7 3 | # Read Modbus RTU Serial 4 | from serial import Serial 5 | from sys import argv,exit 6 | from struct import pack_into 7 | from array import array 8 | from time import sleep 9 | from struct import unpack 10 | if len(argv) < 6 or argv[1] == '-h' or argv[1] == '--help' or argv[1] == '?': 11 | print "usage: python modbus_rtu_read.py Port Unit FC Address Length" 12 | exit() 13 | def CRC(list): 14 | crc = 0xffff 15 | for l in list: 16 | c = l ^ crc 17 | for i in range (8): 18 | if (c & 0x0001) != 0: 19 | c = c >> 1 20 | c = c ^ 0xA001 21 | else: 22 | c = c >> 1 23 | crc = c 24 | b = array('B',[0]*2) 25 | pack_into('H',b,0,crc) 26 | return b 27 | PORT = argv[1] 28 | UNIT = int(argv[2]) 29 | FC = int(argv[3]) 30 | ADD = int(argv[4]) 31 | LEN = int(argv[5]) 32 | lLEN = LEN & 0x00FF 33 | mLEN = LEN >> 8 34 | if (FC < 3): BYT = (lambda x: x/8 if (x%8==0) else x/8+1)(LEN) #Round off the no. of bytes 35 | else: BYT = LEN*2 36 | lADD = ADD & 0x00FF 37 | mADD = (ADD & 0xFF00)>>8 38 | s = Serial('/dev/'+PORT, timeout=0.5) 39 | s.baudrate = 9600 40 | s.parity = "E" 41 | s.databits = 8 42 | s.stopbits = 1 43 | s.handshake = "none" 44 | s.datatype = "raw" 45 | cmd = array('B', [UNIT,FC,mADD,lADD,mLEN,lLEN]) 46 | cmd.extend(CRC(cmd)) 47 | #lcrc = crc & 0x00FF 48 | #mcrc = (crc & 0xFF00)>>8 49 | #cmd = array('B', [UNIT,FC,mADD,lADD,mLEN,lLEN,lcrc,mcrc]) 50 | while 1: 51 | s.write(cmd) 52 | print cmd 53 | sleep(1) 54 | read = s.read(255) 55 | r = '' 56 | if len(read) > 0: r = unpack('B'*len(read),read) 57 | print 'Received =', r 58 | # s.flushInput() 59 | # s.flushOutput() 60 | sleep(0.5) 61 | -------------------------------------------------------------------------------- /misc/modbus_server.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # version 2.7 3 | import socket,thread 4 | from array import array 5 | from time import sleep, ctime 6 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 7 | s.bind(('0.0.0.0',502)) 8 | s.listen(1) 9 | F = open('modbus.log', 'w', 0) 10 | def TCP(conn,addr,F): 11 | buffer = array('B',[0]*300) 12 | while 1: 13 | try: 14 | conn.recv_into(buffer) 15 | ID = buffer[6] 16 | FC = buffer[7] 17 | mADR = buffer[8] 18 | lADR = buffer[9] 19 | ADR = mADR*256+lADR 20 | LEN = buffer[10]*256+buffer[11] 21 | BYT = LEN*2 22 | print "Received = ",buffer[0:13+buffer[12]] 23 | if (FC < 5 and FC > 0): #Read Inputs or Registers 24 | DAT = array('B') 25 | if FC < 3: 26 | BYT = (lambda x: x/8 if (x%8==0) else x/8+1)(LEN) #Round off the no. of bytes 27 | v = 85 #send 85,86.. for bytes. 28 | for i in range(BYT): 29 | DAT.append(v) 30 | v = (lambda x: x+1 if (x<255) else 85)(v) 31 | else: 32 | for i in range(LEN): #Sends back the address as data 33 | DAT.append(mADR) 34 | DAT.append(lADR) 35 | if (lADR == 255): 36 | lADR = 0 37 | mADR = mADR + 1 38 | else: lADR = lADR + 1 39 | print "ID= %d, Fun.Code= %d, Address= %d, Length= %d" %(ID, FC, ADR, LEN) 40 | conn.send(array('B', [0,0,0,0,0, BYT+3, ID, FC, BYT]) + DAT ) 41 | elif (FC == 15 or FC == 16 or FC == 6): #Write Registers 42 | BYT = buffer[12] 43 | conn.send(array('B', [0,0,0,0,0, 6, ID, FC, mADR, lADR, buffer[10], buffer[11] ] ) ) 44 | buf = buffer[13:(13+BYT)] 45 | message = ': ADR:'+str(ADR)+' ' 46 | if FC == 15: 47 | print "ID= %d, Fun.Code= %d, Address= %d, Length= %d, Bytes= %d" %(ID, FC, ADR, LEN, BYT) 48 | for j in range(BYT): message = message+('Byte:'+str(j)+'='+str(buf[j])+', ') 49 | elif FC == 16: 50 | print "ID= %d, Fun.Code= %d, Address= %d, Length= %d, Bytes= %d" %(ID, FC, ADR, LEN, BYT) 51 | for j in range(BYT/2): message = message+('Reg:'+str(j)+'='+str((buf[j*2]<<8)+(buf[j*2+1]))+', ') 52 | elif FC == 6: 53 | print "ID= %d, Fun.Code= %d, Address= %d, Bytes= %d" %(ID, FC, ADR, LEN) 54 | message = message+('Reg:'+str(LEN)) 55 | print message 56 | F.write(ctime() + message + "\n") 57 | else: 58 | print "Funtion Code %d Not Supported" %FC 59 | exit() 60 | sleep(1) 61 | except Exception, e: 62 | print e, "\nConnection with Client terminated" 63 | exit() 64 | while 1: 65 | conn, addr = s.accept() 66 | print "Connected by", addr[0] 67 | thread.start_new_thread(TCP,(conn,addr,F)) 68 | -------------------------------------------------------------------------------- /misc/modbus_write_client.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # version 2.7 3 | import socket 4 | from sys import argv,exit 5 | from array import array 6 | from time import sleep 7 | if len(argv) < 6 or argv[1] == '-h' or argv[1] == '--help' or argv[1] == '?': 8 | print "usage: python modbus_write_client.py Host Unit FC Address Data[1...n]" 9 | exit() 10 | PORT = 502 # The same port as used by the server 11 | HOST = argv[1] # The remote host 12 | UNIT = int(argv[2]) 13 | FC = int(argv[3]) 14 | ADD = int(argv[4]) 15 | val = [] 16 | for v in range(5,len(argv)): 17 | val.append(int(argv[v])) 18 | VAL = [] 19 | for i in val: 20 | VAL.append((int(i) & 0xFF00)>>8) 21 | VAL.append(int(i) & 0x00FF) 22 | if (FC == 5 or FC == 15): LEN = len(VAL)*8 23 | else: LEN = len(VAL)/2 24 | lADD = ADD & 0x00FF 25 | mADD = ADD >> 8 26 | lLEN = LEN & 0x00FF 27 | mLEN = LEN >> 8 28 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 29 | s.connect((HOST, PORT)) 30 | if (FC == 6 or FC == 5): 31 | cmd = array('B', [00,00,00,00,00,6,UNIT,FC,mADD,lADD]) 32 | else: 33 | cmd = array('B', [00,00,00,00,00,7+len(VAL),UNIT,FC,mADD,lADD,mLEN,lLEN,len(VAL)]) 34 | for i in VAL: 35 | cmd.append(i) 36 | buffer = array('B', [0]*20) 37 | try: 38 | while 1: 39 | print "Send", cmd 40 | s.send(cmd) 41 | s.recv_into(buffer) 42 | print 'Received', buffer[:12] 43 | sleep(1) 44 | except Exception: exit() 45 | 46 | -------------------------------------------------------------------------------- /modbus_fuzzer.log: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/y1t2r4/boofuzz-modbus/bfeb48345b56797b48079e0620e7b06b27085789/modbus_fuzzer.log --------------------------------------------------------------------------------