├── .gitignore ├── CHANGELOG ├── CONTRIBUTORS.rst ├── COPYING ├── README.rst ├── cleanup.sh ├── docs ├── Makefile ├── make.bat ├── rtd_requirements.txt └── source │ ├── base_introduction.rst │ ├── conf.py │ ├── index.rst │ ├── katnip.controllers.client.facedancer.rst │ ├── katnip.controllers.client.process.rst │ ├── katnip.controllers.client.rst │ ├── katnip.controllers.client.ssh.rst │ ├── katnip.controllers.rst │ ├── katnip.controllers.server.frida.rst │ ├── katnip.controllers.server.local_process.rst │ ├── katnip.controllers.server.rst │ ├── katnip.controllers.server.tcp_system.rst │ ├── katnip.controllers.server.vmware.rst │ ├── katnip.controllers.server.windbgcontroller.rst │ ├── katnip.legos.bittorrent.rst │ ├── katnip.legos.dynamic.rst │ ├── katnip.legos.json.rst │ ├── katnip.legos.rst │ ├── katnip.legos.tlv.rst │ ├── katnip.legos.url.rst │ ├── katnip.legos.usb_hid.rst │ ├── katnip.legos.xml.rst │ ├── katnip.model.low_level.encoder.rst │ ├── katnip.model.low_level.fs_iterators.rst │ ├── katnip.model.low_level.radamsa.rst │ ├── katnip.model.low_level.rst │ ├── katnip.model.low_level.scapy.rst │ ├── katnip.model.rst │ ├── katnip.monitors.network.rst │ ├── katnip.monitors.rst │ ├── katnip.monitors.serial.rst │ ├── katnip.monitors.ssh.rst │ ├── katnip.monitors.ssh_file.rst │ ├── katnip.monitors.telnet.rst │ ├── katnip.rst │ ├── katnip.targets.application.rst │ ├── katnip.targets.file.rst │ ├── katnip.targets.pykd_dbg.rst │ ├── katnip.targets.raw_udp.rst │ ├── katnip.targets.rst │ ├── katnip.targets.serial.rst │ ├── katnip.targets.ssl.rst │ ├── katnip.targets.tcp.rst │ ├── katnip.targets.udp.rst │ ├── katnip.templates.bittorent.rst │ ├── katnip.templates.bootp.rst │ ├── katnip.templates.ftp.rst │ ├── katnip.templates.png.rst │ ├── katnip.templates.rst │ ├── katnip.templates.usb.rst │ ├── katnip.utils.rst │ ├── katnip.utils.sshutils.rst │ └── modules.rst ├── katnip ├── __init__.py ├── controllers │ ├── __init__.py │ ├── client │ │ ├── __init__.py │ │ ├── facedancer.py │ │ ├── process.py │ │ └── ssh.py │ └── server │ │ ├── __init__.py │ │ ├── frida.py │ │ ├── local_process.py │ │ ├── tcp_system.py │ │ ├── vmware.py │ │ └── windbgcontroller.py ├── legos │ ├── __init__.py │ ├── bittorrent.py │ ├── dynamic.py │ ├── http.py │ ├── json.py │ ├── tlv.py │ ├── url.py │ ├── usb_hid.py │ └── xml.py ├── model │ ├── __init__.py │ └── low_level │ │ ├── __init__.py │ │ ├── encoder.py │ │ ├── fs_iterators.py │ │ ├── radamsa.py │ │ └── scapy.py ├── monitors │ ├── __init__.py │ ├── network.py │ ├── serial.py │ ├── ssh.py │ ├── ssh_file.py │ └── telnet.py ├── targets │ ├── __init__.py │ ├── application.py │ ├── file.py │ ├── pykd_dbg.py │ ├── raw_udp.py │ ├── serial.py │ ├── ssl.py │ ├── tcp.py │ └── udp.py ├── templates │ ├── __init__.py │ ├── apetagv2.py │ ├── bittorent.py │ ├── bootp.py │ ├── ftp.py │ ├── id3v23.py │ ├── m4a.py │ ├── mp3.py │ ├── png.py │ └── usb.py └── utils │ ├── __init__.py │ └── sshutils.py ├── setup.py ├── tools └── code_checker.sh └── unit_tests ├── common.py ├── lego_dynamic.py ├── lego_json.py ├── lego_url.py ├── model_low_level_encoders.py ├── runner.py ├── test_model_low_level_field.py ├── test_model_low_level_radamsa_field.py └── test_model_low_level_scapy_field.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | */kittylogs/ 3 | unit_tests/logs/ 4 | katnip.egg-info/ 5 | docs/build 6 | -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | Version 0.2.5 (2016-10-26) 2 | ========================== 3 | 4 | * new feature: [Controller] FridaLaunchServerController 5 | * new feature: [DataModel] RadamsaField - a wrapper for radamsa 6 | 7 | Version 0.2.4 (2016-06-07) 8 | ========================== 9 | 10 | * bugfix: [ClientProcessController] marked tests as failed only if the return code was -1 11 | * bugfix: [SSHFileMonitor] move to SshUtils, fixed multiple small issues 12 | * enhancement: [Tempaltes] improvements to USB templates 13 | * new feature: [Controller] VMWareController 14 | * new feature: [DataModel] ScapyField - support for using Scapy packets as data model 15 | 16 | Version 0.2.3 (2016-04-10) 17 | ========================== 18 | 19 | * bugfix: [SerialMonitor] imports (using absolute_import now) 20 | * bugfix: [SerialMonitor] thread safety of file operations 21 | * enhancement: [SerialMonitor] can now have multiple patterns with arbitrary actions 22 | * new feature: [SshUtils] SSH utility classes to be used by all SSH related monitors/controllers 23 | 24 | Version 0.2.2 (2016-03-01) 25 | ========================== 26 | 27 | * new feature: [Data Model] Added crypto encoders (removed from kitty 0.6.2) 28 | 29 | -------------------------------------------------------------------------------- /CONTRIBUTORS.rst: -------------------------------------------------------------------------------- 1 | Contributors 2 | ============ 3 | 4 | This file contains a list of the contributors to Katnip (github handles). 5 | The handles are sorted alphbeticaly. 6 | 7 | Core Members 8 | ------------ 9 | 10 | * 0xd3d0 11 | * BinyaminSharet 12 | * dovf 13 | * r-e-l-z 14 | * s1341 15 | 16 | 17 | Other Contributors 18 | ------------------ 19 | 20 | * dark-lbp 21 | - ``ScapyField`` - scapy's ``fuzz()`` based mutations 22 | - ``VMWareController`` - control targets that run in VMWare (for OSX at the moment) 23 | * yformaggio 24 | - ``VMWareController`` - control targets that run in VMWare (for OSX at the moment) 25 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Katnip 2 | ====== 3 | 4 | What is Katnip? 5 | --------------- 6 | 7 | Katnip is a repository of implementations and extensions for Kitty_. 8 | 9 | While Kitty defines the base classes and syntax, 10 | it contains no specific implementation for any of them. 11 | So, for example, in order to send a payload over TCP, 12 | you need to create some class that extends ``ServerTarget`` 13 | and is able to send data over TCP, 14 | and so on. 15 | 16 | Katnip contains such classes. 17 | Currently, Katnip contains various implementations of: 18 | 19 | - Controllers for servers and clients 20 | - Monitors 21 | - Targets 22 | - Legos 23 | - Templates 24 | 25 | Want to know more? 26 | ------------------ 27 | 28 | Read the documentations at `Read The Docs `_. 29 | 30 | How to install 31 | -------------- 32 | 33 | :: 34 | 35 | git clone https://github.com/cisco-sas/katnip.git katnip 36 | cd katnip 37 | pip install -e . 38 | 39 | 40 | Contribution FAQ 41 | ---------------- 42 | 43 | *Found a bug?* 44 | Open an issue. 45 | 46 | *Have a fix?* 47 | Great! please submit a pull request. 48 | 49 | *Want to share you implementation?* 50 | Thank You! Please submit a pull request. 51 | 52 | |docs| 53 | 54 | 55 | .. |docs| image:: https://readthedocs.org/projects/katnip/badge/?version=latest 56 | :alt: Documentation Status 57 | :scale: 100% 58 | :target: https://katnip.readthedocs.org/en/latest/?badge=latest 59 | 60 | .. _Kitty: https://github.com/cisco-sas/kitty 61 | -------------------------------------------------------------------------------- /cleanup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # This script is used for development only! 4 | # it will cleanup the katnip repository and nothing else 5 | # 6 | ROOTDIR=`dirname $0` 7 | echo 'Deleting pyc files' 8 | echo find $ROOTDIR -iname '*.pyc' -delete 9 | find $ROOTDIR -iname '*.pyc' -delete 10 | echo 'Deleting kitty logs' 11 | echo find $ROOTDIR -path "*/kittylogs/*" -delete 12 | find $ROOTDIR -path "*/kittylogs/*" -delete 13 | echo find $ROOTDIR -type d -name kittylogs -delete 14 | find $ROOTDIR -type d -name kittylogs -delete 15 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " applehelp to make an Apple Help Book" 34 | @echo " devhelp to make HTML files and a Devhelp project" 35 | @echo " epub to make an epub" 36 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 37 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 38 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 39 | @echo " text to make text files" 40 | @echo " man to make manual pages" 41 | @echo " texinfo to make Texinfo files" 42 | @echo " info to make Texinfo files and run them through makeinfo" 43 | @echo " gettext to make PO message catalogs" 44 | @echo " changes to make an overview of all changed/added/deprecated items" 45 | @echo " xml to make Docutils-native XML files" 46 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 47 | @echo " linkcheck to check all external links for integrity" 48 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 49 | @echo " coverage to run coverage check of the documentation (if enabled)" 50 | 51 | clean: 52 | rm -rf $(BUILDDIR)/* 53 | 54 | html: 55 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 56 | @echo 57 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 58 | 59 | dirhtml: 60 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 61 | @echo 62 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 63 | 64 | singlehtml: 65 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 66 | @echo 67 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 68 | 69 | pickle: 70 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 71 | @echo 72 | @echo "Build finished; now you can process the pickle files." 73 | 74 | json: 75 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 76 | @echo 77 | @echo "Build finished; now you can process the JSON files." 78 | 79 | htmlhelp: 80 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 81 | @echo 82 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 83 | ".hhp project file in $(BUILDDIR)/htmlhelp." 84 | 85 | qthelp: 86 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 87 | @echo 88 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 89 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 90 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Katnip.qhcp" 91 | @echo "To view the help file:" 92 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Katnip.qhc" 93 | 94 | applehelp: 95 | $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp 96 | @echo 97 | @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." 98 | @echo "N.B. You won't be able to view it unless you put it in" \ 99 | "~/Library/Documentation/Help or install it in your application" \ 100 | "bundle." 101 | 102 | devhelp: 103 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 104 | @echo 105 | @echo "Build finished." 106 | @echo "To view the help file:" 107 | @echo "# mkdir -p $$HOME/.local/share/devhelp/Katnip" 108 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Katnip" 109 | @echo "# devhelp" 110 | 111 | epub: 112 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 113 | @echo 114 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 115 | 116 | latex: 117 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 118 | @echo 119 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 120 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 121 | "(use \`make latexpdf' here to do that automatically)." 122 | 123 | latexpdf: 124 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 125 | @echo "Running LaTeX files through pdflatex..." 126 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 127 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 128 | 129 | latexpdfja: 130 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 131 | @echo "Running LaTeX files through platex and dvipdfmx..." 132 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 133 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 134 | 135 | text: 136 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 137 | @echo 138 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 139 | 140 | man: 141 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 142 | @echo 143 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 144 | 145 | texinfo: 146 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 147 | @echo 148 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 149 | @echo "Run \`make' in that directory to run these through makeinfo" \ 150 | "(use \`make info' here to do that automatically)." 151 | 152 | info: 153 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 154 | @echo "Running Texinfo files through makeinfo..." 155 | make -C $(BUILDDIR)/texinfo info 156 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 157 | 158 | gettext: 159 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 160 | @echo 161 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 162 | 163 | changes: 164 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 165 | @echo 166 | @echo "The overview file is in $(BUILDDIR)/changes." 167 | 168 | linkcheck: 169 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 170 | @echo 171 | @echo "Link check complete; look for any errors in the above output " \ 172 | "or in $(BUILDDIR)/linkcheck/output.txt." 173 | 174 | doctest: 175 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 176 | @echo "Testing of doctests in the sources finished, look at the " \ 177 | "results in $(BUILDDIR)/doctest/output.txt." 178 | 179 | coverage: 180 | $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage 181 | @echo "Testing of coverage in the sources finished, look at the " \ 182 | "results in $(BUILDDIR)/coverage/python.txt." 183 | 184 | xml: 185 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 186 | @echo 187 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 188 | 189 | pseudoxml: 190 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 191 | @echo 192 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 193 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% source 10 | set I18NSPHINXOPTS=%SPHINXOPTS% source 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 31 | echo. text to make text files 32 | echo. man to make manual pages 33 | echo. texinfo to make Texinfo files 34 | echo. gettext to make PO message catalogs 35 | echo. changes to make an overview over all changed/added/deprecated items 36 | echo. xml to make Docutils-native XML files 37 | echo. pseudoxml to make pseudoxml-XML files for display purposes 38 | echo. linkcheck to check all external links for integrity 39 | echo. doctest to run all doctests embedded in the documentation if enabled 40 | echo. coverage to run coverage check of the documentation if enabled 41 | goto end 42 | ) 43 | 44 | if "%1" == "clean" ( 45 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 46 | del /q /s %BUILDDIR%\* 47 | goto end 48 | ) 49 | 50 | 51 | REM Check if sphinx-build is available and fallback to Python version if any 52 | %SPHINXBUILD% 2> nul 53 | if errorlevel 9009 goto sphinx_python 54 | goto sphinx_ok 55 | 56 | :sphinx_python 57 | 58 | set SPHINXBUILD=python -m sphinx.__init__ 59 | %SPHINXBUILD% 2> nul 60 | if errorlevel 9009 ( 61 | echo. 62 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 63 | echo.installed, then set the SPHINXBUILD environment variable to point 64 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 65 | echo.may add the Sphinx directory to PATH. 66 | echo. 67 | echo.If you don't have Sphinx installed, grab it from 68 | echo.http://sphinx-doc.org/ 69 | exit /b 1 70 | ) 71 | 72 | :sphinx_ok 73 | 74 | 75 | if "%1" == "html" ( 76 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 77 | if errorlevel 1 exit /b 1 78 | echo. 79 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 80 | goto end 81 | ) 82 | 83 | if "%1" == "dirhtml" ( 84 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 85 | if errorlevel 1 exit /b 1 86 | echo. 87 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 88 | goto end 89 | ) 90 | 91 | if "%1" == "singlehtml" ( 92 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 93 | if errorlevel 1 exit /b 1 94 | echo. 95 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 96 | goto end 97 | ) 98 | 99 | if "%1" == "pickle" ( 100 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 101 | if errorlevel 1 exit /b 1 102 | echo. 103 | echo.Build finished; now you can process the pickle files. 104 | goto end 105 | ) 106 | 107 | if "%1" == "json" ( 108 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 109 | if errorlevel 1 exit /b 1 110 | echo. 111 | echo.Build finished; now you can process the JSON files. 112 | goto end 113 | ) 114 | 115 | if "%1" == "htmlhelp" ( 116 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 117 | if errorlevel 1 exit /b 1 118 | echo. 119 | echo.Build finished; now you can run HTML Help Workshop with the ^ 120 | .hhp project file in %BUILDDIR%/htmlhelp. 121 | goto end 122 | ) 123 | 124 | if "%1" == "qthelp" ( 125 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 129 | .qhcp project file in %BUILDDIR%/qthelp, like this: 130 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Katnip.qhcp 131 | echo.To view the help file: 132 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Katnip.ghc 133 | goto end 134 | ) 135 | 136 | if "%1" == "devhelp" ( 137 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 138 | if errorlevel 1 exit /b 1 139 | echo. 140 | echo.Build finished. 141 | goto end 142 | ) 143 | 144 | if "%1" == "epub" ( 145 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 146 | if errorlevel 1 exit /b 1 147 | echo. 148 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 149 | goto end 150 | ) 151 | 152 | if "%1" == "latex" ( 153 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 154 | if errorlevel 1 exit /b 1 155 | echo. 156 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 157 | goto end 158 | ) 159 | 160 | if "%1" == "latexpdf" ( 161 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 162 | cd %BUILDDIR%/latex 163 | make all-pdf 164 | cd %~dp0 165 | echo. 166 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 167 | goto end 168 | ) 169 | 170 | if "%1" == "latexpdfja" ( 171 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 172 | cd %BUILDDIR%/latex 173 | make all-pdf-ja 174 | cd %~dp0 175 | echo. 176 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 177 | goto end 178 | ) 179 | 180 | if "%1" == "text" ( 181 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 182 | if errorlevel 1 exit /b 1 183 | echo. 184 | echo.Build finished. The text files are in %BUILDDIR%/text. 185 | goto end 186 | ) 187 | 188 | if "%1" == "man" ( 189 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 190 | if errorlevel 1 exit /b 1 191 | echo. 192 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 193 | goto end 194 | ) 195 | 196 | if "%1" == "texinfo" ( 197 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 198 | if errorlevel 1 exit /b 1 199 | echo. 200 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 201 | goto end 202 | ) 203 | 204 | if "%1" == "gettext" ( 205 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 206 | if errorlevel 1 exit /b 1 207 | echo. 208 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 209 | goto end 210 | ) 211 | 212 | if "%1" == "changes" ( 213 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 214 | if errorlevel 1 exit /b 1 215 | echo. 216 | echo.The overview file is in %BUILDDIR%/changes. 217 | goto end 218 | ) 219 | 220 | if "%1" == "linkcheck" ( 221 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 222 | if errorlevel 1 exit /b 1 223 | echo. 224 | echo.Link check complete; look for any errors in the above output ^ 225 | or in %BUILDDIR%/linkcheck/output.txt. 226 | goto end 227 | ) 228 | 229 | if "%1" == "doctest" ( 230 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 231 | if errorlevel 1 exit /b 1 232 | echo. 233 | echo.Testing of doctests in the sources finished, look at the ^ 234 | results in %BUILDDIR%/doctest/output.txt. 235 | goto end 236 | ) 237 | 238 | if "%1" == "coverage" ( 239 | %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage 240 | if errorlevel 1 exit /b 1 241 | echo. 242 | echo.Testing of coverage in the sources finished, look at the ^ 243 | results in %BUILDDIR%/coverage/python.txt. 244 | goto end 245 | ) 246 | 247 | if "%1" == "xml" ( 248 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml 249 | if errorlevel 1 exit /b 1 250 | echo. 251 | echo.Build finished. The XML files are in %BUILDDIR%/xml. 252 | goto end 253 | ) 254 | 255 | if "%1" == "pseudoxml" ( 256 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml 257 | if errorlevel 1 exit /b 1 258 | echo. 259 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. 260 | goto end 261 | ) 262 | 263 | :end 264 | -------------------------------------------------------------------------------- /docs/rtd_requirements.txt: -------------------------------------------------------------------------------- 1 | mock 2 | paramiko 3 | scapy 4 | pycrypto 5 | pyserial 6 | frida 7 | -------------------------------------------------------------------------------- /docs/source/base_introduction.rst: -------------------------------------------------------------------------------- 1 | Introduction 2 | ============ 3 | 4 | What is Katnip? 5 | --------------- 6 | 7 | Katnip is a repository of implementations and extensions for Kitty_. 8 | 9 | While Kitty defines the base classes and syntax, 10 | it contains no specific implementation for any of them. 11 | So, for example, in order to send a payload over TCP, 12 | you need to create some class that extends ServerTarget 13 | and is able to send data over TCP, 14 | and so on. 15 | 16 | Katnip contains such classes. 17 | Currently, Katnip contains various implementations of: 18 | 19 | - :doc:`Controllers ` for :doc:`servers ` and :doc:`clients ` 20 | - :doc:`Monitors ` 21 | - :doc:`Targets ` 22 | - :doc:`Legos ` 23 | - :doc:`Templates ` 24 | 25 | Contribution FAQ 26 | ---------------- 27 | 28 | *Found a bug?* 29 | Open an issue. 30 | 31 | *Have a fix?* 32 | Great! please submit a pull request. 33 | 34 | *Want to share you implementation?* 35 | Thank You! Please submit a pull request. 36 | 37 | .. _Kitty: https://github.com/cisco-sas/kitty -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | .. Katnip documentation master file, created by 2 | sphinx-quickstart on Mon Jan 25 13:27:12 2016. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to Katnip's documentation! 7 | ================================== 8 | 9 | Contents: 10 | 11 | .. toctree:: 12 | :maxdepth: 1 13 | 14 | base_introduction 15 | modules 16 | 17 | 18 | Indices and tables 19 | ================== 20 | 21 | * :ref:`genindex` 22 | * :ref:`modindex` 23 | * :ref:`search` 24 | 25 | -------------------------------------------------------------------------------- /docs/source/katnip.controllers.client.facedancer.rst: -------------------------------------------------------------------------------- 1 | katnip.controllers.client.facedancer module 2 | =========================================== 3 | 4 | .. automodule:: katnip.controllers.client.facedancer 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/source/katnip.controllers.client.process.rst: -------------------------------------------------------------------------------- 1 | katnip.controllers.client.process module 2 | ======================================== 3 | 4 | .. automodule:: katnip.controllers.client.process 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/source/katnip.controllers.client.rst: -------------------------------------------------------------------------------- 1 | katnip.controllers.client package 2 | ================================= 3 | 4 | .. automodule:: katnip.controllers.client 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | 9 | Submodules 10 | ---------- 11 | 12 | .. toctree:: 13 | 14 | katnip.controllers.client.facedancer 15 | katnip.controllers.client.process 16 | katnip.controllers.client.ssh 17 | 18 | -------------------------------------------------------------------------------- /docs/source/katnip.controllers.client.ssh.rst: -------------------------------------------------------------------------------- 1 | katnip.controllers.client.ssh module 2 | ==================================== 3 | 4 | .. automodule:: katnip.controllers.client.ssh 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/source/katnip.controllers.rst: -------------------------------------------------------------------------------- 1 | katnip.controllers package 2 | ========================== 3 | 4 | .. automodule:: katnip.controllers 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | 9 | Subpackages 10 | ----------- 11 | 12 | .. toctree:: 13 | 14 | katnip.controllers.client 15 | katnip.controllers.server 16 | 17 | -------------------------------------------------------------------------------- /docs/source/katnip.controllers.server.frida.rst: -------------------------------------------------------------------------------- 1 | katnip.controllers.server.frida module 2 | ====================================== 3 | 4 | .. automodule:: katnip.controllers.server.frida 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/source/katnip.controllers.server.local_process.rst: -------------------------------------------------------------------------------- 1 | katnip.controllers.server.local_process module 2 | ============================================== 3 | 4 | .. automodule:: katnip.controllers.server.local_process 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/source/katnip.controllers.server.rst: -------------------------------------------------------------------------------- 1 | katnip.controllers.server package 2 | ================================= 3 | 4 | .. automodule:: katnip.controllers.server 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | 9 | Submodules 10 | ---------- 11 | 12 | .. toctree:: 13 | 14 | katnip.controllers.server.frida 15 | katnip.controllers.server.local_process 16 | katnip.controllers.server.tcp_system 17 | katnip.controllers.server.windbgcontroller 18 | katnip.controllers.server.vmware 19 | 20 | -------------------------------------------------------------------------------- /docs/source/katnip.controllers.server.tcp_system.rst: -------------------------------------------------------------------------------- 1 | katnip.controllers.server.tcp_system module 2 | =========================================== 3 | 4 | .. automodule:: katnip.controllers.server.tcp_system 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/source/katnip.controllers.server.vmware.rst: -------------------------------------------------------------------------------- 1 | katnip.controllers.server.vmware module 2 | ======================================= 3 | 4 | .. automodule:: katnip.controllers.server.vmware 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/source/katnip.controllers.server.windbgcontroller.rst: -------------------------------------------------------------------------------- 1 | katnip.controllers.server.windbgcontroller module 2 | ================================================= 3 | 4 | .. automodule:: katnip.controllers.server.windbgcontroller 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/source/katnip.legos.bittorrent.rst: -------------------------------------------------------------------------------- 1 | katnip.legos.bittorrent module 2 | ============================== 3 | 4 | .. automodule:: katnip.legos.bittorrent 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/source/katnip.legos.dynamic.rst: -------------------------------------------------------------------------------- 1 | katnip.legos.dynamic module 2 | =========================== 3 | 4 | .. automodule:: katnip.legos.dynamic 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/source/katnip.legos.json.rst: -------------------------------------------------------------------------------- 1 | katnip.legos.json module 2 | ======================== 3 | 4 | .. automodule:: katnip.legos.json 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/source/katnip.legos.rst: -------------------------------------------------------------------------------- 1 | katnip.legos package 2 | ==================== 3 | 4 | .. automodule:: katnip.legos 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | 9 | Submodules 10 | ---------- 11 | 12 | .. toctree:: 13 | 14 | katnip.legos.bittorrent 15 | katnip.legos.dynamic 16 | katnip.legos.json 17 | katnip.legos.tlv 18 | katnip.legos.url 19 | katnip.legos.usb_hid 20 | katnip.legos.xml 21 | 22 | -------------------------------------------------------------------------------- /docs/source/katnip.legos.tlv.rst: -------------------------------------------------------------------------------- 1 | katnip.legos.tlv module 2 | ======================= 3 | 4 | .. automodule:: katnip.legos.tlv 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/source/katnip.legos.url.rst: -------------------------------------------------------------------------------- 1 | katnip.legos.url module 2 | ======================= 3 | 4 | .. automodule:: katnip.legos.url 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/source/katnip.legos.usb_hid.rst: -------------------------------------------------------------------------------- 1 | katnip.legos.usb_hid module 2 | =========================== 3 | 4 | .. automodule:: katnip.legos.usb_hid 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/source/katnip.legos.xml.rst: -------------------------------------------------------------------------------- 1 | katnip.legos.xml module 2 | ======================= 3 | 4 | .. automodule:: katnip.legos.xml 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/source/katnip.model.low_level.encoder.rst: -------------------------------------------------------------------------------- 1 | katnip.model.low_level.encoder module 2 | ===================================== 3 | 4 | .. automodule:: katnip.model.low_level.encoder 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/source/katnip.model.low_level.fs_iterators.rst: -------------------------------------------------------------------------------- 1 | katnip.model.low_level.fs_iterators module 2 | ========================================== 3 | 4 | .. automodule:: katnip.model.low_level.fs_iterators 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/source/katnip.model.low_level.radamsa.rst: -------------------------------------------------------------------------------- 1 | katnip.model.low_level.radamsa module 2 | ===================================== 3 | 4 | .. automodule:: katnip.model.low_level.radamsa 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/source/katnip.model.low_level.rst: -------------------------------------------------------------------------------- 1 | katnip.model.low_level package 2 | ============================== 3 | 4 | .. automodule:: katnip.model.low_level 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | 9 | Subpackages 10 | ----------- 11 | 12 | .. toctree:: 13 | 14 | katnip.model.low_level.encoder 15 | katnip.model.low_level.fs_iterators 16 | katnip.model.low_level.radamsa 17 | katnip.model.low_level.scapy 18 | 19 | -------------------------------------------------------------------------------- /docs/source/katnip.model.low_level.scapy.rst: -------------------------------------------------------------------------------- 1 | katnip.model.low_level.scapy module 2 | =================================== 3 | 4 | .. automodule:: katnip.model.low_level.scapy 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/source/katnip.model.rst: -------------------------------------------------------------------------------- 1 | katnip.model package 2 | ==================== 3 | 4 | .. automodule:: katnip.model 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | 9 | Subpackages 10 | ----------- 11 | 12 | .. toctree:: 13 | 14 | katnip.model.low_level 15 | 16 | -------------------------------------------------------------------------------- /docs/source/katnip.monitors.network.rst: -------------------------------------------------------------------------------- 1 | katnip.monitors.network module 2 | ============================== 3 | 4 | .. automodule:: katnip.monitors.network 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/source/katnip.monitors.rst: -------------------------------------------------------------------------------- 1 | katnip.monitors package 2 | ======================= 3 | 4 | .. automodule:: katnip.monitors 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | 9 | Submodules 10 | ---------- 11 | 12 | .. toctree:: 13 | 14 | katnip.monitors.network 15 | katnip.monitors.serial 16 | katnip.monitors.ssh 17 | katnip.monitors.ssh_file 18 | katnip.monitors.telnet 19 | 20 | -------------------------------------------------------------------------------- /docs/source/katnip.monitors.serial.rst: -------------------------------------------------------------------------------- 1 | katnip.monitors.serial module 2 | ============================= 3 | 4 | .. automodule:: katnip.monitors.serial 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/source/katnip.monitors.ssh.rst: -------------------------------------------------------------------------------- 1 | katnip.monitors.ssh module 2 | ========================== 3 | 4 | .. automodule:: katnip.monitors.ssh 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/source/katnip.monitors.ssh_file.rst: -------------------------------------------------------------------------------- 1 | katnip.monitors.ssh_file module 2 | =============================== 3 | 4 | .. automodule:: katnip.monitors.ssh_file 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/source/katnip.monitors.telnet.rst: -------------------------------------------------------------------------------- 1 | katnip.monitors.telnet module 2 | ============================= 3 | 4 | .. automodule:: katnip.monitors.telnet 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/source/katnip.rst: -------------------------------------------------------------------------------- 1 | Katnip API Reference 2 | ==================== 3 | 4 | .. automodule:: katnip 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | 9 | Subpackages 10 | ----------- 11 | 12 | .. toctree:: 13 | 14 | katnip.controllers 15 | katnip.legos 16 | katnip.model 17 | katnip.monitors 18 | katnip.targets 19 | katnip.templates 20 | katnip.utils 21 | 22 | -------------------------------------------------------------------------------- /docs/source/katnip.targets.application.rst: -------------------------------------------------------------------------------- 1 | katnip.targets.application module 2 | ================================= 3 | 4 | .. automodule:: katnip.targets.application 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/source/katnip.targets.file.rst: -------------------------------------------------------------------------------- 1 | katnip.targets.file module 2 | ========================== 3 | 4 | .. automodule:: katnip.targets.file 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/source/katnip.targets.pykd_dbg.rst: -------------------------------------------------------------------------------- 1 | katnip.targets.pykd_dbg module 2 | ============================== 3 | 4 | .. automodule:: katnip.targets.pykd_dbg 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/source/katnip.targets.raw_udp.rst: -------------------------------------------------------------------------------- 1 | katnip.targets.raw_udp module 2 | ============================= 3 | 4 | .. automodule:: katnip.targets.raw_udp 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/source/katnip.targets.rst: -------------------------------------------------------------------------------- 1 | katnip.targets package 2 | ====================== 3 | 4 | .. automodule:: katnip.targets 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | 9 | Submodules 10 | ---------- 11 | 12 | .. toctree:: 13 | 14 | katnip.targets.application 15 | katnip.targets.file 16 | katnip.targets.raw_udp 17 | katnip.targets.serial 18 | katnip.targets.ssl 19 | katnip.targets.tcp 20 | katnip.targets.udp 21 | katnip.targets.pykd_dbg 22 | 23 | -------------------------------------------------------------------------------- /docs/source/katnip.targets.serial.rst: -------------------------------------------------------------------------------- 1 | katnip.targets.serial module 2 | ============================ 3 | 4 | .. automodule:: katnip.targets.serial 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/source/katnip.targets.ssl.rst: -------------------------------------------------------------------------------- 1 | katnip.targets.ssl module 2 | ========================= 3 | 4 | .. automodule:: katnip.targets.ssl 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/source/katnip.targets.tcp.rst: -------------------------------------------------------------------------------- 1 | katnip.targets.tcp module 2 | ========================= 3 | 4 | .. automodule:: katnip.targets.tcp 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/source/katnip.targets.udp.rst: -------------------------------------------------------------------------------- 1 | katnip.targets.udp module 2 | ========================= 3 | 4 | .. automodule:: katnip.targets.udp 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/source/katnip.templates.bittorent.rst: -------------------------------------------------------------------------------- 1 | katnip.templates.bittorent module 2 | ================================= 3 | 4 | .. automodule:: katnip.templates.bittorent 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/source/katnip.templates.bootp.rst: -------------------------------------------------------------------------------- 1 | katnip.templates.bootp module 2 | ============================= 3 | 4 | .. automodule:: katnip.templates.bootp 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/source/katnip.templates.ftp.rst: -------------------------------------------------------------------------------- 1 | katnip.templates.ftp module 2 | =========================== 3 | 4 | .. automodule:: katnip.templates.ftp 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/source/katnip.templates.png.rst: -------------------------------------------------------------------------------- 1 | katnip.templates.png module 2 | =========================== 3 | 4 | .. automodule:: katnip.templates.png 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/source/katnip.templates.rst: -------------------------------------------------------------------------------- 1 | katnip.templates package 2 | ======================== 3 | 4 | .. automodule:: katnip.templates 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | 9 | Submodules 10 | ---------- 11 | 12 | .. toctree:: 13 | 14 | katnip.templates.bittorent 15 | katnip.templates.bootp 16 | katnip.templates.ftp 17 | katnip.templates.png 18 | katnip.templates.usb 19 | 20 | -------------------------------------------------------------------------------- /docs/source/katnip.templates.usb.rst: -------------------------------------------------------------------------------- 1 | katnip.templates.usb module 2 | =========================== 3 | 4 | .. automodule:: katnip.templates.usb 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/source/katnip.utils.rst: -------------------------------------------------------------------------------- 1 | katnip.utils package 2 | ==================== 3 | 4 | .. automodule:: katnip.utils 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | 9 | Submodules 10 | ---------- 11 | 12 | .. toctree:: 13 | 14 | katnip.utils.sshutils 15 | 16 | -------------------------------------------------------------------------------- /docs/source/katnip.utils.sshutils.rst: -------------------------------------------------------------------------------- 1 | katnip.utils.sshutils module 2 | ============================ 3 | 4 | .. automodule:: katnip.utils.sshutils 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/source/modules.rst: -------------------------------------------------------------------------------- 1 | API Reference 2 | ============= 3 | 4 | .. toctree:: 5 | :maxdepth: 6 6 | 7 | katnip 8 | -------------------------------------------------------------------------------- /katnip/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2016 Cisco Systems, Inc. and/or its affiliates. All rights reserved. 2 | # 3 | # This file is part of Katnip. 4 | # 5 | # Katnip is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 2 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Katnip is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Katnip. If not, see . 17 | -------------------------------------------------------------------------------- /katnip/controllers/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2016 Cisco Systems, Inc. and/or its affiliates. All rights reserved. 2 | # 3 | # This file is part of Kitty. 4 | # 5 | # Kitty is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 2 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Kitty is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have Katnip a copy of the GNU General Public License 16 | # along with Kitty. If not, see . 17 | 18 | -------------------------------------------------------------------------------- /katnip/controllers/client/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2016 Cisco Systems, Inc. and/or its affiliates. All rights reserved. 2 | # 3 | # This file is part of Katnip. 4 | # 5 | # Katnip is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 2 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Katnip is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Katnip. If not, see . 17 | 18 | ''' 19 | Collection of client controllers. 20 | 21 | When performing client fuzzing, 22 | the main usage of the controller is to trigger the client 23 | to start communication with the server stack. 24 | ''' 25 | -------------------------------------------------------------------------------- /katnip/controllers/client/facedancer.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2016 Cisco Systems, Inc. and/or its affiliates. All rights reserved. 2 | # 3 | # This file is part of Katnip. 4 | # 5 | # Katnip is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 2 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Katnip is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Katnip. If not, see . 17 | 18 | ''' 19 | This controller is used to signal the SAS version of the Facedancer stack 20 | to perform a USB reset. 21 | ''' 22 | from kitty.controllers.client import ClientController 23 | 24 | 25 | class ClientFacedancerController(ClientController): 26 | ''' 27 | ClientFacedancerController is a controller that uses files in /tmp 28 | to communicate with the facedancer stack. 29 | .. note:: This requires a modified version of the facedancer stack. 30 | ''' 31 | 32 | RESTART_FILE = '/tmp/restart_facedancer' 33 | 34 | def __init__(self, name, restart_file=RESTART_FILE, logger=None): 35 | ''' 36 | :param name: name of the object 37 | :param controller_port: the device controller port (i.e. '/dev/ttyACM0') 38 | :param connect_delay: 39 | :param logger: logger for the object (default: None) 40 | ''' 41 | super(ClientFacedancerController, self).__init__(name, logger) 42 | self._restart_file = restart_file 43 | 44 | def trigger(self): 45 | ''' 46 | Trigger a data exchange from the tested client 47 | ''' 48 | f = open(self._restart_file, 'w') 49 | f.close() 50 | -------------------------------------------------------------------------------- /katnip/controllers/client/process.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2016 Cisco Systems, Inc. and/or its affiliates. All rights reserved. 2 | # 3 | # This file is part of Katnip. 4 | # 5 | # Katnip is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 2 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Katnip is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Katnip. If not, see . 17 | 18 | import time 19 | import os 20 | import signal 21 | from subprocess import Popen, PIPE 22 | from kitty.controllers.client import ClientController 23 | 24 | 25 | class ClientProcessController(ClientController): 26 | ''' 27 | ClientProcessController controls a process 28 | by starting it on each trigger. 29 | It uses subprocess.Popen and logs the process output (stdout, stderr) 30 | ''' 31 | sig_dict = { 32 | k: v for v, k in reversed(sorted(signal.__dict__.items())) if v.startswith('SIG') and not v.startswith('SIG_') 33 | } 34 | 35 | def __init__(self, name, process_path, process_args, process_env=None, logger=None): 36 | ''' 37 | :param name: name of the object 38 | :param process_path: path to the target executable 39 | :param process_args: arguments to pass to the process 40 | :param process_env: the process environment (default: None) 41 | :param logger: logger for this object (default: None) 42 | ''' 43 | super(ClientProcessController, self).__init__(name, logger) 44 | assert(process_path) 45 | assert(os.path.exists(process_path)) 46 | if process_env is None: 47 | process_env = os.environ.copy() 48 | self._process_path = process_path 49 | self._process_name = os.path.basename(process_path) 50 | self._process_args = process_args 51 | self._process = None 52 | self._process_env = process_env 53 | 54 | def teardown(self): 55 | ''' 56 | Stops the process and calls super's teardown. 57 | ''' 58 | self._stop_process() 59 | self._process = None 60 | super(ClientProcessController, self).teardown() 61 | 62 | def post_test(self): 63 | ''' 64 | Logs stdout, stderr amd return code of the target process. 65 | ''' 66 | killed = self._stop_process() 67 | assert(self._process) 68 | self.report.add('stdout', self._process.stdout.read()) 69 | self.report.add('stderr', self._process.stderr.read()) 70 | self.logger.debug('return code: %d', self._process.returncode) 71 | self.logger.debug('killed: %s', killed) 72 | self.report.add('return_code', self._process.returncode) 73 | signame = self.sig_dict.get(-self._process.returncode, None) 74 | if signame: 75 | self.report.add('signal_name', signame) 76 | self.report.add('killed', killed) 77 | if not killed: 78 | if self._process.returncode < 0: 79 | if signame: 80 | self.report.failed('got signal %s' % signame) 81 | else: 82 | self.report.failed('negative return code') 83 | self._process = None 84 | super(ClientProcessController, self).post_test() 85 | 86 | def trigger(self): 87 | ''' 88 | Starts the target in a subprocess 89 | ''' 90 | assert(self._process is None) 91 | cmd = [self._process_path] + self._process_args 92 | self._process = Popen(cmd, stdout=PIPE, stderr=PIPE, env=self._process_env) 93 | self.report.add('process_name', self._process_name) 94 | self.report.add('process_path', self._process_path) 95 | self.report.add('process_args', self._process_args) 96 | self.report.add('process_id', self._process.pid) 97 | 98 | def _stop_process(self): 99 | ''' 100 | Tries to stop the process 101 | :return: True if process was killed, False otherwise 102 | ''' 103 | if self._is_victim_alive(): 104 | self._process.terminate() 105 | time.sleep(0.5) 106 | if self._is_victim_alive(): 107 | self._process.kill() 108 | time.sleep(0.5) 109 | if self._is_victim_alive(): 110 | raise Exception('Failed to kill client process') 111 | return True 112 | else: 113 | return False 114 | 115 | def _is_victim_alive(self): 116 | return self._process and (self._process.poll() is None) 117 | -------------------------------------------------------------------------------- /katnip/controllers/client/ssh.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2016 Cisco Systems, Inc. and/or its affiliates. All rights reserved. 2 | # 3 | # This file is part of Katnip. 4 | # 5 | # Katnip is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 2 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Katnip is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Katnip. If not, see . 17 | 18 | import socket 19 | 20 | import paramiko 21 | from kitty.controllers.client import ClientController 22 | 23 | 24 | class ClientSshController(ClientController): 25 | ''' 26 | ClientSshController controlls a remote process by 27 | starting it on each trigger using ssh. 28 | ''' 29 | 30 | def __init__(self, name, username, password, hostname, port, command, process_name, logger=None): 31 | ''' 32 | :param name: name of the object 33 | :param username: ssh login username 34 | :param password: ssh login password 35 | :param hostname: ssh server ip 36 | :param port: ssh server port 37 | :param command: client trigger command 38 | :param process_name: command process name 39 | :param logger: logger for this object (default: None) 40 | ''' 41 | super(ClientSshController, self).__init__(name, logger) 42 | 43 | self._username = username 44 | self._password = password 45 | self._hostname = hostname 46 | self._port = port 47 | self._command = command 48 | self._process_name = process_name 49 | self._ssh = None 50 | 51 | def teardown(self): 52 | ''' 53 | Closes the SSH connection and calls super's teardown. 54 | ''' 55 | if self._ssh: 56 | self._ssh.close() 57 | self._ssh = None 58 | super(ClientSshController, self).teardown() 59 | 60 | def pre_test(self, num): 61 | ''' 62 | Creates an SSH connection 63 | ''' 64 | super(ClientSshController, self).pre_test(num) 65 | self._ssh = paramiko.SSHClient() 66 | self._ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 67 | self._ssh.connect(self._hostname, self._port, self._username, self._password) 68 | 69 | def post_test(self): 70 | ''' 71 | Log output of process, check if crashed 72 | ''' 73 | self._stdout.channel.settimeout(3) 74 | self._stderr.channel.settimeout(3) 75 | self.logger.debug('reading stdout...') 76 | try: 77 | self.report.add('stdout', self._stdout.read()) 78 | self.logger.debug('getting process return code...') 79 | return_code = self._stdout.channel.recv_exit_status() 80 | self.logger.debug('return code: %d', return_code) 81 | self.report.add('return_code', return_code) 82 | except socket.timeout: 83 | self.report.add('stdout', 'Timeout reading stdout.)') 84 | return_code = -2 85 | self.report.add('return_code', return_code) 86 | self.logger.debug('return code: %d', return_code) 87 | self.logger.debug('reading stderr...') 88 | try: 89 | self.report.add('stderr', self._stderr.read()) 90 | except socket.timeout: 91 | self.report.add('stderr', 'Timeout reading stderr.)') 92 | self.report.add('failed', return_code < 0) 93 | self._stop_process() 94 | self._ssh.close() 95 | super(ClientSshController, self).post_test() 96 | 97 | def trigger(self): 98 | ''' 99 | Trigger the target communication with the server stack. 100 | ''' 101 | self.report.add('command', self._command) 102 | (self._stdin, self._stdout, self._stderr) = self._ssh.exec_command(self._command) 103 | 104 | def _stop_process(self): 105 | ''' 106 | Kills the target process. 107 | ''' 108 | kill_cmd = 'killall %s' % self._process_name 109 | self._ssh.exec_command(kill_cmd) 110 | 111 | def _is_victim_alive(self): 112 | # return self._process and (self._process.poll() is None) 113 | return True 114 | -------------------------------------------------------------------------------- /katnip/controllers/server/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2016 Cisco Systems, Inc. and/or its affiliates. All rights reserved. 2 | # 3 | # This file is part of Katnip. 4 | # 5 | # Katnip is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 2 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Katnip is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Katnip. If not, see . 17 | -------------------------------------------------------------------------------- /katnip/controllers/server/frida.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2016 Cisco Systems, Inc. and/or its affiliates. All rights reserved. 2 | # 3 | # This file is part of Katnip. 4 | # 5 | # Katnip is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 2 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Katnip is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Katnip. If not, see . 17 | 18 | ''' 19 | `Frida `_ based controllers for server fuzzing. 20 | ''' 21 | from __future__ import absolute_import 22 | import frida 23 | from kitty.controllers.base import BaseController 24 | 25 | 26 | class FridaLaunchServerController(BaseController): 27 | ''' 28 | This controller uses frida to launch an application. 29 | You can pass JS script so kitty will be able to detect failures 30 | using Frida's capabilities. 31 | 32 | :example: 33 | 34 | :: 35 | 36 | js_code = """ 37 | Interceptor.attach(ptr(failure_func_addr), { 38 | onEnter: function(args) { 39 | send('[kitty:failed] this function should never be called!'); 40 | } 41 | }); 42 | 43 | """ 44 | ctl = FridaLaunchServerController('fritty', None, 'local', ['someproc'], js_code) 45 | ''' 46 | 47 | def __init__(self, name, logger, device_path, argv, js_script=None): 48 | ''' 49 | :param name: name of the object 50 | :param logger: logger for the object 51 | :param device_path: frida target device path 52 | :type argv: list of str 53 | :param argv: arguments to launch the application 54 | :type js_script: str 55 | :param js_script: JS script to run on the target. 56 | in this script you can perform hooks and detect "failures" on the device. 57 | if a failure is detected, call `send('[kitty:failed] (reason)');` from JS. 58 | if somehow a pass is detected, call `send('[kitty:passed] (reason)');` from JS. 59 | (default: None) 60 | ''' 61 | super(FridaLaunchServerController, self).__init__(name, logger) 62 | self._frida_device_path = device_path 63 | self._frida_argv = argv 64 | self._frida_js_script = js_script 65 | self._frida_session = None 66 | self._frida_pid = None 67 | self._frida_device = None 68 | self._frida_script = None 69 | 70 | def _frida_reset(self): 71 | self._frida_session = None 72 | self._frida_pid = None 73 | self._frida_device = None 74 | self._frida_script = None 75 | 76 | def _frida_session_on_detached(self): 77 | self.logger.error('detached callback called') 78 | self._frida_reset() 79 | 80 | def _frida_script_on_message(self, message, data): 81 | ''' 82 | This function is called when the JS script calls "send" 83 | if the message payload starts with '[kitty:passed]' the test will be marked as passed 84 | if the message payload starts with '[kitty:failed]' the test will be marked as failed 85 | if the message payload starts with '[kitty:log]' the rest of the payload with go to log 86 | ''' 87 | payload = message['payload'] 88 | parts = payload.split(' ', 1) 89 | if len(parts) == 1: 90 | parts.append(None) 91 | if parts[0].lower() == '[kitty:failed]': 92 | self.report.failed(parts[1]) 93 | elif parts[0].lower() == '[kitty:passed]': 94 | self.report.passed() 95 | elif parts[0].lower() == '[kitty:log]': 96 | self.logger.info('Message from JS script: %s' % payload) 97 | 98 | def setup(self): 99 | super(FridaLaunchServerController, self).setup() 100 | if not self._is_victim_alive(): 101 | self._frida_device = frida.get_device(self._frida_device_path) 102 | self._frida_pid = self._frida_device.spawn(self._frida_argv) 103 | self._frida_session = self._frida_device.attach(self._frida_pid) 104 | self._frida_session.on('detached', self._frida_session_on_detached) 105 | if self._frida_js_script is not None: 106 | self._frida_script = self._frida_session.create_script(self._frida_js_script) 107 | self._frida_script.on('message', self._frida_script_on_message) 108 | self._frida_script.load() 109 | self._frida_device.resume(self._frida_pid) 110 | 111 | def teardown(self): 112 | if not self._is_victim_alive(): 113 | msg = 'victim is already down' 114 | self.logger.error(msg) 115 | raise Exception(msg) 116 | else: 117 | if self._frida_script is not None: 118 | self._frida_script.unload() 119 | self._frida_session.off('detached', self._frida_session_on_detached) 120 | self._frida_session.detach() 121 | self._frida_device.kill(self._frida_pid) 122 | self._frida_reset() 123 | super(FridaLaunchServerController, self).teardown() 124 | 125 | def pre_test(self, test_number): 126 | super(FridaLaunchServerController, self).pre_test(test_number) 127 | if not self._is_victim_alive(): 128 | self._restart() 129 | self.report.add('pre_test_pid', self._get_pid()) 130 | 131 | def post_test(self): 132 | super(FridaLaunchServerController, self).post_test() 133 | 134 | def _restart(self): 135 | self.logger.info('restart called') 136 | self.teardown() 137 | self.setup() 138 | 139 | def _get_pid(self): 140 | return self._frida_pid 141 | 142 | def _is_victim_alive(self): 143 | if self._frida_pid: 144 | return True 145 | else: 146 | return False 147 | -------------------------------------------------------------------------------- /katnip/controllers/server/local_process.py: -------------------------------------------------------------------------------- 1 | import os 2 | from kitty.controllers.base import BaseController 3 | from subprocess import Popen, PIPE 4 | import time 5 | 6 | 7 | class LocalProcessController(BaseController): 8 | ''' 9 | LocalProcessController runs a server application using python's subprocess.Popen() 10 | It can restart the process upon exit, or at the beginning of each test 11 | 12 | :example: 13 | 14 | :: 15 | 16 | controller = LocalProcessController('PyHttpServer', '/usr/bin/python', ['-m', 'SimpleHttpServer', '1234']) 17 | ''' 18 | 19 | def __init__(self, name, process_path, process_args, delay_after_start=None, start_each_test=False, logger=None): 20 | ''' 21 | :param name: name of the object 22 | :param process_path: path to the target executable. note that it requires the actual path, not only executable name 23 | :param process_args: arguments to pass to the process 24 | :param delay_after_start: delay after opening a process, in seconds (default: None) 25 | :param start_each_test: should restart the process every test, or only upon failures (default: False) 26 | :param logger: logger for this object (default: None) 27 | ''' 28 | super(LocalProcessController, self).__init__(name, logger) 29 | assert(process_path) 30 | assert(os.path.exists(process_path)) 31 | self._process_path = process_path 32 | self._process_name = os.path.basename(process_path) 33 | self._process_args = process_args 34 | self._process = None 35 | self._delay_after_start = delay_after_start 36 | self._start_each_test = start_each_test 37 | 38 | def pre_test(self, test_number): 39 | '''start the victim''' 40 | super(LocalProcessController, self).pre_test(test_number) 41 | if self._start_each_test or not self._is_victim_alive(): 42 | if self._process: 43 | self._stop_process() 44 | cmd = [self._process_path] + self._process_args 45 | self._process = Popen(cmd, stdout=PIPE, stderr=PIPE) 46 | if self._delay_after_start: 47 | time.sleep(self._delay_after_start) 48 | self.report.add('process_name', self._process_name) 49 | self.report.add('process_path', self._process_path) 50 | self.report.add('process_args', self._process_args) 51 | self.report.add('process_id', self._process.pid) 52 | 53 | def post_test(self): 54 | '''Called when test is done''' 55 | assert(self._process) 56 | if self._is_victim_alive(): 57 | if self._start_each_test: 58 | self._stop_process() 59 | # if we should stop every test let us be over with it 60 | else: 61 | # if process is dead, than we should check the error code 62 | if self._process.returncode != 0: 63 | self.report.failed('return code is not zero: %s' % self._process.returncode) 64 | 65 | # if the process is dead, let's add all of its info to the report 66 | if not self._is_victim_alive(): 67 | self.report.add('stdout', self._process.stdout.read()) 68 | self.report.add('stderr', self._process.stderr.read()) 69 | self.logger.debug('return code: %d', self._process.returncode) 70 | self.report.add('return_code', self._process.returncode) 71 | self._process = None 72 | super(LocalProcessController, self).post_test() 73 | 74 | def teardown(self): 75 | ''' 76 | Called at the end of the fuzzing session, override with victim teardown 77 | ''' 78 | self._stop_process() 79 | self._process = None 80 | super(LocalProcessController, self).teardown() 81 | 82 | def _stop_process(self): 83 | if self._is_victim_alive(): 84 | self._process.terminate() 85 | time.sleep(0.5) 86 | if self._is_victim_alive(): 87 | self._process.kill() 88 | time.sleep(0.5) 89 | if self._is_victim_alive(): 90 | raise Exception('Failed to kill client process') 91 | 92 | def _is_victim_alive(self): 93 | return self._process and (self._process.poll() is None) 94 | -------------------------------------------------------------------------------- /katnip/controllers/server/tcp_system.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2016 Cisco Systems, Inc. and/or its affiliates. All rights reserved. 2 | # 3 | # This file is part of Katnip. 4 | # 5 | # Katnip is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 2 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Katnip is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Katnip. If not, see . 17 | 18 | import socket 19 | import time 20 | from kitty.controllers.base import BaseController 21 | 22 | 23 | class ServerTcpSystemController(BaseController): 24 | ''' 25 | This controller controls a process on a remote machine 26 | by sending tcp commands over the network to a local agent 27 | on the remote machine to execute the target using popen. 28 | 29 | .. note:: 30 | 31 | The implementation of the agent is not part of the code 32 | it might be added someday, but currently it is not... 33 | ''' 34 | 35 | def __init__(self, name, logger, proc_name, host, port): 36 | ''' 37 | :param name: name of the object 38 | :param logger: logger for the object 39 | :param proc_name: trigger's process name 40 | :param host: hostname of the agent 41 | :param port: port of the agent 42 | ''' 43 | super(ServerTcpSystemController, self).__init__(name, logger) 44 | self._proc_name = proc_name 45 | self._host = host 46 | self._port = port 47 | self._agent_socket = None 48 | 49 | def setup(self): 50 | super(ServerTcpSystemController, self).setup() 51 | if not self._is_victim_alive(): 52 | msg = 'ServerTcpSystemController cannot start victim' 53 | self.logger.error(msg) 54 | raise Exception(msg) 55 | 56 | def teardown(self): 57 | super(ServerTcpSystemController, self).teardown() 58 | if not self.is_victim_alive(): 59 | msg = 'victim is already down' 60 | self.logger.error(msg) 61 | raise Exception(msg) 62 | else: 63 | msg = 'ServerTcpSystemController does not actually stop the process' 64 | self.logger.info(msg) 65 | 66 | def pre_test(self, test_number): 67 | super(ServerTcpSystemController, self).pre_test(test_number) 68 | if not self._is_victim_alive(): 69 | self._restart() 70 | self.report.add('pre_test_pid', self._get_pid()) 71 | 72 | def post_test(self): 73 | super(ServerTcpSystemController, self).post_test() 74 | self.report.add('post_test_pid', self._get_pid()) 75 | 76 | def _restart(self): 77 | self.logger.info('restart called') 78 | self._do_remote_command('reboot', True) 79 | self.setup() 80 | 81 | def _get_pid(self): 82 | data = self._do_remote_command("pgrep %s" % self._proc_name, False) 83 | curr_pid = int(data) 84 | return curr_pid 85 | 86 | def _is_victim_alive(self): 87 | active = False 88 | try: 89 | self._get_pid() 90 | active = True 91 | except Exception: 92 | pass 93 | 94 | return active 95 | 96 | def _connect_to_agent(self, retry): 97 | while True: 98 | try: 99 | self._agent_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 100 | self._agent_socket.settimeout(2) 101 | self._agent_socket.connect((self._host, self._port)) 102 | break 103 | except Exception: 104 | if retry: 105 | self.logger.warning("Failed to connect to agent") 106 | self.logger.warning("Sleep 5 seconds and retry") 107 | time.sleep(5) 108 | if not retry: 109 | break 110 | 111 | def _do_remote_command(self, command, retry=True): 112 | self.logger.info('do remote command: %s' % command) 113 | self._connect_to_agent(retry) 114 | self._agent_socket.send(command) 115 | data = self._agent_socket.recv(1024) 116 | self.logger.info('response: %s' % data) 117 | self._agent_socket.close() 118 | self._agent_socket = None 119 | return data 120 | -------------------------------------------------------------------------------- /katnip/legos/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2016 Cisco Systems, Inc. and/or its affiliates. All rights reserved. 2 | # 3 | # This file is part of Katnip. 4 | # 5 | # Katnip is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 2 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Katnip is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Katnip. If not, see . 17 | 18 | ''' 19 | Collection of low level data model structures 20 | that can save time when implementing some protocols. 21 | 22 | For example, TLV legos to implement templates 23 | for TLV-based protocol. 24 | ''' 25 | -------------------------------------------------------------------------------- /katnip/legos/bittorrent.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2016 Cisco Systems, Inc. and/or its affiliates. All rights reserved. 2 | # 3 | # This file is part of Katnip. 4 | # 5 | # Katnip is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 2 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Katnip is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Katnip. If not, see . 17 | 18 | ''' 19 | Bittorent file (.torrent) protocol lego. 20 | Those legos impelent the bencoding format: 21 | https://wiki.theory.org/BitTorrentSpecification#Bencoding 22 | ''' 23 | from kitty.model import Container, TakeFrom 24 | from kitty.model import Delimiter, String, SizeInBytes, SInt64 25 | from kitty.model import ENC_INT_DEC 26 | 27 | 28 | _unique_ids = {} 29 | 30 | 31 | def _merge(*args): 32 | return '-'.join(args) 33 | 34 | 35 | def _unique_name(name): 36 | if name not in _unique_ids: 37 | _unique_ids[name] = 0 38 | _unique_ids[name] += 1 39 | return _merge(name, str(_unique_ids[name])) 40 | 41 | 42 | class TString(Container): 43 | ''' 44 | Bencoded String. 45 | Format: ``:`` 46 | ''' 47 | 48 | def __init__(self, value, fuzz_value=True, fuzz_length=True, fuzz_delim=True, name=None): 49 | ''' 50 | :param value: str, will be enclosed in String 51 | :param fuzz_value: bool (default: True) 52 | :param fuzz_length: bool (default: True) 53 | :param fuzz_delim: bool (default: True) 54 | :param name: name of container (default: None) 55 | ''' 56 | name = name if name is not None else _unique_name(type(self).__name__) 57 | if isinstance(value, str): 58 | fvalue = String(value=value, fuzzable=fuzz_value, name=_merge(name, 'value')) 59 | else: 60 | fvalue = value 61 | fvalue_name = fvalue.get_name() 62 | super(TString, self).__init__(name=name, fields=[ 63 | SizeInBytes(sized_field=fvalue_name, length=32, encoder=ENC_INT_DEC, fuzzable=fuzz_length, name=_merge(name, 'length')), 64 | Delimiter(value=':', fuzzable=fuzz_delim, name=_merge(name, 'delim')), 65 | fvalue 66 | ]) 67 | 68 | 69 | class TInteger(Container): 70 | ''' 71 | Bencoded integer. 72 | Format: `` ie`` 73 | ''' 74 | 75 | def __init__(self, value, fuzz_value=True, fuzz_delims=True, name=None): 76 | ''' 77 | :param value: int, will be enclosed in a Int32 78 | :fuzz_value: bool (default: True) 79 | :fuzz_delims: bool (default: True) 80 | :param name: name of container (default: None) 81 | ''' 82 | name = name if name is not None else _unique_name(type(self).__name__) 83 | super(TInteger, self).__init__(name=name, fields=[ 84 | String(value='i', max_size=1, fuzzable=fuzz_delims, name=_merge(name, 'start')), 85 | SInt64(value=value, encoder=ENC_INT_DEC, fuzzable=fuzz_value, name=_merge(name, 'value')), 86 | String(value='e', max_size=1, fuzzable=fuzz_delims, name=_merge(name, 'end')), 87 | ]) 88 | 89 | 90 | class TList(Container): 91 | ''' 92 | Bencoded list. 93 | Format: ``le`` 94 | ''' 95 | def __init__(self, fields=[], fuzz_delims=True, name=None): 96 | ''' 97 | :param fields: content of the list, Fields... 98 | :fuzz_delims: bool (default: True) 99 | :param name: name of container (default: None) 100 | ''' 101 | name = name if name is not None else _unique_name(type(self).__name__) 102 | super(TList, self).__init__(name=name, fields=[ 103 | String(value='l', max_size=1, fuzzable=fuzz_delims, name=_merge(name, 'start')), 104 | TakeFrom(fields=fields, name=_merge(name, 'fields'), min_elements=len(fields)/2), 105 | String(value='e', max_size=1, fuzzable=fuzz_delims, name=_merge(name, 'end')) 106 | ]) 107 | 108 | 109 | class TDict(Container): 110 | ''' 111 | Bencoded dictionary. 112 | Format: ``de`` 113 | ''' 114 | 115 | def __init__(self, fields={}, fuzz_keys=True, fuzz_delims=True, name=None): 116 | ''' 117 | :param fields: dictionary of strings and torrent fields 118 | :fuzz_delims: bool (default: True) 119 | :param name: name of container (default: None) 120 | ''' 121 | name = name if name is not None else _unique_name(type(self).__name__) 122 | dictionary_fields = [] 123 | for k, v in fields.items(): 124 | dictionary_fields.append(Container(name=_merge(name, 'container', k), fields=[ 125 | TString(value=k, fuzz_value=fuzz_keys, fuzz_length=fuzz_keys, fuzz_delim=fuzz_delims, name=_merge(name, 'key', k)), 126 | v 127 | ])) 128 | super(TDict, self).__init__(name=name, fields=[ 129 | String(value='d', max_size=1, fuzzable=fuzz_delims, name=_merge(name, 'start')), 130 | TakeFrom(name=_merge(name, 'fields'), fields=dictionary_fields, min_elements=len(dictionary_fields)/2), 131 | String(value='e', max_size=1, fuzzable=fuzz_delims, name=_merge(name, 'end')) 132 | ]) 133 | -------------------------------------------------------------------------------- /katnip/legos/dynamic.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2016 Cisco Systems, Inc. and/or its affiliates. All rights reserved. 2 | # 3 | # This file is part of Kitty. 4 | # 5 | # Kitty is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 2 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Kitty is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Kitty. If not, see . 17 | ''' 18 | Extensions for the DynamicField, 19 | allowing advanced mutations on dynamic fields. 20 | ''' 21 | from kitty.model import Dynamic, String 22 | from kitty.model import OneOf, Pad 23 | from kitty.model import ENC_STR_DEFAULT 24 | 25 | 26 | def _join_name(prefix, postfix): 27 | if prefix is None: 28 | return None 29 | return '%s_%s' % (prefix, postfix) 30 | 31 | 32 | class DynamicExtended(OneOf): 33 | ''' 34 | Container that provides mutations based on the dynamic value, 35 | or based on the other given field. 36 | ''' 37 | 38 | def __init__(self, key, value, additional_field, fuzzable=True, name=None): 39 | ''' 40 | :param key: key for data in the session information 41 | :param value: the default value of the Dynamic field 42 | :param additional_field: the additional field to base the mutations on 43 | :param fuzzable: is this field fuzzable (default: False) 44 | :param name: name of the container (default: None) 45 | ''' 46 | if name is None: 47 | name = key 48 | fields = [ 49 | Dynamic(key=key, default_value=value, length=len(value), fuzzable=True, name=_join_name(name, 'dynamic')), 50 | additional_field 51 | ] 52 | super(DynamicExtended, self).__init__(fields=fields, fuzzable=fuzzable, name=name) 53 | 54 | 55 | class DynamicString(DynamicExtended): 56 | ''' 57 | Container that provides mutations based on the dynamic value, 58 | or based on string mutations. 59 | ''' 60 | 61 | def __init__(self, key, value, keep_size=False, encoder=ENC_STR_DEFAULT, fuzzable=True, name=None): 62 | ''' 63 | :param key: key for data in the session information 64 | :param value: the default value of the Dynamic field 65 | :param keep_size: should limit the size of the string based on the original string (default: False) 66 | :param encoder: string encoder (default: ``ENC_STR_DEFAULT``) 67 | :param fuzzable: is this field fuzzable (default: True) 68 | :param name: name of the container (default: None) 69 | ''' 70 | str_len = len(value) if keep_size else None 71 | additional_field = String(value=value, max_size=str_len, encoder=encoder, name=_join_name(name, 'string')) 72 | if keep_size: 73 | additional_field = Pad(str_len * 8, fields=additional_field, name=_join_name(name, 'string_wrap')) 74 | super(DynamicString, self).__init__( 75 | key=key, 76 | value=value, 77 | additional_field=additional_field, 78 | fuzzable=fuzzable, 79 | name=name 80 | ) 81 | 82 | 83 | class DynamicInt(DynamicExtended): 84 | ''' 85 | Container that provides mutations based on the dynamic value, 86 | or based on BitField mutations. 87 | ''' 88 | 89 | def __init__(self, key, bitfield, fuzzable=True, name=None): 90 | ''' 91 | :param key: key for data in the session information 92 | :param bitfield: a bitfield to base the value on 93 | :param fuzzable: (default: True) 94 | :param name: (default: None) 95 | ''' 96 | super(DynamicInt, self).__init__( 97 | key=key, 98 | value=bitfield.render().bytes, 99 | additional_field=bitfield, 100 | fuzzable=fuzzable, 101 | name=name 102 | ) 103 | -------------------------------------------------------------------------------- /katnip/legos/http.py: -------------------------------------------------------------------------------- 1 | from katnip.legos.url import DecimalNumber, Search, Path, urlparse 2 | from kitty.model import Container 3 | from kitty.model import String, Static, Group, Delimiter, Float 4 | from kitty.model import ENC_BITS_BASE64 5 | 6 | 7 | def _valuename(txt): 8 | return '%s_value' % txt 9 | 10 | 11 | def _keyname(txt): 12 | return '%s_key' % txt 13 | 14 | 15 | class CustomHeaderField(Container): 16 | def __init__(self, key, value, end=False, fuzzable_key=False, fuzzable_value=True): 17 | fields = [ 18 | String(name=_keyname(key), value=key, fuzzable=fuzzable_key), 19 | Static(': '), 20 | Container(name=_valuename(key), fields=value, fuzable=fuzzable_value), 21 | Static('\r\n') 22 | ] 23 | if end: 24 | fields.append(Static('\r\n')) 25 | super(CustomHeaderField, self).__init__(name=key, fields=fields, fuzzable=fuzzable_value) 26 | 27 | 28 | class TextField(CustomHeaderField): 29 | def __init__(self, key, value, end=False, fuzzable_key=False, fuzzable_value=True): 30 | value_field = [String(name="value", value=value)] 31 | super(TextField, self).__init__(key, value_field, end, fuzzable_key, fuzzable_value) 32 | 33 | 34 | class IntField(CustomHeaderField): 35 | def __init__(self, key, value, end=False, fuzzable_key=False, fuzzable_value=True): 36 | value_field = [DecimalNumber( 37 | name="value", 38 | value=value, 39 | num_bits=32, 40 | signed=True 41 | )] 42 | super(IntField, self).__init__(key, value_field, end, fuzzable_key, fuzzable_value) 43 | 44 | 45 | class AuthorizationField(CustomHeaderField): 46 | def __init__(self, key, username, password, end=False, delim=':', fuzz_username=True, fuzz_password=True, fuzzable_key=False, fuzzable=True): 47 | value_field = [ 48 | Static('Basic '), 49 | Container(name='base64_auth', fields=[ 50 | String(name='username', value=username, fuzzable=fuzz_username), 51 | Delimiter(delim, fuzzable=False), 52 | String(name='password', value=password, fuzzable=fuzz_password), 53 | ], encoder=ENC_BITS_BASE64) 54 | ] 55 | super(AuthorizationField, self).__init__(key, value_field, end, fuzzable_key, fuzzable) 56 | 57 | 58 | class HttpRequestLine(Container): 59 | def __init__(self, method='GET', uri='/', protocol='HTTP', version=1.0, fuzzable_method=False, fuzzable_uri=False, fuzzable=True): 60 | method_value = [method] if isinstance(method, str) else method 61 | parsed = urlparse(uri) 62 | uri_value = [Path(parsed.path, name='path', fuzz_delims=False)] 63 | if parsed.query: 64 | uri_value.append(Search(parsed.query, name='search', fuzz_value=True)) 65 | fields = [ 66 | Group(name='method', values=method_value, fuzzable=fuzzable_method), 67 | Static(' '), 68 | Container(name='uri', fields=uri_value, fuzzable=fuzzable_uri), 69 | Static(' '), 70 | String(name='protocol', value=protocol, fuzzable=False), 71 | Float(name='version', value=version), 72 | Static('\r\n'), 73 | ] 74 | super(HttpRequestLine, self).__init__(name='http url', fields=fields, fuzzable=fuzzable) 75 | -------------------------------------------------------------------------------- /katnip/legos/tlv.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2016 Cisco Systems, Inc. and/or its affiliates. All rights reserved. 2 | # 3 | # This file is part of Katnip. 4 | # 5 | # Katnip is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 2 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Katnip is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Katnip. If not, see . 17 | 18 | ''' 19 | TLV (tag/type-length-value) legos. 20 | Simplify fuzzing of TLV-based protocol. 21 | ''' 22 | from kitty.model import BitField 23 | from kitty.model import SizeInBytes 24 | from kitty.model import ENC_INT_BE, ENC_BITS_DEFAULT 25 | from kitty.model import Container 26 | 27 | 28 | class TLV(Container): 29 | ''' 30 | A container for fuzzing TLV elements, 31 | it represents a full binary TLV element. 32 | ''' 33 | 34 | def __init__(self, name, tag, fields=None, tag_size=32, length_size=32, encoder=ENC_INT_BE, fuzzable=True, fuzz_tag=False, fuzz_length=True): 35 | ''' 36 | :param name: name of the tlv element 37 | :param tag: tag of element 38 | :param fields: element fields, e.g. value (default: None) 39 | :param tag_size: size of tag field in bits (default: 32) 40 | :param length_size: size of length field in bits (default: 32) 41 | :param encoder: encoder for tag and length fields (default: ENC_INT_BE) 42 | :param fuzzable: should fuzz the element (default: True) 43 | :param fuzz_tag: should fuzz the tag value (default: False) 44 | :param fuzz_length: should fuzz the element length (default: True) 45 | ''' 46 | tag_name = '%s-tag' % name 47 | len_name = '%s-length' % name 48 | val_name = '%s-value' % name 49 | if fields is None: 50 | fields = [] 51 | _fields = [ 52 | BitField(name=tag_name, value=tag, length=tag_size, signed=False, encoder=encoder, fuzzable=fuzz_tag), 53 | SizeInBytes(name=len_name, sized_field=val_name, length=length_size, encoder=encoder, fuzzable=fuzz_length), 54 | Container(name=val_name, fields=fields) 55 | ] 56 | super(TLV, self).__init__(fields=_fields, encoder=ENC_BITS_DEFAULT, fuzzable=fuzzable, name=name) 57 | 58 | 59 | class TLVFactory(object): 60 | ''' 61 | Factory class for TLV elements, which allows configuration for all TLV blocks, including: 62 | 63 | - Size of the tag/type field in bits 64 | - Size of the length field in bits 65 | - Encoder for tag and length fields 66 | ''' 67 | 68 | def __init__(self, tag_size=32, length_size=32, encoder=ENC_INT_BE): 69 | ''' 70 | :param tag_size: size of tag field in bits (default: 32) 71 | :param length_size: size of length field in bits (default: 32) 72 | :param encoder: encoder for tag and length (default: ENC_INT_BE) 73 | ''' 74 | self._tag_size = tag_size 75 | self._len_size = length_size 76 | self._encoder = encoder 77 | 78 | def element(self, name, tag, fields=None, fuzzable=True, fuzz_tag=False, fuzz_length=True): 79 | ''' 80 | Generate a TLV element. 81 | 82 | :param name: name of the element 83 | :param tag: value of the element tag 84 | :param fields: fields of the element may be a field or list of fields - e.g. value (default: None) 85 | :param fuzzable: should fuzz the element (default: True) 86 | :param fuzz_tag: should fuzz the tag value (default: False) 87 | :param fuzz_length: should fuzz the element length (default: True) 88 | ''' 89 | return TLV( 90 | name=name, tag=tag, value=fields, tag_size=self._tag_size, length_size=self._len_size, 91 | encoder=self._encoder, fuzzable=fuzzable, fuzz_tag=fuzz_tag, fuzz_length=fuzz_length) 92 | 93 | 94 | if __name__ == '__main__': 95 | from kitty.model import String 96 | tlv = TLVFactory() 97 | elem = tlv.element('version', 0x1, value=String(name='version-string', value='1.2.3')) 98 | print elem.num_mutations() 99 | print elem.render().bytes.encode('hex') 100 | while elem.mutate(): 101 | print elem.render().bytes.encode('hex') 102 | -------------------------------------------------------------------------------- /katnip/legos/usb_hid.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2016 Cisco Systems, Inc. and/or its affiliates. All rights reserved. 2 | # 3 | # This file is part of Katnip. 4 | # 5 | # Katnip is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 2 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Katnip is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Katnip. If not, see . 17 | ''' 18 | Legos to generate USB HID reports 19 | ''' 20 | from kitty.model import Container, OneOf, TakeFrom 21 | from kitty.model import MutableField 22 | from kitty.model import U8, BitField, Static 23 | from kitty.model import ENC_INT_LE 24 | from kitty.core import KittyException 25 | from random import Random 26 | 27 | 28 | opcodes = { 29 | 0x04: 'Usage Page', 30 | 0x08: 'Usage', 31 | 0x14: 'Logical Minimum', 32 | 0x18: 'Usage Minimum', 33 | 0x24: 'Logical Maximum', 34 | 0x28: 'Usage Maximum', 35 | 0x34: 'Physical Minimum', 36 | 0x38: 'Designator Index', 37 | 0x44: 'Physical Maximum', 38 | 0x48: 'Designator Minimum', 39 | 0x54: 'Unit Exponent', 40 | 0x58: 'Designator Maximum', 41 | 0x64: 'Unit', 42 | 0x74: 'Report Size', 43 | 0x78: 'String Index', 44 | 0x80: 'Input', 45 | 0x84: 'Report ID', 46 | 0x88: 'String Minimum', 47 | 0x90: 'Output', 48 | 0x94: 'Report Count', 49 | 0x98: 'String Maximum', 50 | 0xA0: 'Collection', 51 | 0xA4: 'Push', 52 | 0xA8: 'Delimiter', 53 | 0xB0: 'Feature', 54 | 0xB4: 'Pop', 55 | 0xC0: 'End Collection', 56 | } 57 | 58 | 59 | class NameGen(object): 60 | def __init__(self): 61 | self.names = {} 62 | 63 | def gen(self, opcode): 64 | args = opcode & 0x3 65 | tag = opcode & 0xfc 66 | base_name = ('opcode_%02x' % tag) if tag not in opcodes else opcodes[tag] 67 | base_name += '[%d]' % args 68 | if base_name not in self.names: 69 | self.names[base_name] = 0 70 | cur_name = base_name 71 | else: 72 | self.names[base_name] += 1 73 | cur_name = '%s <%d>' % (base_name, self.names[base_name]) 74 | return cur_name 75 | 76 | 77 | class RandomHidReport(TakeFrom): 78 | ''' 79 | Generate random sequences of valid, interesting opcodes, and try to screw them up. 80 | ''' 81 | 82 | def __init__(self, name=None, fuzzable=True): 83 | namer = NameGen() 84 | fields = [] 85 | r = Random() 86 | for tag in opcodes: 87 | for i in range(4): 88 | opcode = tag | i 89 | current = chr(opcode) 90 | for j in range(i): 91 | current += chr(r.randint(0, 255)) 92 | fields.append(Static(name=namer.gen(opcode), value=current)) 93 | super(RandomHidReport, self).__init__( 94 | fields=fields, 95 | min_elements=10, 96 | max_elements=40, 97 | fuzzable=fuzzable, 98 | name=name 99 | ) 100 | 101 | 102 | def GenerateHidReport(report_str, name=None): 103 | ''' 104 | Generate an HID report Container from a HID report string 105 | 106 | :param report_str: HID report string 107 | :param name: name of generated Container (default: None) 108 | :raises: KittyException if not enough bytes are left for command 109 | 110 | :examples: 111 | 112 | :: 113 | 114 | Template( 115 | name='MyHidReport', 116 | fields=GenerateHidReport( 117 | '05010906A101050719E029E7150025017501950881029501750881011900296515002565750895018100C0', 118 | ) 119 | ) 120 | ''' 121 | fields = [] 122 | index = 0 123 | namer = NameGen() 124 | while index < len(report_str): 125 | opcode = ord(report_str[index]) 126 | num_args = opcode & 3 127 | if index + num_args >= len(report_str): 128 | raise KittyException('Not enough bytes in hid report for last opcode') 129 | index += 1 130 | cur_name = namer.gen(opcode) 131 | if num_args == 0: 132 | fields.append(U8(opcode, name=cur_name)) 133 | else: 134 | args = report_str[index:index + num_args] 135 | value = sum(ord(args[i]) << (i * 8) for i in range(len(args))) # little endian... 136 | fields.append(Container( 137 | name=cur_name, 138 | fields=[ 139 | U8(opcode, name='opcode'), 140 | BitField(value, 8 * len(args), encoder=ENC_INT_LE, name='value') 141 | ] 142 | )) 143 | index += num_args 144 | return OneOf( 145 | name=name, 146 | fields=[ 147 | Container( 148 | name='generation', 149 | fields=fields 150 | ), 151 | MutableField( 152 | name='mutation', 153 | value=report_str 154 | ), 155 | RandomHidReport( 156 | name='random_sequences' 157 | ), 158 | ]) 159 | -------------------------------------------------------------------------------- /katnip/legos/xml.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2016 Cisco Systems, Inc. and/or its affiliates. All rights reserved. 2 | # 3 | # This file is part of Katnip. 4 | # 5 | # Katnip is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 2 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Katnip is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Katnip. If not, see . 17 | 18 | ''' 19 | XML (tag/type-length-value) legos. 20 | Simplify template creation of XML-based protocol. 21 | ''' 22 | from types import StringTypes, ListType, IntType 23 | from kitty.model import Container, Template 24 | from kitty.model import String, Static, SInt32, Clone 25 | from kitty.model import ENC_INT_DEC 26 | 27 | 28 | def _valuename(name): 29 | return '%s_value' % name 30 | 31 | 32 | def _keyname(name): 33 | return '%s_key' % name 34 | 35 | 36 | def _check_type(v, ts, name): 37 | if not isinstance(v, ts): 38 | raise ValueError('type of %s should be one of %s, but is %s' % (name, ts, type(v))) 39 | 40 | 41 | class XmlAttribute(Container): 42 | ''' 43 | XML attribute field, consists of tag and value 44 | ''' 45 | 46 | def __init__(self, name, attribute, value, fuzz_attribute=False, fuzz_value=True): 47 | ''' 48 | :param name: name of the block 49 | :param attribute: attribute 50 | :type value: str/unicode/int 51 | :param value: value of the attribute 52 | :param fuzz_attribute: should we fuzz the attribute field (default: False) 53 | :param fuzz_value: should we fuzz the value field (default: True) 54 | ''' 55 | _check_type(attribute, StringTypes, 'attribute') 56 | _check_type(value, StringTypes + (IntType, ), 'value') 57 | 58 | value_name = _valuename(name) 59 | if isinstance(value, StringTypes): 60 | value_field = String(value, name=value_name, fuzzable=fuzz_value) 61 | else: 62 | value_field = SInt32(value, encoder=ENC_INT_DEC, fuzzable=fuzz_value, name=value_name) 63 | fields = [ 64 | String(attribute, fuzzable=fuzz_attribute, name='%s_attribute' % name), 65 | Static('='), 66 | Static('"'), 67 | value_field, 68 | Static('"') 69 | ] 70 | super(XmlAttribute, self).__init__(fields, name=name) 71 | 72 | 73 | class XmlElement(Container): 74 | ''' 75 | XML element field 76 | ''' 77 | 78 | def __init__(self, name, element_name, attributes=[], content=None, fuzz_name=True, fuzz_content=False, delimiter=''): 79 | ''' 80 | :param name: name of the field 81 | :param element_name: element name 82 | :type attributes: list 83 | :param attributes: list of attributes of this element (default: []) 84 | :type content: str/unicode/int/[XmlElement] 85 | :param content: content of this element (default=None) 86 | :param fuzz_name: should we fuzz the element name 87 | :param fuzz_content: should we fuzz the content (n/a for XmlElement) 88 | ''' 89 | _check_type(element_name, StringTypes, 'element_name') 90 | _check_type(attributes, ListType, 'attributes') 91 | if content: 92 | _check_type(content, StringTypes + (ListType, IntType), 'content') 93 | 94 | value_field = String(element_name, fuzzable=fuzz_name, name='%s_element' % name) 95 | 96 | fields = [ 97 | Static('<'), 98 | value_field 99 | ] 100 | for i, attribute in enumerate(attributes): 101 | fields.append(Static(' ')) 102 | fields.append(attribute) 103 | fields.append(Static('>')) 104 | if content: 105 | content_name = '%s_content' + name 106 | if isinstance(content, StringTypes): 107 | fields.append(String(content, fuzzable=fuzz_content, name=content_name)) 108 | elif isinstance(content, IntType): 109 | fields.append(SInt32(content, encoder=ENC_INT_DEC, fuzzable=fuzz_content, name=content_name)) 110 | elif isinstance(content, ListType): 111 | fields.append(Static(delimiter)) 112 | for elem in content: 113 | _check_type(elem, XmlElement, 'element inside the content list') 114 | fields.append(elem) 115 | fields.append(Static('' + delimiter)) 118 | super(XmlElement, self).__init__(fields, name=name) 119 | 120 | 121 | if __name__ == '__main__': 122 | # name, attribute, value, fuzz_attribute=False, fuzz_value=True 123 | attributes = [ 124 | XmlAttribute(name='attr1', attribute='boom', value='attr1 value'), 125 | XmlAttribute(name='attr2', attribute='box', value=2), 126 | ] 127 | inner_elements = [ 128 | XmlElement(name='inner element', element_name='an_inner_element', content=1, delimiter='\n'), 129 | XmlElement(name='inner element 2', element_name='an_inner_element', content='brrr', delimiter='\n') 130 | ] 131 | element = XmlElement(name='element1', element_name='an_element', attributes=attributes, content=inner_elements, delimiter='\n') 132 | t = Template(element, name='test') 133 | print(t.render().tobytes()) 134 | -------------------------------------------------------------------------------- /katnip/model/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | from .low_level import * 3 | -------------------------------------------------------------------------------- /katnip/model/low_level/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | from .encoder import * 3 | -------------------------------------------------------------------------------- /katnip/model/low_level/fs_iterators.py: -------------------------------------------------------------------------------- 1 | ''' 2 | FS iterators contains fields that are based on files. 3 | 4 | * :class:`katnip.templates.fs_iterators.FsNames` will return file names from the system based on its configuration. 5 | * :class:`katnip.templates.fs_iterators.FsContent` will return the content of files from the system. 6 | 7 | ''' 8 | import os 9 | from fnmatch import fnmatch 10 | from kitty.core import KittyObject, KittyException 11 | from kitty.model.low_level import BaseField 12 | from kitty.model.low_level import ENC_STR_DEFAULT, StrEncoder 13 | 14 | 15 | class _FsIterator(KittyObject): 16 | ''' 17 | This class is able to iterate in a reproducible and consistent way over 18 | files in a directory, it is used by the kitty fields that are defined in 19 | this module. 20 | ''' 21 | 22 | def __init__(self, path, name_filter, recurse=False): 23 | ''' 24 | :param path: base path to iterate over files 25 | :param name_filter: string to filter filenames, same as shell, not regex 26 | :param recurse: should iterate inner directories (default: False) 27 | 28 | :example: 29 | 30 | Iterate all log files in current directory 31 | >> _FsIterator('.', '*.log') 32 | ''' 33 | super(_FsIterator, self).__init__('_FsIterator') 34 | self._path = path 35 | self._name_filter = name_filter 36 | self._recurse = recurse 37 | self._path_list = [] 38 | self._filename_dict = {} 39 | self._count = 0 40 | self._index = -1 41 | self._path_index = 0 42 | self._file_index = 0 43 | self._enumerate() 44 | 45 | def _enumerate(self): 46 | self._count = 0 47 | if self._recurse: 48 | for path, _, files in os.walk(self._path): 49 | current = sorted(self._filter_filenames(files)) 50 | if len(current): 51 | self._path_list.append(path) 52 | self._filename_dict[path] = current 53 | self._count += len(current) 54 | self._path_list = sorted(self._path_list) 55 | else: 56 | files = os.listdir(self._path) 57 | self._path_list = [self._path] 58 | current = sorted(self._filter_filenames(files)) 59 | self._filename_dict[self._path] = current 60 | self._count += len(current) 61 | 62 | def _matches(self, filename): 63 | return fnmatch(filename, self._name_filter) 64 | 65 | def _filter_filenames(self, filenames): 66 | return [f for f in filenames if self._matches(f)] 67 | 68 | def count(self): 69 | return self._count 70 | 71 | def reset(self): 72 | self._index = -1 73 | self._path_index = 0 74 | self._file_index = 0 75 | 76 | def current(self): 77 | ''' 78 | :return: tuple (path, filename) of current case 79 | ''' 80 | if self._index == -1: 81 | path = self._path_list[0] 82 | return path, self._filename_dict[path][0] 83 | elif self._index < self._count: 84 | path = self._path_list[self._path_index] 85 | files = self._filename_dict[path] 86 | return path, files[self._file_index] 87 | else: 88 | raise KittyException('Current is invalid!') 89 | 90 | def next(self): 91 | ''' 92 | Move to next case 93 | 94 | :return: True if there's another case, False otherwise 95 | ''' 96 | if self._index == self._count - 1: 97 | return False 98 | else: 99 | self._index += 1 100 | if self._index > 0: 101 | path = self._path_list[self._path_index] 102 | files = self._filename_dict[path] 103 | if self._file_index < len(files) - 1: 104 | self._file_index += 1 105 | else: 106 | self._file_index = 0 107 | self._path_index += 1 108 | return True 109 | 110 | def skip(self, count): 111 | ''' 112 | Skip up to [count] cases 113 | 114 | :count: number of cases to skip 115 | :rtype: int 116 | :return: number of cases skipped 117 | ''' 118 | left = self.count - self._index - 1 119 | if count > left: 120 | return left 121 | else: 122 | # progress here 123 | to_skip = count 124 | while to_skip: 125 | path = self._path_list[self._path_index] 126 | files = self._filename_dict[path] 127 | len_files = len(files[self._file_index:]) 128 | if len_files > to_skip: 129 | self._path_index += 1 130 | self._file_index = 0 131 | self._index += len_files 132 | to_skip -= len_files 133 | else: 134 | self._file_index += to_skip 135 | self._index += to_skip 136 | to_skip = 0 137 | return count 138 | 139 | 140 | class FsNames(BaseField): 141 | ''' 142 | This field mutations are the file names in a given directory. 143 | It is pretty useful if you have files that were generated by a different 144 | fuzzer, and you only need to pass their name to your target. 145 | You can filter the files based on the file name (name_filter), 146 | you can recurse into subdirectories (recurse) and 147 | you can pass full path or only the file name (full_path), 148 | ''' 149 | 150 | _encoder_type_ = StrEncoder 151 | 152 | def __init__(self, path, name_filter, recurse=False, full_path=True, encoder=ENC_STR_DEFAULT, fuzzable=True, name=None): 153 | ''' 154 | :param path: base path to iterate over files 155 | :param name_filter: string to filter filenames, same as shell, not regex 156 | :param recurse: should iterate inner directories (default: False) 157 | :param full_path: should include full path rather than only file name (default: True) 158 | :type encoder: :class:`~kitty.model.low_level.encoder.StrEncoder` 159 | :param encoder: encoder for the field 160 | :param fuzzable: is field fuzzable (default: True) 161 | :param name: name of the object (default: None) 162 | ''' 163 | self._fsi = _FsIterator(path, name_filter, recurse) 164 | self._full_path = full_path 165 | if self._full_path: 166 | default_value = os.path.join(*self._fsi.current()) 167 | else: 168 | default_value = self._fsi.current()[1] 169 | super(FsNames, self).__init__(default_value, encoder, fuzzable, name) 170 | self._num_mutations = self._fsi.count() 171 | 172 | def _mutate(self): 173 | if self._fsi.next(): 174 | path, name = self._fsi.current() 175 | if self._full_path: 176 | name = os.path.join(path, name) 177 | self._current_value = name 178 | 179 | def reset(self): 180 | super(FsNames, self).reset() 181 | self._fsi.reset() 182 | 183 | def skip(self, count): 184 | skipped = self._fsi.skip(count) 185 | self._current_index += skipped 186 | return skipped 187 | 188 | def get_info(self): 189 | info = super(FsNames, self).get_info() 190 | info['filepath'], info['filename'] = self._fsi.current() 191 | return info 192 | 193 | 194 | class FsContent(BaseField): 195 | ''' 196 | This field mutations are the contents of files in a given directory. 197 | It is pretty useful if you have files that were generated by a different 198 | fuzzer. 199 | You can filter the files based on the file name (name_filter), 200 | you can recurse into subdirectories (recurse) and 201 | you can pass full path or only the file name (full_path), 202 | ''' 203 | 204 | _encoder_type_ = StrEncoder 205 | 206 | def __init__(self, path, name_filter, recurse=False, encoder=ENC_STR_DEFAULT, fuzzable=True, name=None): 207 | ''' 208 | :param path: base path to iterate over files 209 | :param name_filter: string to filter filenames, same as shell, not regex 210 | :param recurse: should iterate inner directories (default: False) 211 | :type encoder: :class:`~kitty.model.low_level.encoder.StrEncoder` 212 | :param encoder: encoder for the field 213 | :param fuzzable: is field fuzzable (default: True) 214 | :param name: name of the object (default: None) 215 | ''' 216 | self._fsi = _FsIterator(path, name_filter, recurse) 217 | super(FsContent, self).__init__(b'', encoder, fuzzable, name) 218 | self._num_mutations = self._fsi.count() 219 | 220 | def _mutate(self): 221 | if self._fsi.next(): 222 | full_path = os.path.join(*self._fsi.current()) 223 | self._current_value = open(full_path, 'rb').read() 224 | 225 | def reset(self): 226 | super(FsContent, self).reset() 227 | self._fsi.reset() 228 | 229 | def skip(self, count): 230 | skipped = self._fsi.skip(count) 231 | self._current_index += skipped 232 | return skipped 233 | 234 | def get_info(self): 235 | info = super(FsContent, self).get_info() 236 | info['filepath'], info['filename'] = self._fsi.current() 237 | return info 238 | -------------------------------------------------------------------------------- /katnip/model/low_level/radamsa.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2016 Cisco Systems, Inc. and/or its affiliates. All rights reserved. 2 | # 3 | # This file is part of Katnip. 4 | # 5 | # Katnip is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 2 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Katnip is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Katnip. If not, see . 17 | ''' 18 | Radamsa wrapper to generate mutations using radamsa. 19 | 20 | You can get radamsa at https://github.com/aoh/radamsa 21 | ''' 22 | from random import Random 23 | import subprocess 24 | from kitty.model import BaseField 25 | from kitty.model import StrEncoder, ENC_STR_DEFAULT 26 | from kitty.core import KittyException 27 | 28 | 29 | class RadamsaField(BaseField): 30 | ''' 31 | This class uses radamsa to generate payload based on a given input. 32 | Since radamsa can run infinitly, it is limited by the user, 33 | by specifying the amount of payloads to generate (fuzz_count). 34 | To provide repeatablity, the user provides a seed that is used to 35 | generate seeds for radamsa. 36 | If radamsa is not installed in the system path, the user can provide 37 | the path (bin_path). 38 | If bin_path not specified, it will be assumed that the radamsa binary 39 | is in the path already. 40 | 41 | :example: 42 | 43 | :: 44 | 45 | from katnip.model.low_level.radamsa import RadamsaField 46 | RadamsaField(name='ip address', value='127.0.0.1', fuzz_count=20, bin_path='/path/to/radamsa') 47 | ''' 48 | 49 | _encoder_type_ = StrEncoder 50 | 51 | def __init__(self, value, encoder=ENC_STR_DEFAULT, fuzzable=True, name=None, fuzz_count=1000, seed=123456, bin_path=None): 52 | ''' 53 | :param value: default value 54 | :type encoder: :class:`~kitty.model.low_levele.encoder.ENC_STR_DEFAULT` 55 | :param encoder: encoder for the field 56 | :param fuzzable: is field fuzzable (default: True) 57 | :param name: name of the object (default: None) 58 | :param fuzz_count: fuzz count (default: 1000) 59 | :param seed: random seed for generating radamsa seeds (default: 123456) 60 | :param bin_path: path to the radamsa binary (default: None) 61 | ''' 62 | self._random = Random() 63 | self._seed = seed 64 | self._current_seed = None 65 | self._random.seed(self._seed) 66 | self._fuzz_count = fuzz_count 67 | self._bin_path = bin_path if bin_path else 'radamsa' 68 | self._radamsa_err = None 69 | self._radamsa_out = None 70 | super(RadamsaField, self).__init__(value=value, encoder=encoder, fuzzable=fuzzable, name=name) 71 | self._check_radamsa_available() 72 | 73 | def num_mutations(self): 74 | ''' 75 | :return: number of mutations this field will perform 76 | ''' 77 | if self._fuzzable: 78 | return self._fuzz_count 79 | else: 80 | return 0 81 | 82 | def _check_radamsa_available(self): 83 | ''' 84 | Check whether we can run radamsa. 85 | ''' 86 | try: 87 | sp = subprocess.Popen(self._bin_path) 88 | sp.terminate() 89 | except Exception as ex: 90 | raise KittyException('Can\'t run %s. error: %s' % (self._bin_path, ex)) 91 | 92 | def _run_radamsa(self): 93 | command = self._get_command() 94 | sp = subprocess.Popen(command, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 95 | self._radamsa_out, self._radamsa_err = sp.communicate(self._default_value) 96 | return self._radamsa_out 97 | 98 | def _get_command(self): 99 | return [self._bin_path, '-s', str(self._current_seed)] 100 | 101 | def _mutate(self): 102 | self._current_seed = self._random.randint(-100000000000, 100000000000) 103 | self._current_value = self._run_radamsa() 104 | 105 | def reset(self): 106 | super(RadamsaField, self).reset() 107 | self._random.seed(self._seed) 108 | self._random._current_seed = None 109 | self._radamsa_err = None 110 | self._radamsa_out = None 111 | 112 | def get_info(self): 113 | info = super(RadamsaField, self).get_info() 114 | info['base_seed'] = self._seed 115 | if self._current_seed is not None: 116 | info['radamsa'] = { 117 | 'seed': self._current_seed, 118 | 'command': ' '.join(str(x) for x in self._get_command()), 119 | } 120 | if self._radamsa_err: 121 | info['radamsa']['stderr'] = self._radamsa_err 122 | return info 123 | -------------------------------------------------------------------------------- /katnip/model/low_level/scapy.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2016 Cisco Systems, Inc. and/or its affiliates. All rights reserved. 2 | # 3 | # This module was authored and contributed by dark-lbp 4 | # 5 | # This file is part of Katnip. 6 | # 7 | # Katnip is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 2 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # Katnip is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with Katnip. If not, see . 19 | from __future__ import absolute_import 20 | from kitty.model import BaseField 21 | from kitty.model.low_level.encoder import ENC_STR_DEFAULT, StrEncoder 22 | import random 23 | from scapy.all import * 24 | 25 | 26 | class ScapyField(BaseField): 27 | ''' 28 | Wrap a fuzzed scapy.packet.Packet object as a kitty field. 29 | Since the fuzzing parameters can be configured by the fuzz function of Scapy, 30 | this field assumes that the fuzz function was already called on the given field 31 | 32 | :example: 33 | 34 | :: 35 | 36 | from scapy.all import * 37 | tcp_packet = IP()/TCP() 38 | field = ScapyField(value=fuzz(tcp_packet), name='tcp packet', fuzz_count=50, seed=1000) 39 | 40 | ''' 41 | 42 | _encoder_type_ = StrEncoder 43 | 44 | def __init__(self, value, encoder=ENC_STR_DEFAULT, fuzzable=True, name=None, fuzz_count=1000, seed=1024): 45 | ''' 46 | :param value: scapy_packet_class 47 | :type encoder: :class:`~kitty.model.low_levele.encoder.ENC_STR_DEFAULT` 48 | :param encoder: encoder for the field 49 | :param fuzzable: is field fuzzable (default: True) 50 | :param name: name of the object (default: None) 51 | :param fuzz_count: fuzz count (default: 1000) 52 | :param seed: random seed (default: 1024) 53 | ''' 54 | self._seed = seed 55 | # set the random seed 56 | random.seed(self._seed) 57 | # set the fuzz count 58 | self._fuzz_count = fuzz_count 59 | # keep reference to the field for the _mutate method 60 | self._fuzz_packet = value 61 | super(ScapyField, self).__init__(value=str(value), encoder=encoder, fuzzable=fuzzable, name=name) 62 | # reset random count 63 | random.seed(self._seed) 64 | 65 | def num_mutations(self): 66 | ''' 67 | :return: number of mutations this field will perform 68 | ''' 69 | if self._fuzzable: 70 | return self._fuzz_count 71 | else: 72 | return 0 73 | 74 | def _mutate(self): 75 | # during mutation, all we really do is call str(self.fuzz_packet) 76 | # as scapy performs mutation each time str() is called... 77 | self._current_value = str(self._fuzz_packet) 78 | 79 | def reset(self): 80 | super(ScapyField, self).reset() 81 | # reset fuzz_packet to default status 82 | random.seed(self._seed) 83 | 84 | 85 | def get_info(self): 86 | info = super(ScapyField, self).get_info() 87 | # add seed to report 88 | info['seed'] = self._seed 89 | if isinstance(self._fuzz_packet, Packet): 90 | info['scapy/command'] = self._fuzz_packet.command() 91 | return info 92 | -------------------------------------------------------------------------------- /katnip/monitors/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2016 Cisco Systems, Inc. and/or its affiliates. All rights reserved. 2 | # 3 | # This file is part of Katnip. 4 | # 5 | # Katnip is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 2 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Katnip is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Katnip. If not, see . 17 | 18 | -------------------------------------------------------------------------------- /katnip/monitors/network.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2016 Cisco Systems, Inc. and/or its affiliates. All rights reserved. 2 | # 3 | # This file is part of Katnip. 4 | # 5 | # Katnip is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 2 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Katnip is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Katnip. If not, see . 17 | 18 | ''' 19 | Network monitor 20 | ''' 21 | import os 22 | from scapy.all import wrpcap, conf 23 | 24 | from kitty.monitors import BaseMonitor 25 | 26 | 27 | class NetworkMonitor(BaseMonitor): 28 | ''' 29 | NetworkMonitor is a monitor for network activity on a specific interface. 30 | It runs on a separate thread, and currently requires root permissions 31 | or CAP_NET_RAW capabilities on Linux. 32 | ''' 33 | 34 | def __init__(self, interface, dir_path, name, logger=None): 35 | ''' 36 | :param interface: name of interface to listen to 37 | :param dir_path: path to store captured pcaps 38 | :param name: name of the monitor 39 | :param logger: logger for the monitor instance 40 | ''' 41 | super(NetworkMonitor, self).__init__(name, logger) 42 | self._iface = interface 43 | self._path = dir_path 44 | self._packets = None 45 | self._sock = None 46 | 47 | def setup(self): 48 | ''' 49 | Open the L2socket. 50 | ''' 51 | self._packets = None 52 | self._sock = conf.L2socket(iface=self._iface) 53 | super(NetworkMonitor, self).setup() 54 | 55 | def pre_test(self, test_number): 56 | ''' 57 | Clean the packet list. 58 | ''' 59 | super(NetworkMonitor, self).pre_test(test_number) 60 | self._packets = [] 61 | 62 | def post_test(self): 63 | ''' 64 | Store the pcap. 65 | ''' 66 | filename = os.path.join(self._path, 'test_%s.pcap' % self.test_number) 67 | packets = self._packets[:] 68 | if len(packets): 69 | wrpcap(filename, packets) 70 | else: 71 | self.logger.debug('No packets to write') 72 | self._packets = None 73 | self.report.add('pcap file', filename) 74 | super(NetworkMonitor, self).post_test() 75 | 76 | def teardown(self): 77 | ''' 78 | Close the L2socket. 79 | ''' 80 | self.logger.debug('closing socket') 81 | self._sock.close() 82 | super(NetworkMonitor, self).teardown() 83 | 84 | def _monitor_func(self): 85 | ''' 86 | Called in a loop. 87 | ''' 88 | self.logger.debug('in _monitor_func') 89 | if self._sock: 90 | packet = self._sock.recv(1600) 91 | if packet and (self._packets is not None): 92 | self._packets.append(packet) 93 | 94 | 95 | if __name__ == '__main__': 96 | import time 97 | print('creating monitor') 98 | mon = NetworkMonitor('lo', '/tmp/', 'Dummy Monitor') 99 | print('calling mon.setup') 100 | mon.setup() 101 | print('calling mon.pre_test') 102 | mon.pre_test(1) 103 | print('calling time.sleep(5)') 104 | time.sleep(5) 105 | print('calling mon.post_test') 106 | mon.post_test() 107 | print('calling mon.teardown') 108 | mon.teardown() 109 | print('---- done ----') 110 | -------------------------------------------------------------------------------- /katnip/monitors/serial.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2016 Cisco Systems, Inc. and/or its affiliates. All rights reserved. 2 | # 3 | # This file is part of Katnip. 4 | # 5 | # Katnip is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 2 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Katnip is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Katnip. If not, see . 17 | from __future__ import absolute_import 18 | import re 19 | import os 20 | import time 21 | import serial 22 | from threading import Lock 23 | from kitty.monitors.base import BaseMonitor 24 | 25 | 26 | class SerialMonitor(BaseMonitor): 27 | ''' 28 | SerialMonitor monitors the output of a serial connection by looking for a 29 | pattern in the serial output. 30 | 31 | This monitor captures all the received data from the serial, 32 | but it is also able to detect successful/failed tests 33 | by looking for specific patterns in the serial output. 34 | 35 | .. note:: 36 | 37 | The monitor can work either with a success pattern 38 | (failure if pattern was not found) 39 | or with a failure pattern 40 | (success if pattern was not found) 41 | 42 | :examples: 43 | 44 | Setting a monitor that will fail a test if a line that contains 45 | "stack smashing detected" appears in the serial 46 | 47 | :: 48 | 49 | monitor = SerialMonitor('detect smash monitor', '/dev/ttyUSB0', capture_dir='serial_caps') 50 | monitor.set_failure_pattern('stack smashing detected') 51 | 52 | Setting a monitor that will fail a test if a line that contains either 53 | "reboot" or "restart" appears on the serial (utilizing regex) 54 | 55 | :: 56 | 57 | monitor = SerialMonitor('detect reboot monitor', '/dev/ttyUSB0', capture_dir='serial_caps') 58 | monitor.set_failure_pattern('(reboot)|(restart)') 59 | ''' 60 | 61 | def __init__(self, name, dev_name=None, baudrate=115200, 62 | capture_dir='.', logger=None): 63 | ''' 64 | :param name: name of the monitor object 65 | :param dev_name: serial device 66 | :param baudrate: serial baudrate 67 | :param capture_dir: where to store the captured serial output 68 | :param logger: logger for the monitor object 69 | ''' 70 | super(SerialMonitor, self).__init__(name, logger) 71 | self.pattern_cbs = [] 72 | self.dev_name = dev_name 73 | self.baudrate = baudrate 74 | self.serial = None 75 | self.fd = None 76 | self.fdlock = Lock() 77 | file_template = 'test_%(test_num)d_%(timestamp)s_serial.txt' 78 | self.name_pattern = os.path.join(capture_dir, file_template) 79 | self.current_file_name = None 80 | 81 | def setup(self): 82 | self.serial = serial.Serial(self.dev_name, self.baudrate) 83 | super(SerialMonitor, self).setup() 84 | 85 | def teardown(self): 86 | super(SerialMonitor, self).teardown() 87 | if self.serial is not None: 88 | self.serial.close() 89 | if self.fd is not None: 90 | self.fd.close() 91 | 92 | def add_pattern_callback(self, pattern, cb): 93 | ''' 94 | Add a pattern to search for on the serial output, and the callback that 95 | will be called when the pattern is found. 96 | 97 | :type pattern: str 98 | :param pattern: regular expression pattern to be searched for in the serial output 99 | :type cb: callable 100 | :param cb: the callback to be called when pattern is found; must accept 3 params: 101 | (1) a SerialMonitor instance 102 | (2) the matching line 103 | (3) the re match object of the found match 104 | ''' 105 | self.pattern_cbs.append((re.compile(pattern), cb)) 106 | 107 | def add_success_pattern(self, success_pattern): 108 | ''' 109 | Set a pattern that declares the test successful if received 110 | 111 | :type success_pattern: str 112 | :param success_pattern: regular expression pattern of output that signifies success (e.g. no bug there) 113 | ''' 114 | def success_cb(self, line, match): 115 | self.report.success() 116 | self.add_pattern_callback(success_pattern, success_cb) 117 | 118 | def set_success_pattern(self, success_pattern): 119 | return self.add_success_pattern(success_pattern) 120 | 121 | def add_failure_pattern(self, failure_pattern): 122 | ''' 123 | Set a pattern that declares the test as failed if received 124 | 125 | :type failure_pattern: str 126 | :param failure_pattern: regular expression pattern of output that signifies failure (e.g. potential bug there) 127 | ''' 128 | def failure_cb(self, line, match): 129 | self.report.failed('failure pattern [%s] matched line [%s]' % (match.re.pattern, line)) 130 | self.add_pattern_callback(failure_pattern, failure_cb) 131 | 132 | def set_failure_pattern(self, failure_pattern): 133 | return self.add_failure_pattern(failure_pattern) 134 | 135 | def close_fd(self): 136 | if self.fd is not None: 137 | self.fdlock.acquire() 138 | self.fd.close() 139 | self.fd = None 140 | self.fdlock.release() 141 | 142 | def post_test(self): 143 | self.report.add('capture_file_name', self.current_file_name) 144 | if self.fd is not None: 145 | self.close_fd() 146 | self.current_file_name = None 147 | super(SerialMonitor, self).post_test() 148 | 149 | def pre_test(self, test_number): 150 | super(SerialMonitor, self).pre_test(test_number) 151 | newfilename = self.name_pattern % { 152 | 'test_num': self.test_number, 153 | 'timestamp': str(int(time.time())) 154 | } 155 | dirname = os.path.dirname(newfilename) 156 | if not os.path.exists(dirname): 157 | os.makedirs(dirname) 158 | newfd = open(newfilename, 'wb') 159 | self.fdlock.acquire() 160 | oldfd = self.fd 161 | self.fd = newfd 162 | self.fdlock.release() 163 | self.current_file_name = newfilename 164 | if oldfd is not None: 165 | oldfd.close() 166 | 167 | def _monitor_func(self): 168 | ''' 169 | Called in a loop. 170 | ''' 171 | line = self.serial.readline() 172 | if line: 173 | for pattern, cb in self.pattern_cbs: 174 | match = pattern.search(line) 175 | if match: 176 | cb(self, line, match) 177 | self.fdlock.acquire() 178 | if self.fd is not None: 179 | self.fd.write(line) 180 | self.fdlock.release() 181 | 182 | -------------------------------------------------------------------------------- /katnip/monitors/ssh.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2016 Cisco Systems, Inc. and/or its affiliates. All rights reserved. 2 | # 3 | # This file is part of Katnip. 4 | # 5 | # Katnip is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 2 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Katnip is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Katnip. If not, see . 17 | 18 | import time 19 | 20 | from kitty.monitors.base import BaseMonitor 21 | from katnip.utils.sshutils import ReconnectingSSHConnection 22 | 23 | 24 | class SSHMonitor(BaseMonitor): 25 | ''' 26 | SSHMonitor monitors target ip 27 | and runs a command over SSH in case it is not responding. 28 | ''' 29 | 30 | def __init__(self, name, username, password, hostname, port, 31 | status_command, restart_command=None, logger=None): 32 | ''' 33 | :param name: name of the object 34 | :param username: ssh login username 35 | :param password: ssh login password 36 | :param hostname: ssh server ip 37 | :param port: ssh server port 38 | :param status_command: command to make sure target is alive 39 | :param restart_command: command to restart the target in case it is deadore 40 | :param logger: logger for this object (default: None) 41 | ''' 42 | super(SSHMonitor, self).__init__(name, logger) 43 | 44 | self._status_command = status_command 45 | self._restart_command = restart_command 46 | self._ssh = ReconnectingSSHConnection(hostname, port, username, password) 47 | 48 | def teardown(self): 49 | self._ssh.close() 50 | super(SSHMonitor, self).teardown() 51 | 52 | def _ssh_command(self, cmd): 53 | return_code = stdout = stderr = None 54 | try: 55 | return_code, stdout, stderr = self._ssh.exec_command(cmd) 56 | except KeyboardInterrupt: 57 | raise 58 | except Exception as e: 59 | self.logger.debug('SSHMonitor: ssh command exec error: %s' % str(e)) 60 | return return_code, stdout, stderr 61 | 62 | def post_test(self): 63 | return_code, stdout, stderr = self._ssh_command(self._status_command) 64 | if return_code != 0: 65 | self.report.add('status_command', self._status_command) 66 | self.report.add('status_command return code', return_code) 67 | self.report.add('status_command stdout', stdout) 68 | self.report.add('status_command stderr', stderr) 69 | self.report.failed("got non-zero return code") 70 | if self._restart_command: 71 | self.logger.info('target not responding - restarting target !!!') 72 | return_code, stdout, stderr = self._ssh_command(self._restart_command) 73 | self.report.add('restart_command', self._restart_command) 74 | self.report.add('restart_command return code', return_code) 75 | self.report.add('restart_command stdout', stdout) 76 | self.report.add('restart_command stderr', stderr) 77 | super(SSHMonitor, self).post_test() 78 | 79 | def pre_test(self, test_number): 80 | super(SSHMonitor, self).pre_test(test_number) 81 | 82 | while self._ssh_command(self._status_command)[0] != 0: 83 | self.logger.debug('waiting for target to be up') 84 | time.sleep(1) 85 | 86 | def _monitor_func(self): 87 | ''' 88 | Nothing is done here, so we use a sleep for now. 89 | ''' 90 | time.sleep(0.1) 91 | -------------------------------------------------------------------------------- /katnip/monitors/ssh_file.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2016 Cisco Systems, Inc. and/or its affiliates. All rights reserved. 2 | # 3 | # This file is part of Katnip. 4 | # 5 | # Katnip is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 2 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Katnip is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Katnip. If not, see . 17 | 18 | import os 19 | import time 20 | import datetime 21 | 22 | from kitty.monitors.base import BaseMonitor 23 | from katnip.utils.sshutils import ReconnectingSSHConnection 24 | 25 | 26 | class SshFileMonitor(BaseMonitor): 27 | ''' 28 | SshFileMonitor monitors for files using a file_mask. 29 | If found - moves files to local folder, renaming with test number. 30 | ''' 31 | 32 | def __init__(self, name, username, password, hostname, port, 33 | file_mask, 34 | local_dir, 35 | use_scp=False, 36 | fail_if_exists=True, 37 | setup_commands=[], 38 | on_fail_command=None, 39 | on_fail_delay=0, 40 | logger=None): 41 | ''' 42 | :param name: name of the object 43 | :param username: ssh login username 44 | :param password: ssh login password 45 | :param hostname: ssh server ip 46 | :param port: ssh server port 47 | :param file_mask: file_mask to fetch 48 | :param local_dir: local_path to store fetched files 49 | :param use_scp: use the SCP protocol for transferring files instead of SFTP 50 | :param fail_if_exists: fail test if file exists (default: True) 51 | :param on_fail_command: command to run on failure (default: None) 52 | :param on_fail_delay: time to sleep after running on_fail_command (default: 0) 53 | :param logger: logger for this object (default: None) 54 | ''' 55 | super(SshFileMonitor, self).__init__(name, logger) 56 | 57 | self._file_mask = file_mask 58 | self._local_dir = local_dir 59 | self._fail_if_exists = fail_if_exists 60 | self._setup_commands = setup_commands 61 | self._on_fail_command = on_fail_command 62 | self._on_fail_delay = on_fail_delay 63 | self._ssh = ReconnectingSSHConnection(hostname, port, username, password, use_scp) 64 | 65 | def _ssh_command(self, cmd): 66 | return_code = None 67 | try: 68 | return_code, self._stdout, self._stderr = self._ssh.exec_command(cmd) 69 | except KeyboardInterrupt: 70 | raise 71 | except Exception as e: 72 | self.logger.debug('SSHFileMonitor: ssh command exec error: %s' % str(e)) 73 | 74 | return return_code 75 | 76 | def setup(self): 77 | ''' 78 | Called at the begining of the fuzzing session 79 | ''' 80 | super(SshFileMonitor, self).setup() 81 | timestamp = datetime.datetime.now().strftime("%Y_%m_%d-%H_%M_%S") 82 | self._local_dir = os.path.join(self._local_dir, timestamp) 83 | os.makedirs(self._local_dir) 84 | 85 | for command in self._setup_commands: 86 | self.logger.debug('running remote setup command: %s' % command) 87 | res = self._ssh_command(command) 88 | if res != 0: 89 | self.logger.debug('Error running command: %s. got %s' % (command, res)) 90 | self.logger.debug('stdout: %s' % self._stdout) 91 | self.logger.debug('stderr: %s' % self._stderr) 92 | 93 | def teardown(self): 94 | if self._ssh: 95 | self._ssh.close() 96 | self._ssh = None 97 | super(SshFileMonitor, self).teardown() 98 | 99 | def post_test(self): 100 | cmd = 'ls %s' % self._file_mask 101 | return_code = self._ssh_command(cmd) 102 | self.report.add('file_mask', self._file_mask) 103 | if return_code == 0: 104 | ls_stdout = self._stdout 105 | ls_stderr = self._stderr 106 | del ls_stderr 107 | self.report.failed('found core file') 108 | self.report.add('remote file', ls_stdout.strip()) 109 | remote_path = ls_stdout.strip() 110 | local_path = os.path.join(self._local_dir, 'test_%05d' % self.test_number) 111 | self._ssh.get(str(remote_path), str(local_path)) 112 | self._ssh.remove(str(remote_path)) 113 | 114 | self.report.add('local file', local_path) 115 | if self._on_fail_command: 116 | self.logger.info('running remote on fail command: %s', self._on_fail_command) 117 | self.report.add('on_fail_command', self._on_fail_command) 118 | self.report.add('on_fail_delay', self._on_fail_delay) 119 | retval = self._ssh_command(self._on_fail_command) 120 | self.logger.info('on fail command returned %s' % retval) 121 | if retval != 0: 122 | self.logger.info('on fail command stdout %s' % self._stdout) 123 | self.logger.info('on fail command stderr %s' % self._stderr) 124 | else: 125 | self.logger.info('sleeping for %s seconds' % self._on_fail_delay) 126 | time.sleep(self._on_fail_delay) 127 | 128 | super(SshFileMonitor, self).post_test() 129 | 130 | def X_pre_test(self, test_number): 131 | super(SshFileMonitor, self).pre_test(test_number) 132 | 133 | def _monitor_func(self): 134 | ''' 135 | Nothing is done here, so we use a sleep for now. 136 | ''' 137 | time.sleep(0.1) 138 | -------------------------------------------------------------------------------- /katnip/monitors/telnet.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2016 Cisco Systems, Inc. and/or its affiliates. All rights reserved. 2 | # 3 | # This file is part of Katnip. 4 | # 5 | # Katnip is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 2 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Katnip is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Katnip. If not, see . 17 | 18 | ''' 19 | TelnetMonitor monitors the output of a telnet connection by looking for a 20 | pattern in the command output 21 | ''' 22 | import re 23 | import os 24 | import time 25 | import telnetlib 26 | from kitty.monitors.base import BaseMonitor 27 | 28 | 29 | class TelnetMonitor(BaseMonitor): 30 | def __init__(self, name, username, password, host, port=23, 31 | cmd_timeout=3, capture_dir='.', logger=None): 32 | ''' 33 | :param name: name of the monitor 34 | :param username: remote username 35 | :param password: remote password 36 | :param host: telnet host 37 | :param port: telnet port (default: 23) 38 | :param cmd_timeout: timtout for running the command (default: 3) 39 | :param capture_dir: where to store the telnet output (default: ='.') 40 | :param logger: logger for the object (default: None) 41 | ''' 42 | super(TelnetMonitor, self).__init__(name, logger) 43 | self.success_pattern = None 44 | self.success_pattern_str = None 45 | self.failure_pattern = None 46 | self.failure_pattern_str = None 47 | self.username = username 48 | self.password = password 49 | self.host = host 50 | self.port = port 51 | self.cmd_timeout = cmd_timeout 52 | self.tn = None 53 | self.tn_mon = None 54 | self.fd = None 55 | file_template = 'test_%(test_num)d_%(timestamp)s_telnet.txt' 56 | self.name_pattern = os.path.join(capture_dir, file_template) 57 | self.current_file_name = None 58 | self._pre_test_cmds = [] 59 | self._post_test_cmds = [] 60 | self._monitor_cmds = [] 61 | 62 | def _read_until(self, tn, expected): 63 | resp = tn.read_until(expected, self.cmd_timeout) 64 | if expected in resp: 65 | return resp 66 | else: 67 | raise Exception('%s: timeout while waiting for expected: "%s"' 68 | % (self.name, expected)) 69 | 70 | def _login(self, tn): 71 | ''' 72 | .. todo:: need to make it more robust 73 | ''' 74 | self._read_until(tn, 'login:') 75 | tn.write(self.username + '\n') 76 | self._read_until(tn, 'Password:') 77 | tn.write(self.password + '\n') 78 | self._read_until(tn, 'Using network console') 79 | 80 | def _do_cmd(self, tn, cmd, expected_output): 81 | tn.write(cmd + '\n') 82 | if expected_output is not None: 83 | output = tn.read_until(expected_output, self.cmd_timeout) 84 | if expected_output in output: 85 | return (True, output) 86 | else: 87 | return (False, output) 88 | else: 89 | output = tn.read_some() 90 | return (True, output) 91 | 92 | def setup(self): 93 | self.tn = telnetlib.Telnet(self.host, self.port) 94 | self.tn_mon = telnetlib.Telnet(self.host, self.port) 95 | self._login(self.tn) 96 | self._login(self.tn_mon) 97 | for cmd, expected_output in self._monitor_cmds: 98 | self._do_cmd(self.tn_mon, cmd, expected_output) 99 | super(TelnetMonitor, self).setup() 100 | 101 | def teardown(self): 102 | super(TelnetMonitor, self).teardown() 103 | if self.tn is not None: 104 | self.tn.close() 105 | if self.tn_mon is not None: 106 | self.tn_mon.close() 107 | if self.fd is not None: 108 | self.fd.close() 109 | 110 | def set_monitor_command(self, cmd): 111 | self.monitor_command = cmd 112 | 113 | def set_success_pattern(self, success_pattern): 114 | '''set a pattern that declares the test successful if received''' 115 | self.success_pattern = re.compile(success_pattern) 116 | self.success_pattern_str = success_pattern 117 | 118 | def set_failure_pattern(self, failure_pattern): 119 | '''set a pattern that declares the test a failure if received''' 120 | self.failure_pattern = re.compile(failure_pattern) 121 | self.failure_pattern_str = failure_pattern 122 | 123 | def add_pre_test_cmd(self, cmd, expected_output=None): 124 | self._pre_test_cmds.append((cmd, expected_output)) 125 | 126 | def add_post_test_cmd(self, cmd, expected_output=None): 127 | self._post_test_cmds.append((cmd, expected_output)) 128 | 129 | def add_monitor_cmd(self, cmd, expected_output=None): 130 | self._monitor_cmds.append((cmd, expected_output)) 131 | 132 | def post_test(self): 133 | cmd_results = [] 134 | for cmd, expected_output in self._post_test_cmds: 135 | success, output = self._do_cmd(self.tn, cmd, expected_output) 136 | result = (cmd, output, expected_output, success) 137 | if not success: 138 | self.logger.debug('MONITOR MARKED REPORT FAILED AS TRUE !') 139 | self.report.failed('post test command failed') 140 | self.report.add('cmd', cmd) 141 | self.report.add('expected_output', expected_output) 142 | self.report.add('actual output', output) 143 | cmd_results.append(result) 144 | self.report.add('post test commands', cmd_results) 145 | 146 | self.report.add('capture_file_name', self.current_file_name) 147 | if self.fd is not None: 148 | fd = self.fd 149 | self.fd = None 150 | fd.close() 151 | self.current_file_name = None 152 | 153 | super(TelnetMonitor, self).post_test() 154 | 155 | def pre_test(self, test_number): 156 | super(TelnetMonitor, self).pre_test(test_number) 157 | newfilename = self.name_pattern % { 158 | 'test_num': self.test_number, 159 | 'timestamp': str(int(time.time())) 160 | } 161 | dirname = os.path.dirname(newfilename) 162 | if not os.path.exists(dirname): 163 | os.makedirs(dirname) 164 | newfd = open(newfilename, 'wb') 165 | oldfd = self.fd 166 | self.fd = newfd 167 | self.current_file_name = newfilename 168 | if oldfd is not None: 169 | oldfd.close() 170 | 171 | for cmd, expected_output in self._pre_test_cmds: 172 | success, output = self._do_cmd(self.tn, cmd, expected_output) 173 | if not success: 174 | self.report.failed('pre test command failed') 175 | self.report.add('cmd', cmd) 176 | self.report.add('expected_output', expected_output) 177 | self.report.add('actual output', output) 178 | 179 | def _monitor_func(self): 180 | ''' 181 | Nothing is done here, so we use a sleep for now. 182 | ''' 183 | time.sleep(0.1) 184 | -------------------------------------------------------------------------------- /katnip/targets/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2016 Cisco Systems, Inc. and/or its affiliates. All rights reserved. 2 | # 3 | # This file is part of Katnip. 4 | # 5 | # Katnip is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 2 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Katnip is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Katnip. If not, see . 17 | 18 | -------------------------------------------------------------------------------- /katnip/targets/application.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2016 Cisco Systems, Inc. and/or its affiliates. All rights reserved. 2 | # 3 | # This file is part of Katnip. 4 | # 5 | # Katnip is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 2 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Katnip is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Katnip. If not, see . 17 | 18 | import os 19 | import time 20 | import select 21 | from subprocess import Popen, PIPE 22 | from kitty.targets.server import ServerTarget 23 | 24 | 25 | class ApplicationTarget(ServerTarget): 26 | ''' 27 | ApplicationTarget will run an application for each fuzzed payloads 28 | ''' 29 | 30 | def __init__(self, name, path, args, env=None, tempfile=None, timeout=2, logger=None): 31 | ''' 32 | :param name: name of the object 33 | :param path: path to the target executable 34 | :param args: arguments to pass to the process 35 | :param env: the process environment (default: None) 36 | :param tempfile: temp filename to be created with the mutated data as contents (default: None) 37 | :param timeout: seconds to wait for the process stdout and stderr output before kill (default: 2) 38 | :param logger: logger for this object (default: None) 39 | 40 | :example: 41 | 42 | :: 43 | 44 | ApplicationTarget( 45 | name='ApplicationTarget', 46 | path='/tmp/myApp', 47 | args=['-a', '-b', '-c tempdata.bin'], 48 | env=None, 49 | tempfile='/tmp/tempdata.bin', 50 | timeout=1.5) 51 | 52 | Will run ``/tmp/myApp -a -b -c /tmp/tempdata.bin`` for evey mutation with timout of 1.5 seconds 53 | 54 | ''' 55 | super(ApplicationTarget, self).__init__(name, logger) 56 | self.path = path 57 | self.args = args 58 | self.env = env 59 | self.tempfile = tempfile 60 | self.timeout = timeout 61 | self.set_expect_response(False) 62 | self._process = None 63 | 64 | def pre_test(self, test_num): 65 | super(ApplicationTarget, self).pre_test(test_num) 66 | if self.tempfile: 67 | filename = self.tempfile 68 | if os.path.exists(filename): 69 | self.logger.debug('deleting %s', filename) 70 | os.unlink(filename) 71 | 72 | def _is_victim_alive(self): 73 | ''' 74 | :return: True if process is still running 75 | ''' 76 | return self._process and (self._process.poll() is None) 77 | 78 | def _stop_process(self): 79 | ''' 80 | Tries to stop the process 81 | :return: True if process was killed, False otherwise 82 | ''' 83 | if self._is_victim_alive(): 84 | self._process.terminate() 85 | time.sleep(0.5) 86 | if self._is_victim_alive(): 87 | self._process.kill() 88 | time.sleep(0.5) 89 | if self._is_victim_alive(): 90 | raise Exception('Failed to kill client process') 91 | return True 92 | else: 93 | return False 94 | 95 | def _send_to_target(self, data): 96 | self.logger.info('send called') 97 | if self.tempfile: 98 | self.logger.info('tempfile path is %s', self.tempfile) 99 | if data: 100 | self.logger.info('data length: %#x' % len(data)) 101 | end = min(len(data) - 1, 100) 102 | self.logger.info('data (start): %s', data[:end].encode('hex')) 103 | cmd = [self.path] + self.args 104 | if self.tempfile: 105 | nfile = open(self.tempfile, 'wb') 106 | nfile.write(data) 107 | nfile.close() 108 | self.logger.debug('tempfile written successfully') 109 | self.logger.debug('starting cmd: "%s"' % cmd) 110 | self._process = Popen(cmd, stdout=PIPE, stderr=PIPE, env=self.env) 111 | self.logger.debug('cmd done') 112 | else: # pipe mode 113 | self._process = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE, env=self.env) 114 | self._process.stdin.write(data) 115 | self.report.add('path', self.path) 116 | self.report.add('args', str(self.args)) 117 | self.report.add('process_id', self._process.pid) 118 | 119 | def _read(self, fd): 120 | resp = '' 121 | start = time.time() 122 | end = start + self.timeout 123 | wait_time = end - start 124 | while wait_time > 0: 125 | rl, wl, xl = select.select([fd], [], [], wait_time) 126 | if rl: 127 | resp += rl[0].read() 128 | wait_time = end - time.time() 129 | return resp 130 | 131 | def post_test(self, test_num): 132 | self.report.add('stdout', self._read(self._process.stdout)) 133 | self.report.add('stderr', self._read(self._process.stderr)) 134 | if self._process.poll() is None: 135 | self.logger.info('process is running, lets kill it!') 136 | self._stop_process() 137 | else: 138 | self.logger.debug('return code: %d', self._process.returncode) 139 | self.report.add('return_code', self._process.returncode) 140 | if self._process.returncode != 0: 141 | self.report.failed('Application failed. Return Code: %d' % self._process.returncode) 142 | self._process = None 143 | super(ApplicationTarget, self).post_test(test_num) 144 | -------------------------------------------------------------------------------- /katnip/targets/file.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2016 Cisco Systems, Inc. and/or its affiliates. All rights reserved. 2 | # 3 | # This file is part of Katnip. 4 | # 5 | # Katnip is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 2 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Katnip is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Katnip. If not, see . 17 | 18 | import os 19 | from kitty.targets.server import ServerTarget 20 | 21 | 22 | class FileTarget(ServerTarget): 23 | ''' 24 | FileTarget will create files with the fuzzed payloads 25 | ''' 26 | 27 | def __init__(self, name, file_path, base_name, postfix=None, logger=None): 28 | ''' 29 | :param name: name of the target 30 | :param file_path: path to stores files at 31 | :param base_name: base file name, it will be appended by the test number 32 | :param postfix: filename postfix (default: None) 33 | :param logger: logger for the object (default: None) 34 | 35 | :example: 36 | 37 | :: 38 | 39 | FileTarget('FileTarget', '/tmp', 'fuzzed', '.bin') 40 | 41 | Will generate the followinf files: 42 | 43 | :: 44 | 45 | /tmp/fuzzed_0.bin 46 | /tmp/fuzzed_1.bin 47 | /tmp/fuzzed_2.bin 48 | ... 49 | ''' 50 | super(FileTarget, self).__init__(name, logger) 51 | self.path = file_path 52 | self.base_name = base_name 53 | self.postfix = postfix 54 | self.full_path = None 55 | self.set_expect_response(False) 56 | 57 | def pre_test(self, test_num): 58 | super(FileTarget, self).pre_test(test_num) 59 | filename = '%s_%d' % (self.base_name, self.test_number) 60 | if self.postfix: 61 | filename = '%s.%s' % (filename, self.postfix) 62 | self.full_path = os.path.join(self.path, filename) 63 | if os.path.exists(self.full_path): 64 | self.logger.debug('deleting %s', self.full_path) 65 | os.unlink(self.full_path) 66 | self.report.add('fuzzed_file_path', self.full_path) 67 | 68 | def _send_to_target(self, data): 69 | self.logger.debug('file path is %s', self.full_path) 70 | if data: 71 | self.logger.debug('data length: %#x' % len(data)) 72 | end = min(len(data) - 1, 100) 73 | self.logger.debug('data (start): %s', data[:end].encode('hex')) 74 | if self.full_path: 75 | self.logger.debug('opening file') 76 | nfile = open(self.full_path, 'wb') 77 | nfile.write(data) 78 | nfile.close() 79 | self.logger.debug('file written successfully') 80 | else: 81 | self.logger.error( 82 | 'send called without setting path (in pre_transmit)' 83 | ) 84 | raise ValueError( 85 | 'send called without setting path (in pre_transmit)' 86 | ) 87 | -------------------------------------------------------------------------------- /katnip/targets/raw_udp.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2016 Cisco Systems, Inc. and/or its affiliates. All rights reserved. 2 | # 3 | # This file is part of Katnip. 4 | # 5 | # Katnip is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 2 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Katnip is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Katnip. If not, see . 17 | 18 | import socket 19 | from scapy.all import Ether, IP, UDP, Raw 20 | from udp import UdpTarget 21 | 22 | 23 | class RawUdpTarget(UdpTarget): 24 | ''' 25 | RawUdpTarget is implementation of a UDP target using a raw socket 26 | ''' 27 | 28 | def __init__(self, name, interface, host, port, timeout=None, logger=None): 29 | ''' 30 | :param name: name of the target 31 | :param interface: interface name 32 | :param host: host ip (to send data to) currently unused 33 | :param port: port to send to 34 | :param timeout: socket timeout (default: None) 35 | :param logger: logger for the object (default: None) 36 | ''' 37 | super(RawUdpTarget, self).__init__(name, host, port, timeout, logger) 38 | self._interface = interface 39 | 40 | def _prepare_socket(self): 41 | self.socket = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.IPPROTO_IP) 42 | self.socket.bind((self._interface, 0)) 43 | 44 | def _send_to_target(self, data): 45 | ether = Ether(dst='ff:ff:ff:ff:ff:ff') 46 | ip = IP(src=self.host, dst='255.255.255.255') 47 | udp = UDP(sport=68, dport=self.port) 48 | payload = Raw(load=data) 49 | packet = str(ether / ip / udp / payload) 50 | self.logger.debug('Sending header+data to host: %s:%d' % (self.host, self.port)) 51 | self.socket.send(packet) 52 | self.logger.debug('Header+data sent to host') 53 | 54 | def _receive_from_target(self): 55 | return self.socket.recvfrom(1024) 56 | -------------------------------------------------------------------------------- /katnip/targets/serial.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2016 Cisco Systems, Inc. and/or its affiliates. All rights reserved. 2 | # 3 | # This file is part of Kitty. 4 | # 5 | # Kitty is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 2 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Kitty is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Kitty. If not, see . 17 | ''' 18 | .. warning:: This module is not tested yet. 19 | ''' 20 | from __future__ import absolute_import 21 | import serial 22 | from kitty.core import KittyException 23 | from kitty.targets.server import ServerTarget 24 | 25 | 26 | class SerialTarget(ServerTarget): 27 | ''' 28 | Fuzzing over serial (uart) line. 29 | 30 | You can tell the target whether to reconnect each test ('pre_test'), 31 | or only at the beginning of the entire fuzzing session ('setup'), 32 | by specifying the matching string as the open_at parameter to ``__init__`` 33 | ''' 34 | 35 | def __init__(self, name, device, baudrate=115200, timeout=0.5, 36 | open_at='setup', logger=None, expect_response=False): 37 | ''' 38 | :param name: name of the target 39 | :param device: serial device name/path 40 | :param baudrate: baud rate of the serial channel (default: 115200) 41 | :param timeout: receive timeout on the channel in seconds (default: 0.5) 42 | :type open_at: str 43 | :param open_at: 44 | at what stage should the port be opened. 45 | Either 'setup' or 'pre_test' (default: 'setup') 46 | :param logger: logger for this object (default: None) 47 | :param expect_response: 48 | should wait for response from the victim (default: False) 49 | 50 | :examples: 51 | 52 | >>> SerialTarget('SomeTarget', '/dev/ttyUSB0', 57600) 53 | >>> SerialTarget('ToTarget', '/dev/ttyUSB0', timeout=5) 54 | ''' 55 | super(SerialTarget, self).__init__(name, logger, expect_response) 56 | self.device = device 57 | self.baudrate = baudrate 58 | self.timeout = timeout 59 | self.open_at = open_at 60 | if self.open_at not in ['setup', 'pre_test']: 61 | raise KittyException('open_at must be either "setup" or "pre_test"') 62 | 63 | def _send_to_target(self, payload): 64 | self.serial.write(payload) 65 | 66 | def _receive_from_target(self): 67 | return self.serial.read(10000) 68 | 69 | def setup(self): 70 | super(SerialTarget, self).setup() 71 | self._conn_open('setup') 72 | 73 | def teardown(self): 74 | self._conn_close('setup') 75 | super(SerialTarget, self).teardown() 76 | 77 | def pre_test(self, test_num): 78 | ''' 79 | Called before each test 80 | 81 | :param test_num: the test number 82 | ''' 83 | self._conn_open('pre_test') 84 | super(SerialTarget, self).pre_test(test_num) 85 | 86 | def post_test(self, test_num): 87 | ''' 88 | Called after each test 89 | 90 | :param test_num: the test number 91 | ''' 92 | self._conn_close('pre_test') 93 | super(SerialTarget, self).post_test(test_num) 94 | 95 | def _conn_open(self, stage): 96 | if self.open_at == stage: 97 | self._conn_close(stage) 98 | self.serial = serial.Serial(self.device, self.baudrate) 99 | self.serial.timeout = self.timeout 100 | 101 | def _conn_close(self, stage): 102 | if self.open_at == stage: 103 | if self.serial: 104 | self.serial.close() 105 | self.serial = None 106 | 107 | -------------------------------------------------------------------------------- /katnip/targets/ssl.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2016 Cisco Systems, Inc. and/or its affiliates. All rights reserved. 2 | # 3 | # This file is part of Katnip. 4 | # 5 | # Katnip is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 2 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Katnip is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Katnip. If not, see . 17 | 18 | import socket 19 | import ssl 20 | from katnip.targets.tcp import TcpTarget 21 | 22 | 23 | class SslTarget(TcpTarget): 24 | ''' 25 | SslTarget is an implementation of SSL target, 26 | used for testing HTTPs etc. 27 | ''' 28 | 29 | def __init__(self, name, host, port, timeout=None, logger=None): 30 | ''' 31 | :param name: name of the target 32 | :param host: host ip (to send data to) currently unused 33 | :param port: port to send to 34 | :param timeout: socket timeout (default: None) 35 | :param logger: logger for the object (default: None) 36 | ''' 37 | super(SslTarget, self).__init__(name, host, port, timeout, logger=None) 38 | 39 | def _get_socket(self): 40 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 41 | return ssl.wrap_socket(sock) 42 | -------------------------------------------------------------------------------- /katnip/targets/tcp.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2016 Cisco Systems, Inc. and/or its affiliates. All rights reserved. 2 | # 3 | # This file is part of Katnip. 4 | # 5 | # Katnip is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 2 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Katnip is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Katnip. If not, see . 17 | 18 | import socket 19 | import time 20 | import traceback 21 | from kitty.targets import ServerTarget 22 | from kitty.core import KittyException 23 | 24 | 25 | class TcpTarget(ServerTarget): 26 | ''' 27 | TcpTarget is implementation of a TCP target for the ServerFuzzer 28 | ''' 29 | 30 | def __init__(self, name, host, port, max_retries=10, timeout=None, logger=None): 31 | ''' 32 | :param name: name of the target 33 | :param host: host ip (to send data to) currently unused 34 | :param port: port to send to 35 | :param max_retries: maximum connection retries (default: 10) 36 | :param timeout: socket timeout (default: None) 37 | :param logger: logger for the object (default: None) 38 | ''' 39 | super(TcpTarget, self).__init__(name, logger) 40 | self.host = host 41 | self.port = port 42 | if (host is None) or (port is None): 43 | raise ValueError('host and port may not be None') 44 | self.timeout = timeout 45 | self.socket = None 46 | self.max_retries = max_retries 47 | 48 | def pre_test(self, test_num): 49 | super(TcpTarget, self).pre_test(test_num) 50 | retry_count = 0 51 | while self.socket is None and retry_count < self.max_retries: 52 | sock = self._get_socket() 53 | if self.timeout is not None: 54 | sock.settimeout(self.timeout) 55 | try: 56 | retry_count += 1 57 | sock.connect((self.host, self.port)) 58 | self.socket = sock 59 | except Exception: 60 | sock.close() 61 | self.logger.error('Error: %s' % traceback.format_exc()) 62 | self.logger.error('Failed to connect to target server, retrying...') 63 | time.sleep(1) 64 | if self.socket is None: 65 | raise(KittyException('TCPTarget: (pre_test) cannot connect to server (retries = %d' % retry_count)) 66 | 67 | def _get_socket(self): 68 | ''' 69 | get a socket object 70 | ''' 71 | return socket.socket(socket.AF_INET, socket.SOCK_STREAM) 72 | 73 | def post_test(self, test_num): 74 | ''' 75 | Called after a test is completed, perform cleanup etc. 76 | ''' 77 | if self.socket is not None: 78 | self.socket.close() 79 | self.socket = None 80 | super(TcpTarget, self).post_test(test_num) 81 | 82 | def _send_to_target(self, data): 83 | self.socket.send(data) 84 | 85 | def _receive_from_target(self): 86 | return self.socket.recv(10000) 87 | -------------------------------------------------------------------------------- /katnip/targets/udp.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2016 Cisco Systems, Inc. and/or its affiliates. All rights reserved. 2 | # 3 | # This file is part of Katnip. 4 | # 5 | # Katnip is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 2 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Katnip is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Katnip. If not, see . 17 | 18 | import socket 19 | from kitty.targets.server import ServerTarget 20 | 21 | 22 | class UdpTarget(ServerTarget): 23 | ''' 24 | UdpTarget is implementation of a UDP target 25 | ''' 26 | 27 | def __init__(self, name, host, port, timeout=None, logger=None): 28 | ''' 29 | :param name: name of the target 30 | :param host: host ip (to send data to) currently unused 31 | :param port: port to send to 32 | :param timeout: socket timeout (default: None) 33 | :param logger: logger for the object (default: None) 34 | ''' 35 | super(UdpTarget, self).__init__(name, logger) 36 | self.host = host 37 | self.port = port 38 | if (host is None) or (port is None): 39 | raise ValueError('host and port may not be None') 40 | self.timeout = timeout 41 | self.socket = None 42 | self.bind_host = None 43 | self.bind_port = None 44 | self.expect_response = False 45 | 46 | def set_binding(self, host, port, expect_response=False): 47 | ''' 48 | enable binding of socket to given ip/address 49 | ''' 50 | self.bind_host = host 51 | self.bind_port = port 52 | self.expect_response = expect_response 53 | self._do_bind() 54 | 55 | def _do_bind(self): 56 | self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 57 | self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 58 | self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) 59 | self.socket.bind((self.bind_host, self.bind_port)) 60 | 61 | def _prepare_socket(self): 62 | if self.bind_host is None: 63 | self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 64 | else: 65 | self._do_bind() 66 | 67 | def pre_test(self, test_num): 68 | super(UdpTarget, self).pre_test(test_num) 69 | if self.socket is None: 70 | self._prepare_socket() 71 | if self.timeout is not None: 72 | self.socket.settimeout(self.timeout) 73 | 74 | def post_test(self, test_num): 75 | super(UdpTarget, self).post_test(test_num) 76 | if self.socket is not None: 77 | self.socket.close() 78 | self.socket = None 79 | 80 | def _send_to_target(self, data): 81 | self.logger.debug('Sending data to host: %s:%d' % (self.host, self.port)) 82 | self.socket.sendto(data, (self.host, self.port)) 83 | 84 | def _receive_from_target(self): 85 | return self.socket.recvfrom(1024) 86 | -------------------------------------------------------------------------------- /katnip/templates/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2016 Cisco Systems, Inc. and/or its affiliates. All rights reserved. 2 | # 3 | # This file is part of Katnip. 4 | # 5 | # Katnip is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 2 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Katnip is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Katnip. If not, see . 17 | 18 | ''' 19 | Templates for different protocols 20 | 21 | .. note:: 22 | 23 | the templates themselves are not documented in here, 24 | please take a look at the source for the list of templates. 25 | ''' 26 | -------------------------------------------------------------------------------- /katnip/templates/apetagv2.py: -------------------------------------------------------------------------------- 1 | from kitty.model import Container, Static, OneOf, ElementCount 2 | from kitty.model import LE32, SizeInBytes, String, RandomBytes 3 | from kitty.model import ENC_INT_LE 4 | import struct 5 | 6 | 7 | class apev2item(Container): 8 | def __init__(self, key, value, flags=0, fuzzable=True): 9 | fields = [ 10 | SizeInBytes(name="Size of Item Value", sized_field="Item Value", length=32, 11 | encoder=ENC_INT_LE), 12 | LE32(name="Item Flags", value=flags), 13 | String(name="Item Key", value=key, fuzzable=False), 14 | Static("\x00"), 15 | Container(name="Item Value", fields=[value]) 16 | ] 17 | super(apev2item, self).__init__(name=key, fields=fields, fuzzable=fuzzable) 18 | 19 | 20 | class apev2textitem(apev2item): 21 | def __init__(self, key, value, fuzzable=True): 22 | value_field = String(name="key", value=value) 23 | super(apev2textitem, self).__init__(key, value_field, flags=0, fuzzable=fuzzable) 24 | 25 | 26 | apev2container = Container(name="apev2container", fields=[ 27 | Static("APETAGEX"), 28 | LE32(name="version", value=struct.unpack(". 17 | 18 | ''' 19 | Tempalte of a bittorent file. 20 | 21 | Use it directly, or copy and modify it, 22 | as it generates many ( > 1M ) payloads. 23 | 24 | This template is based on the MetaInfo file structure: 25 | https://wiki.theory.org/BitTorrentSpecification#Metainfo_File_Structure 26 | ''' 27 | from katnip.legos.bittorrent import * 28 | from kitty.model import * 29 | 30 | 31 | bittorent_base_template = Template(name='metainfo', fields=[ 32 | TDict(fuzz_delims=False, fields={ 33 | 'info': Container(name='info', fields=[ 34 | OneOf(name='info_multi', fields=[ 35 | TDict(name='info_single', fuzz_delims=False, fields={ 36 | # common fields 37 | 'piece-length': TInteger(value=20), 38 | 'pieces': TString(value=RandomBytes(value='\x00'*20*20, min_length=0, max_length=1000, step=20, name='pieces value')), 39 | 'private': TInteger(value=0), 40 | # single file fields 41 | 'name': TString(value='the file name', name='name value'), 42 | 'length': TInteger(value=400), 43 | 'md5sum': TString(value=RandomBytes(value='AA' * 16, min_length=0, max_length=1000, name='md5sum value')), 44 | }), 45 | TDict(name='info_multi', fuzz_delims=False, fields={ 46 | # common fields 47 | 'piece-length': TInteger(value=20), 48 | 'pieces': TString(value=RandomBytes(value='\x00'*20*20, min_length=0, max_length=1000, step=20, name='pieces value')), 49 | 'private': TInteger(value=0), 50 | # multi file fields 51 | 'name': TString(value='kitty', name='directory path'), 52 | 'files': TList(name='files', fuzz_delims=False, fields=[ 53 | TDict(name='file1', fuzz_delims=False, fields={ 54 | 'name': TString(value='the file name', name='file1 name value'), 55 | 'length': TInteger(value=400), 56 | 'md5sum': TString(value=RandomBytes(value='AA' * 16, min_length=0, max_length=1000, name='file1 md5sum value')), 57 | }), 58 | TDict(name='file2', fuzz_delims=False, fields={ 59 | 'name': TString(value='the file name', name='file2 name value'), 60 | 'length': TInteger(value=400), 61 | 'md5sum': TString(value=RandomBytes(value='AA' * 16, min_length=0, max_length=1000, name='file2 md5sum value')), 62 | }), 63 | TDict(name='file3', fuzz_delims=False, fields={ 64 | 'name': TString(value='the file name', name='file3 name value'), 65 | 'length': TInteger(value=400), 66 | 'md5sum': TString(value=RandomBytes(value='AA' * 16, min_length=0, max_length=1000, name='file3 md5sum value')), 67 | }) 68 | ]) 69 | }) 70 | ]) 71 | ]), 72 | 'announce': TString('http://anounce.url'), # TODO 73 | 'announce-list': TList(), # TODO 74 | 'creation-date': TInteger(1), # TODO 75 | 'comment': TString(value='my comment'), 76 | 'created-by': TString(value='kitty'), 77 | 'encoding': TString('utf-8') 78 | }, name='topmost') 79 | ]) -------------------------------------------------------------------------------- /katnip/templates/bootp.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2016 Cisco Systems, Inc. and/or its affiliates. All rights reserved. 2 | # 3 | # This file is part of Katnip. 4 | # 5 | # Katnip is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 2 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Katnip is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Katnip. If not, see . 17 | 18 | ''' 19 | bootp basic Template 20 | ''' 21 | 22 | from kitty.model import * 23 | 24 | 25 | test_bootp_template = Template(name='test bootp', fields=[ 26 | # 1 is Request 2 is reply 27 | Byte(1, encoder=ENC_INT_LE, name='Op'), 28 | # Hardware type 29 | Byte(1, encoder=ENC_INT_LE, name='HType'), 30 | # Hardware Address Length 31 | Byte(6, encoder=ENC_INT_LE, name='HLen'), 32 | # Hops 33 | Byte(0, encoder=ENC_INT_LE, name='Hops'), 34 | # Transaction ID 35 | RandomBytes('\xaa\xbb\xcc\xdd', min_length=4, max_length=4, name='XID'), 36 | # Seconds elapsed 37 | Word(0, encoder=ENC_INT_LE, name='Secs'), 38 | # Unicast flag 39 | Word(0, encoder=ENC_INT_LE, name='Flags'), 40 | # Client IP Address 41 | Dword(0, encoder=ENC_INT_LE, name='CIAddr'), 42 | # Your IP Address 43 | Dword(0, encoder=ENC_INT_LE, name='YIAddr'), 44 | # Server IP Address 45 | Dword(0, encoder=ENC_INT_LE, name='SIAddr'), 46 | # Gateway IP Address 47 | Dword(0, encoder=ENC_INT_LE, name='GIAddr'), 48 | Pad( 49 | pad_length=16 * 8, 50 | # Client Hardware Address 51 | fields=String('\xaa\xbb\xcc\xdd\xee\xff', max_size=16, name='CHAddr') 52 | ), 53 | Pad( 54 | pad_length=64 * 8, 55 | # Server Host Name 56 | fields=String('\x00' * 64, max_size=64, name='SName') 57 | ), 58 | Pad( 59 | pad_length=128 * 8, 60 | # Boot filename 61 | fields=String('\x00' * 128, max_size=128, name='SName') 62 | ), 63 | Dword(0x63538263, encoder=ENC_INT_LE, name='Magic_Cookie'), 64 | Pad( 65 | pad_length=60 * 8, 66 | # Server Host Name 67 | fields=String('\x00' * 60, max_size=60, name='Vend') 68 | ), 69 | Repeat( 70 | min_times=0, 71 | max_times=160, 72 | fields=[ 73 | RandomBytes('\x00' * 8, min_length=8, max_length=9, name='TLV Type'), 74 | SizeInBytes('TLV Value', length=8, encoder=ENC_INT_LE, fuzzable=True, name='TLV Length'), 75 | String('\x00', max_size=255, name='TLV Value'), 76 | ], 77 | name='Random TLV' 78 | ) 79 | ]) 80 | -------------------------------------------------------------------------------- /katnip/templates/ftp.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2016 Cisco Systems, Inc. and/or its affiliates. All rights reserved. 2 | # 3 | # This file is part of Katnip. 4 | # 5 | # Katnip is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 2 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Katnip is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Katnip. If not, see . 17 | 18 | ''' 19 | FTP Protocol command templates. 20 | Based on RFC 959 (https://www.ietf.org/rfc/rfc959.txt) 21 | 22 | Nice presentation about FTP can be found here: 23 | http://www.csun.edu/~jeffw/Semesters/2006Fall/COMP429/Presentations/Ch25-FTP.pdf 24 | ''' 25 | 26 | from kitty.model import * 27 | 28 | 29 | class TelnetString(Template): 30 | ''' 31 | represents: [Command][Parameter] 32 | ''' 33 | def __init__(self, command, optional=False, parameter=None, name=None): 34 | ''' 35 | :param command: command string 36 | :param optional: has optional parameter (default: False) 37 | :param parameter: optional parameter string(default: None) 38 | :param name: name of the field (default: None) 39 | ''' 40 | if name is None: 41 | name = 'CMD_%s' % command 42 | fields = [] 43 | fields.append(String(value=command, name='string_COMMAND_%s' % command)) 44 | if parameter is not None: 45 | if optional: 46 | optional_fields = [] 47 | optional_fields.append(Delimiter(value=' ', name='delim_space')) 48 | optional_fields.append(String(value=parameter, name='string_%s_PARAM_' % command)) 49 | fields.append(Repeat(optional_fields, min_times=0, max_times=2)) 50 | else: 51 | fields.append(Delimiter(value=' ', name='delim_space')) 52 | fields.append(String(value=parameter, name='string_%s_PARAM_' % command)) 53 | fields.append(Delimiter(value='\r\n', name='delim_CRLF', fuzzable=False)) 54 | super(TelnetString, self).__init__(name=name, fields=fields) 55 | 56 | # 4.1.1. ACCESS CONTROL COMMANDS (page 25) 57 | # USER 58 | user_command = TelnetString(command='USER', parameter='user') 59 | 60 | # PASS 61 | pass_command = TelnetString(command='PASS', parameter='user') 62 | 63 | # ACCT 64 | acct_command = TelnetString(command='ACCT', parameter='anonymous') 65 | 66 | # CWD 67 | cwd_command = TelnetString(command='CWD', parameter='/') 68 | 69 | # CHANGE TO PARENT DIRECTORY (CDUP) 70 | # CDUP 71 | cdup_command = TelnetString(command='CDUP') 72 | 73 | # more commands ... 74 | # SMNT 75 | smnt_command = TelnetString(command='SMNT', parameter='/etc/passwd') 76 | 77 | # LOGOUT (QUIT) 78 | # QUIT 79 | quit_command = TelnetString(command='QUIT') 80 | 81 | # REIN 82 | rein_command = TelnetString(command='REIN') 83 | # PORT 84 | port_command = TelnetString(command='PORT', parameter='1234') 85 | # PASV 86 | pasv_command = TelnetString(command='PASV') 87 | # TYPE 88 | type_command = TelnetString(command='TYPE', parameter='1234') 89 | # STRU 90 | stru_command = TelnetString(command='STRU', parameter='1234') 91 | # MODE 92 | mode_command = TelnetString(command='MODE', parameter='KittyDir') 93 | # RETR 94 | retr_command = TelnetString(command='RETR', parameter='KittyDir') 95 | # STOR 96 | stor_command = TelnetString(command='STOR', parameter='KittyDir') 97 | # STOU 98 | stou_command = TelnetString(command='STOU') 99 | # APPE 100 | appe_command = TelnetString(command='APPE', parameter='KittyDir') 101 | # ALLO 102 | # [ R ] 103 | allo_command = Template(name='CMD_ALLO', fields=[ 104 | String(value='ALLO', name='string_COMMAND_ALLO'), 105 | Repeat( [ 106 | Delimiter(value=' ', name='delim_space1'), 107 | UInt8(value=17, name='decimal_ALLO_PARAM_1', encoder=ENC_INT_DEC), 108 | Delimiter(value=' ', name='delim_space2'), 109 | String(value='R', name='string_ALLO_PARAM_2'), 110 | Delimiter(value=' ', name='delim_space3'), 111 | UInt8(value=17, name='decimal_ALLO_PARAM_3', encoder=ENC_INT_DEC) 112 | ], 113 | min_times=0, max_times=2), 114 | Delimiter(value='\r\n', name='delim_CRLF', fuzzable=False) 115 | ]) 116 | # REST 117 | rest_command = TelnetString(command='REST', parameter='HelloKitty') 118 | # RNFR 119 | rnfr_command = TelnetString(command='RNFR', parameter='KittyDir') 120 | # RNTO 121 | rnto_command = TelnetString(command='RNTO', parameter='KittyDir') 122 | # ABOR 123 | abor_command = TelnetString(command='ABOR') 124 | # DELE 125 | dele_command = TelnetString(command='DELE', parameter='KittyDir') 126 | # RMD 127 | rmd_command = TelnetString(command='RMD', parameter='KittyDir') 128 | # MKD 129 | mkd_command = TelnetString(command='MKD', parameter='KittyDir') 130 | # PWD 131 | pwd_command = TelnetString(command='PWD') 132 | # LIST [ ] 133 | list_command = TelnetString(command='LIST', parameter='/') 134 | # NLST [ ] 135 | nlst_command = TelnetString(command='NLST', parameter='KittyDir') 136 | # SITE 137 | site_command = TelnetString(command='SITE', parameter='HelloKitty') 138 | # SYST 139 | syst_command = TelnetString(command='SYST') 140 | # STAT [ ] 141 | stat_command = TelnetString(command='STAT', parameter='KittyDir', optional=True) 142 | # HELP [ ] 143 | help_command = TelnetString(command='HELP', parameter='HelloKitty', optional=True) 144 | # NOOP 145 | noop_command = TelnetString(command='SYST') 146 | 147 | -------------------------------------------------------------------------------- /katnip/templates/mp3.py: -------------------------------------------------------------------------------- 1 | from kitty.model import Template, Container, Static, OneOf 2 | from id3v23 import id3v23container 3 | from apetagv2 import apev2container 4 | 5 | mp3base = Template(name="mp3base", fields=[ 6 | OneOf(name="tag", fields=[ 7 | id3v23container, 8 | apev2container 9 | ]), 10 | Container(name="audio_frame", fields=[ 11 | Static(name="default_data", 12 | value="fff310c40002d11aec01401001fffffffffa9c0c0c0c0cf9d5fffff312c40103593f1401803800ffffffd3ff512097c1f37a81aa27fff310c40102f91ee809c08000fffffffff288b841789e3f0008fff310c40202f91b007800143e0403f5fffff0a809cd56000c07fff310c403036916ec7800612003f3ffffea082126016d4d000cfff310c40203291f1c78000e3f0703f5fffff106507aaee2000afff310c40202e1170c7800143f0503f5fffff0606980fa000e07fff310c40302e803307800447203fffffb7fea702755000e0703fff310c40402a123307800872af5fffff8d19ea50281fffffffffff312c40602a11f2819401002fe71a21c85fffffffffff3140dfcfff310c40903493f1401803801207f424a2afffffffc1a51e416fff310c408028006d801c000000e164c414d45332e3937202861fff310c40b00000348000000006c70686129aaaaaaaaaaaaaaaa".decode("hex")), 13 | ]) 14 | ]) 15 | -------------------------------------------------------------------------------- /katnip/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cisco-sas/katnip/6252c44ba69eb9206cedc8dcca3122b3b1e322ff/katnip/utils/__init__.py -------------------------------------------------------------------------------- /katnip/utils/sshutils.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2016 Cisco Systems, Inc. and/or its affiliates. All rights reserved. 2 | # 3 | # This file is part of Katnip. 4 | # 5 | # Katnip is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 2 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Katnip is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Katnip. If not, see . 17 | import paramiko 18 | try: 19 | import scp 20 | scp_imported = True 21 | except ImportError: 22 | scp_imported = False 23 | 24 | 25 | class ReconnectingSSHConnection(object): 26 | """ 27 | A wrapper around paramiko's SSHClient which handles connection dropouts gracefully. 28 | """ 29 | def __init__(self, hostname, port, username, password, use_scp=False, scp_sanitize=None): 30 | """ 31 | :param hostname: ssh server hostname or ip 32 | :param port: ssh server port 33 | :param username: ssh login username 34 | :param password: ssh login password 35 | :param use_scp: use the SCP protocol for transferring files instead of SFTP (default: False) 36 | :param scp_sanitize: sanitization function used on filenames passed to the scp module, if used. (defaut: no sanitization) 37 | """ 38 | self._hostname = hostname 39 | self._port = port 40 | self._username = username 41 | self._password = password 42 | self._use_scp = use_scp 43 | self._scp_sanitize = scp_sanitize if callable(scp_sanitize) else lambda s:s 44 | 45 | if self._use_scp and not scp_imported: 46 | raise Exception("The scp package needs to be installed in order to copy files with scp") 47 | 48 | self._paramiko = paramiko.SSHClient() 49 | self._paramiko.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 50 | 51 | def _ensure_connected(self): 52 | if self._paramiko.get_transport() is None or not self._paramiko.get_transport().isAlive(): 53 | self._paramiko.connect(self._hostname, self._port, self._username, self._password) 54 | 55 | def _sftp(self): 56 | return self._paramiko.open_sftp() 57 | 58 | def _scp(self): 59 | return scp.SCPClient(self._paramiko.get_transport(), sanitize=self._scp_sanitize) 60 | 61 | def exec_command(self, command): 62 | """ 63 | Execute a command on the ssh server. 64 | 65 | :param command: the command string to execute 66 | :returns: a tuple of the return_code from the command, the stdout output and the stderr output 67 | """ 68 | self._ensure_connected() 69 | stdin, stdout, stderr = self._paramiko.exec_command(command) 70 | return_code = stdout.channel.recv_exit_status() 71 | return return_code, stdout.read(), stderr.read() 72 | 73 | def close(self): 74 | """ 75 | Close the connection 76 | """ 77 | self._paramiko.close() 78 | 79 | def put(self, localpath, remotepath): 80 | """ 81 | Put a file on the ssh server using sftp or scp. 82 | 83 | :param localpath: the local path to the file to copy 84 | :param remotepath: the remote path to which the file should be copied 85 | :raises: OSException or IOException if file not found 86 | """ 87 | self._ensure_connected() 88 | if not self._use_scp: 89 | self._sftp().put(localpath, remotepath) 90 | else: 91 | self._scp().put(localpath, remotepath) 92 | 93 | def get(self, remotepath, localpath): 94 | """ 95 | Get a file from the ssh server using sftp or scp. 96 | 97 | :param remotepath: the remote path of the file to be copied 98 | :param localpath: the local path to which the file should be copied 99 | :raises: OSException or IOException if file not found 100 | """ 101 | self._ensure_connected() 102 | if not self._use_scp: 103 | self._sftp().get(remotepath, localpath) 104 | else: 105 | self._scp().get(remotepath, localpath) 106 | 107 | def remove(self, remotepath): 108 | """ 109 | Remove a file from the ssh server using sftp or scp. 110 | 111 | :param remotepath: the remote path of the file to be removed 112 | :raises: OSException or IOException if file not found 113 | """ 114 | self._ensure_connected() 115 | if not self._use_scp: 116 | self._sftp().remove(remotepath) 117 | else: 118 | res, stdout, stderr = self.exec_command("/bin/rm %s" % remotepath) 119 | if res != 0: 120 | raise IOError(stderr) 121 | 122 | 123 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | import os 3 | 4 | VERSION = '0.2.5' 5 | AUTHOR = 'Cisco SAS team' 6 | EMAIL = 'kitty-fuzzer@googlegroups.com' 7 | URL = 'https://github.com/cisco-sas/katnip.git' 8 | 9 | 10 | def read(fname): 11 | return open(os.path.join(os.path.dirname(__file__), fname)).read() 12 | 13 | setup( 14 | name='katnip', 15 | version=VERSION, 16 | description='Extensions for the Kitty fuzzing framework', 17 | long_description=read('README.rst'), 18 | author=AUTHOR, 19 | author_email=EMAIL, 20 | url=URL, 21 | packages=find_packages(), 22 | install_requires=['kittyfuzzer'], 23 | keywords='fuzz,fuzzing,framework,sulley,kitty,katnip', 24 | package_data={}, 25 | classifiers=[ 26 | 'Programming Language :: Python', 27 | 'Programming Language :: Python :: 2', 28 | 'Programming Language :: Python :: 2.7', 29 | 'Programming Language :: Python :: 2 :: Only', 30 | 'Topic :: Security', 31 | 'Topic :: Software Development :: Libraries :: Application Frameworks', 32 | 'Topic :: Utilities' 33 | ] 34 | ) 35 | -------------------------------------------------------------------------------- /tools/code_checker.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | _checkcommand() { 4 | hash $1 2>/dev/null 5 | } 6 | 7 | # 8 | # Check for some possible issues 9 | # 10 | echo "Looking for potential issues in the code" 11 | 12 | base_dir=$(cd $(dirname $0)/..; pwd) 13 | 14 | # 15 | # We should use logger in our code 16 | # 17 | echo "=== Using print instead of logger ===" 18 | find $base_dir -name "*.py" -exec grep -Hn -e "^\s*print" {} \; 19 | 20 | # 21 | # Run pyflakes 22 | # 23 | if _checkcommand pyflakes; then 24 | echo "=== running pep8 compliance test ===" 25 | pyflakes $base_dir/katnip/ 26 | fi -------------------------------------------------------------------------------- /unit_tests/common.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2016 Cisco Systems, Inc. and/or its affiliates. All rights reserved. 2 | # 3 | # This file is part of Katnip. 4 | # 5 | # Katnip is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 2 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Katnip is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Katnip. If not, see . 17 | ''' 18 | Common functions / classes for unit tests 19 | ''' 20 | 21 | import unittest 22 | import logging 23 | from kitty.model import Template 24 | 25 | 26 | test_logger = None 27 | 28 | 29 | def get_test_logger(module_name): 30 | global test_logger 31 | if test_logger is None: 32 | logger = logging.getLogger(module_name) 33 | logger.setLevel(logging.DEBUG) 34 | formatter = logging.Formatter( 35 | '[%(asctime)s] [%(levelname)s] -> %(message)s' 36 | ) 37 | handler = logging.FileHandler('logs/%s.log' % module_name, mode='w') 38 | handler.setFormatter(formatter) 39 | handler.setLevel(logging.DEBUG) 40 | logger.addHandler(handler) 41 | test_logger = logger 42 | return test_logger 43 | 44 | 45 | def warp_with_template(fields): 46 | ''' 47 | wrap a lego with template 48 | ''' 49 | return Template(name='uut template', fields=fields) 50 | 51 | 52 | def get_mutation_set(t, reset=True): 53 | ''' 54 | return a list of all mutations for the template 55 | ''' 56 | res = set([]) 57 | res.add(t.render().bytes) 58 | while t.mutate(): 59 | res.add(t.render().bytes) 60 | if reset: 61 | t.reset() 62 | return res 63 | 64 | 65 | def metaTest(func): 66 | def test_wrap(self): 67 | if self.__class__.__meta__: 68 | self.skipTest('Test should not run from meta class') 69 | else: 70 | return func(self) 71 | return test_wrap 72 | 73 | 74 | class BaseTestCase(unittest.TestCase): 75 | def setUp(self, field_class=None): 76 | self.logger = get_test_logger(type(self).__module__) 77 | self.logger.info('TESTING METHOD: %s', self._testMethodName) 78 | self.todo = [] 79 | self.cls = field_class 80 | 81 | def get_all_mutations(self, field, reset=True): 82 | res = [] 83 | while field.mutate(): 84 | rendered = field.render() 85 | res.append(rendered) 86 | self.logger.debug(rendered.tobytes().encode('hex')) 87 | if reset: 88 | field.reset() 89 | return res 90 | -------------------------------------------------------------------------------- /unit_tests/lego_dynamic.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2016 Cisco Systems, Inc. and/or its affiliates. All rights reserved. 2 | # 3 | # This file is part of Katnip. 4 | # 5 | # Katnip is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 2 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Katnip is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Katnip. If not, see . 17 | 18 | from katnip.legos.dynamic import DynamicExtended, DynamicInt, DynamicString 19 | from kitty.model import String 20 | from kitty.model import BE32 21 | from kitty.model import ENC_STR_BASE64_NO_NL 22 | 23 | from common import BaseTestCase, get_mutation_set 24 | 25 | 26 | class DynamicTestCase(BaseTestCase): 27 | 28 | def setUp(self): 29 | super(DynamicTestCase, self).setUp() 30 | self.def_value = '1234' 31 | self.the_key = 'the_key' 32 | 33 | 34 | class DynamicExtendedTestCase(DynamicTestCase): 35 | ''' 36 | Tests for the :class:`~katnip.legos.dynamic.DynamicExtended` 37 | ''' 38 | 39 | def test_default_value(self): 40 | additional_field = String('the_string') 41 | uut = DynamicExtended(key=self.the_key, value=self.def_value, additional_field=additional_field) 42 | res = uut.render().bytes 43 | self.assertEqual(res, self.def_value) 44 | 45 | def test_vanilla_with_string(self): 46 | additional_field = String('the_string') 47 | uut = DynamicExtended(key=self.the_key, value=self.def_value, additional_field=additional_field) 48 | addional_mutations = get_mutation_set(additional_field) 49 | uut_mutations = get_mutation_set(uut) 50 | if not any(x in uut_mutations for x in addional_mutations): 51 | raise AssertionError('Not all addional_mutations are in uut_mutations') 52 | self.assertGreater(len(uut_mutations), len(addional_mutations)) 53 | 54 | def test_set_session_data(self): 55 | additional_field = String(self.def_value) 56 | new_value = 'new_value' 57 | uut = DynamicExtended(key=self.the_key, value=self.def_value, additional_field=additional_field) 58 | res = uut.render().bytes 59 | self.assertEqual(res, self.def_value) 60 | uut.set_session_data({self.the_key: new_value}) 61 | res = uut.render().bytes 62 | self.assertEqual(res, new_value) 63 | 64 | def test_not_fuzzable(self): 65 | additional_field = String('the_string') 66 | uut = DynamicExtended(key=self.the_key, value=self.def_value, additional_field=additional_field, fuzzable=False) 67 | res = uut.render().bytes 68 | self.assertEqual(res, self.def_value) 69 | self.assertEqual(uut.num_mutations(), 0) 70 | 71 | 72 | class DynamicStringTestCase(DynamicTestCase): 73 | ''' 74 | Tests for the :class:`~katnip.legos.dynamic.DynamicString` 75 | ''' 76 | 77 | def test_default_value(self): 78 | uut = DynamicString(key=self.the_key, value=self.def_value) 79 | res = uut.render().bytes 80 | self.assertEqual(res, self.def_value) 81 | 82 | def test_vanilla(self): 83 | similar_string = String(self.def_value) 84 | uut = DynamicString(key=self.the_key, value=self.def_value) 85 | similar_mutations = get_mutation_set(similar_string) 86 | uut_mutations = get_mutation_set(uut) 87 | if not any(x in uut_mutations for x in similar_mutations): 88 | raise AssertionError('Not all similar_mutations are in uut_mutations') 89 | self.assertGreater(len(uut_mutations), len(similar_mutations)) 90 | 91 | def test_vanilla_with_encoder(self): 92 | similar_string = String(self.def_value, encoder=ENC_STR_BASE64_NO_NL) 93 | uut = DynamicString(key=self.the_key, value=self.def_value, encoder=ENC_STR_BASE64_NO_NL) 94 | similar_mutations = get_mutation_set(similar_string) 95 | uut_mutations = get_mutation_set(uut) 96 | if not any(x in uut_mutations for x in similar_mutations): 97 | raise AssertionError('Not all similar_mutations are in uut_mutations') 98 | self.assertGreater(len(uut_mutations), len(similar_mutations)) 99 | 100 | def test_limited_string(self): 101 | similar_string = String(self.def_value, max_size=len(self.def_value)) 102 | uut = DynamicString(key=self.the_key, value=self.def_value, keep_size=True) 103 | similar_mutations = get_mutation_set(similar_string) 104 | uut_mutations = get_mutation_set(uut) 105 | if not any(x in uut_mutations for x in similar_mutations): 106 | raise AssertionError('Not all similar_mutations are in uut_mutations') 107 | self.assertGreater(len(uut_mutations), len(similar_mutations)) 108 | if any(len(x) != len(self.def_value) for x in uut_mutations): 109 | raise AssertionError('There are results with different size than the default value') 110 | 111 | def test_set_session_data(self): 112 | new_value = 'new_value' 113 | uut = DynamicString(key=self.the_key, value=self.def_value) 114 | res = uut.render().bytes 115 | self.assertEqual(res, self.def_value) 116 | uut.set_session_data({self.the_key: new_value}) 117 | res = uut.render().bytes 118 | self.assertEqual(res, new_value) 119 | 120 | def test_not_fuzzable(self): 121 | uut = DynamicString(key=self.the_key, value=self.def_value, fuzzable=False) 122 | res = uut.render().bytes 123 | self.assertEqual(res, self.def_value) 124 | self.assertEqual(uut.num_mutations(), 0) 125 | 126 | 127 | class DynamicIntTestCase(DynamicTestCase): 128 | ''' 129 | Tests for the :class:`~katnip.legos.dynamic.DynamicInt` 130 | ''' 131 | def setUp(self): 132 | super(DynamicIntTestCase, self).setUp() 133 | self.bf = BE32(1234) 134 | self.def_value = self.bf.render().bytes 135 | 136 | def test_default_value(self): 137 | uut = DynamicInt(self.the_key, self.bf) 138 | res = uut.render().bytes 139 | self.assertEqual(res, self.def_value) 140 | 141 | def test_set_session_data(self): 142 | new_value = 'new_value' 143 | uut = DynamicInt(self.the_key, self.bf) 144 | res = uut.render().bytes 145 | self.assertEqual(res, self.def_value) 146 | uut.set_session_data({self.the_key: new_value}) 147 | res = uut.render().bytes 148 | self.assertEqual(res, new_value) 149 | 150 | def test_vanilla(self): 151 | uut = DynamicInt(self.the_key, self.bf) 152 | bf_mutations = get_mutation_set(self.bf) 153 | uut_mutations = get_mutation_set(uut) 154 | if not any(x in bf_mutations for x in bf_mutations): 155 | raise AssertionError('Not all similar_mutations are in uut_mutations') 156 | self.assertGreater(len(uut_mutations), len(bf_mutations)) 157 | 158 | def test_not_fuzzable(self): 159 | uut = DynamicInt(self.the_key, self.bf, fuzzable=False) 160 | res = uut.render().bytes 161 | self.assertEqual(res, self.def_value) 162 | self.assertEqual(uut.num_mutations(), 0) 163 | -------------------------------------------------------------------------------- /unit_tests/runner.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Copyright (C) 2016 Cisco Systems, Inc. and/or its affiliates. All rights reserved. 3 | # 4 | # This file is part of Katnip. 5 | # 6 | # Katnip is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 2 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # Katnip is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with Katnip. If not, see . 18 | 19 | import unittest 20 | import os 21 | import sys 22 | import inspect 23 | 24 | module_dir = 'katnip' 25 | currentdir = os.path.dirname( 26 | os.path.abspath( 27 | inspect.getfile(inspect.currentframe()))) 28 | parentdir = os.path.dirname(currentdir) 29 | sys.path.insert(0, parentdir) 30 | 31 | # Test files to use 32 | from lego_json import * 33 | from lego_url import * 34 | from lego_dynamic import * 35 | from model_low_level_encoders import * 36 | from test_model_low_level_scapy_field import * 37 | 38 | 39 | if __name__ == '__main__': 40 | if not os.path.exists('./logs'): 41 | os.mkdir('./logs') 42 | unittest.main(verbosity=10) 43 | -------------------------------------------------------------------------------- /unit_tests/test_model_low_level_radamsa_field.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2016 Cisco Systems, Inc. and/or its affiliates. All rights reserved. 2 | # 3 | # This file is part of Katnip. 4 | # 5 | # Katnip is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 2 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Katnip is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Katnip. If not, see . 17 | 18 | ''' 19 | Tests for RadamsaField: 20 | ''' 21 | 22 | from common import metaTest 23 | from test_model_low_level_field import ValueTestCase 24 | from bitstring import Bits 25 | from katnip.model.low_level.radamsa import RadamsaField 26 | 27 | 28 | class RadamsaFieldTests(ValueTestCase): 29 | 30 | __meta__ = False 31 | 32 | def setUp(self, cls=RadamsaField): 33 | super(RadamsaFieldTests, self).setUp(cls) 34 | self._fuzz_count = 500 35 | self.seed = 123111 36 | self.default_value = 'RadamsaField test' 37 | self.default_value_rendered = Bits(bytes=self.default_value) 38 | self.uut_name = 'RadamsaFieldTest' 39 | 40 | def get_default_field(self, fuzzable=True): 41 | return self.cls(value=self.default_value, fuzzable=fuzzable, name=self.uut_name, fuzz_count=self._fuzz_count, seed=self.seed) 42 | 43 | def _base_check(self, field): 44 | num_mutations = field.num_mutations() 45 | mutations = self._get_all_mutations(field) 46 | self.assertEqual(num_mutations, len(mutations)) 47 | mutations = self._get_all_mutations(field) 48 | self.assertEqual(num_mutations, len(mutations)) 49 | 50 | @metaTest 51 | def testMutateAllDifferent(self): 52 | # some time will got same data, so we skip this test. 53 | pass 54 | -------------------------------------------------------------------------------- /unit_tests/test_model_low_level_scapy_field.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2016 Cisco Systems, Inc. and/or its affiliates. All rights reserved. 2 | # 3 | # This module was authored and contributed by dark-lbp 4 | # 5 | # This file is part of Katnip. 6 | # 7 | # Katnip is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 2 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # Katnip is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with Katnip. If not, see . 19 | 20 | ''' 21 | Tests for Scapy field: 22 | ''' 23 | 24 | from common import metaTest 25 | from test_model_low_level_field import ValueTestCase 26 | from bitstring import Bits 27 | from katnip.model.low_level.scapy import * 28 | from scapy.all import * 29 | 30 | 31 | class ScapyFieldTests(ValueTestCase): 32 | 33 | __meta__ = False 34 | 35 | def setUp(self, cls=ScapyField): 36 | super(ScapyFieldTests, self).setUp(cls) 37 | self._fuzz_count = 2000 38 | self.seed = 1000 39 | random.seed(self.seed) 40 | self._fuzz_packet = IP(ttl=RandByte()) 41 | self.default_value = str(IP(ttl=RandByte())) 42 | self.default_value_rendered = Bits(bytes=self.default_value) 43 | self.uut_name = 'ScapyFieldTest' 44 | 45 | 46 | def runTest(self): 47 | pass 48 | 49 | def get_default_field(self, fuzzable=True): 50 | return self.cls(value=self._fuzz_packet, fuzzable=fuzzable, name=self.uut_name, fuzz_count=self._fuzz_count, seed=self.seed) 51 | 52 | def _base_check(self, field): 53 | num_mutations = field.num_mutations() 54 | mutations = self._get_all_mutations(field) 55 | self.assertEqual(num_mutations, len(mutations)) 56 | mutations = self._get_all_mutations(field) 57 | self.assertEqual(num_mutations, len(mutations)) 58 | 59 | 60 | @metaTest 61 | def testMutateAllDifferent(self): 62 | # some time will got same data, so we skip this test. 63 | pass 64 | 65 | 66 | 67 | 68 | 69 | --------------------------------------------------------------------------------