├── .coveragerc ├── .gitignore ├── .gitmodules ├── .travis.yml ├── CONTRIBUTING.md ├── LICENSE ├── MANIFEST.in ├── Makefile ├── README.md ├── docs ├── Makefile ├── make.bat ├── requirements.txt └── source │ ├── conf.py │ ├── customize │ └── index.rst │ ├── final_remarks │ └── index.rst │ ├── images │ └── viper.png │ ├── index.rst │ ├── installation │ └── index.rst │ ├── known_issues │ └── index.rst │ └── usage │ ├── commands.rst │ ├── concepts.rst │ ├── index.rst │ └── web.rst ├── setup.py ├── tests ├── __init__.py ├── common │ ├── __init__.py │ └── test_objects.py ├── conftest.py ├── core │ ├── __init__.py │ ├── test_archiver.py │ ├── test_config.py │ ├── test_database.py │ └── ui │ │ ├── __init__.py │ │ ├── test_commands.py │ │ └── test_console.py ├── modules │ ├── __init__.py │ ├── test_apk.py │ ├── test_clamav.py │ ├── test_email.py │ ├── test_fuzzy.py │ ├── test_lief.py │ ├── test_macho.py │ ├── test_misp.py │ ├── test_office.py │ ├── test_pe.py │ ├── test_rat.py │ └── test_swf.py └── test_usecases.py ├── tox.ini └── viper ├── __init__.py ├── common ├── __init__.py ├── abstracts.py ├── autorun.py ├── colors.py ├── exceptions.py ├── network.py ├── objects.py ├── out.py ├── utils.py └── version.py ├── core ├── __init__.py ├── archiver.py ├── config.py ├── database.py ├── logger.py ├── plugins.py ├── project.py ├── session.py ├── storage.py └── ui │ ├── __init__.py │ ├── cmd │ ├── __init__.py │ ├── about.py │ ├── analysis.py │ ├── clear.py │ ├── close.py │ ├── copy.py │ ├── delete.py │ ├── export.py │ ├── find.py │ ├── help.py │ ├── info.py │ ├── new.py │ ├── notes.py │ ├── open.py │ ├── parent.py │ ├── projects.py │ ├── rename.py │ ├── sessions.py │ ├── stats.py │ ├── store.py │ ├── tags.py │ └── update-modules.py │ ├── commands.py │ ├── console.py │ └── main.py └── data ├── peid └── UserDB.TXT ├── viper.conf.sample └── yara ├── embedded.yara ├── rats.yara └── vmdetect.yara /.coveragerc: -------------------------------------------------------------------------------- 1 | [html] 2 | directory = /tmp/htmlcov 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | 3 | # C extensions 4 | *.so 5 | 6 | # Packages 7 | *.egg 8 | *.egg-info 9 | .eggs 10 | .cache 11 | dist 12 | build 13 | eggs 14 | parts 15 | bin 16 | var 17 | sdist 18 | develop-eggs 19 | .installed.cfg 20 | lib 21 | lib64 22 | 23 | # Installer logs 24 | pip-log.txt 25 | 26 | # Unit test / coverage reports 27 | .coverage 28 | .tox 29 | nosetests.xml 30 | 31 | # docs 32 | htmlcov 33 | 34 | # Translations 35 | *.mo 36 | 37 | # Mr Developer 38 | .mr.developer.cfg 39 | .project 40 | .pydevproject 41 | .idea 42 | 43 | history 44 | binaries/ 45 | projects/ 46 | *.db 47 | *.conf 48 | *.bak 49 | *.log 50 | 51 | data/yara/index.yara 52 | 53 | installed_files.txt 54 | 55 | keys.py 56 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "tests/viper-test-files"] 2 | path = tests/viper-test-files 3 | url = https://github.com/viper-framework/viper-test-files 4 | branch = master 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | sudo: required 3 | 4 | dist: bionic 5 | group: edge 6 | 7 | cache: 8 | pip: true 9 | 10 | python: 11 | - 3.6 12 | - 3.6-dev 13 | - 3.7 14 | - 3.7-dev 15 | - 3.8 16 | - 3.8-dev 17 | 18 | before_install: 19 | - sudo apt-get update -qq 20 | - sudo apt-get install gcc python-socks libssl-dev swig p7zip-full unrar ssdeep libfuzzy-dev tor clamav-daemon unrar -qq 21 | - sudo service clamav-freshclam stop 22 | - sudo freshclam 23 | - sudo service clamav-freshclam start 24 | 25 | install: 26 | - pip install -U tox-travis 27 | - pip install -e . 28 | - echo "update-modules" | viper 29 | - sudo service clamav-daemon restart 30 | - sudo service clamav-freshclam status 31 | - sudo service clamav-daemon status 32 | 33 | script: 34 | - echo "exit" | viper 35 | - tox 36 | - tox -e pep8 37 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | **Wanna contribute?** Viper is an open, BSD-licensed, collaborative development effort that heavily 2 | relies on contributions from the whole community. We welcome tickets, pull requests, feature suggestions. 3 | 4 | When develping new modules or patches, please try to comply to the general code style that we try to 5 | maintain across the project. When introducing new features or fixing significant bugs, please also 6 | include some concise information and possibly also introduce comprehensive documentation in our 7 | guide. Before submitting code, please try to check it first with some code verifications tools like 8 | *pyflakes*, *pylint*, *pychecker*, and *pep8*. They help identifying basic mistakes improve the 9 | quality of the code 10 | 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2013-2020, Claudio Guarnieri (https://nex.sx) 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | Individual Licenses for modules 32 | 33 | IDX Module Licensed under the Apache License, Version 2.0. Original Copyright @bbaskin 34 | pymacho Licensed under the GNU License Copyright 2013 Jérémie BOUTOILLE 35 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE 2 | include README.rst 3 | include README.md 4 | include CHANGELOG 5 | include requirements.txt 6 | include requirements-*.txt 7 | include viper/data/viper.conf.sample 8 | include settings_local.py.sample 9 | recursive-include data/peid/ *.* 10 | recursive-include data/yara/ *.* 11 | global-exclude __pycache__ 12 | global-exclude *.py[co] 13 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PWD = $(shell pwd) 2 | 3 | clean: 4 | rm -rf $(PWD)/build $(PWD)/dist $(PWD)/*.egg-info 5 | 6 | dist: 7 | python3 setup.py sdist bdist_wheel 8 | 9 | upload: 10 | python3 -m twine upload dist/* 11 | 12 | test-upload: 13 | python3 -m twine upload --repository-url https://test.pypi.org/legacy/ dist/* 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **This repository is archived and development of Viper 1.x is discontinued. A full refactor of the project started with [Viper 2](https://github.com/viper-framework/viper2).** 2 | 3 | --- 4 | 5 | ![Viper](https://viper-framework.readthedocs.io/en/latest/_images/viper.png) 6 | 7 | Viper is a binary analysis and management framework. Its fundamental objective is to provide a solution to easily organize your collection of malware and exploit samples as well as your collection of scripts you created or found over the time to facilitate your daily research. 8 | 9 | ### Get started! 10 | 11 | ```bash 12 | $ pip3 install viper-framework 13 | $ viper 14 | ``` 15 | 16 | For more information and instructions on how to install it visit [viper-framework.readthedocs.io](https://viper-framework.readthedocs.io/) 17 | 18 |
19 | 20 | [![Build Status](https://api.travis-ci.org/viper-framework/viper.png?branch=master)](https://travis-ci.org/viper-framework/viper) 21 | [![Documentation Status](https://readthedocs.org/projects/viper-framework/badge/?version=latest)](http://viper-framework.readthedocs.io/en/latest/?badge=latest) 22 | [![codecov](https://codecov.io/gh/viper-framework/viper/branch/master/graph/badge.svg)](https://codecov.io/gh/viper-framework/viper) 23 | 24 | **Wanna contribute?** Viper is an open, BSD-licensed, collaborative development effort that heavily relies on contributions from the whole community. We welcome tickets, pull requests, feature suggestions. 25 | 26 | When develping new modules or patches, please try to comply to the general code style that we try to maintain across the project. When introducing new features or fixing significant bugs, please also include some concise information and possibly also introduce comprehensive documentation in our guide. 27 | -------------------------------------------------------------------------------- /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 | # Internal variables. 11 | PAPEROPT_a4 = -D latex_paper_size=a4 12 | PAPEROPT_letter = -D latex_paper_size=letter 13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source 14 | # the i18n builder cannot share the environment and doctrees with the others 15 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source 16 | 17 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 18 | 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 " latexpdf to make LaTeX files and run them through pdflatex" 32 | @echo " text to make text files" 33 | @echo " man to make manual pages" 34 | @echo " texinfo to make Texinfo files" 35 | @echo " info to make Texinfo files and run them through makeinfo" 36 | @echo " gettext to make PO message catalogs" 37 | @echo " changes to make an overview of all changed/added/deprecated items" 38 | @echo " linkcheck to check all external links for integrity" 39 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 40 | 41 | clean: 42 | -rm -rf $(BUILDDIR)/* 43 | 44 | html: 45 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 46 | @echo 47 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 48 | 49 | dirhtml: 50 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 51 | @echo 52 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 53 | 54 | singlehtml: 55 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 56 | @echo 57 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 58 | 59 | pickle: 60 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 61 | @echo 62 | @echo "Build finished; now you can process the pickle files." 63 | 64 | json: 65 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 66 | @echo 67 | @echo "Build finished; now you can process the JSON files." 68 | 69 | htmlhelp: 70 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 71 | @echo 72 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 73 | ".hhp project file in $(BUILDDIR)/htmlhelp." 74 | 75 | qthelp: 76 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 77 | @echo 78 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 79 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 80 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Viper.qhcp" 81 | @echo "To view the help file:" 82 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Viper.qhc" 83 | 84 | devhelp: 85 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 86 | @echo 87 | @echo "Build finished." 88 | @echo "To view the help file:" 89 | @echo "# mkdir -p $$HOME/.local/share/devhelp/Viper" 90 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Viper" 91 | @echo "# devhelp" 92 | 93 | epub: 94 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 95 | @echo 96 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 97 | 98 | latex: 99 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 100 | @echo 101 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 102 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 103 | "(use \`make latexpdf' here to do that automatically)." 104 | 105 | latexpdf: 106 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 107 | @echo "Running LaTeX files through pdflatex..." 108 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 109 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 110 | 111 | text: 112 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 113 | @echo 114 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 115 | 116 | man: 117 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 118 | @echo 119 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 120 | 121 | texinfo: 122 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 123 | @echo 124 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 125 | @echo "Run \`make' in that directory to run these through makeinfo" \ 126 | "(use \`make info' here to do that automatically)." 127 | 128 | info: 129 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 130 | @echo "Running Texinfo files through makeinfo..." 131 | make -C $(BUILDDIR)/texinfo info 132 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 133 | 134 | gettext: 135 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 136 | @echo 137 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 138 | 139 | changes: 140 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 141 | @echo 142 | @echo "The overview file is in $(BUILDDIR)/changes." 143 | 144 | linkcheck: 145 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 146 | @echo 147 | @echo "Link check complete; look for any errors in the above output " \ 148 | "or in $(BUILDDIR)/linkcheck/output.txt." 149 | 150 | doctest: 151 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 152 | @echo "Testing of doctests in the sources finished, look at the " \ 153 | "results in $(BUILDDIR)/doctest/output.txt." 154 | -------------------------------------------------------------------------------- /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. linkcheck to check all external links for integrity 37 | echo. doctest to run all doctests embedded in the documentation if enabled 38 | goto end 39 | ) 40 | 41 | if "%1" == "clean" ( 42 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 43 | del /q /s %BUILDDIR%\* 44 | goto end 45 | ) 46 | 47 | if "%1" == "html" ( 48 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 49 | if errorlevel 1 exit /b 1 50 | echo. 51 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 52 | goto end 53 | ) 54 | 55 | if "%1" == "dirhtml" ( 56 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 57 | if errorlevel 1 exit /b 1 58 | echo. 59 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 60 | goto end 61 | ) 62 | 63 | if "%1" == "singlehtml" ( 64 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 65 | if errorlevel 1 exit /b 1 66 | echo. 67 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 68 | goto end 69 | ) 70 | 71 | if "%1" == "pickle" ( 72 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 73 | if errorlevel 1 exit /b 1 74 | echo. 75 | echo.Build finished; now you can process the pickle files. 76 | goto end 77 | ) 78 | 79 | if "%1" == "json" ( 80 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 81 | if errorlevel 1 exit /b 1 82 | echo. 83 | echo.Build finished; now you can process the JSON files. 84 | goto end 85 | ) 86 | 87 | if "%1" == "htmlhelp" ( 88 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 89 | if errorlevel 1 exit /b 1 90 | echo. 91 | echo.Build finished; now you can run HTML Help Workshop with the ^ 92 | .hhp project file in %BUILDDIR%/htmlhelp. 93 | goto end 94 | ) 95 | 96 | if "%1" == "qthelp" ( 97 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 98 | if errorlevel 1 exit /b 1 99 | echo. 100 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 101 | .qhcp project file in %BUILDDIR%/qthelp, like this: 102 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Viper.qhcp 103 | echo.To view the help file: 104 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Viper.ghc 105 | goto end 106 | ) 107 | 108 | if "%1" == "devhelp" ( 109 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 110 | if errorlevel 1 exit /b 1 111 | echo. 112 | echo.Build finished. 113 | goto end 114 | ) 115 | 116 | if "%1" == "epub" ( 117 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 118 | if errorlevel 1 exit /b 1 119 | echo. 120 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 121 | goto end 122 | ) 123 | 124 | if "%1" == "latex" ( 125 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 129 | goto end 130 | ) 131 | 132 | if "%1" == "text" ( 133 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 134 | if errorlevel 1 exit /b 1 135 | echo. 136 | echo.Build finished. The text files are in %BUILDDIR%/text. 137 | goto end 138 | ) 139 | 140 | if "%1" == "man" ( 141 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 142 | if errorlevel 1 exit /b 1 143 | echo. 144 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 145 | goto end 146 | ) 147 | 148 | if "%1" == "texinfo" ( 149 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 150 | if errorlevel 1 exit /b 1 151 | echo. 152 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 153 | goto end 154 | ) 155 | 156 | if "%1" == "gettext" ( 157 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 158 | if errorlevel 1 exit /b 1 159 | echo. 160 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 161 | goto end 162 | ) 163 | 164 | if "%1" == "changes" ( 165 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 166 | if errorlevel 1 exit /b 1 167 | echo. 168 | echo.The overview file is in %BUILDDIR%/changes. 169 | goto end 170 | ) 171 | 172 | if "%1" == "linkcheck" ( 173 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 174 | if errorlevel 1 exit /b 1 175 | echo. 176 | echo.Link check complete; look for any errors in the above output ^ 177 | or in %BUILDDIR%/linkcheck/output.txt. 178 | goto end 179 | ) 180 | 181 | if "%1" == "doctest" ( 182 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 183 | if errorlevel 1 exit /b 1 184 | echo. 185 | echo.Testing of doctests in the sources finished, look at the ^ 186 | results in %BUILDDIR%/doctest/output.txt. 187 | goto end 188 | ) 189 | 190 | :end 191 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viper-framework/viper/fdd7855249340cdf114447822026ded66f17d6a3/docs/requirements.txt -------------------------------------------------------------------------------- /docs/source/final_remarks/index.rst: -------------------------------------------------------------------------------- 1 | Final Remarks 2 | ============= 3 | 4 | Developers 5 | ---------- 6 | 7 | Viper is an open and collaborative development effort. It is built by volunteers from all 8 | over the world. Following are the ones who contributed up to the latest stable release:: 9 | 10 | $ git shortlog -s -n 11 | 717 Nex 12 | 517 Raphaël Vinot 13 | 169 Robert Habermann 14 | 154 kevthehermit 15 | 70 Alexander J 16 | 68 dusby 17 | 33 frennkie 18 | 27 Christophe Vandeplas 19 | 22 emdel 20 | 19 seamus tuohy 21 | 17 xorhex 22 | 15 Chris Taylor 23 | 14 Neriberto C.Prado 24 | 9 kovacsbalu 25 | 8 Luke Snyder 26 | 6 Joakim Kennedy 27 | 6 Sascha Rommelfangen 28 | 6 ptcNOP 29 | 5 2sec4u 30 | 5 Antonio S 31 | 5 Beercow 32 | 5 SnakeByte Lab 33 | 5 magrazia 34 | 5 xor_hex 35 | 4 Brian Maloney 36 | 4 Chris Gardner 37 | 4 Georg Schölly 38 | 4 Jerome Marty 39 | 4 S2R2 40 | 4 Seth Hardy 41 | 4 ltroot 42 | 3 18z 43 | 3 = 44 | 3 Colin Cowie 45 | 3 Csaba Fitzl 46 | 3 Dionysis Grigoropoulos 47 | 3 TcM1911 48 | 3 Yu Ogawa 49 | 3 nidsche 50 | 3 razu 51 | 2 2*yo 52 | 2 Deventual 53 | 2 Enchantertim 54 | 2 Lost4Now 55 | 2 Maxou56800 56 | 2 Sam Brown 57 | 2 aaroncoffey 58 | 2 dewiestr 59 | 2 haellowyyn 60 | 2 keram79 61 | 2 ph0sec 62 | 2 wesinator 63 | 1 Akeem Spencer 64 | 1 Alex Harvey 65 | 1 Ali Ikinci 66 | 1 Allen Swackhamer 67 | 1 Boris Ryutin 68 | 1 Chris Higgins 69 | 1 David André 70 | 1 Gabor Vaspori 71 | 1 Javier Rascón Mesa 72 | 1 KevTheHermit 73 | 1 Nasicus 74 | 1 Nick Price 75 | 1 Peter 76 | 1 Rocky 77 | 1 Rocky De Wiest 78 | 1 S. Egbert 79 | 1 S0urceC0der 80 | 1 Steffen Sauler 81 | 1 Steve Clement 82 | 1 Tango43 83 | 1 Thao Vo 84 | 1 Tobias Jarmuzek 85 | 1 Tom King 86 | 1 William Robinet 87 | 1 binjo 88 | 1 deralexxx 89 | 1 dukebarman 90 | 1 dukebarman@gmail.com 91 | 1 jekil 92 | 1 nheijmans 93 | 1 tykkz 94 | 95 | 96 | Join Us 97 | ------- 98 | 99 | The best way to start contributing to the project is by start digging through the open 100 | tickets on our `GitHub`_. Before submitting code make sure you read our `Contribution Guidelines`_ 101 | and that you thoroughly tested it. 102 | 103 | You can also join our conversions by getting on IRC on `FreeNode`_ 104 | on channel ``###viper``. 105 | 106 | .. _GitHub: https://github.com/viper-framework/viper/issues 107 | .. _Contribution Guidelines: https://github.com/viper-framework/viper/blob/master/CONTRIBUTING.md 108 | .. _FreeNode: http://www.freenode.net 109 | -------------------------------------------------------------------------------- /docs/source/images/viper.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viper-framework/viper/fdd7855249340cdf114447822026ded66f17d6a3/docs/source/images/viper.png -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | ====================== 2 | Official Documentation 3 | ====================== 4 | 5 | .. image:: /images/viper.png 6 | :align: center 7 | 8 | What is Viper? 9 | ============== 10 | 11 | `Viper`_ is a binary analysis and management framework. Its fundamental objective is to provide a solution to easily organize your collection of malware and exploit samples as well as your collection of scripts you created or found over the time to facilitate your daily research. Think of it as a *Metasploit* for malware researchers: it provides a terminal interface that you can use to store, search and analyze arbitrary files with and a framework to easily create plugins of any sort. 12 | 13 | Viper is released under `BSD 3-Clause`_ license and is copyrighted by `Claudio Guarnieri`_. The source code is available on `GitHub`_, where also all development efforts and contributions are coordinated. For questions and inquiries, you can find the author's contact details `here`_. 14 | 15 | Table of Content 16 | ================ 17 | 18 | .. toctree:: 19 | introduction/index 20 | installation/index 21 | usage/index 22 | http/index 23 | customize/index 24 | known_issues/index 25 | final_remarks/index 26 | 27 | .. _Viper: http://www.viper.li 28 | .. _BSD 3-Clause: http://opensource.org/licenses/BSD-3-Clause 29 | .. _Claudio Guarnieri: https://twitter.com/botherder 30 | .. _VxCage: https://github.com/botherder/vxcage 31 | .. _GitHub: https://github.com/viper-framework/viper 32 | .. _here: https://nex.sx 33 | -------------------------------------------------------------------------------- /docs/source/installation/index.rst: -------------------------------------------------------------------------------- 1 | Installation 2 | ============ 3 | 4 | Viper is written in Python and requires **Python >= 3.6**. As of Viper 2.0, Python 2.x is no longer supported. In this documentation we will use Debian GNU/Linux based distributions, such as Ubuntu, as a reference platform. The following installation instructions should apply similarly to other distributions and possibly to Mac OS X as well, although it has not been properly tested. 5 | 6 | Before proceeding, you should make sure you have the basic tools installed to be able to compile additional Python extensions:: 7 | 8 | $ sudo apt-get install git gcc python3-dev python3-pip 9 | 10 | To install Viper from pip:: 11 | 12 | $ pip3 install viper-framework 13 | 14 | To update Viper from pip:: 15 | 16 | $ pip3 install -U viper-framework 17 | 18 | The console script `viper` will then be installed in `$HOME/.local/bin`, make sure to have the folder added to your `$PATH`. If you wish to install Viper globally:: 19 | 20 | $ sudo pip3 install viper-framework 21 | 22 | To install Viper from sources:: 23 | 24 | $ git clone https://github.com/viper-framework/viper 25 | $ cd viper 26 | $ pip3 install . 27 | 28 | 29 | First launch 30 | ------------ 31 | 32 | If everything worked out fine, you should be able to launch Viper's shell without raising any exceptions, like following:: 33 | 34 | nex@nex:~/$ viper 35 | _ 36 | (_) 37 | _ _ _ ____ _____ ____ 38 | | | | | | _ \| ___ |/ ___) 39 | \ V /| | |_| | ____| | 40 | \_/ |_| __/|_____)_| v2.0 41 | |_| 42 | 43 | You have 0 files in your default repository 44 | 45 | You do not have any modules installed! 46 | If you wish to download community modules from GitHub run: 47 | update-modules 48 | viper > 49 | 50 | On the first launch you will notice that Viper warns you that you do not have any modules installed. Since Viper 2.0 modules are installed separately. 51 | 52 | In order to have support for the most basic modules, you will need to install the following dependencies too before proceeding:: 53 | 54 | $ sudo apt-get install libssl-dev swig libffi-dev ssdeep libfuzzy-dev unrar-free p7zip-full 55 | 56 | You can now download the modules directly from our community GitHub repository using:: 57 | 58 | viper > update-modules 59 | 60 | Modules will be installed in `$HOME/.viper/modules`. If you wish to do so, you can manually add modules of your own to that folder. 61 | 62 | .. _official website: http://ssdeep.sourceforge.net 63 | .. _Tor: https://www.torproject.org 64 | .. _YARA: http://virustotal.github.io/yara/ 65 | .. _YARA-Python: https://github.com/plusvic/yara-python 66 | 67 | Uninstall 68 | --------- 69 | 70 | To uninstall Viper:: 71 | 72 | $ pip3 uninstall -y viper-framework 73 | 74 | 75 | Module Dependencies 76 | ------------------ 77 | 78 | The following dependencies are required to use specific modules. 79 | 80 | Exif:: 81 | 82 | $ sudo apt-get install exiftool 83 | 84 | ClamAV:: 85 | 86 | $ sudo apt-get install clamav-daemon 87 | 88 | Tor:: 89 | 90 | $ sudo apt-get install tor 91 | 92 | Scraper:: 93 | 94 | $ sudo apt-get install libdpkg-perl 95 | -------------------------------------------------------------------------------- /docs/source/known_issues/index.rst: -------------------------------------------------------------------------------- 1 | ============ 2 | Known issues 3 | ============ 4 | 5 | Various errors when using unicode characters 6 | ============================================ 7 | 8 | unicode and python is a not easy and using unicode in notes, tags or filenames (or other modules where userinput is allowed) might result in unhandled exceptions. 9 | 10 | Error storing file names containing unicode characters in database 11 | ================================================================== 12 | 13 | If you try to store a file with a filename containing Unicode chars it will not be stored to the database. 14 | 15 | 16 | Problem importing certain modules 17 | ================================= 18 | 19 | If you experience an issue like:: 20 | 21 | [!] Something wrong happened while importing the module modules.office: No module named oletools.olevba 22 | 23 | You are likely missing dependencies. 24 | 25 | To install required python modules run:: 26 | 27 | pip install -r requirements.txt 28 | 29 | 30 | The API interface isn't fully aware of projects 31 | =============================================== 32 | 33 | Most of the API commands are not able yet to interact with different projects, so most of the commands will 34 | be executed against the default repository. 35 | 36 | 37 | PreprocessError: data/yara/index.yara:0:Invalid file extension '.yara'.Can only include .yar 38 | ============================================================================================ 39 | 40 | If you running yara or RAT module and receiving that issue:: 41 | 42 | ... 43 | PreprocessError: data/yara/index.yara:0:Invalid file extension '.yara'.Can only include .yar 44 | ... 45 | 46 | 47 | It is most likely the versions of yara are not correct, try to run:: 48 | 49 | viper@viper:/home/viper# yara -version 50 | yara 2.1 51 | 52 | And check for the yara-python bindings:: 53 | 54 | viper@viper:/home/viper# pip freeze | grep yara 55 | yara-python==2.1 56 | 57 | 58 | If you have installed yara-python using pip it is likely you are running an older version of yara (see yara documentation for compiling howto) 59 | 60 | 61 | Error Messages in log: ssl.SSLEOFError: EOF occurred in violation of protocol 62 | ============================================================================= 63 | 64 | When running the built-in HTTPS server several error messages are logged, then the favicon is accessed. 65 | This does not represent a problem and the favicon is loaded and display. So this is currently in status ``wontfix``. 66 | 67 | Log:: 68 | 69 | 2018-02-05 14:29:33 - django.server - INFO - basehttp.py:124 - "GET /favicon.ico HTTP/1.1" 301 0 70 | Traceback (most recent call last): 71 | File "/usr/lib/python3.4/wsgiref/handlers.py", line 138, in run 72 | self.finish_response() 73 | File "/usr/lib/python3.4/wsgiref/handlers.py", line 180, in finish_response 74 | self.write(data) 75 | File "/usr/lib/python3.4/wsgiref/handlers.py", line 279, in write 76 | self._write(data) 77 | File "/usr/lib/python3.4/wsgiref/handlers.py", line 453, in _write 78 | self.stdout.write(data) 79 | File "/usr/lib/python3.4/socket.py", line 394, in write 80 | return self._sock.send(b) 81 | File "/usr/lib/python3.4/ssl.py", line 702, in send 82 | v = self._sslobj.write(data) 83 | ssl.SSLEOFError: EOF occurred in violation of protocol (_ssl.c:1638) 84 | 2018-02-05 14:29:33 - django.server - ERROR - basehttp.py:124 - "GET /favicon.ico HTTP/1.1" 500 59 85 | ---------------------------------------- 86 | Exception happened during processing of request from ('192.168.92.66', 52014) 87 | Traceback (most recent call last): 88 | File "/usr/lib/python3.4/wsgiref/handlers.py", line 138, in run 89 | self.finish_response() 90 | File "/usr/lib/python3.4/wsgiref/handlers.py", line 180, in finish_response 91 | self.write(data) 92 | File "/usr/lib/python3.4/wsgiref/handlers.py", line 279, in write 93 | self._write(data) 94 | File "/usr/lib/python3.4/wsgiref/handlers.py", line 453, in _write 95 | self.stdout.write(data) 96 | File "/usr/lib/python3.4/socket.py", line 394, in write 97 | return self._sock.send(b) 98 | File "/usr/lib/python3.4/ssl.py", line 702, in send 99 | v = self._sslobj.write(data) 100 | ssl.SSLEOFError: EOF occurred in violation of protocol (_ssl.c:1638) 101 | 102 | During handling of the above exception, another exception occurred: 103 | 104 | Traceback (most recent call last): 105 | File "/usr/lib/python3.4/wsgiref/handlers.py", line 141, in run 106 | self.handle_error() 107 | File "/home/robbie/work/viper/venv/lib/python3.4/site-packages/django/core/servers/basehttp.py", line 86, in handle_error 108 | super().handle_error() 109 | File "/usr/lib/python3.4/wsgiref/handlers.py", line 368, in handle_error 110 | self.finish_response() 111 | File "/usr/lib/python3.4/wsgiref/handlers.py", line 180, in finish_response 112 | self.write(data) 113 | File "/usr/lib/python3.4/wsgiref/handlers.py", line 274, in write 114 | self.send_headers() 115 | File "/usr/lib/python3.4/wsgiref/handlers.py", line 331, in send_headers 116 | if not self.origin_server or self.client_is_modern(): 117 | File "/usr/lib/python3.4/wsgiref/handlers.py", line 344, in client_is_modern 118 | return self.environ['SERVER_PROTOCOL'].upper() != 'HTTP/0.9' 119 | TypeError: 'NoneType' object is not subscriptable 120 | 121 | During handling of the above exception, another exception occurred: 122 | 123 | Traceback (most recent call last): 124 | File "/usr/lib/python3.4/socketserver.py", line 305, in _handle_request_noblock 125 | self.process_request(request, client_address) 126 | File "/usr/lib/python3.4/socketserver.py", line 331, in process_request 127 | self.finish_request(request, client_address) 128 | File "/usr/lib/python3.4/socketserver.py", line 344, in finish_request 129 | self.RequestHandlerClass(request, client_address, self) 130 | File "/usr/lib/python3.4/socketserver.py", line 673, in __init__ 131 | self.handle() 132 | File "/home/robbie/work/viper/venv/lib/python3.4/site-packages/django/core/servers/basehttp.py", line 154, in handle 133 | handler.run(self.server.get_app()) 134 | File "/usr/lib/python3.4/wsgiref/handlers.py", line 144, in run 135 | self.close() 136 | File "/usr/lib/python3.4/wsgiref/simple_server.py", line 35, in close 137 | self.status.split(' ',1)[0], self.bytes_sent 138 | AttributeError: 'NoneType' object has no attribute 'split' 139 | ---------------------------------------- 140 | 2018-02-05 14:29:33 - django.server - INFO - basehttp.py:124 - "GET /static/viperweb/images/favicon.png HTTP/1.1" 200 2041 141 | -------------------------------------------------------------------------------- /docs/source/usage/concepts.rst: -------------------------------------------------------------------------------- 1 | ======== 2 | Concepts 3 | ======== 4 | 5 | Before proceeding in learning the functioning of each available command and module, you need to understand some fundamental design concept that represent the foundation of Viper itself. 6 | 7 | Projects 8 | ======== 9 | 10 | Viper allows you to create and operate on a collection of files. One collection represent one **project**. 11 | 12 | You can create as many projects as you want and you can easily switch from one to another. Each project will have its own local repositories of binary files, a SQLite database containing metadata and an history file which contains all the commands you provided through Viper's shell exclusively in the context of the opened project. 13 | 14 | In this way you can for example create different workbenches for each malware campaign, malware family or threat actor you're investigating. You can also easily pack up and share the whole project folder with your friends and colleagues. 15 | 16 | As you can see from Viper's help message, you can specify a project name at startup:: 17 | 18 | nex@nex:~/$ viper -h 19 | usage: viper [-h] [-p PROJECT] 20 | 21 | optional arguments: 22 | -h, --help show this help message and exit 23 | -p PROJECT, --project PROJECT 24 | Specify a new or existing project name 25 | 26 | 27 | When doing so, Viper will try to open an existing project with the given name and if it doesn't exist it will initialize it under the ``projects/`` folder. 28 | 29 | If you opened a project, it will appear both in a startup message as well as in Viper's terminal:: 30 | 31 | nex@nex:~/$ viper -p test 32 | _ 33 | (_) 34 | _ _ _ ____ _____ ____ 35 | | | | | | _ \| ___ |/ ___) 36 | \ V /| | |_| | ____| | 37 | \_/ |_| __/|_____)_| v2.0 38 | |_| 39 | 40 | You have 0 files in your test repository 41 | test viper > 42 | 43 | From within the terminal, you can see which projects exist and eventually you can switch from one to another:: 44 | 45 | test1 viper > projects --list 46 | [*] Projects Available: 47 | +--------------+--------------------------+---------+ 48 | | Project Name | Creation Time | Current | 49 | +--------------+--------------------------+---------+ 50 | | test2 | Fri Jul 11 02:05:55 2014 | | 51 | | test1 | Fri Jul 11 02:05:51 2014 | Yes | 52 | +--------------+--------------------------+---------+ 53 | test1 viper > projects --switch test2 54 | [*] Switched to project test2 55 | test2 viper > 56 | 57 | More details on the ``projects`` command are available in the :doc:`commands` chapter. 58 | 59 | Sessions 60 | ======== 61 | 62 | Most of commands and especially modules provided by Viper, are designed to operate on a single file, being a Windows executable or a PDF or whatever else. 63 | 64 | In order to do so, you'll have to open the file of your choice and every time you do so a new **session** will be created. You'll be able to see the name of the file you opened in the terminal:: 65 | 66 | viper > open 9f2520a3056543d49bb0f822d85ce5dd 67 | [*] Session opened on ~/viper/binaries/2/d/7/9/2d79fcc6b02a2e183a0cb30e0e25d103f42badda9fbf86bbee06f93aa3855aff 68 | viper darkcomet.exe > 69 | 70 | From then on, every command and module you launch will execute against the file you just opened (if the module requires to do so obviously). 71 | 72 | Similarly to the projects, you can just as easily see which sessions you have currently opened:: 73 | 74 | viper darkcomet.exe > sessions --list 75 | [*] Opened Sessions: 76 | +---+-----------------+----------------------------------+---------------------+---------+ 77 | | # | Name | MD5 | Created At | Current | 78 | +---+-----------------+----------------------------------+---------------------+---------+ 79 | | 1 | blackshades.exe | 0d1bd081974a4dcdeee55f025423a72b | 2014-07-11 02:28:45 | | 80 | | 2 | poisonivy.exe | 22f77c113cc6d43d8c12ed3c9fb39825 | 2014-07-11 02:28:49 | | 81 | | 3 | darkcomet.exe | 9f2520a3056543d49bb0f822d85ce5dd | 2014-07-11 02:29:29 | Yes | 82 | +---+-----------------+----------------------------------+---------------------+---------+ 83 | 84 | You can eventually decide to switch to a different one:: 85 | 86 | viper darkcomet.exe > sessions --switch 1 87 | [*] Switched to session #1 on ~/viper/binaries/1/5/c/3/15c34d2b0e834727949dbacea897db33c785a32ac606c0935e3758c8dc975535 88 | viper blackshades.exe > 89 | 90 | You can also abandon the current session with the ``close`` command (the session will remain available if you wish to re-open it later):: 91 | 92 | viper blackshades.exe > close 93 | viper > 94 | 95 | A session will also keep track of the results of the last ``find`` command so that you'll be able to easily open new sessions without having to perform repeated searches on your repository. You can find more details about this in the :doc:`commands` chapter. 96 | 97 | Please note that if you switch to a whole different project, you'll lose the opened sessions. 98 | 99 | Commands & Modules 100 | ================== 101 | 102 | The operations you can execute within Viper are fundamentally distinguished between **commands** and **modules**. Commands are functions that are provided by Viper's core and enable you to interact with the file repository (by adding, searching, tagging and removing files), with projects and with sessions. They are static and they should not be modified. 103 | 104 | Modules are plugins that are dynamically loaded by Viper at startup and are contained under the ``~/.viper/modules/`` folder. Modules implement additional analytical functions that can be executed on an opened file or on the whole repository, for example: analyzing PE32 executables, parsing PDF documents, analyzing Office documents, clustering files by fuzzy hashing or imphash, etc. 105 | 106 | Modules are the most actively developed portion of Viper and they represent the most important avenue for contributions from the community: if you have an idea or you want to re-implement a script that you have lying around, make sure you `submit it`_ to Viper. 107 | 108 | .. _submit it: https://github.com/viper-framework/viper-modules 109 | 110 | Database 111 | ======== 112 | 113 | The database that stores all meta information is per default in an sqlite database stored at:: 114 | 115 | $HOME/.viper/viper.db 116 | 117 | Binaries 118 | ======== 119 | 120 | The files are stored in a folder structure within:: 121 | 122 | $HOME/.viper/binaries 123 | -------------------------------------------------------------------------------- /docs/source/usage/index.rst: -------------------------------------------------------------------------------- 1 | Usage 2 | ===== 3 | 4 | .. toctree:: 5 | concepts 6 | commands 7 | web 8 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # This file is part of Viper - https://github.com/viper-framework/viper 4 | # See the file 'LICENSE' for copying permission. 5 | 6 | import os 7 | from setuptools import setup 8 | 9 | from viper.common.version import __version__ 10 | 11 | __description__ = "Binary Analysis & Management Framework" 12 | 13 | 14 | def get_packages(package): 15 | """ 16 | Return root package and all sub-packages. 17 | """ 18 | return [dirpath 19 | for dirpath, dirnames, filenames in os.walk(package) 20 | if os.path.exists(os.path.join(dirpath, "__init__.py"))] 21 | 22 | 23 | def get_package_data(package): 24 | """ 25 | Return all files under the root package, that are not in a 26 | package themselves. 27 | """ 28 | walk = [(dirpath.replace(package + os.sep, "", 1), filenames) 29 | for dirpath, dirnames, filenames in os.walk(package) 30 | if not os.path.exists(os.path.join(dirpath, "__init__.py"))] 31 | 32 | filepaths = [] 33 | for base, filenames in walk: 34 | filepaths.extend([os.path.join(base, filename) 35 | for filename in filenames 36 | if not filename.endswith('pyc')]) 37 | return {package: filepaths} 38 | 39 | 40 | setup( 41 | name="viper-framework", 42 | version=__version__, 43 | author="Claudio Guarnieri", 44 | author_email="nex@nex.sx", 45 | description=__description__, 46 | long_description=__description__, 47 | url="http://viper.li", 48 | platforms="any", 49 | entry_points={ 50 | "console_scripts": [ 51 | "viper = viper.core.ui.main:main", 52 | ], 53 | }, 54 | packages=get_packages("viper"), 55 | package_data=get_package_data("viper"), 56 | include_package_data=True, 57 | install_requires=[ 58 | "python-magic==0.4.18", 59 | "requests[socks]==2.24.0", 60 | "requests-cache==0.5.2", 61 | "scandir==1.10", 62 | "six==1.15.0", 63 | "sqlalchemy==1.3.13", 64 | "terminaltables==3.1.0", 65 | "pydeep==0.4", 66 | "rarfile==3.1", 67 | ], 68 | zip_safe=False, 69 | tests_require=["pytest"], 70 | # BSD 3-Clause License: 71 | # - http://choosealicense.com/licenses/bsd-3-clause 72 | # - http://opensource.org/licenses/BSD-3-Clause 73 | license="BSD 3-Clause", 74 | # See https://pypi.python.org/pypi?%3Aaction=list_classifiers 75 | classifiers=[ 76 | "Topic :: Security", 77 | "License :: OSI Approved :: BSD License", 78 | "Programming Language :: Python :: 3", 79 | "Programming Language :: Python :: 3.4", 80 | "Programming Language :: Python :: 3.5", 81 | "Programming Language :: Python :: 3.6", 82 | "Programming Language :: Python :: 3.7", 83 | "Operating System :: POSIX :: Linux", 84 | ], 85 | keywords="binary analysis management malware research", 86 | ) 87 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # This file is part of Viper - https://github.com/viper-framework/viper 3 | # See the file 'LICENSE' for copying permission. 4 | -------------------------------------------------------------------------------- /tests/common/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # This file is part of Viper - https://github.com/viper-framework/viper 3 | # See the file 'LICENSE' for copying permission. 4 | -------------------------------------------------------------------------------- /tests/common/test_objects.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # This file is part of Viper - https://github.com/viper-framework/viper 3 | # See the file 'LICENSE' for copying permission. 4 | from __future__ import unicode_literals 5 | 6 | import os 7 | import sys 8 | from tests.conftest import FIXTURE_DIR 9 | from viper.common.objects import File, MispEvent 10 | import pytest 11 | 12 | 13 | class TestMispEvent: 14 | 15 | @pytest.mark.parametrize("filename", ["58e902cd-dae8-49b9-882b-186c02de0b81.json"]) 16 | def test_mispevent(self, capsys, filename): 17 | mispevent = MispEvent(os.path.join(FIXTURE_DIR, filename)) 18 | mispevent.online() 19 | mispevent.offline() 20 | ips = mispevent.get_all_ips() 21 | domains = mispevent.get_all_domains() 22 | urls = mispevent.get_all_urls() 23 | hashes = mispevent.get_all_hashes() 24 | assert '191.101.230.149' in ips 25 | assert not domains 26 | assert not urls 27 | assert '722050c1b3f110c0ac9f80bc80723407' in hashes[0] 28 | assert not hashes[1] 29 | 30 | 31 | class TestFile: 32 | @pytest.mark.parametrize("filename, name", [ 33 | ("string_handling/ascii.txt", "ascii.txt"), 34 | ("string_handling/with blank.txt", "with blank.txt") 35 | ]) 36 | def test_init(self, capsys, filename, name): 37 | instance = File(os.path.join(FIXTURE_DIR, filename)) 38 | 39 | assert isinstance(instance, File) 40 | assert instance.path == os.path.join(FIXTURE_DIR, filename) 41 | assert instance.name == name 42 | 43 | out, err = capsys.readouterr() 44 | assert out == "" 45 | 46 | @pytest.mark.skipif(sys.version_info < (3, 3), reason="requires at least python3.3") 47 | @pytest.mark.parametrize("filename, name", [ 48 | ("string_handling/dümmy.txt", "dümmy.txt"), 49 | ]) 50 | def test_init_unicode(self, capsys, filename, name): 51 | instance = File(os.path.join(FIXTURE_DIR, filename)) 52 | 53 | assert isinstance(instance, File) 54 | assert instance.path == os.path.join(FIXTURE_DIR, filename) 55 | assert instance.name == name 56 | 57 | out, err = capsys.readouterr() 58 | assert out == "" 59 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # This file is part of Viper - https://github.com/viper-framework/viper 3 | # See the file 'LICENSE' for copying permission. 4 | 5 | import pytest 6 | import tempfile 7 | import os 8 | import shutil 9 | import sys 10 | 11 | FIXTURE_DIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'viper-test-files/test_files',) 12 | 13 | collect_ignore = [] 14 | if sys.version_info < (3, 6,): 15 | collect_ignore.append('modules/test_misp.py') 16 | collect_ignore.append('modules/test_lief.py') 17 | 18 | 19 | @pytest.fixture() 20 | def cleandir(): 21 | newpath = tempfile.mkdtemp(prefix="viper_tests_tmp_") 22 | old_cwd = os.getcwd() 23 | os.chdir(newpath) 24 | yield 25 | os.chdir(old_cwd) 26 | shutil.rmtree(newpath) 27 | -------------------------------------------------------------------------------- /tests/core/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # This file is part of Viper - https://github.com/viper-framework/viper 3 | # See the file 'LICENSE' for copying permission. 4 | -------------------------------------------------------------------------------- /tests/core/test_config.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # This file is part of Viper - https://github.com/viper-framework/viper 3 | # See the file 'LICENSE' for copying permission. 4 | 5 | import re 6 | 7 | from viper.core.config import Config 8 | 9 | 10 | class TestConfig: 11 | def test_init(self): 12 | instance = Config() 13 | assert isinstance(instance, Config) 14 | assert re.search("viper.conf", instance.config_file) 15 | 16 | def test_sample(self): 17 | instance = Config() 18 | assert isinstance(instance, Config) 19 | assert instance.modules.store_output is True 20 | 21 | def test_missing_section_http_client(self): 22 | instance = Config() 23 | assert hasattr(instance, "http_client") 24 | 25 | delattr(instance, "http_client") 26 | assert instance.http_client is None 27 | 28 | instance.parse_http_client() 29 | assert hasattr(instance, "http_client") 30 | 31 | def test_sample_parse_global(self): 32 | instance = Config() 33 | 34 | instance.parse_http_client() 35 | assert instance.http_client.proxies is None 36 | assert instance.http_client.verify is True 37 | assert instance.http_client.cert is None 38 | 39 | def test_sample_parse_global_section(self): 40 | instance = Config() 41 | 42 | instance.parse_http_client(instance.cuckoo) 43 | 44 | assert instance.http_client.proxies is None 45 | assert instance.http_client.verify is True 46 | assert instance.http_client.cert is None 47 | 48 | assert instance.cuckoo.proxies is None 49 | assert instance.cuckoo.verify is True 50 | assert instance.cuckoo.cert is None 51 | 52 | def test_custom_parse_global(self): 53 | instance = Config() 54 | 55 | # http_proxy, no_proxy 56 | instance.http_client.https_proxy = None 57 | instance.parse_http_client() 58 | assert instance.http_client.proxies is None 59 | 60 | instance.http_client.https_proxy = False 61 | instance.parse_http_client() 62 | assert instance.http_client.proxies == {'http': '', 'https': '', 'no': None} 63 | 64 | instance.http_client.https_proxy = "http://prx1.example.com:3128" 65 | instance.parse_http_client() 66 | assert instance.http_client.proxies == {'http': 'http://prx1.example.com:3128', 'https': 'http://prx1.example.com:3128', 'no': None} 67 | 68 | # tls_verify 69 | instance.http_client.tls_verify = None 70 | instance.parse_http_client() 71 | assert instance.http_client.verify is True 72 | 73 | instance.http_client.tls_verify = True 74 | instance.parse_http_client() 75 | assert instance.http_client.verify is True 76 | 77 | instance.http_client.tls_verify = False 78 | instance.parse_http_client() 79 | assert instance.http_client.verify is False 80 | 81 | # tls_ca_bundle 82 | instance.http_client.tls_verify = True 83 | instance.http_client.tls_ca_bundle = "/etc/ssl/certs/ca_bundle.crt" 84 | instance.parse_http_client() 85 | assert instance.http_client.verify == "/etc/ssl/certs/ca_bundle.crt" 86 | 87 | # tls_client_cert 88 | instance.http_client.tls_client_cert = None 89 | instance.parse_http_client() 90 | assert instance.http_client.cert is None 91 | 92 | instance.http_client.tls_client_cert = "client.pem" 93 | instance.parse_http_client() 94 | assert instance.http_client.cert == "client.pem" 95 | 96 | # TODO (frennkie) Write some more parser logic validations here 97 | def test_custom_parse_global_section(self): 98 | instance = Config() 99 | 100 | # http_proxy, no_proxy 101 | instance.http_client.https_proxy = None 102 | instance.koodous.https_proxy = None 103 | instance.parse_http_client(section=instance.koodous) 104 | assert instance.koodous.proxies is None 105 | 106 | instance.http_client.https_proxy = "http://prx1.example.com:3128" 107 | instance.koodous.https_proxy = None 108 | instance.parse_http_client(section=instance.koodous) 109 | assert instance.koodous.proxies == {'http': 'http://prx1.example.com:3128', 'https': 'http://prx1.example.com:3128', 'no': None} 110 | 111 | instance.http_client.https_proxy = "http://prx1.example.com:3128" 112 | instance.koodous.https_proxy = False 113 | instance.parse_http_client(section=instance.koodous) 114 | assert instance.koodous.proxies == {'http': '', 'https': '', 'no': None} 115 | 116 | instance.http_client.https_proxy = "http://prx1.example.com:3128" 117 | instance.koodous.https_proxy = "http://prx2.example.com:8080" 118 | instance.parse_http_client(section=instance.koodous) 119 | assert instance.koodous.proxies == {'http': 'http://prx2.example.com:8080', 'https': 'http://prx2.example.com:8080', 'no': None} 120 | 121 | # tls_verify 122 | instance.parse_http_client(section=instance.koodous) 123 | assert instance.koodous.verify is True 124 | 125 | instance.koodous.tls_verify = False 126 | instance.parse_http_client(section=instance.koodous) 127 | assert instance.koodous.verify is False 128 | 129 | instance.koodous.tls_verify = True 130 | instance.parse_http_client(section=instance.koodous) 131 | assert instance.koodous.verify is True 132 | 133 | # tls_ca_bundle 134 | instance.koodous.tls_verify = True 135 | instance.koodous.tls_ca_bundle = "/etc/ssl/certs/ca_bundle2.crt" 136 | instance.parse_http_client(section=instance.koodous) 137 | assert instance.koodous.verify == "/etc/ssl/certs/ca_bundle2.crt" 138 | 139 | # tls_client_cert 140 | instance.koodous.tls_client_cert = "client_koodous.pem" 141 | instance.parse_http_client(section=instance.koodous) 142 | assert instance.koodous.cert == "client_koodous.pem" 143 | -------------------------------------------------------------------------------- /tests/core/test_database.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # This file is part of Viper - https://github.com/viper-framework/viper 3 | # See the file 'LICENSE' for copying permission. 4 | 5 | import os 6 | import sys 7 | import pytest 8 | from tests.conftest import FIXTURE_DIR 9 | 10 | from viper.core.database import Malware, Tag, Note, Analysis, Database 11 | from viper.common.objects import File 12 | from viper.common.exceptions import Python2UnsupportedUnicode 13 | 14 | 15 | class TestMalware: 16 | def test_init(self): 17 | instance = Malware(md5="ad7b9c14083b52bc532fba5948342b98", 18 | sha1="ee8cbf12d87c4d388f09b4f69bed2e91682920b5", 19 | crc32="C1BA11D1", 20 | sha256="17f746d82695fa9b35493b41859d39d786d32b23a9d2e00f4011dec7a02402ae", 21 | sha512="e12aad20c824187b39edb3c7943709290b5ddbf1b4032988db46f2e86da3cf7e7783f78c82e4dc5da232f666b8f9799a260a1f8e2694eb4d0cdaf78da710fde1", # noqa 22 | size="302592") 23 | assert isinstance(instance, Malware) 24 | assert instance.__repr__() == "" 25 | 26 | def test_to_dict(self): 27 | instance = Malware(md5="ad7b9c14083b52bc532fba5948342b98", 28 | sha1="ee8cbf12d87c4d388f09b4f69bed2e91682920b5", 29 | crc32="C1BA11D1", 30 | sha256="17f746d82695fa9b35493b41859d39d786d32b23a9d2e00f4011dec7a02402ae", 31 | sha512="e12aad20c824187b39edb3c7943709290b5ddbf1b4032988db46f2e86da3cf7e7783f78c82e4dc5da232f666b8f9799a260a1f8e2694eb4d0cdaf78da710fde1", # noqa 32 | size="302592") 33 | assert isinstance(instance, Malware) 34 | assert instance.to_dict() == {'id': None, 35 | 'md5': "ad7b9c14083b52bc532fba5948342b98", 36 | 'sha1': "ee8cbf12d87c4d388f09b4f69bed2e91682920b5", 37 | 'crc32': "C1BA11D1", 38 | 'sha256': "17f746d82695fa9b35493b41859d39d786d32b23a9d2e00f4011dec7a02402ae", 39 | 'sha512': "e12aad20c824187b39edb3c7943709290b5ddbf1b4032988db46f2e86da3cf7e7783f78c82e4dc5da232f666b8f9799a260a1f8e2694eb4d0cdaf78da710fde1", # noqa 40 | 'size': "302592", 41 | 'type': None, 42 | 'mime': None, 43 | 'ssdeep': None, 44 | 'name': None, 45 | 'created_at': None, 46 | 'parent_id': None} 47 | 48 | 49 | class TestTag: 50 | def test_init(self): 51 | instance = Tag(tag="spam") 52 | assert isinstance(instance, Tag) 53 | assert instance.__repr__() == "" 54 | 55 | def test_to_dict(self): 56 | instance = Tag(tag="eggs") 57 | assert isinstance(instance, Tag) 58 | assert instance.to_dict() == {'id': None, 'tag': 'eggs'} 59 | 60 | 61 | class TestNote: 62 | def test_init(self): 63 | instance = Note(title="MyTitle", body="MyBody") 64 | assert isinstance(instance, Note) 65 | assert instance.__repr__() == "" 66 | 67 | def test_to_dict(self): 68 | instance = Note(title="MyTitle", body="MyBody") 69 | assert isinstance(instance, Note) 70 | assert instance.to_dict() == {'id': None, 'title': "MyTitle", 'body': 'MyBody'} 71 | 72 | 73 | class TestAnalysis: 74 | def test_init(self): 75 | instance = Analysis(cmd_line="some_cmd -a", results="Foobar") 76 | assert isinstance(instance, Analysis) 77 | assert instance.__repr__() == "" 78 | 79 | def test_to_dict(self): 80 | instance = Analysis(cmd_line="some_cmd -a", results="Foobar") 81 | assert isinstance(instance, Analysis) 82 | assert instance.to_dict() == {'id': None, 83 | 'cmd_line': "some_cmd -a", 84 | 'results': 'Foobar', 85 | 'stored_at': None} 86 | 87 | 88 | class TestDatabase: 89 | def test_init(self): 90 | instance = Database() 91 | assert isinstance(instance, Database) 92 | assert instance.__repr__() == "" 93 | 94 | @pytest.mark.parametrize("filename, name", [ 95 | ("string_handling/ascii.txt", "ascii.txt"), 96 | ("string_handling/with blank.txt", "with blank.txt") 97 | ]) 98 | def test_add(self, capsys, filename, name): 99 | f = File(os.path.join(FIXTURE_DIR, filename)) 100 | 101 | instance = Database() 102 | ret = instance.add(f) 103 | assert ret is True 104 | 105 | @pytest.mark.skipif(sys.version_info >= (3, 0), reason="requires python2") 106 | @pytest.mark.xfail(raises=Python2UnsupportedUnicode) 107 | @pytest.mark.parametrize("filename, name", [ 108 | ("string_handling/dümmy.txt", "dümmy.txt"), 109 | ]) 110 | def test_add_unicode_py2(self, capsys, filename, name): 111 | f = File(os.path.join(FIXTURE_DIR, filename)) 112 | 113 | instance = Database() 114 | ret = instance.add(f) 115 | assert ret is True 116 | 117 | @pytest.mark.skipif(sys.version_info < (3, 3), reason="requires at least python3.3") 118 | @pytest.mark.parametrize("filename, name", [ 119 | ("string_handling/dümmy.txt", "dümmy.txt") 120 | ]) 121 | def test_add_unicode_py3(self, capsys, filename, name): 122 | f = File(os.path.join(FIXTURE_DIR, filename)) 123 | 124 | instance = Database() 125 | ret = instance.add(f) 126 | assert ret is True 127 | -------------------------------------------------------------------------------- /tests/core/ui/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # This file is part of Viper - https://github.com/viper-framework/viper 3 | # See the file 'LICENSE' for copying permission. 4 | -------------------------------------------------------------------------------- /tests/core/ui/test_console.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # This file is part of Viper - https://github.com/viper-framework/viper 3 | # See the file 'LICENSE' for copying permission. 4 | 5 | from tests.conftest import FIXTURE_DIR 6 | from viper.core.session import __sessions__ 7 | from viper.core.ui import console 8 | from viper.common.objects import MispEvent 9 | try: 10 | from unittest import mock 11 | except ImportError: 12 | # Python2 13 | import mock 14 | import sys 15 | import re 16 | import pytest 17 | import os 18 | 19 | 20 | class TestConsole: 21 | 22 | def teardown_method(self): 23 | __sessions__.close() 24 | 25 | def test_init(self): 26 | instance = console.Console() 27 | assert isinstance(instance, console.Console) 28 | 29 | def test_start(self, capsys): 30 | instance = console.Console() 31 | if sys.version_info <= (3, 0): 32 | in_fct = 'viper.core.ui.console.input' 33 | else: 34 | in_fct = 'builtins.input' 35 | with mock.patch(in_fct, return_value='help;exit'): 36 | instance.start() 37 | out, err = capsys.readouterr() 38 | assert re.search(r".*You have .* files in your .* repository.*", out) 39 | assert re.search(r".* Commands.*", out) 40 | assert re.search(r".* Modules.*", out) 41 | 42 | @pytest.mark.usefixtures("cleandir") 43 | def test_redirect(self, capsys): 44 | instance = console.Console() 45 | if sys.version_info <= (3, 0): 46 | in_fct = 'viper.core.ui.console.input' 47 | else: 48 | in_fct = 'builtins.input' 49 | with mock.patch(in_fct, return_value='help > ./redirect;exit'): 50 | instance.start() 51 | out, err = capsys.readouterr() 52 | assert re.search(r".*Output written to ./redirect.*", out) 53 | 54 | @pytest.mark.parametrize("filename,command,expected", 55 | [("chromeinstall-8u31.exe", 'pe imphash', '697c52d3bf08cccfd62da7bc503fdceb'), 56 | ('58e902cd-dae8-49b9-882b-186c02de0b81.json', 'misp --off show', 'Session opened on MISP event 6322')]) 57 | def test_opened_session(self, capsys, filename, command, expected): 58 | if filename == "chromeinstall-8u31.exe": 59 | __sessions__.new(path=os.path.join(FIXTURE_DIR, filename)) 60 | elif filename == '58e902cd-dae8-49b9-882b-186c02de0b81.json': 61 | me = MispEvent(os.path.join(FIXTURE_DIR, filename), True) 62 | __sessions__.new(misp_event=me) 63 | instance = console.Console() 64 | if sys.version_info <= (3, 0): 65 | in_fct = 'viper.core.ui.console.input' 66 | else: 67 | in_fct = 'builtins.input' 68 | with mock.patch(in_fct, return_value='{};exit'.format(command)): 69 | instance.start() 70 | out, err = capsys.readouterr() 71 | assert re.search(r".*{}.*".format(expected), out) 72 | -------------------------------------------------------------------------------- /tests/modules/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # This file is part of Viper - https://github.com/viper-framework/viper 3 | # See the file 'LICENSE' for copying permission. 4 | -------------------------------------------------------------------------------- /tests/modules/test_apk.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # This file is part of Viper - https://github.com/viper-framework/viper 3 | # See the file 'LICENSE' for copying permission. 4 | 5 | import os 6 | import re 7 | import sys 8 | 9 | import pytest 10 | 11 | from tests.conftest import FIXTURE_DIR 12 | 13 | from viper.common.abstracts import Module 14 | from viper.common.abstracts import ArgumentErrorCallback 15 | 16 | from viper.core.session import __sessions__ 17 | from viper.core.plugins import __modules__ 18 | 19 | apk = __modules__['apk']["obj"] 20 | 21 | 22 | class TestAPK: 23 | def teardown_method(self): 24 | __sessions__.close() 25 | 26 | def test_init(self): 27 | instance = apk() 28 | assert isinstance(instance, apk) 29 | assert isinstance(instance, Module) 30 | 31 | def test_args_exception(self): 32 | instance = apk() 33 | with pytest.raises(ArgumentErrorCallback) as excinfo: 34 | instance.parser.parse_args(["-h"]) 35 | excinfo.match(r".*Parse Android Applications.*") 36 | 37 | def test_no_session(self, capsys): 38 | instance = apk() 39 | instance.command_line = ["-i"] 40 | 41 | instance.run() 42 | out, err = capsys.readouterr() 43 | 44 | assert re.search(r".*No open session.*", out) 45 | # assert out == "" 46 | 47 | @pytest.mark.parametrize("filename", ["hello-world.apk"]) 48 | def test_info(self, capsys, filename): 49 | __sessions__.new(os.path.join(FIXTURE_DIR, filename)) 50 | instance = apk() 51 | instance.command_line = ["-i"] 52 | 53 | instance.run() 54 | out, err = capsys.readouterr() 55 | 56 | assert re.search(r".*Package Name: de.rhab.helloworld*", out) 57 | 58 | @pytest.mark.parametrize("filename", ["hello-world.apk"]) 59 | def test_perm(self, capsys, filename): 60 | __sessions__.new(os.path.join(FIXTURE_DIR, filename)) 61 | instance = apk() 62 | instance.command_line = ["-p"] 63 | 64 | instance.run() 65 | out, err = capsys.readouterr() 66 | 67 | assert re.search(r".*APK Permissions.*", out) 68 | 69 | @pytest.mark.parametrize("filename", ["hello-world.apk"]) 70 | def test_file(self, capsys, filename): 71 | __sessions__.new(os.path.join(FIXTURE_DIR, filename)) 72 | instance = apk() 73 | instance.command_line = ["-f"] 74 | 75 | instance.run() 76 | out, err = capsys.readouterr() 77 | 78 | assert re.search(r".*APK Contents.*", out) 79 | 80 | @pytest.mark.parametrize("filename", ["hello-world.apk"]) 81 | def test_url(self, capsys, filename): 82 | __sessions__.new(os.path.join(FIXTURE_DIR, filename)) 83 | instance = apk() 84 | instance.command_line = ["-u"] 85 | 86 | instance.run() 87 | out, err = capsys.readouterr() 88 | 89 | assert re.search(r".*http://schemas.android.com/apk/res/android.*", out) 90 | assert not re.search(r".*http://foo.example.bar.*", out) 91 | 92 | @pytest.mark.parametrize("filename", ["hello-world.apk"]) 93 | def test_cert(self, capsys, filename): 94 | __sessions__.new(os.path.join(FIXTURE_DIR, filename)) 95 | instance = apk() 96 | instance.command_line = ["-c"] 97 | 98 | instance.run() 99 | out, err = capsys.readouterr() 100 | 101 | assert re.search(r"SHA1: 65 2F 61 29 C8 7D 05 40 BF 98 6F C0 0E FD 9A B8 A7 87 84 DE", out) 102 | assert re.search(r"SHA256: 6E 56 64 27 DA 36 DD 91 36 39 B1 11 2F 74 7B 77 40 88 51 B4 85 7A 1D 63 EB F9 1E 02 B0 6F 20 88", out) 103 | 104 | @pytest.mark.parametrize("filename,pkg_name", [("hello-world.apk", "de.rhab.helloworld")]) 105 | def test_all(self, capsys, filename, pkg_name): 106 | __sessions__.new(os.path.join(FIXTURE_DIR, filename)) 107 | instance = apk() 108 | instance.command_line = ["-a"] 109 | 110 | instance.run() 111 | out, err = capsys.readouterr() 112 | 113 | assert re.search(r".*Package Name: {}.*".format(pkg_name), out) 114 | 115 | @pytest.mark.parametrize("filename", ["hello-world.apk"]) 116 | def test_dump_no_parameter(self, capsys, filename): 117 | __sessions__.new(os.path.join(FIXTURE_DIR, filename)) 118 | instance = apk() 119 | instance.command_line = ["-d"] 120 | 121 | instance.run() 122 | out, err = capsys.readouterr() 123 | 124 | assert re.search(r".*argument -d/--dump: expected one argument.*", out) 125 | 126 | @pytest.mark.skipif(sys.version_info < (3, 3), reason="Too slow on python2.7, makes travis fail.") 127 | @pytest.mark.skipif(sys.version_info >= (3, 3), reason="Uses way too much memory. Running the same commands in the client works fine...") 128 | @pytest.mark.usefixtures("cleandir") 129 | @pytest.mark.parametrize("filename", ["hello-world.apk"]) 130 | def test_dump(self, capsys, filename): 131 | __sessions__.new(os.path.join(FIXTURE_DIR, filename)) 132 | instance = apk() 133 | instance.command_line = ["-d hello-world.dump"] 134 | 135 | instance.run() 136 | out, err = capsys.readouterr() 137 | 138 | assert re.search(r".*Decompiling Code.*", out) 139 | assert re.search(r".*Decompiled code saved to.*", out) 140 | -------------------------------------------------------------------------------- /tests/modules/test_clamav.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # This file is part of Viper - https://github.com/viper-framework/viper 3 | # See the file 'LICENSE' for copying permission. 4 | 5 | import os 6 | import re 7 | 8 | import pytest 9 | from tests.conftest import FIXTURE_DIR 10 | 11 | from viper.common.abstracts import Module 12 | from viper.common.abstracts import ArgumentErrorCallback 13 | 14 | from viper.core.session import __sessions__ 15 | from viper.core.plugins import __modules__ 16 | 17 | clamav = __modules__['clamav']["obj"] 18 | 19 | 20 | class TestClamAV: 21 | def test_init(self): 22 | instance = clamav() 23 | assert isinstance(instance, clamav) 24 | assert isinstance(instance, Module) 25 | 26 | def test_args_exception(self): 27 | instance = clamav() 28 | with pytest.raises(ArgumentErrorCallback) as excinfo: 29 | instance.parser.parse_args(["-h"]) 30 | excinfo.match(r".*Scan file from local ClamAV daemon.*") 31 | 32 | def test_run_help(self, capsys): 33 | instance = clamav() 34 | instance.set_commandline(["--help"]) 35 | 36 | instance.run() 37 | out, err = capsys.readouterr() 38 | assert re.search(r"^usage:.*", out) 39 | 40 | def test_run_short_help(self, capsys): 41 | instance = clamav() 42 | instance.set_commandline(["-h"]) 43 | 44 | instance.run() 45 | out, err = capsys.readouterr() 46 | assert re.search(r"^usage:.*", out) 47 | 48 | def test_run_invalid_option(self, capsys): 49 | instance = clamav() 50 | instance.set_commandline(["invalid"]) 51 | 52 | instance.run() 53 | out, err = capsys.readouterr() 54 | assert re.search(r".*unrecognized arguments:.*", out) 55 | 56 | @pytest.mark.parametrize("filename", ["whoami.exe"]) 57 | def test_run_session(self, capsys, filename): 58 | __sessions__.new(os.path.join(FIXTURE_DIR, filename)) 59 | instance = clamav() 60 | instance.command_line = [] 61 | 62 | instance.run() 63 | out, err = capsys.readouterr() 64 | 65 | assert re.search(r".*Clamav identify.*", out) 66 | 67 | def test_run_all(self, capsys): 68 | instance = clamav() 69 | instance.set_commandline(['-a', '-t']) 70 | 71 | instance.run() 72 | out, err = capsys.readouterr() 73 | assert re.search(r".*Clamav identify.*", out) 74 | -------------------------------------------------------------------------------- /tests/modules/test_email.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # This file is part of Viper - https://github.com/viper-framework/viper 3 | # See the file 'LICENSE' for copying permission. 4 | 5 | import os 6 | import re 7 | 8 | import pytest 9 | from tests.conftest import FIXTURE_DIR 10 | 11 | from viper.common.abstracts import Module 12 | from viper.common.abstracts import ArgumentErrorCallback 13 | 14 | from viper.core.session import __sessions__ 15 | from viper.core.plugins import __modules__ 16 | 17 | 18 | emailparse = __modules__['email']["obj"] 19 | 20 | 21 | class TestEmailParse: 22 | def test_init(self): 23 | instance = emailparse() 24 | assert isinstance(instance, emailparse) 25 | assert isinstance(instance, Module) 26 | 27 | def test_args_exception(self): 28 | instance = emailparse() 29 | with pytest.raises(ArgumentErrorCallback) as excinfo: 30 | instance.parser.parse_args(["-h"]) 31 | excinfo.match(r".*Parse eml and msg email files.*") 32 | 33 | def test_run_help(self, capsys): 34 | instance = emailparse() 35 | instance.set_commandline(["--help"]) 36 | 37 | instance.run() 38 | out, err = capsys.readouterr() 39 | assert re.search(r"^usage:.*", out) 40 | 41 | def test_run_short_help(self, capsys): 42 | instance = emailparse() 43 | instance.set_commandline(["-h"]) 44 | 45 | instance.run() 46 | out, err = capsys.readouterr() 47 | assert re.search(r"^usage:.*", out) 48 | 49 | def test_run_invalid_option(self, capsys): 50 | instance = emailparse() 51 | instance.set_commandline(["invalid"]) 52 | 53 | instance.run() 54 | out, err = capsys.readouterr() 55 | assert re.search(r".*unrecognized arguments:.*", out) 56 | 57 | @pytest.mark.parametrize("filename,expected", [("junk.eml", [r'.*Google Award 2017.pdf.*']), 58 | ("junk2.eml", [r'.*Photocopy04062017.*']), 59 | ("junk3.eml", [r'.*http://www.earthworksjax.com.*']), 60 | ("unicode.msg", [r'.*raisedva.tif.*']), 61 | ]) 62 | def test_all(self, capsys, filename, expected): 63 | __sessions__.new(os.path.join(FIXTURE_DIR, filename)) 64 | instance = emailparse() 65 | instance.command_line = ['-a'] 66 | 67 | instance.run() 68 | out, err = capsys.readouterr() 69 | 70 | for e in expected: 71 | assert re.search(e, out) 72 | -------------------------------------------------------------------------------- /tests/modules/test_fuzzy.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # This file is part of Viper - https://github.com/viper-framework/viper 3 | # See the file 'LICENSE' for copying permission. 4 | 5 | import os 6 | import re 7 | 8 | import pytest 9 | from tests.conftest import FIXTURE_DIR 10 | 11 | from viper.common.abstracts import Module 12 | from viper.common.abstracts import ArgumentErrorCallback 13 | 14 | from viper.core.session import __sessions__ 15 | from viper.core.plugins import __modules__ 16 | 17 | fuzzy = __modules__['fuzzy']["obj"] 18 | 19 | 20 | class TestFuzzy: 21 | def test_init(self): 22 | instance = fuzzy() 23 | assert isinstance(instance, fuzzy) 24 | assert isinstance(instance, Module) 25 | 26 | def test_args_exception(self): 27 | instance = fuzzy() 28 | with pytest.raises(ArgumentErrorCallback) as excinfo: 29 | instance.parser.parse_args(["-h"]) 30 | excinfo.match(r".*Search for similar files through fuzzy hashing.*") 31 | 32 | def test_run_help(self, capsys): 33 | instance = fuzzy() 34 | instance.set_commandline(["--help"]) 35 | 36 | instance.run() 37 | out, err = capsys.readouterr() 38 | assert re.search(r"^usage:.*", out) 39 | 40 | def test_run_short_help(self, capsys): 41 | instance = fuzzy() 42 | instance.set_commandline(["-h"]) 43 | 44 | instance.run() 45 | out, err = capsys.readouterr() 46 | assert re.search(r"^usage:.*", out) 47 | 48 | def test_run_invalid_option(self, capsys): 49 | instance = fuzzy() 50 | instance.set_commandline(["invalid"]) 51 | 52 | instance.run() 53 | out, err = capsys.readouterr() 54 | assert re.search(r".*unrecognized arguments:.*", out) 55 | 56 | def test_run_cluster(self, capsys): 57 | instance = fuzzy() 58 | instance.set_commandline(['-c']) 59 | 60 | instance.run() 61 | out, err = capsys.readouterr() 62 | assert re.search(r".*Generating clusters, this might take a while.*", out) 63 | 64 | @pytest.mark.parametrize("filename", ["cmd.exe"]) 65 | def test_run_session(self, capsys, filename): 66 | __sessions__.new(os.path.join(FIXTURE_DIR, filename)) 67 | instance = fuzzy() 68 | instance.command_line = [] 69 | 70 | instance.run() 71 | out, err = capsys.readouterr() 72 | 73 | assert re.search(r".*relevant matches found.*", out) 74 | -------------------------------------------------------------------------------- /tests/modules/test_macho.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # This file is part of Viper - https://github.com/viper-framework/viper 3 | # See the file 'LICENSE' for copying permission. 4 | 5 | import os 6 | import re 7 | 8 | import pytest 9 | from tests.conftest import FIXTURE_DIR 10 | 11 | from viper.common.abstracts import Module 12 | 13 | from viper.core.session import __sessions__ 14 | from viper.core.plugins import __modules__ 15 | 16 | macho = __modules__['macho']["obj"] 17 | 18 | 19 | class Testmacho: 20 | 21 | def teardown_method(self): 22 | __sessions__.close() 23 | 24 | def test_init(self): 25 | instance = macho() 26 | assert isinstance(instance, macho) 27 | assert isinstance(instance, Module) 28 | 29 | @pytest.mark.usefixtures("cleandir") 30 | def test_no_session(self, capsys): 31 | instance = macho() 32 | instance.command_line = ["-a"] 33 | 34 | instance.run() 35 | out, err = capsys.readouterr() 36 | 37 | assert re.search(r".*No open session.*", out) 38 | 39 | @pytest.mark.parametrize("filename", ["whoami.exe"]) 40 | def test_no_macho_file(self, capsys, filename): 41 | __sessions__.new(os.path.join(FIXTURE_DIR, filename)) 42 | instance = macho() 43 | instance.command_line = ["-hd"] 44 | 45 | instance.run() 46 | out, err = capsys.readouterr() 47 | 48 | lines = out.split("\n") 49 | assert re.search(r".*Not a Mach-O file.*", lines[1]) 50 | 51 | @pytest.mark.parametrize("filename", ["MachO-OSX-x86-ls"]) 52 | def test_no_argument(self, capsys, filename): 53 | __sessions__.new(os.path.join(FIXTURE_DIR, filename)) 54 | instance = macho() 55 | 56 | instance.run() 57 | out, err = capsys.readouterr() 58 | 59 | lines = out.split("\n") 60 | assert re.search(r".*Session opened on.*", lines[0]) 61 | 62 | @pytest.mark.parametrize("filename,magic,cputype", [ 63 | ("MachO-OSX-x86-ls", "0xfeedface - 32 bits", "0x7 - i386"), 64 | ("MachO-OSX-x64-ls", "0xfeedfacf - 64 bits", "0x1000007 - x86_64") 65 | ]) 66 | def test_headers(self, capsys, filename, magic, cputype): 67 | __sessions__.new(os.path.join(FIXTURE_DIR, filename)) 68 | instance = macho() 69 | instance.command_line = ["-hd"] 70 | 71 | instance.run() 72 | out, err = capsys.readouterr() 73 | 74 | lines = out.split("\n") 75 | assert re.search(r".*Headers", lines[1]) 76 | assert re.search(r".*{}.*".format(magic), out) 77 | assert re.search(r".*{}.*".format(cputype), out) 78 | 79 | @pytest.mark.parametrize("filename,amount_segments", [ 80 | ("MachO-OSX-x86-ls", 4) 81 | ]) 82 | def test_segments(self, capsys, filename, amount_segments): 83 | __sessions__.new(os.path.join(FIXTURE_DIR, filename)) 84 | instance = macho() 85 | instance.command_line = ["-sg"] 86 | 87 | instance.run() 88 | out, err = capsys.readouterr() 89 | 90 | lines = out.split("\n") 91 | assert re.search(r".*Segments \({}\)".format(amount_segments), lines[1]) 92 | 93 | @pytest.mark.parametrize("filename,amount_commands", [ 94 | ("MachO-OSX-x86-ls", 12), 95 | ]) 96 | def test_load_commands(self, capsys, filename, amount_commands): 97 | __sessions__.new(os.path.join(FIXTURE_DIR, filename)) 98 | instance = macho() 99 | instance.command_line = ["-lc"] 100 | 101 | instance.run() 102 | out, err = capsys.readouterr() 103 | 104 | lines = out.split("\n") 105 | assert re.search(r".*Load Commands \({}\)".format(amount_commands), lines[1]) 106 | 107 | @pytest.mark.parametrize("filename", ["MachO-OSX-x86-ls"]) 108 | def test_all(self, capsys, filename): 109 | __sessions__.new(os.path.join(FIXTURE_DIR, filename)) 110 | instance = macho() 111 | instance.command_line = ["-a"] 112 | 113 | instance.run() 114 | out, err = capsys.readouterr() 115 | 116 | lines = out.split("\n") 117 | assert re.search(r".*Headers", lines[1]) 118 | -------------------------------------------------------------------------------- /tests/modules/test_misp.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # This file is part of Viper - https://github.com/viper-framework/viper 3 | # See the file 'LICENSE' for copying permission. 4 | 5 | import os 6 | import re 7 | import sys 8 | 9 | import pytest 10 | from tests.conftest import FIXTURE_DIR 11 | 12 | from viper.core.session import __sessions__ 13 | from viper.core.config import __config__ 14 | from viper.core.plugins import __modules__ 15 | 16 | from viper.common.abstracts import Module 17 | from viper.common.abstracts import ArgumentErrorCallback 18 | 19 | try: 20 | from .keys import url, apikey, vt_key 21 | live_tests = True 22 | except ImportError: 23 | live_tests = False 24 | 25 | 26 | misp = __modules__['misp']["obj"] 27 | 28 | 29 | class TestMISP: 30 | def test_init(self): 31 | instance = misp() 32 | assert isinstance(instance, misp) 33 | assert isinstance(instance, Module) 34 | 35 | def test_args_exception(self): 36 | instance = misp() 37 | with pytest.raises(ArgumentErrorCallback) as excinfo: 38 | instance.parser.parse_args(["-h"]) 39 | excinfo.match(r".*Upload and query IOCs to/from a MISP instance*") 40 | 41 | def test_run_help(self, capsys): 42 | instance = misp() 43 | instance.set_commandline(["--help"]) 44 | 45 | instance.run() 46 | out, err = capsys.readouterr() 47 | assert re.search(r"^usage:.*", out) 48 | 49 | def test_run_short_help(self, capsys): 50 | instance = misp() 51 | instance.set_commandline(["-h"]) 52 | 53 | instance.run() 54 | out, err = capsys.readouterr() 55 | assert re.search(r"^usage:.*", out) 56 | 57 | def test_run_invalid_option(self, capsys): 58 | instance = misp() 59 | instance.set_commandline(["invalid"]) 60 | 61 | instance.run() 62 | out, err = capsys.readouterr() 63 | assert re.search(r".*argument subname: invalid choice: 'invalid'.*", out) 64 | 65 | def test_tag_list(self, capsys): 66 | instance = misp() 67 | instance.command_line = ['--off', 'tag', '--list'] 68 | 69 | instance.run() 70 | out, err = capsys.readouterr() 71 | 72 | assert re.search(r".*CIRCL Taxonomy.*", out) 73 | 74 | @pytest.mark.skipif(sys.version_info < (3, 0), reason="Encoding foobar, don't care.") 75 | def test_tag_search(self, capsys): 76 | instance = misp() 77 | instance.command_line = ['--off', 'tag', '-s', 'ciRcl'] 78 | 79 | instance.run() 80 | out, err = capsys.readouterr() 81 | 82 | assert re.search(r".*circl:incident-classification=\"system-compromise\".*", out) 83 | 84 | def test_tag_details(self, capsys): 85 | instance = misp() 86 | instance.command_line = ['--off', 'tag', '-d', 'circl'] 87 | 88 | instance.run() 89 | out, err = capsys.readouterr() 90 | 91 | assert re.search(r".*Denial of Service | denial-of-service.*", out) 92 | 93 | def test_galaxies_list(self, capsys): 94 | instance = misp() 95 | instance.command_line = ['--off', 'galaxies', '--list'] 96 | 97 | instance.run() 98 | out, err = capsys.readouterr() 99 | 100 | assert re.search(r".*microsoft-activity-group.*", out) 101 | 102 | def test_galaxies_search(self, capsys): 103 | instance = misp() 104 | instance.command_line = ['--off', 'galaxies', '--search', 'foo'] 105 | 106 | instance.run() 107 | out, err = capsys.readouterr() 108 | 109 | assert re.search(r".*Foozer.*", out) 110 | 111 | def test_galaxies_list_cluster(self, capsys): 112 | instance = misp() 113 | instance.command_line = ['--off', 'galaxies', '-d', 'rat'] 114 | 115 | instance.run() 116 | out, err = capsys.readouterr() 117 | 118 | assert re.search(r".*BlackNix.*", out) 119 | 120 | def test_galaxies_list_cluster_value(self, capsys): 121 | instance = misp() 122 | instance.command_line = ['--off', 'galaxies', '-d', 'rat', '-v', 'BlackNix'] 123 | 124 | instance.run() 125 | out, err = capsys.readouterr() 126 | 127 | assert re.search(r".*leakforums.net.*", out) 128 | 129 | # Live tests - require a MISP instance. 130 | @pytest.mark.skipif(not live_tests, reason="No API key provided") 131 | def test_create_event(self, capsys): 132 | instance = misp() 133 | instance.command_line = ['--url', url, '-k', apikey, '-v', 'create_event', '-i', 'Viper test event'] 134 | 135 | instance.run() 136 | out, err = capsys.readouterr() 137 | 138 | assert re.search(r".*Session opened on MISP event.*", out) 139 | event_id = re.findall(r".*Session opened on MISP event (.*)\..*", out)[0] 140 | 141 | instance.command_line = ['--url', url, '-k', apikey, '-v', 'add', 'ip-dst', '8.8.8.8'] 142 | instance.run() 143 | out, err = capsys.readouterr() 144 | assert re.search(rf".*Session on MISP event {event_id} refreshed.*", out) 145 | 146 | instance.command_line = ['--url', url, '-k', apikey, '-v', 'show'] 147 | instance.run() 148 | out, err = capsys.readouterr() 149 | assert re.search(r".*ip-dst | 8.8.8.8.*", out) 150 | 151 | __sessions__.new(os.path.join(FIXTURE_DIR, 'chromeinstall-8u31.exe')) 152 | 153 | instance.command_line = ['add_hashes'] 154 | instance.run() 155 | out, err = capsys.readouterr() 156 | assert re.search(rf".*Session on MISP event {event_id} refreshed.*", out) 157 | instance.command_line = ['--url', url, '-k', apikey, '-v', 'show'] 158 | instance.run() 159 | out, err = capsys.readouterr() 160 | assert re.search(rf".*sha256[ ]*| 583a2d05ff0d4864f525a6cdd3bfbd549616d9e1d84e96fe145794ba0519d752.*", out) 161 | 162 | # Live tests - require a MISP instance. 163 | @pytest.mark.skipif(not live_tests, reason="No API key provided") 164 | def test_check_hashes(self, capsys): 165 | instance = misp() 166 | instance.command_line = ['--url', url, '-k', apikey, '-v', 'create_event', '-i', 'Viper test event - check hashes'] 167 | 168 | instance.run() 169 | out, err = capsys.readouterr() 170 | 171 | assert re.search(r".*Session opened on MISP event.*", out) 172 | event_id = re.findall(r".*Session opened on MISP event (.*)\..*", out)[0] 173 | 174 | instance.command_line = ['--url', url, '-k', apikey, '-v', 'add', 'sha1', 'afeee8b4acff87bc469a6f0364a81ae5d60a2add'] 175 | instance.run() 176 | out, err = capsys.readouterr() 177 | 178 | assert re.search(rf".*Session on MISP event {event_id} refreshed.*", out) 179 | 180 | __config__.virustotal.virustotal_key = vt_key 181 | 182 | instance.command_line = ['--url', url, '-k', apikey, 'check_hashes', '-p'] 183 | instance.run() 184 | out, err = capsys.readouterr() 185 | # print(out, err) 186 | assert re.search(r".*Sample available in VT.*", out) 187 | -------------------------------------------------------------------------------- /tests/modules/test_office.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # This file is part of Viper - https://github.com/viper-framework/viper 3 | # See the file 'LICENSE' for copying permission. 4 | 5 | import os 6 | import re 7 | 8 | import pytest 9 | from tests.conftest import FIXTURE_DIR 10 | 11 | from viper.common.abstracts import Module 12 | from viper.common.abstracts import ArgumentErrorCallback 13 | 14 | from viper.core.session import __sessions__ 15 | from viper.core.plugins import __modules__ 16 | 17 | office = __modules__['office']["obj"] 18 | 19 | 20 | class TestOffice: 21 | def test_init(self): 22 | instance = office() 23 | assert isinstance(instance, office) 24 | assert isinstance(instance, Module) 25 | 26 | def test_args_exception(self): 27 | instance = office() 28 | with pytest.raises(ArgumentErrorCallback) as excinfo: 29 | instance.parser.parse_args(["-h"]) 30 | excinfo.match(r".*Office Document Parser.*") 31 | 32 | def test_run_help(self, capsys): 33 | instance = office() 34 | instance.set_commandline(["--help"]) 35 | 36 | instance.run() 37 | out, err = capsys.readouterr() 38 | assert re.search(r"^usage:.*", out) 39 | 40 | def test_run_short_help(self, capsys): 41 | instance = office() 42 | instance.set_commandline(["-h"]) 43 | 44 | instance.run() 45 | out, err = capsys.readouterr() 46 | assert re.search(r"^usage:.*", out) 47 | 48 | def test_run_invalid_option(self, capsys): 49 | instance = office() 50 | instance.set_commandline(["invalid"]) 51 | 52 | instance.run() 53 | out, err = capsys.readouterr() 54 | assert re.search(r".*unrecognized arguments:.*", out) 55 | 56 | @pytest.mark.parametrize("filename", ["Douglas-Resume.doc"]) 57 | def test_meta(self, capsys, filename): 58 | __sessions__.new(os.path.join(FIXTURE_DIR, filename)) 59 | instance = office() 60 | instance.command_line = ["-m"] 61 | 62 | instance.run() 63 | out, err = capsys.readouterr() 64 | 65 | assert re.search(r".*comments .*| htsgraghtfgyrwthwwb*", out) 66 | assert re.search(r".*create_time .*| 2017-03-08 16:00:00*", out) 67 | assert re.search(r".*last_saved_time .*| 2017-04-09 19:03:00.*", out) 68 | 69 | @pytest.mark.parametrize("filename", ["Douglas-Resume.doc"]) 70 | def test_oleid(self, capsys, filename): 71 | __sessions__.new(os.path.join(FIXTURE_DIR, filename)) 72 | instance = office() 73 | instance.command_line = ["-o"] 74 | 75 | instance.run() 76 | out, err = capsys.readouterr() 77 | 78 | assert re.search(r".*Macros .*| True.*", out) 79 | 80 | @pytest.mark.parametrize("filename", ["Douglas-Resume.doc"]) 81 | def test_streams(self, capsys, filename): 82 | __sessions__.new(os.path.join(FIXTURE_DIR, filename)) 83 | instance = office() 84 | instance.command_line = ["-s"] 85 | 86 | instance.run() 87 | out, err = capsys.readouterr() 88 | 89 | assert re.search(r".*Macros/kfjtir .* 2017-04-09 19:03:45.905000 | 2017-04-09 19:03:45.920000.*", out) 90 | 91 | @pytest.mark.parametrize("filename,expected", 92 | [("Douglas-Resume.doc", [r".*zxsfg.bas.*", r".*.paya.exe.*"]), 93 | ("9afa90370cfd217ae1ec36e752a393537878a2f3b5f9159f61690e7790904b0d", [r".*Workbook_Open.*", r".*SbieDll.dll.*"])]) 94 | def test_vba(self, capsys, filename, expected): 95 | __sessions__.new(os.path.join(FIXTURE_DIR, filename)) 96 | instance = office() 97 | instance.command_line = ["-v"] 98 | 99 | instance.run() 100 | out, err = capsys.readouterr() 101 | 102 | for e in expected: 103 | assert re.search(e, out) 104 | 105 | @pytest.mark.usefixtures("cleandir") 106 | @pytest.mark.parametrize("filename", ["c026ebfa3a191d4f27ee72f34fa0d97656113be368369f605e7845a30bc19f6a"]) 107 | def test_export(self, capsys, filename): 108 | __sessions__.new(os.path.join(FIXTURE_DIR, filename)) 109 | instance = office() 110 | instance.command_line = ["-e", 'out_all'] 111 | 112 | instance.run() 113 | out, err = capsys.readouterr() 114 | 115 | assert re.search(r".*out_all/ObjectPool-_1398590705-Contents*", out) 116 | 117 | @pytest.mark.usefixtures("cleandir") 118 | @pytest.mark.parametrize("filename", ["Douglas-Resume.doc"]) 119 | def test_code(self, capsys, filename): 120 | __sessions__.new(os.path.join(FIXTURE_DIR, filename)) 121 | instance = office() 122 | instance.command_line = ["-c", 'out_macro'] 123 | 124 | instance.run() 125 | out, err = capsys.readouterr() 126 | 127 | assert re.search(r".*Writing VBA Code to out_macro.*", out) 128 | -------------------------------------------------------------------------------- /tests/modules/test_pe.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # This file is part of Viper - https://github.com/viper-framework/viper 3 | # See the file 'LICENSE' for copying permission. 4 | 5 | import os 6 | import re 7 | from datetime import datetime 8 | 9 | import pytest 10 | from tests.conftest import FIXTURE_DIR 11 | 12 | from viper.common.abstracts import Module 13 | from viper.common.abstracts import ArgumentErrorCallback 14 | 15 | from viper.core.session import __sessions__ 16 | from viper.core.plugins import __modules__ 17 | 18 | 19 | pe = __modules__['pe']["obj"] 20 | 21 | 22 | class TestPE: 23 | def test_init(self): 24 | instance = pe() 25 | assert isinstance(instance, pe) 26 | assert isinstance(instance, Module) 27 | 28 | def test_args_exception(self): 29 | instance = pe() 30 | 31 | with pytest.raises(ArgumentErrorCallback) as excinfo: 32 | instance.parser.parse_args(["-h"]) 33 | excinfo.match(r".*Extract information from PE32 headers.*") 34 | 35 | def test_run_help(self, capsys): 36 | instance = pe() 37 | instance.set_commandline(["--help"]) 38 | 39 | instance.run() 40 | out, err = capsys.readouterr() 41 | assert re.search(r"^usage:.*", out) 42 | 43 | def test_run_short_help(self, capsys): 44 | instance = pe() 45 | instance.set_commandline(["-h"]) 46 | 47 | instance.run() 48 | out, err = capsys.readouterr() 49 | assert re.search(r"^usage:.*", out) 50 | 51 | def test_run_invalid_option(self, capsys): 52 | instance = pe() 53 | instance.set_commandline(["invalid"]) 54 | 55 | instance.run() 56 | out, err = capsys.readouterr() 57 | assert re.search(r".*argument subname: invalid choice.*", out) 58 | 59 | @pytest.mark.parametrize("filename, expected", [ 60 | ("chromeinstall-8u31.exe", "1418884325 ({0})".format(datetime(2014, 12, 18, 6, 32, 5))), 61 | ]) 62 | def test_compiletime(self, capsys, filename, expected): 63 | __sessions__.new(os.path.join(FIXTURE_DIR, filename)) 64 | instance = pe() 65 | instance.command_line = ["compiletime"] 66 | 67 | instance.run() 68 | out, err = capsys.readouterr() 69 | lines = out.split("\n") 70 | assert re.search(r".*Session opened*", lines[0]) 71 | assert re.search(r".*Compile Time*", lines[1]) 72 | assert re.search(r".*{}*".format(expected), lines[1]) 73 | assert instance.result_compile_time == expected 74 | 75 | @pytest.mark.parametrize("filename, expected", [ 76 | ("chromeinstall-8u31.exe", 3), 77 | ]) 78 | def test_sections(self, capsys, filename, expected): 79 | __sessions__.new(os.path.join(FIXTURE_DIR, filename)) 80 | instance = pe() 81 | instance.command_line = ["sections"] 82 | 83 | instance.run() 84 | out, err = capsys.readouterr() 85 | lines = out.split("\n") 86 | 87 | assert re.search(r".*Session opened*", lines[0]) 88 | assert re.search(r".*PE Sections.*", lines[1]) 89 | assert len(instance.result_sections) == expected 90 | 91 | @pytest.mark.parametrize("filename, expected", [ 92 | ("513a6e4e94369c64cab49324cd49c44137d2b66967bb6d16394ab145a8e32c45", 'e8fbb742e0d29f1ed5f587b3b769b426'), 93 | ]) 94 | def test_security(self, capsys, filename, expected): 95 | __sessions__.new(os.path.join(FIXTURE_DIR, filename)) 96 | instance = pe() 97 | instance.command_line = ["security"] 98 | 99 | instance.run() 100 | out, err = capsys.readouterr() 101 | 102 | assert re.search(r".*{}*".format(expected), out) 103 | 104 | @pytest.mark.parametrize("filename, expected", [("cmd.exe", r".*Probable language:.*C.*")]) 105 | def test_language(self, capsys, filename, expected): 106 | __sessions__.new(os.path.join(FIXTURE_DIR, filename)) 107 | instance = pe() 108 | instance.command_line = ["language"] 109 | 110 | instance.run() 111 | out, err = capsys.readouterr() 112 | lines = out.split("\n") 113 | 114 | assert re.search(expected, lines[1]) 115 | 116 | @pytest.mark.parametrize("filename, expected", [ 117 | ("513a6e4e94369c64cab49324cd49c44137d2b66967bb6d16394ab145a8e32c45", 118 | r'.*2ab445d9a9ffeaab2ab3da4a68de3578.*86bbbd7c06317e171949a0566cdd05ae.*5c073d8e75dc926dd04bc73e32d41beb.*a19a2658ba69030c6ac9d11fd7d7e3c1.*'), 119 | ]) 120 | def test_resources(self, capsys, filename, expected): 121 | __sessions__.new(os.path.join(FIXTURE_DIR, filename)) 122 | instance = pe() 123 | instance.command_line = ["resources"] 124 | 125 | instance.run() 126 | out, err = capsys.readouterr() 127 | 128 | assert re.search(expected, expected) 129 | 130 | # @pytest.mark.parametrize("filename,expected", [ 131 | # ("cmd.exe", 9), 132 | # ]) 133 | # def test_sections(self, filename, expected): 134 | # __sessions__.new(os.path.join(FIXTURE_DIR, filename)) 135 | # 136 | # instance = pe() 137 | # instance.sections() 138 | # 139 | # assert 0 140 | -------------------------------------------------------------------------------- /tests/modules/test_rat.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # This file is part of Viper - https://github.com/viper-framework/viper 3 | # See the file 'LICENSE' for copying permission. 4 | 5 | import os 6 | import re 7 | import pytest 8 | from tests.conftest import FIXTURE_DIR 9 | 10 | from viper.common.abstracts import Module 11 | from viper.common.abstracts import ArgumentErrorCallback 12 | 13 | from viper.core.session import __sessions__ 14 | from viper.core.plugins import __modules__ 15 | 16 | rat = __modules__['rat']["obj"] 17 | 18 | 19 | class TestRAT: 20 | 21 | def teardown_method(self): 22 | __sessions__.close() 23 | 24 | def test_init(self): 25 | instance = rat() 26 | assert isinstance(instance, Module) 27 | 28 | def test_args_exception(self): 29 | instance = rat() 30 | with pytest.raises(ArgumentErrorCallback) as excinfo: 31 | instance.parser.parse_args(["-h"]) 32 | excinfo.match(r".*Extract information from known RAT families.*") 33 | 34 | def test_run_help(self, capsys): 35 | instance = rat() 36 | instance.set_commandline(["--help"]) 37 | 38 | instance.run() 39 | out, err = capsys.readouterr() 40 | assert re.search(r"^usage:.*", out) 41 | 42 | def test_run_short_help(self, capsys): 43 | instance = rat() 44 | instance.set_commandline(["-h"]) 45 | 46 | instance.run() 47 | out, err = capsys.readouterr() 48 | assert re.search(r"^usage:.*", out) 49 | 50 | def test_run_list(self, capsys): 51 | instance = rat() 52 | instance.set_commandline(["-l"]) 53 | 54 | instance.run() 55 | out, err = capsys.readouterr() 56 | assert re.search(r".*List of available RAT modules:.*", out) 57 | 58 | def test_run_auto_no_session(self, capsys): 59 | instance = rat() 60 | instance.set_commandline(["-a"]) 61 | 62 | instance.run() 63 | out, err = capsys.readouterr() 64 | assert re.search(r'.*No open session.*', out) 65 | 66 | def test_run_family_no_session(self, capsys): 67 | instance = rat() 68 | instance.set_commandline(["-f", "adwind"]) 69 | 70 | instance.run() 71 | out, err = capsys.readouterr() 72 | assert re.search(r'.*No open session.*', out) 73 | 74 | @pytest.mark.parametrize("filename, expected", [ 75 | ("chromeinstall-8u31.exe", False), 76 | ]) 77 | def test_run_family_no_module(self, capsys, filename, expected): 78 | __sessions__.new(os.path.join(FIXTURE_DIR, filename)) 79 | 80 | instance = rat() 81 | instance.run() 82 | out, err = capsys.readouterr() 83 | instance.set_commandline(["-f", "foobar"]) 84 | 85 | instance.run() 86 | out, err = capsys.readouterr() 87 | assert re.search(r'.*There is no module for family.*', out) 88 | 89 | @pytest.mark.parametrize("filename, expected", [ 90 | ("chromeinstall-8u31.exe", False), 91 | # ("chromeinstall-8u31.exe", True), 92 | ]) 93 | def test_run_auto(self, capsys, filename, expected): 94 | __sessions__.new(os.path.join(FIXTURE_DIR, filename)) 95 | 96 | instance = rat() 97 | instance.set_commandline(["-a"]) 98 | 99 | instance.run() 100 | out, err = capsys.readouterr() 101 | if expected: 102 | assert re.search(r'.*Automatically detected supported.*', out) 103 | else: 104 | assert re.search(r'.*No known RAT detected.*', out) 105 | 106 | @pytest.mark.parametrize("filename, expected", [ 107 | ("chromeinstall-8u31.exe", False), 108 | # ("chromeinstall-8u31.exe", True), 109 | ]) 110 | def test_run_family_adwind(self, capsys, filename, expected): 111 | # FIXME: this test isn't really useful. 112 | __sessions__.new(os.path.join(FIXTURE_DIR, filename)) 113 | 114 | instance = rat() 115 | instance.set_commandline(["-f", "adwind"]) 116 | 117 | instance.run() 118 | out, err = capsys.readouterr() 119 | if expected: 120 | assert re.search(r'.*Automatically detected supported.*', out) 121 | else: 122 | assert re.search(r'.*There is no module for family.*', out) 123 | -------------------------------------------------------------------------------- /tests/modules/test_swf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # This file is part of Viper - https://github.com/viper-framework/viper 3 | # See the file 'LICENSE' for copying permission. 4 | 5 | import os 6 | import re 7 | 8 | import pytest 9 | from tests.conftest import FIXTURE_DIR 10 | 11 | from viper.common.abstracts import Module 12 | from viper.common.abstracts import ArgumentErrorCallback 13 | 14 | from viper.core.session import __sessions__ 15 | from viper.core.plugins import __modules__ 16 | 17 | swf = __modules__['swf']["obj"] 18 | 19 | 20 | class TestSWF: 21 | def test_init(self): 22 | instance = swf() 23 | assert isinstance(instance, swf) 24 | assert isinstance(instance, Module) 25 | 26 | def test_args_exception(self): 27 | instance = swf() 28 | with pytest.raises(ArgumentErrorCallback) as excinfo: 29 | instance.parser.parse_args(["-h"]) 30 | excinfo.match(r".*Parse, analyze and decompress Flash objects.*") 31 | 32 | def test_run_help(self, capsys): 33 | instance = swf() 34 | instance.set_commandline(["--help"]) 35 | 36 | instance.run() 37 | out, err = capsys.readouterr() 38 | assert re.search(r"^usage:.*", out) 39 | 40 | def test_run_short_help(self, capsys): 41 | instance = swf() 42 | instance.set_commandline(["-h"]) 43 | 44 | instance.run() 45 | out, err = capsys.readouterr() 46 | assert re.search(r"^usage:.*", out) 47 | 48 | def test_run_invalid_option(self, capsys): 49 | instance = swf() 50 | instance.set_commandline(["invalid"]) 51 | 52 | instance.run() 53 | out, err = capsys.readouterr() 54 | assert re.search(r".*unrecognized arguments:.*", out) 55 | 56 | @pytest.mark.parametrize("filename", ["ObjectPool-_1398590705-Contents-FLASH-Decompressed1"]) 57 | def test_meta(self, capsys, filename): 58 | __sessions__.new(os.path.join(FIXTURE_DIR, filename)) 59 | instance = swf() 60 | instance.command_line = [] 61 | 62 | instance.run() 63 | out, err = capsys.readouterr() 64 | 65 | assert re.search(r".*The opened file doesn't appear to be compressed.*", out) 66 | -------------------------------------------------------------------------------- /tests/test_usecases.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # This file is part of Viper - https://github.com/viper-framework/viper 3 | # See the file 'LICENSE' for copying permission. 4 | from __future__ import unicode_literals 5 | 6 | import os 7 | import re 8 | import sys 9 | from shutil import copyfile 10 | 11 | from hashlib import sha256 12 | 13 | import pytest 14 | from tests.conftest import FIXTURE_DIR 15 | 16 | from viper.core.plugins import load_commands 17 | from viper.core.session import __sessions__ 18 | from viper.common.exceptions import Python2UnsupportedUnicode 19 | 20 | try: 21 | from unittest import mock 22 | except ImportError: 23 | # Python2 24 | import mock 25 | 26 | 27 | class TestUseCases: 28 | 29 | cmd = load_commands() 30 | 31 | def teardown_method(self): 32 | __sessions__.close() 33 | 34 | @pytest.mark.usefixtures("cleandir") 35 | @pytest.mark.parametrize("filename, name", [ 36 | ("chromeinstall-8u31.exe", "chromeinstall-8u31.exe"), 37 | ("string_handling/with blank.txt", "with blank.txt"), 38 | ]) 39 | def test_store(self, capsys, filename, name): 40 | # use cleandir fixture operate on clean ./ local dir 41 | copyfile(os.path.join(FIXTURE_DIR, filename), os.path.join(".", os.path.basename(filename))) 42 | self.cmd['open']['obj']('-f', os.path.join(".", os.path.basename(filename))) 43 | self.cmd['store']['obj']() 44 | if sys.version_info <= (3, 0): 45 | in_fct = 'builtins.raw_input' 46 | else: 47 | in_fct = 'builtins.input' 48 | with mock.patch(in_fct, return_value='y'): 49 | self.cmd['delete']['obj']() 50 | out, err = capsys.readouterr() 51 | lines = out.split("\n") 52 | 53 | assert re.search(r".*Session opened on.*", lines[0]) 54 | assert not re.search(r".*Unable to store file.*", out) 55 | assert re.search(r".*{}.*".format(name), lines[1]) 56 | assert re.search(r".*Running command.*", lines[5]) 57 | assert re.search(r".*Deleted opened file.*", lines[7]) 58 | 59 | @pytest.mark.skipif(sys.version_info >= (3, 0), reason="requires python2") 60 | @pytest.mark.xfail(raises=Python2UnsupportedUnicode) 61 | @pytest.mark.usefixtures("cleandir") 62 | @pytest.mark.parametrize("filename, name", [ 63 | ("string_handling/dümmy.txt", "dümmy.txt") 64 | ]) 65 | def test_store_unicode_py2(self, capsys, filename, name): 66 | # use cleandir fixture operate on clean ./ local dir 67 | copyfile(os.path.join(FIXTURE_DIR, filename), os.path.join(".", os.path.basename(filename))) 68 | self.cmd['open']['obj']('-f', os.path.join(".", os.path.basename(filename))) 69 | self.cmd['store']['obj']() 70 | if sys.version_info <= (3, 0): 71 | in_fct = 'builtins.raw_input' 72 | else: 73 | in_fct = 'builtins.input' 74 | with mock.patch(in_fct, return_value='y'): 75 | self.cmd['delete']['obj']() 76 | out, err = capsys.readouterr() 77 | lines = out.split("\n") 78 | 79 | assert re.search(r".*Session opened on.*", lines[0]) 80 | assert not re.search(r".*Unable to store file.*", out) 81 | assert re.search(r".*{}.*".format(name), lines[1]) 82 | assert re.search(r".*Running command.*", lines[5]) 83 | assert re.search(r".*Deleted opened file.*", lines[7]) 84 | 85 | @pytest.mark.skipif(sys.version_info < (3, 3), reason="requires at least python3.3") 86 | @pytest.mark.usefixtures("cleandir") 87 | @pytest.mark.parametrize("filename, name", [ 88 | ("string_handling/dümmy.txt", "dümmy.txt") 89 | ]) 90 | def test_store_unicode_py3(self, capsys, filename, name): 91 | # use cleandir fixture operate on clean ./ local dir 92 | copyfile(os.path.join(FIXTURE_DIR, filename), os.path.join(".", os.path.basename(filename))) 93 | self.cmd['open']['obj']('-f', os.path.join(".", os.path.basename(filename))) 94 | self.cmd['store']['obj']() 95 | if sys.version_info <= (3, 0): 96 | in_fct = 'builtins.raw_input' 97 | else: 98 | in_fct = 'builtins.input' 99 | with mock.patch(in_fct, return_value='y'): 100 | self.cmd['delete']['obj']() 101 | out, err = capsys.readouterr() 102 | lines = out.split("\n") 103 | 104 | assert re.search(r".*Session opened on.*", lines[0]) 105 | assert not re.search(r".*Unable to store file.*", out) 106 | assert re.search(r".*{}.*".format(name), lines[1]) 107 | assert re.search(r".*Running command.*", lines[5]) 108 | assert re.search(r".*Deleted opened file.*", lines[7]) 109 | 110 | @pytest.mark.skipif(sys.version_info >= (3, 0), reason="requires python2") 111 | @pytest.mark.xfail(raises=Python2UnsupportedUnicode) 112 | @pytest.mark.parametrize("filename", ["chromeinstall-8u31.exe"]) 113 | def test_store_all_py2(self, capsys, filename): 114 | self.cmd['open']['obj']('-f', os.path.join(FIXTURE_DIR, filename)) 115 | self.cmd['store']['obj']() 116 | self.cmd['store']['obj']('-f', FIXTURE_DIR) 117 | out, err = capsys.readouterr() 118 | lines = out.split("\n") 119 | 120 | assert re.search(r".*Session opened on.*", lines[0]) 121 | assert re.search(r".*appears to be already stored.*", out) 122 | assert re.search(r".*Skip, file \"chromeinstall-8u31.exe\" appears to be already stored.*", out) 123 | 124 | @pytest.mark.skipif(sys.version_info < (3, 3), reason="requires at least python3.3") 125 | @pytest.mark.parametrize("filename", ["chromeinstall-8u31.exe"]) 126 | def test_store_all_py3(self, capsys, filename): 127 | self.cmd['open']['obj']('-f', os.path.join(FIXTURE_DIR, filename)) 128 | self.cmd['store']['obj']() 129 | self.cmd['store']['obj']('-f', FIXTURE_DIR) 130 | out, err = capsys.readouterr() 131 | lines = out.split("\n") 132 | 133 | assert re.search(r".*Session opened on.*", lines[0]) 134 | assert re.search(r".*appears to be already stored.*", out) 135 | assert re.search(r".*Skip, file \"chromeinstall-8u31.exe\" appears to be already stored.*", out) 136 | 137 | @pytest.mark.parametrize("filename", ["chromeinstall-8u31.exe"]) 138 | def test_open(self, capsys, filename): 139 | with open(os.path.join(FIXTURE_DIR, filename), 'rb') as f: 140 | hashfile = sha256(f.read()).hexdigest() 141 | self.cmd['open']['obj'](hashfile) 142 | self.cmd['info']['obj']() 143 | self.cmd['close']['obj']() 144 | out, err = capsys.readouterr() 145 | lines = out.split("\n") 146 | 147 | assert re.search(r".*Session opened on.*", lines[0]) 148 | assert re.search(r".*| SHA1 | 56c5b6cd34fa9532b5a873d6bdd4380cfd102218.*", lines[11]) 149 | 150 | @pytest.mark.parametrize("filename", ["chromeinstall-8u31.exe"]) 151 | def test_find(self, capsys, filename): 152 | with open(os.path.join(FIXTURE_DIR, filename), 'rb') as f: 153 | data = f.read() 154 | hashfile_sha = sha256(data).hexdigest() 155 | self.cmd['find']['obj']('all') 156 | self.cmd['find']['obj']('sha256', hashfile_sha) 157 | self.cmd['open']['obj']('-l', '1') 158 | self.cmd['close']['obj']() 159 | self.cmd['tags']['obj']('-a', 'blah') 160 | self.cmd['find']['obj']('-t') 161 | self.cmd['tags']['obj']('-d', 'blah') 162 | out, err = capsys.readouterr() 163 | 164 | assert re.search(r".*EICAR.com.*", out) 165 | assert re.search(r".*{0}.*".format(filename), out) 166 | assert re.search(r".*Tag.*|.*# Entries.*", out) 167 | 168 | def test_stats(self, capsys): 169 | self.cmd['stats']['obj']() 170 | out, err = capsys.readouterr() 171 | 172 | assert re.search(r".*Projects.*Name | Count.*", out) 173 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | # TOX Setup 2 | [tox] 3 | envlist = 4 | pep8 5 | py34 6 | py35 7 | py36 8 | py37 9 | 10 | [testenv] 11 | deps = 12 | -U 13 | tox 14 | pytest 15 | pytest-cov 16 | coverage 17 | #codecov 18 | mock 19 | setenv = 20 | HOME = {toxworkdir} 21 | 22 | passenv = CI TRAVIS TRAVIS_* 23 | 24 | whitelist_externals = 25 | git 26 | bash 27 | rm 28 | python 29 | commands = 30 | git submodule sync -q 31 | git submodule update --init 32 | rm -rf {toxworkdir}/.viper 33 | bash -c 'echo "update-modules" | viper' 34 | bash -c 'echo "exit" | viper' 35 | pytest {posargs} 36 | #codecov 37 | rm -rf {toxworkdir}/.viper 38 | 39 | 40 | # Style/Lint 41 | [testenv:pep8] 42 | deps = 43 | flake8 44 | commands = flake8 . viper 45 | 46 | 47 | # Flake8 Configuration 48 | [flake8] 49 | max-line-length = 400 50 | 51 | exclude = 52 | .tox, 53 | .git, 54 | .build, 55 | __pycache__, 56 | docs/source/conf.py, 57 | build, 58 | dist, 59 | old, 60 | tests/fixtures/*, 61 | *.pyc, 62 | *.egg-info, 63 | .cache, 64 | .eggs, 65 | viper/modules/pymacho_helper, 66 | viper/modules/pdftools, 67 | tests 68 | -------------------------------------------------------------------------------- /viper/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # This file is part of Viper - https://github.com/viper-framework/viper 3 | # See the file 'LICENSE' for copying permission. 4 | -------------------------------------------------------------------------------- /viper/common/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # This file is part of Viper - https://github.com/viper-framework/viper 3 | # See the file 'LICENSE' for copying permission. 4 | -------------------------------------------------------------------------------- /viper/common/abstracts.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # This file is part of Viper - https://github.com/viper-framework/viper 3 | # See the file 'LICENSE' for copying permission. 4 | 5 | import argparse 6 | import viper.common.out as out 7 | from viper.core.config import console_output 8 | from viper.common.exceptions import ArgumentErrorCallback 9 | 10 | 11 | class ArgumentParser(argparse.ArgumentParser): 12 | def print_usage(self, file=None): 13 | raise ArgumentErrorCallback(self.format_usage()) 14 | 15 | def print_help(self, file=None): 16 | raise ArgumentErrorCallback(self.format_help()) 17 | 18 | def error(self, message): 19 | raise ArgumentErrorCallback(message, 'error') 20 | 21 | def exit(self, status=0, message=None): 22 | if message is not None: 23 | raise ArgumentErrorCallback(message) 24 | 25 | 26 | class Command(object): 27 | cmd = "" 28 | description = "" 29 | command_line = [] 30 | args = None 31 | authors = [] 32 | output = [] 33 | fs_path_completion = False 34 | 35 | def __init__(self): 36 | self.parser = argparse.ArgumentParser(prog=self.cmd, description=self.description) 37 | 38 | def log(self, event_type, event_data): 39 | self.output.append(dict( 40 | type=event_type, 41 | data=event_data 42 | )) 43 | out.print_output([{'type': event_type, 'data': event_data}], console_output['filename']) 44 | 45 | 46 | class Module(object): 47 | cmd = '' 48 | description = '' 49 | command_line = [] 50 | args = None 51 | authors = [] 52 | output = [] 53 | 54 | def __init__(self): 55 | self.parser = ArgumentParser(prog=self.cmd, description=self.description) 56 | 57 | def set_commandline(self, command): 58 | self.command_line = command 59 | 60 | def log(self, event_type, event_data): 61 | self.output.append(dict( 62 | type=event_type, 63 | data=event_data 64 | )) 65 | out.print_output([{'type': event_type, 'data': event_data}], console_output['filename']) 66 | 67 | def usage(self): 68 | self.log('', self.parser.format_usage()) 69 | 70 | def help(self): 71 | self.log('', self.parser.format_help()) 72 | 73 | def run(self): 74 | try: 75 | self.args = self.parser.parse_args(self.command_line) 76 | except ArgumentErrorCallback as e: 77 | self.log(*e.get()) 78 | 79 | 80 | def get_argparse_parser_actions(parser): 81 | """introspect argparse object and return list of parameters/options/arguments""" 82 | ret = {} 83 | 84 | parser_actions = [(x.option_strings, x.choices, x.help) for x in parser._actions] 85 | for parser_action in parser_actions: 86 | if parser_action[1]: 87 | for action in parser_action[1]: 88 | ret.update({action: parser_action[2]}) 89 | if isinstance(parser_action[0], list): 90 | for option in parser_action[0]: 91 | # ignore short options (only add --help and not -h) 92 | if option.startswith("--"): 93 | ret.update({option: parser_action[2]}) 94 | else: 95 | ret.update({parser_action[0]: parser_action[2]}) 96 | 97 | return ret 98 | 99 | 100 | def get_argparse_subparser_actions(parser): 101 | """introspect argparse subparser object""" 102 | ret = {} 103 | try: 104 | for subparser_action in parser._subparsers._actions: 105 | if isinstance(subparser_action, argparse._SubParsersAction): 106 | for item in list(subparser_action.choices): 107 | ret.update({item: get_argparse_parser_actions(subparser_action.choices[item])}) 108 | 109 | except AttributeError: 110 | pass 111 | 112 | return ret 113 | -------------------------------------------------------------------------------- /viper/common/autorun.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # This file is part of Viper - https://github.com/viper-framework/viper 3 | # See the file 'LICENSE' for copying permission. 4 | 5 | from viper.common.out import print_info 6 | from viper.common.out import print_error 7 | from viper.common.out import print_output 8 | from viper.core.plugins import __modules__ 9 | from viper.core.session import __sessions__ 10 | from viper.core.database import Database 11 | from viper.core.config import __config__ 12 | from viper.core.storage import get_sample_path 13 | 14 | cfg = __config__ 15 | 16 | 17 | def parse_commands(data): 18 | root = '' 19 | args = [] 20 | words = data.split() 21 | root = words[0] 22 | 23 | if len(words) > 1: 24 | args = words[1:] 25 | 26 | return root, args 27 | 28 | 29 | def autorun_module(file_hash): 30 | if not file_hash: 31 | return 32 | 33 | if not __sessions__.is_set(): 34 | __sessions__.new(get_sample_path(file_hash)) 35 | 36 | for cmd_line in cfg.autorun.commands.split(','): 37 | split_commands = cmd_line.split(';') 38 | 39 | for split_command in split_commands: 40 | split_command = split_command.strip() 41 | 42 | if not split_command: 43 | continue 44 | 45 | root, args = parse_commands(split_command) 46 | 47 | try: 48 | if root in __modules__: 49 | print_info("Running command \"{0}\"".format(split_command)) 50 | 51 | module = __modules__[root]['obj']() 52 | module.set_commandline(args) 53 | module.run() 54 | 55 | if cfg.modules.store_output and __sessions__.is_set(): 56 | Database().add_analysis(file_hash, split_command, module.output) 57 | 58 | if cfg.autorun.verbose: 59 | print_output(module.output) 60 | 61 | del(module.output[:]) 62 | else: 63 | print_error("\"{0}\" is not a valid command. Please check your viper.conf file.".format(cmd_line)) 64 | except Exception: 65 | print_error("Viper was unable to complete the command {0}".format(cmd_line)) 66 | -------------------------------------------------------------------------------- /viper/common/colors.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # This file is part of Viper - https://github.com/viper-framework/viper 3 | # See the file 'LICENSE' for copying permission. 4 | 5 | import os 6 | import sys 7 | 8 | 9 | def color(text, color_code, readline=False): 10 | """Colorize text. 11 | @param text: text. 12 | @param color_code: color. 13 | @return: colorized text. 14 | """ 15 | # $TERM under Windows: 16 | # cmd.exe -> "" (what would you expect..?) 17 | # cygwin -> "cygwin" (should support colors, but doesn't work somehow) 18 | # mintty -> "xterm" (supports colors) 19 | if sys.platform == "win32" and os.getenv("TERM") != "xterm": 20 | return str(text) 21 | 22 | if readline: 23 | # special readline escapes to fix colored input prompts 24 | # http://bugs.python.org/issue17337 25 | return "\x01\x1b[%dm\x02%s\x01\x1b[0m\x02" % (color_code, text) 26 | 27 | return "\x1b[%dm%s\x1b[0m" % (color_code, text) 28 | 29 | 30 | def black(text, readline=False): 31 | return color(text, 30, readline) 32 | 33 | 34 | def red(text, readline=False): 35 | return color(text, 31, readline) 36 | 37 | 38 | def green(text, readline=False): 39 | return color(text, 32, readline) 40 | 41 | 42 | def yellow(text, readline=False): 43 | return color(text, 33, readline) 44 | 45 | 46 | def blue(text, readline=False): 47 | return color(text, 34, readline) 48 | 49 | 50 | def magenta(text, readline=False): 51 | return color(text, 35, readline) 52 | 53 | 54 | def cyan(text, readline=False): 55 | return color(text, 36, readline) 56 | 57 | 58 | def white(text, readline=False): 59 | return color(text, 37, readline) 60 | 61 | 62 | def bold(text, readline=False): 63 | return color(text, 1, readline) 64 | -------------------------------------------------------------------------------- /viper/common/exceptions.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # This file is part of Viper - https://github.com/viper-framework/viper 3 | # See the file 'LICENSE' for copying permission. 4 | 5 | 6 | class GenericException(Exception): 7 | def __init__(self, message, level=''): 8 | self.message = message.strip() + '\n' 9 | self.level = level.strip() 10 | 11 | def __str__(self): 12 | return '{}: {}'.format(self.level, self.message) 13 | 14 | def get(self): 15 | return self.level, self.message 16 | 17 | 18 | class ArgumentErrorCallback(GenericException): 19 | pass 20 | 21 | 22 | class Python2UnsupportedUnicode(GenericException): 23 | pass 24 | -------------------------------------------------------------------------------- /viper/common/network.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # This file is part of Viper - https://github.com/viper-framework/viper 3 | # See the file 'LICENSE' for copying permission. 4 | 5 | import requests 6 | from requests import ConnectionError 7 | from viper.common.out import print_error 8 | 9 | 10 | def download(url, tor=False): 11 | s = requests.Session() 12 | s.headers.update({'User-agent': 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)'}) 13 | proxies = {} 14 | if tor: 15 | proxies = {'http': 'socks5://{}:{}'.format('127.0.0.1', 9050), 16 | 'https': 'socks5://{}:{}'.format('127.0.0.1', 9050)} 17 | try: 18 | res = s.get(url, proxies=proxies) 19 | res.raise_for_status() 20 | except ConnectionError as e: 21 | if tor: 22 | print_error("Connection refused, maybe Tor is not running?") 23 | print_error(e) 24 | except Exception as e: 25 | print_error("Failed download: {0}".format(e)) 26 | else: 27 | return res.content 28 | -------------------------------------------------------------------------------- /viper/common/out.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # This file is part of Viper - https://github.com/viper-framework/viper 3 | # See the file 'LICENSE' for copying permission. 4 | 5 | try: 6 | from terminaltables import AsciiTable 7 | HAVE_TERMTAB = True 8 | except ImportError: 9 | HAVE_TERMTAB = False 10 | 11 | import textwrap 12 | import six 13 | import sys 14 | 15 | from viper.common.colors import cyan, yellow, red, green, bold 16 | 17 | 18 | def print_info(message): 19 | print(bold(cyan("[*]")) + " {0}".format(message)) 20 | 21 | 22 | def print_item(message, tabs=0): 23 | print(" {0}".format(" " * tabs) + cyan("-") + " {0}".format(message)) 24 | 25 | 26 | def print_warning(message): 27 | print(bold(yellow("[!]")) + " {0}".format(message)) 28 | 29 | 30 | def print_error(message): 31 | print(bold(red("[!]")) + " {0}".format(message)) 32 | 33 | 34 | def print_success(message): 35 | print(bold(green("[+]")) + " {0}".format(message)) 36 | 37 | 38 | def table(header, rows): 39 | if not HAVE_TERMTAB: 40 | print_error("Missing dependency, install terminaltables (`pip install terminaltables`)") 41 | return 42 | 43 | # TODO: Refactor this function, it is some serious ugly code. 44 | 45 | content = [] 46 | for l in [header] + rows: 47 | to_append = [] 48 | for a in l: 49 | if isinstance(a, bytes): 50 | if sys.version_info < (3, 4): 51 | a = a.decode('utf-8', 'ignore') 52 | else: 53 | a = a.decode('utf-8', 'backslashreplace') 54 | if not isinstance(a, six.text_type): 55 | a = six.text_type(a) 56 | # Prevents terminaltables from parsing escape characters 57 | to_append.append(a.replace('\t', ' ').replace('\v', '\\v').replace('\f', '\\f').replace('\r', '\\r').replace('\n', '\\n')) 58 | content.append(to_append) 59 | t = AsciiTable(content) 60 | if not t.ok: 61 | t.inner_row_border = True 62 | longest_col = t.column_widths.index(max(t.column_widths)) 63 | max_length_col = t.column_max_width(longest_col) 64 | if max_length_col > 0: 65 | for i, content in enumerate(t.table_data): 66 | if len(content[longest_col]) > max_length_col: 67 | temp = '' 68 | for l in content[longest_col].splitlines(): 69 | if len(l) > max_length_col: 70 | temp += '\n'.join(textwrap.wrap(l, max_length_col)) + '\n' 71 | else: 72 | temp += l + '\n' 73 | content[longest_col] = temp.strip() 74 | t.table_data[i] = content 75 | 76 | return t.table 77 | 78 | 79 | def print_output(output, filename=None): 80 | if not output: 81 | return 82 | 83 | if filename: 84 | with open(filename.strip(), 'a') as out: 85 | for entry in output: 86 | if entry['type'] == 'info': 87 | out.write('[*] {0}\n'.format(entry['data'])) 88 | elif entry['type'] == 'item': 89 | out.write(' [-] {0}\n'.format(entry['data'])) 90 | elif entry['type'] == 'warning': 91 | out.write('[!] {0}\n'.format(entry['data'])) 92 | elif entry['type'] == 'error': 93 | out.write('[!] {0}\n'.format(entry['data'])) 94 | elif entry['type'] == 'success': 95 | out.write('[+] {0}\n'.format(entry['data'])) 96 | elif entry['type'] == 'table': 97 | out.write(str(table( 98 | header=entry['data']['header'], 99 | rows=entry['data']['rows'] 100 | ))) 101 | out.write('\n') 102 | else: 103 | out.write('{0}\n'.format(entry['data'])) 104 | print_success("Output written to {0}".format(filename)) 105 | else: 106 | for entry in output: 107 | if entry['type'] == 'info': 108 | print_info(entry['data']) 109 | elif entry['type'] == 'item': 110 | print_item(entry['data']) 111 | elif entry['type'] == 'warning': 112 | print_warning(entry['data']) 113 | elif entry['type'] == 'error': 114 | print_error(entry['data']) 115 | elif entry['type'] == 'success': 116 | print_success(entry['data']) 117 | elif entry['type'] == 'table': 118 | print(table( 119 | header=entry['data']['header'], 120 | rows=entry['data']['rows'] 121 | )) 122 | else: 123 | print(entry['data']) 124 | -------------------------------------------------------------------------------- /viper/common/utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # This file is part of Viper - https://github.com/viper-framework/viper 3 | # See the file 'LICENSE' for copying permission. 4 | 5 | import string 6 | import hashlib 7 | import binascii 8 | import sys 9 | 10 | try: 11 | import magic 12 | except ImportError: 13 | pass 14 | 15 | 16 | # The following couple of functions are redundant. 17 | # TODO: find a way to better integrate these generic methods 18 | # with the ones available in the File class. 19 | def get_type(data): 20 | try: 21 | ms = magic.open(magic.MAGIC_NONE) 22 | ms.load() 23 | file_type = ms.buffer(data) 24 | except Exception: 25 | try: 26 | file_type = magic.from_buffer(data) 27 | except Exception: 28 | return '' 29 | finally: 30 | try: 31 | ms.close() 32 | except Exception: 33 | pass 34 | 35 | return file_type 36 | 37 | 38 | def get_md5(data): 39 | md5 = hashlib.md5() 40 | md5.update(data) 41 | return md5.hexdigest() 42 | 43 | 44 | def string_clean(line): 45 | try: 46 | if isinstance(line, bytes): 47 | line = line.decode('utf-8') 48 | return ''.join([x for x in line if x in string.printable]) 49 | except Exception: 50 | return line 51 | 52 | 53 | def string_clean_hex(line): 54 | new_line = '' 55 | for c in line: 56 | if c in string.printable: 57 | new_line += c 58 | else: 59 | if sys.version_info >= (3, 0): 60 | new_line += '\\x' + binascii.hexlify(c.encode('utf-8')).decode('utf-8') 61 | else: 62 | new_line += '\\x' + c.encode('hex') 63 | return new_line 64 | 65 | 66 | # Snippet taken from: 67 | # https://gist.github.com/sbz/1080258 68 | def hexdump(src, length=16, maxlines=None): 69 | FILTER = ''.join([(len(repr(chr(x))) == 3) and chr(x) or '.' for x in range(256)]) 70 | lines = [] 71 | for c in range(0, len(src), length): 72 | chars = src[c:c + length] 73 | if isinstance(chars, str): 74 | chars = [ord(x) for x in chars] 75 | hex = ' '.join(["%02x" % x for x in chars]) 76 | printable = ''.join(["%s" % ((x <= 127 and FILTER[x]) or '.') for x in chars]) 77 | lines.append("%04x %-*s %s\n" % (c, length * 3, hex, printable)) 78 | 79 | if maxlines: 80 | if len(lines) == maxlines: 81 | break 82 | 83 | return ''.join(lines) 84 | 85 | 86 | # Snippet taken from: 87 | # http://stackoverflow.com/questions/1094841/reusable-library-to-get-human-readable-version-of-file-size 88 | def convert_size(num, suffix='B'): 89 | for unit in ['', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi']: 90 | if abs(num) < 1024.0: 91 | return "%3.2f %s%s" % (num, unit, suffix) 92 | num /= 1024.0 93 | return "%.2f %s%s" % (num, 'Yi', suffix) 94 | -------------------------------------------------------------------------------- /viper/common/version.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # This file is part of Viper - https://github.com/viper-framework/viper 3 | # See the file 'LICENSE' for copying permission. 4 | 5 | # Store the version here so: 6 | # 1) we don't load dependencies by storing it in __init__.py 7 | # 2) we can import it in setup.py for the same reason 8 | # 3) we can import it into modules 9 | 10 | __version_info__ = ('2', '0-rc11') 11 | __version__ = '.'.join(__version_info__) 12 | -------------------------------------------------------------------------------- /viper/core/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # This file is part of Viper - https://github.com/viper-framework/viper 3 | # See the file 'LICENSE' for copying permission. 4 | -------------------------------------------------------------------------------- /viper/core/logger.py: -------------------------------------------------------------------------------- 1 | # This file is part of Viper - https://github.com/viper-framework/viper 2 | # See the file 'LICENSE' for copying permission. 3 | 4 | import logging 5 | import logging.handlers 6 | 7 | log = logging.getLogger('viper') 8 | 9 | 10 | def init_logger(log_file_path="viper.log", debug=False): 11 | if debug: 12 | formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(filename)s:%(lineno)d - %(message)s") 13 | else: 14 | formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') 15 | 16 | handler = logging.handlers.RotatingFileHandler(log_file_path, encoding='utf8', 17 | maxBytes=10000000, backupCount=1) 18 | handler.setFormatter(formatter) 19 | handler.setLevel(logging.INFO) 20 | 21 | if debug: 22 | handler.setLevel(logging.DEBUG) 23 | 24 | log.addHandler(handler) 25 | log.setLevel(logging.INFO) 26 | 27 | if debug: 28 | log.setLevel(logging.DEBUG) 29 | 30 | return log 31 | -------------------------------------------------------------------------------- /viper/core/plugins.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # This file is part of Viper - https://github.com/viper-framework/viper 3 | # See the file 'LICENSE' for copying permission. 4 | 5 | import sys 6 | import pkgutil 7 | import inspect 8 | import importlib 9 | 10 | from viper.common.abstracts import Command, Module 11 | from viper.common.abstracts import get_argparse_parser_actions 12 | from viper.common.abstracts import get_argparse_subparser_actions 13 | from viper.common.out import print_warning 14 | from viper.core.config import __config__ 15 | 16 | cfg = __config__ 17 | 18 | 19 | def load_commands(): 20 | # Import modules package. 21 | import viper.core.ui.cmd as cmd 22 | 23 | plugins = dict() 24 | 25 | # Walk recursively through all cmd and packages. 26 | for loader, cmd_name, ispkg in pkgutil.walk_packages(cmd.__path__, cmd.__name__ + '.'): 27 | # If current item is a package, skip. 28 | if ispkg: 29 | continue 30 | 31 | # Try to import the command, otherwise skip. 32 | try: 33 | cmd_module = importlib.import_module(cmd_name) 34 | except ImportError as e: 35 | print_warning("Something wrong happened while importing the command {0}: {1}".format(cmd_name, e)) 36 | continue 37 | 38 | # Walk through all members of currently imported cmd. 39 | for member_name, member_object in inspect.getmembers(cmd_module): 40 | # Check if current member is a class. 41 | if inspect.isclass(member_object): 42 | # Yield the class if it's a subclass of Command. 43 | if issubclass(member_object, Command) and member_object is not Command: 44 | instance = member_object() 45 | plugins[member_object.cmd] = dict(obj=instance.run, 46 | description=instance.description, 47 | parser_args=get_argparse_parser_actions(instance.parser), 48 | fs_path_completion=instance.fs_path_completion) 49 | 50 | return plugins 51 | 52 | 53 | def load_modules(): 54 | # Add root module_path as a Python path. 55 | sys.path.insert(0, cfg.paths.module_path) 56 | 57 | try: 58 | import modules 59 | except ImportError: 60 | return dict() 61 | else: 62 | plugins = dict() 63 | # Walk recursively through all modules and packages. 64 | for loader, module_name, ispkg in pkgutil.walk_packages(modules.__path__, modules.__name__ + '.'): 65 | # If current item is a package, skip. 66 | if ispkg: 67 | continue 68 | # Try to import the module, otherwise skip. 69 | try: 70 | module = importlib.import_module(module_name) 71 | except ImportError as e: 72 | print_warning("Something wrong happened while importing the module {0}: {1}".format(module_name, e)) 73 | continue 74 | 75 | # Walk through all members of currently imported modules. 76 | for member_name, member_object in inspect.getmembers(module): 77 | # Check if current member is a class. 78 | if inspect.isclass(member_object): 79 | # Yield the class if it's a subclass of Module. 80 | if issubclass(member_object, Module) and member_object is not Module: 81 | plugins[member_object.cmd] = dict(obj=member_object, 82 | description=member_object.description, 83 | categories=getattr(member_object, "categories", []), 84 | parser_args=get_argparse_parser_actions(member_object().parser), 85 | subparser_args=get_argparse_subparser_actions(member_object().parser)) 86 | 87 | return plugins 88 | 89 | 90 | __modules__ = load_modules() 91 | -------------------------------------------------------------------------------- /viper/core/project.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # This file is part of Viper - https://github.com/viper-framework/viper 3 | # See the file 'LICENSE' for copying permission. 4 | 5 | import os 6 | from os.path import expanduser 7 | import logging 8 | 9 | from viper.core.logger import init_logger 10 | from viper.core.config import __config__ 11 | 12 | log = logging.getLogger('viper') 13 | 14 | cfg = __config__ 15 | 16 | 17 | class Project(object): 18 | def __init__(self): 19 | self.name = None 20 | self.path = None 21 | self.base_path = None 22 | if cfg.paths.storage_path: 23 | self.path = cfg.paths.storage_path 24 | self.base_path = cfg.paths.storage_path 25 | else: 26 | self.path = os.path.join(expanduser("~"), '.viper') 27 | self.base_path = os.path.join(expanduser("~"), '.viper') 28 | 29 | if not os.path.exists(self.path): 30 | os.makedirs(self.path) 31 | 32 | # initialize default log settings 33 | log_file = os.path.join(self.base_path, "viper.log") 34 | debug_log = False 35 | 36 | if hasattr(cfg, 'logging'): 37 | if hasattr(cfg.logging, 'log_file') and cfg.logging.log_file: 38 | log_file = cfg.logging.log_file 39 | 40 | if hasattr(cfg.logging, 'debug'): 41 | debug_log = cfg.logging.debug 42 | 43 | init_logger(log_file_path=log_file, debug=debug_log) 44 | log.debug("logger initiated") 45 | 46 | def open(self, name): 47 | if not os.path.exists(self.base_path): 48 | raise Exception("The local storage folder does not exist at path {}".format(self.base_path)) 49 | 50 | if name == 'default': 51 | path = self.base_path 52 | else: 53 | path = os.path.join(self.base_path, 'projects', name) 54 | if not os.path.exists(path): 55 | os.makedirs(path) 56 | 57 | self.name = name 58 | self.path = path 59 | 60 | def close(self): 61 | # We "close" it and switch to default, if it isn't default already. 62 | if self.name != 'default': 63 | self.path = self.base_path 64 | self.name = None 65 | 66 | def get_path(self): 67 | if self.path and os.path.exists(self.path): 68 | return self.path 69 | else: 70 | return self.path 71 | 72 | def get_projects_path(self): 73 | return os.path.join(self.base_path, 'projects') 74 | 75 | 76 | __project__ = Project() 77 | 78 | 79 | def get_project_list(exclude_default=False): 80 | """get_project_list - get list of all projects""" 81 | projects_path = __project__.get_projects_path() 82 | project_list = [] 83 | if os.path.exists(projects_path): 84 | for project in os.listdir(projects_path): 85 | project_path = os.path.join(projects_path, project) 86 | if os.path.isdir(project_path): 87 | project_list.append(project) 88 | 89 | if exclude_default: 90 | pass 91 | else: 92 | project_list.append("default") 93 | 94 | return sorted(project_list) 95 | -------------------------------------------------------------------------------- /viper/core/session.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # This file is part of Viper - https://github.com/viper-framework/viper 3 | # See the file 'LICENSE' for copying permission. 4 | 5 | import time 6 | import datetime 7 | 8 | from viper.common.out import print_info, print_error 9 | from viper.common.objects import File 10 | from viper.core.database import Database 11 | 12 | 13 | class Session(object): 14 | def __init__(self): 15 | self.id = None 16 | # This will be assigned with the File object of the file currently 17 | # being analyzed. 18 | self.file = None 19 | # Timestamp of the creation of the session. 20 | self.created_at = datetime.datetime.fromtimestamp(time.time()).strftime('%Y-%m-%d %H:%M:%S') 21 | # MISP event associated to the object 22 | self.misp_event = None 23 | 24 | 25 | class Sessions(object): 26 | def __init__(self): 27 | self.current = None 28 | self.sessions = [] 29 | # Store the results of the last "find" command. 30 | self.find = None 31 | 32 | def is_attached_misp(self, quiet=False): 33 | if not self.is_set(): 34 | if not quiet: 35 | print_error("No session opened") 36 | return False 37 | if not self.current.misp_event: 38 | if not quiet: 39 | print_error("Not attached to a MISP event") 40 | return False 41 | return True 42 | 43 | def is_attached_file(self, quiet=False): 44 | if not self.is_set(): 45 | if not quiet: 46 | print_error("No session opened") 47 | return False 48 | if not self.current.file: 49 | if not quiet: 50 | print_error("Not attached to a file") 51 | return False 52 | return True 53 | 54 | def close(self): 55 | self.current = None 56 | 57 | def is_set(self): 58 | # Check if the session has been opened or not. 59 | if self.current: 60 | return True 61 | else: 62 | return False 63 | 64 | def switch(self, session): 65 | self.current = session 66 | print_info("Switched to session #{0} on {1}".format(self.current.id, self.current.file.path)) 67 | 68 | def new(self, path=None, misp_event=None): 69 | if not path and not misp_event: 70 | print_error("You have to open a session on a path or on a misp event.") 71 | return 72 | 73 | session = Session() 74 | 75 | total = len(self.sessions) 76 | session.id = total + 1 77 | 78 | if path: 79 | if self.is_set() and misp_event is None and self.current.misp_event: 80 | session.misp_event = self.current.misp_event 81 | 82 | # Open a session on the given file. 83 | session.file = File(path) 84 | # Try to lookup the file in the database. If it is already present 85 | # we get its database ID, file name, and tags. 86 | row = Database().find(key='sha256', value=session.file.sha256) 87 | if row: 88 | session.file.id = row[0].id 89 | session.file.name = row[0].name 90 | session.file.tags = ', '.join(tag.to_dict()['tag'] for tag in row[0].tag) 91 | 92 | if row[0].parent: 93 | session.file.parent = '{0} - {1}'.format(row[0].parent.name, row[0].parent.sha256) 94 | session.file.children = Database().get_children(row[0].id) 95 | 96 | print_info("Session opened on {0}".format(path)) 97 | 98 | if misp_event: 99 | if self.is_set() and path is None and self.current.file: 100 | session.file = self.current.file 101 | refresh = False 102 | if (self.current is not None and self.current.misp_event is not None and 103 | self.current.misp_event.event.id is not None and 104 | self.current.misp_event.event.id == misp_event.event.id): 105 | refresh = True 106 | session.misp_event = misp_event 107 | if refresh: 108 | print_info("Session on MISP event {0} refreshed.".format(misp_event.event.id)) 109 | elif not misp_event.event.id: 110 | print_info("Session opened on a new local MISP event.") 111 | else: 112 | print_info("Session opened on MISP event {0}.".format(misp_event.event.id)) 113 | 114 | if session.file: 115 | # Loop through all existing sessions and check whether there's another 116 | # session open on the same file and delete it. This is to avoid 117 | # duplicates in sessions. 118 | # NOTE: in the future we might want to remove this if sessions have 119 | # unique attributes (for example, an history just for each of them). 120 | for entry in self.sessions: 121 | if entry.file and entry.file.sha256 == session.file.sha256: 122 | self.sessions.remove(entry) 123 | 124 | # Add new session to the list. 125 | self.sessions.append(session) 126 | # Mark the new session as the current one. 127 | self.current = session 128 | 129 | 130 | __sessions__ = Sessions() 131 | -------------------------------------------------------------------------------- /viper/core/storage.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # This file is part of Viper - https://github.com/viper-framework/viper 3 | # See the file 'LICENSE' for copying permission. 4 | 5 | import os 6 | 7 | from viper.common.out import print_warning, print_error 8 | from viper.core.project import __project__ 9 | 10 | 11 | def store_sample(file_object): 12 | sha256 = file_object.sha256 13 | 14 | if not sha256: 15 | print_error("No hash") 16 | return None 17 | 18 | folder = os.path.join( 19 | __project__.get_path(), 20 | 'binaries', 21 | sha256[0], 22 | sha256[1], 23 | sha256[2], 24 | sha256[3] 25 | ) 26 | 27 | if not os.path.exists(folder): 28 | os.makedirs(folder, 0o750) 29 | 30 | file_path = os.path.join(folder, sha256) 31 | 32 | if not os.path.exists(file_path): 33 | with open(file_path, 'wb') as stored: 34 | for chunk in file_object.get_chunks(): 35 | stored.write(chunk) 36 | else: 37 | print_warning("File exists already") 38 | return None 39 | 40 | return file_path 41 | 42 | 43 | def get_sample_path(sha256): 44 | path = os.path.join( 45 | __project__.get_path(), 46 | 'binaries', 47 | sha256[0], 48 | sha256[1], 49 | sha256[2], 50 | sha256[3], 51 | sha256 52 | ) 53 | 54 | if not os.path.exists(path): 55 | return None 56 | 57 | return path 58 | -------------------------------------------------------------------------------- /viper/core/ui/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # This file is part of Viper - https://github.com/viper-framework/viper 3 | # See the file 'LICENSE' for copying permission. 4 | -------------------------------------------------------------------------------- /viper/core/ui/cmd/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # This file is part of Viper - https://github.com/viper-framework/viper 3 | # See the file 'LICENSE' for copying permission. 4 | -------------------------------------------------------------------------------- /viper/core/ui/cmd/about.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # This file is part of Viper - https://github.com/viper-framework/viper 3 | # See the file 'LICENSE' for copying permission. 4 | 5 | import os 6 | import platform 7 | 8 | from viper.common.abstracts import Command 9 | from viper.common.version import __version__ 10 | from viper.core.database import Database 11 | from viper.core.config import __config__ 12 | from viper.core.project import __project__ 13 | 14 | 15 | class About(Command): 16 | """ 17 | This command prints some useful information regarding the running 18 | Viper instance 19 | """ 20 | cmd = "about" 21 | description = "Show information about this Viper instance" 22 | 23 | def run(self, *args): 24 | try: 25 | self.parser.parse_args(args) 26 | except SystemExit: 27 | return 28 | 29 | rows = list() 30 | rows.append(["Viper Version", __version__]) 31 | rows.append(["Python Version", platform.python_version()]) 32 | rows.append(["Homepage", "https://viper.li"]) 33 | rows.append(["Issue Tracker", "https://github.com/viper-framework/viper/issues"]) 34 | 35 | self.log('table', dict(header=['About', ''], rows=rows)) 36 | 37 | rows = list() 38 | rows.append(["Configuration File", __config__.config_file]) 39 | 40 | module_path = os.path.join(__config__.paths.module_path, "modules") 41 | 42 | if __project__.name: 43 | rows.append(["Active Project", __project__.name]) 44 | rows.append(["Storage Path", __project__.path]) 45 | rows.append(["Module Path", module_path]) 46 | rows.append(["Database Path", Database().engine.url]) 47 | else: 48 | rows.append(["Active Project", "default"]) 49 | rows.append(["Storage Path", __project__.path]) 50 | rows.append(["Module Path", module_path]) 51 | rows.append(["Database Path", Database().engine.url]) 52 | 53 | self.log('table', dict(header=['Configuration', ''], rows=rows)) 54 | -------------------------------------------------------------------------------- /viper/core/ui/cmd/analysis.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # This file is part of Viper - https://github.com/viper-framework/viper 3 | # See the file 'LICENSE' for copying permission. 4 | 5 | import json 6 | 7 | from viper.common.abstracts import Command 8 | from viper.common.colors import bold 9 | from viper.core.session import __sessions__ 10 | from viper.core.database import Database 11 | 12 | 13 | class Analysis(Command): 14 | """ 15 | This command allows you to view the stored output from modules that have been run 16 | with the currently opened file. 17 | """ 18 | cmd = "analysis" 19 | description = "View the stored analysis" 20 | 21 | def __init__(self): 22 | super(Analysis, self).__init__() 23 | 24 | group = self.parser.add_mutually_exclusive_group() 25 | group.add_argument('-l', '--list', action='store_true', 26 | help="List all module results available for the current file") 27 | group.add_argument('-v', '--view', metavar='ANALYSIS ID', type=int, help="View the specified analysis") 28 | group.add_argument('-d', '--delete', metavar='ANALYSIS ID', type=int, help="Delete an existing analysis") 29 | 30 | def run(self, *args): 31 | try: 32 | args = self.parser.parse_args(args) 33 | except SystemExit: 34 | return 35 | 36 | if not __sessions__.is_set(): 37 | self.log('error', "No open session. This command expects a file to be open.") 38 | return 39 | 40 | db = Database() 41 | 42 | # check if the file is already stores, otherwise exit 43 | malware = db.find(key='sha256', value=__sessions__.current.file.sha256) 44 | if not malware: 45 | self.log('error', "The opened file doesn't appear to be in the database, have you stored it yet?") 46 | return 47 | 48 | if args.list: 49 | # Retrieve all analysis for the currently opened file. 50 | 51 | analysis_list = malware[0].analysis 52 | if not analysis_list: 53 | self.log('info', "No analysis available for this file yet") 54 | return 55 | 56 | # Populate table rows. 57 | rows = [[analysis.id, analysis.cmd_line, analysis.stored_at] for analysis in analysis_list] 58 | 59 | # Display list of existing results. 60 | self.log('table', dict(header=['ID', 'Cmd Line', 'Saved On (UTC)'], rows=rows)) 61 | 62 | elif args.view: 63 | # Retrieve analysis with the specified ID and print it. 64 | result = db.get_analysis(args.view) 65 | if result: 66 | self.log('info', bold('Cmd Line: ') + result.cmd_line) 67 | for line in json.loads(result.results): 68 | self.log(line['type'], line['data']) 69 | else: 70 | self.log('info', "There is no analysis with ID {0}".format(args.view)) 71 | 72 | elif args.delete: 73 | # Delete the analysis with the specified ID. 74 | db.delete_analysis(args.delete) 75 | else: 76 | self.parser.print_usage() 77 | -------------------------------------------------------------------------------- /viper/core/ui/cmd/clear.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # This file is part of Viper - https://github.com/viper-framework/viper 3 | # See the file 'LICENSE' for copying permission. 4 | 5 | import os 6 | 7 | from viper.common.abstracts import Command 8 | 9 | 10 | class Clear(Command): 11 | """ 12 | This command simply clears the shell. 13 | """ 14 | cmd = "clear" 15 | description = "Clear the console" 16 | 17 | def run(self, *args): 18 | try: 19 | args = self.parser.parse_args(args) 20 | except SystemExit: 21 | return 22 | 23 | os.system('clear') 24 | -------------------------------------------------------------------------------- /viper/core/ui/cmd/close.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # This file is part of Viper - https://github.com/viper-framework/viper 3 | # See the file 'LICENSE' for copying permission. 4 | 5 | from viper.common.abstracts import Command 6 | from viper.core.session import __sessions__ 7 | 8 | 9 | class Close(Command): 10 | """ 11 | This command resets the open session. 12 | After that, all handles to the opened file should be closed and the 13 | shell should be restored to the default prompt. 14 | """ 15 | cmd = "close" 16 | description = "Close the current session" 17 | 18 | def run(self, *args): 19 | try: 20 | self.parser.parse_args(args) 21 | except SystemExit: 22 | return 23 | 24 | __sessions__.close() 25 | -------------------------------------------------------------------------------- /viper/core/ui/cmd/copy.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # This file is part of Viper - https://github.com/viper-framework/viper 3 | # See the file 'LICENSE' for copying permission. 4 | 5 | import os 6 | 7 | from viper.common.abstracts import Command 8 | from viper.core.session import __sessions__ 9 | from viper.core.project import __project__ 10 | from viper.core.database import Database 11 | from viper.core.storage import get_sample_path 12 | 13 | 14 | class Copy(Command): 15 | """ 16 | This command copies the opened file into another project. Analysis, Notes 17 | and Tags are - by default - also copies. Children can (optionally) also 18 | be copied (recursively). 19 | """ 20 | cmd = "copy" 21 | description = "Copy opened file(s) into another project" 22 | 23 | def __init__(self): 24 | super(Copy, self).__init__() 25 | self.parser.add_argument('project', type=str, help="Project to copy file(s) to") 26 | 27 | self.parser.add_argument('-d', '--delete', action='store_true', help="delete original file(s) after copy ('move')") 28 | self.parser.add_argument('--no-analysis', action='store_true', help="do not copy analysis details") 29 | self.parser.add_argument('--no-notes', action='store_true', help="do not copy notes") 30 | self.parser.add_argument('--no-tags', action='store_true', help="do not copy tags") 31 | 32 | self.parser.add_argument('-c', '--children', action='store_true', help="also copy all children - if --delete was " 33 | "selected also the children will be deleted " 34 | "from current project after copy") 35 | 36 | def run(self, *args): 37 | try: 38 | args = self.parser.parse_args(args) 39 | except SystemExit: 40 | return 41 | 42 | if not __sessions__.is_set(): 43 | self.log('error', "No open session. This command expects a file to be open.") 44 | return 45 | 46 | if not __project__.name: 47 | src_project = "default" 48 | else: 49 | src_project = __project__.name 50 | 51 | db = Database() 52 | 53 | db.copied_id_sha256 = [] 54 | res = db.copy(__sessions__.current.file.id, 55 | src_project=src_project, dst_project=args.project, 56 | copy_analysis=True, copy_notes=True, copy_tags=True, copy_children=args.children) 57 | 58 | if args.delete: 59 | __sessions__.close() 60 | for item_id, item_sha256 in db.copied_id_sha256: 61 | db.delete_file(item_id) 62 | os.remove(get_sample_path(item_sha256)) 63 | self.log('info', "Deleted: {}".format(item_sha256)) 64 | 65 | if res: 66 | self.log('success', "Successfully copied sample(s)") 67 | return True 68 | else: 69 | self.log('error', "Something went wrong") 70 | return False 71 | -------------------------------------------------------------------------------- /viper/core/ui/cmd/delete.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # This file is part of Viper - https://github.com/viper-framework/viper 3 | # See the file 'LICENSE' for copying permission. 4 | 5 | import os 6 | 7 | from viper.common.abstracts import Command 8 | from viper.core.session import __sessions__ 9 | from viper.core.database import Database 10 | from viper.core.storage import get_sample_path 11 | 12 | 13 | class Delete(Command): 14 | """ 15 | This command deletes the currently opened file (only if it's stored in 16 | the local repository) and removes the details from the database 17 | """ 18 | cmd = "delete" 19 | description = "Delete the opened file" 20 | 21 | def __init__(self): 22 | super(Delete, self).__init__() 23 | 24 | self.parser.add_argument('-a', '--all', action='store_true', help="Delete ALL files in this project") 25 | self.parser.add_argument('-f', '--find', action="store_true", help="Delete ALL files from last find") 26 | self.parser.add_argument('-y','--yes',action='store_true',help="Delete without confirmation") 27 | 28 | def run(self, *args): 29 | try: 30 | args = self.parser.parse_args(args) 31 | except SystemExit: 32 | return 33 | 34 | if not args.yes: 35 | while True: 36 | choice = input("Are you sure? It can't be reverted! [y/n] ") 37 | if choice == 'y': 38 | break 39 | elif choice == 'n': 40 | return 41 | 42 | db = Database() 43 | 44 | if args.all: 45 | if __sessions__.is_set(): 46 | __sessions__.close() 47 | 48 | samples = db.find('all') 49 | for sample in samples: 50 | db.delete_file(sample.id) 51 | os.remove(get_sample_path(sample.sha256)) 52 | 53 | self.log('info', "Deleted a total of {} files.".format(len(samples))) 54 | elif args.find: 55 | if __sessions__.find: 56 | samples = __sessions__.find 57 | for sample in samples: 58 | db.delete_file(sample.id) 59 | os.remove(get_sample_path(sample.sha256)) 60 | self.log('info', "Deleted {} files.".format(len(samples))) 61 | else: 62 | self.log('error', "No find result") 63 | 64 | else: 65 | if __sessions__.is_set(): 66 | rows = db.find('sha256', __sessions__.current.file.sha256) 67 | if rows: 68 | malware_id = rows[0].id 69 | if db.delete_file(malware_id): 70 | self.log("success", "File deleted") 71 | else: 72 | self.log('error', "Unable to delete file") 73 | 74 | os.remove(__sessions__.current.file.path) 75 | __sessions__.close() 76 | 77 | self.log('info', "Deleted opened file.") 78 | else: 79 | self.log('error', "No session open, and no --all argument. Nothing to delete.") 80 | -------------------------------------------------------------------------------- /viper/core/ui/cmd/export.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # This file is part of Viper - https://github.com/viper-framework/viper 3 | # See the file 'LICENSE' for copying permission. 4 | 5 | import os 6 | import shutil 7 | import getpass 8 | 9 | from viper.common.abstracts import Command 10 | from viper.core.archiver import Compressor 11 | from viper.core.session import __sessions__ 12 | 13 | 14 | def get_password_twice(): 15 | password = getpass.getpass('Password: ') 16 | if password == getpass.getpass('confirm Password: '): 17 | return password 18 | else: 19 | return None 20 | 21 | 22 | class Export(Command): 23 | """ 24 | This command will export the current session to file or zip. 25 | """ 26 | cmd = "export" 27 | description = "Export the current session to file or zip" 28 | fs_path_completion = True 29 | 30 | def __init__(self): 31 | super(Export, self).__init__() 32 | 33 | self.parser.add_argument('-z', '--zip', action='store_true', help="Export session in a zip archive (PW support: No)") 34 | self.parser.add_argument('-7', '--sevenzip', action='store_true', help="Export session in a 7z archive (PW support: Yes)") 35 | self.parser.add_argument('-p', '--password', action='store_true', help="Protect archive with a password (PW) if supported") 36 | self.parser.add_argument('value', help="path or archive name") 37 | 38 | def run(self, *args): 39 | try: 40 | args = self.parser.parse_args(args) 41 | except SystemExit: 42 | return 43 | 44 | # This command requires a session to be opened. 45 | if not __sessions__.is_set(): 46 | self.log('error', "No open session. This command expects a file to be open.") 47 | self.parser.print_usage() 48 | return 49 | 50 | # Check for valid export path. 51 | if args.value is None: 52 | self.parser.print_usage() 53 | return 54 | 55 | if args.zip and args.sevenzip: 56 | self.log('error', "Please select either -z or -7 not both, abort") 57 | 58 | store_path = os.path.join(args.value, __sessions__.current.file.name) 59 | 60 | if not args.zip and not args.sevenzip: 61 | # Abort if the specified path already exists 62 | if os.path.isfile(store_path): 63 | self.log('error', "Unable to export file: File exists: '{0}'".format(store_path)) 64 | return 65 | 66 | if not os.path.isdir(args.value): 67 | try: 68 | os.makedirs(args.value) 69 | except OSError as err: 70 | self.log('error', "Unable to export file: {0}".format(err)) 71 | return 72 | 73 | try: 74 | shutil.copyfile(__sessions__.current.file.path, store_path) 75 | except IOError as e: 76 | self.log('error', "Unable to export file: {0}".format(e)) 77 | else: 78 | self.log('info', "File exported to {0}".format(store_path)) 79 | 80 | return 81 | elif args.zip: 82 | cls = "ZipCompressor" 83 | 84 | elif args.sevenzip: 85 | cls = "SevenZipSystemCompressor" 86 | else: 87 | cls = "" 88 | self.log('error', "Not implemented".format()) 89 | 90 | c = Compressor() 91 | 92 | if args.password: 93 | if c.compressors[cls].supports_password: 94 | _password = get_password_twice() 95 | if not _password: 96 | self.log('error', "Passwords did not match, abort") 97 | return 98 | res = c.compress(__sessions__.current.file.path, file_name=__sessions__.current.file.name, 99 | archive_path=store_path, cls_name=cls, password=_password) 100 | else: 101 | self.log('warning', "ignoring password (not supported): {}".format(cls)) 102 | res = c.compress(__sessions__.current.file.path, file_name=__sessions__.current.file.name, 103 | archive_path=store_path, cls_name=cls) 104 | 105 | else: 106 | res = c.compress(__sessions__.current.file.path, file_name=__sessions__.current.file.name, 107 | archive_path=store_path, cls_name=cls) 108 | 109 | if res: 110 | self.log('info', "File archived and exported to {0}".format(c.output_archive_path)) 111 | else: 112 | self.log('error', "Unable to export file: {0}".format(c.err)) 113 | -------------------------------------------------------------------------------- /viper/core/ui/cmd/find.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # This file is part of Viper - https://github.com/viper-framework/viper 3 | # See the file 'LICENSE' for copying permission. 4 | 5 | from viper.common.abstracts import Command 6 | from viper.core.database import Database 7 | from viper.core.session import __sessions__ 8 | 9 | 10 | class Find(Command): 11 | """ 12 | This command is used to search for files in the database. 13 | """ 14 | cmd = "find" 15 | description = "Find a file" 16 | 17 | def __init__(self): 18 | super(Find, self).__init__() 19 | 20 | group = self.parser.add_mutually_exclusive_group() 21 | group.add_argument('-t', '--tags', action='store_true', help="List available tags and quit") 22 | group.add_argument('type', nargs='?', choices=["all", "latest", "name", "type", "mime", "md5", "sha1", "sha256", "tag", "note", "any", "ssdeep"], help="Where to search.") 23 | self.parser.add_argument("value", nargs='?', help="String to search.") 24 | 25 | def run(self, *args): 26 | try: 27 | args = self.parser.parse_args(args) 28 | except SystemExit: 29 | return 30 | 31 | db = Database() 32 | 33 | # One of the most useful search terms is by tag. With the --tags 34 | # argument we first retrieve a list of existing tags and the count 35 | # of files associated with each of them. 36 | if args.tags: 37 | # Retrieve list of tags. 38 | tags = db.list_tags() 39 | 40 | if tags: 41 | rows = [] 42 | # For each tag, retrieve the count of files associated with it. 43 | for tag in tags: 44 | count = len(db.find('tag', tag.tag)) 45 | rows.append([tag.tag, count]) 46 | 47 | # Generate the table with the results. 48 | header = ['Tag', '# Entries'] 49 | rows.sort(key=lambda x: x[1], reverse=True) 50 | self.log('table', dict(header=header, rows=rows)) 51 | else: 52 | self.log('warning', "No tags available") 53 | 54 | return 55 | 56 | # At this point, if there are no search terms specified, return. 57 | if args.type is None: 58 | self.parser.print_usage() 59 | return 60 | 61 | key = args.type 62 | if key != 'all' and key != 'latest': 63 | try: 64 | # The second argument is the search value. 65 | value = args.value 66 | except IndexError: 67 | self.log('error', "You need to include a search term.") 68 | return 69 | else: 70 | value = None 71 | 72 | # Search all the files matching the given parameters. 73 | items = db.find(key, value) 74 | if not items: 75 | return 76 | 77 | # Populate the list of search results. 78 | rows = [] 79 | count = 1 80 | for item in items: 81 | tag = ', '.join([t.tag for t in item.tag if t.tag]) 82 | row = [count, item.name, item.mime, item.md5, tag] 83 | if key == 'ssdeep': 84 | row.append(item.ssdeep) 85 | if key == 'latest': 86 | row.append(item.created_at) 87 | 88 | rows.append(row) 89 | count += 1 90 | 91 | # Update find results in current session. 92 | __sessions__.find = items 93 | 94 | # Generate a table with the results. 95 | header = ['#', 'Name', 'Mime', 'MD5', 'Tags'] 96 | if key == 'latest': 97 | header.append('Created At') 98 | if key == 'ssdeep': 99 | header.append("Ssdeep") 100 | 101 | self.log("table", dict(header=header, rows=rows)) 102 | -------------------------------------------------------------------------------- /viper/core/ui/cmd/help.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # This file is part of Viper - https://github.com/viper-framework/viper 3 | # See the file "LICENSE" for copying permission. 4 | 5 | from viper.common.abstracts import Command 6 | from viper.core.plugins import load_commands, __modules__ 7 | 8 | 9 | class Help(Command): 10 | """ 11 | This command simply prints the help message. 12 | It lists both embedded commands and loaded modules. 13 | """ 14 | cmd = "help" 15 | description = "Show this help message" 16 | 17 | def run(self, *args): 18 | try: 19 | args = self.parser.parse_args(args) 20 | except SystemExit: 21 | return 22 | 23 | self.log("info", "Commands") 24 | 25 | rows = [] 26 | commands = load_commands() 27 | for command_name, command_item in commands.items(): 28 | rows.append([command_name, command_item["description"]]) 29 | 30 | rows.append(["exit, quit", "Exit Viper"]) 31 | rows = sorted(rows, key=lambda entry: entry[0]) 32 | 33 | self.log("table", dict(header=["Command", "Description"], rows=rows)) 34 | 35 | if len(__modules__) == 0: 36 | self.log("info", "No modules installed.") 37 | else: 38 | self.log("info", "Modules") 39 | rows = [] 40 | for module_name, module_item in __modules__.items(): 41 | rows.append([module_name, module_item["description"], 42 | ", ".join(c for c in module_item["categories"])]) 43 | 44 | rows = sorted(rows, key=lambda entry: entry[0]) 45 | 46 | self.log("table", dict(header=["Command", "Description", "Categories"], rows=rows)) 47 | -------------------------------------------------------------------------------- /viper/core/ui/cmd/info.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # This file is part of Viper - https://github.com/viper-framework/viper 3 | # See the file 'LICENSE' for copying permission. 4 | 5 | from viper.common.abstracts import Command 6 | from viper.core.session import __sessions__ 7 | 8 | 9 | class Info(Command): 10 | """ 11 | This command returns information on the open session. It returns details 12 | on the file (e.g. hashes) and other information that might available from 13 | the database. 14 | """ 15 | cmd = "info" 16 | description = "Show information on the opened file" 17 | 18 | def run(self, *args): 19 | try: 20 | args = self.parser.parse_args(args) 21 | except SystemExit: 22 | return 23 | 24 | if not __sessions__.is_set(): 25 | self.log('error', "No open session. This command expects a file to be open.") 26 | return 27 | 28 | self.log('table', dict( 29 | header=['Key', 'Value'], 30 | rows=[ 31 | ['Name', __sessions__.current.file.name], 32 | ['Tags', __sessions__.current.file.tags], 33 | ['Path', __sessions__.current.file.path], 34 | ['Size', __sessions__.current.file.size], 35 | ['Type', __sessions__.current.file.type], 36 | ['Mime', __sessions__.current.file.mime], 37 | ['MD5', __sessions__.current.file.md5], 38 | ['SHA1', __sessions__.current.file.sha1], 39 | ['SHA256', __sessions__.current.file.sha256], 40 | ['SHA512', __sessions__.current.file.sha512], 41 | ['SSdeep', __sessions__.current.file.ssdeep], 42 | ['CRC32', __sessions__.current.file.crc32], 43 | ['Parent', __sessions__.current.file.parent], 44 | ['Children', __sessions__.current.file.children] 45 | ] 46 | )) 47 | -------------------------------------------------------------------------------- /viper/core/ui/cmd/new.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # This file is part of Viper - https://github.com/viper-framework/viper 3 | # See the file 'LICENSE' for copying permission. 4 | 5 | import os 6 | import tempfile 7 | 8 | from viper.common.abstracts import Command 9 | from viper.common.colors import bold 10 | from viper.core.session import __sessions__ 11 | 12 | 13 | class New(Command): 14 | """ 15 | This command is used to create a new session on a new file, 16 | useful for copy & paste of content like Email headers. 17 | """ 18 | cmd = "new" 19 | description = "Create new file" 20 | 21 | def run(self, *args): 22 | try: 23 | args = self.parser.parse_args(args) 24 | except SystemExit: 25 | return 26 | 27 | title = input("Enter a title for the new file: ") 28 | 29 | # Create a new temporary file. 30 | tmp = tempfile.NamedTemporaryFile(delete=False) 31 | 32 | # Open the temporary file with the default editor, or with nano. 33 | os.system('"${EDITOR:-nano}" ' + tmp.name) 34 | 35 | __sessions__.new(tmp.name) 36 | __sessions__.current.file.name = title 37 | 38 | self.log('info', "New file with title \"{0}\" added to the current session".format(bold(title))) 39 | -------------------------------------------------------------------------------- /viper/core/ui/cmd/notes.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # This file is part of Viper - https://github.com/viper-framework/viper 3 | # See the file 'LICENSE' for copying permission. 4 | 5 | import os 6 | import tempfile 7 | 8 | from viper.common.abstracts import Command 9 | from viper.common.colors import bold 10 | from viper.core.database import Database 11 | from viper.core.session import __sessions__ 12 | 13 | 14 | class Notes(Command): 15 | """ 16 | This command allows you to view, add, modify and delete notes associated 17 | with the currently opened file or project. 18 | """ 19 | cmd = "notes" 20 | description = "View, add and edit notes on the opened file or project" 21 | 22 | def __init__(self): 23 | super(Notes, self).__init__() 24 | group = self.parser.add_mutually_exclusive_group() 25 | group.add_argument('-l', '--list', action='store_true', help="List all notes available for the current file or project") 26 | group.add_argument('-a', '--add', action='store_true', help="Add a new note to the current file or project") 27 | group.add_argument('-v', '--view', metavar='NOTE ID', type=int, help="View the specified note") 28 | group.add_argument('-e', '--edit', metavar='NOTE ID', type=int, help="Edit an existing note") 29 | group.add_argument('-d', '--delete', metavar='NOTE ID', type=int, help="Delete an existing note") 30 | self.parser.add_argument('-p', '--project', action='store_true', help="Use project notes instead of notes being tied to a file") 31 | 32 | def run(self, *args): 33 | try: 34 | args = self.parser.parse_args(args) 35 | except SystemExit: 36 | return 37 | 38 | db = Database() 39 | malware = None 40 | if __sessions__.is_set() and not args.project: 41 | malware = db.find(key='sha256', value=__sessions__.current.file.sha256) 42 | if not malware: 43 | self.log('error', "The opened file doesn't appear to be in the database, have you stored it yet?") 44 | 45 | if args.list: 46 | # Retrieve all notes for the currently opened file. 47 | notes = malware[0].note if malware is not None else db.list_notes() 48 | if not notes: 49 | self.log('info', "No notes available for this file or project yet") 50 | return 51 | 52 | # Populate table rows. 53 | rows = [[note.id, note.title] for note in notes] 54 | 55 | # Display list of existing notes. 56 | self.log('table', dict(header=['ID', 'Title'], rows=rows)) 57 | 58 | elif args.add: 59 | title = input("Enter a title for the new note: ") 60 | 61 | # Create a new temporary file. 62 | with tempfile.NamedTemporaryFile(mode='w+') as tmp: 63 | # Open the temporary file with the default editor, or with nano. 64 | os.system('"${EDITOR:-nano}" ' + tmp.name) 65 | # Once the user is done editing, we need to read the content and 66 | # store it in the database. 67 | body = tmp.read() 68 | if args.project or not __sessions__.is_set(): 69 | db.add_note(None, title, body) 70 | self.log('info', 'New note with title "{0}" added to the current project'.format(bold(title))) 71 | else: 72 | db.add_note(__sessions__.current.file.sha256, title, body) 73 | self.log('info', 'New note with title "{0}" added to the current file'.format(bold(title))) 74 | 75 | elif args.view: 76 | # Retrieve note with the specified ID and print it. 77 | note = db.get_note(args.view) 78 | if note: 79 | self.log('info', bold('Title: ') + note.title) 80 | if isinstance(note.body, bytes): 81 | # OLD: Old style, the content is stored as bytes 82 | # This is fixed when the user edits the old note. 83 | body = note.body.decode() 84 | else: 85 | body = note.body 86 | self.log('info', '{}\n{}'.format(bold('Body:'), body)) 87 | else: 88 | self.log('info', "There is no note with ID {0}".format(args.view)) 89 | 90 | elif args.edit: 91 | # Retrieve note with the specified ID. 92 | note = db.get_note(args.edit) 93 | if note: 94 | # Create a new temporary file. 95 | with tempfile.NamedTemporaryFile(mode='w+') as tmp: 96 | # Write the old body to the temporary file. 97 | if isinstance(note.body, bytes): 98 | # OLD: Old style, the content is stored as bytes 99 | body = note.body.decode() 100 | else: 101 | body = note.body 102 | tmp.write(body) 103 | tmp.flush() 104 | tmp.seek(0) 105 | # Open the old body with the text editor. 106 | os.system('"${EDITOR:-nano}" ' + tmp.name) 107 | # Read the new body from the temporary file. 108 | body = tmp.read() 109 | # Update the note entry with the new body. 110 | db.edit_note(args.edit, body) 111 | 112 | self.log('info', "Updated note with ID {0}".format(args.edit)) 113 | 114 | elif args.delete: 115 | # Delete the note with the specified ID. 116 | db.delete_note(args.delete) 117 | else: 118 | self.parser.print_usage() 119 | -------------------------------------------------------------------------------- /viper/core/ui/cmd/open.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # This file is part of Viper - https://github.com/viper-framework/viper 3 | # See the file 'LICENSE' for copying permission. 4 | 5 | import os 6 | import tempfile 7 | import argparse 8 | 9 | from viper.common.abstracts import Command 10 | from viper.common.network import download 11 | from viper.core.database import Database 12 | from viper.core.storage import get_sample_path 13 | from viper.core.session import __sessions__ 14 | 15 | 16 | class Open(Command): 17 | """ 18 | This command is used to open a session on a given file. 19 | It either can be an external file path, or a SHA256 hash of a file which 20 | has been previously imported and stored. 21 | While the session is active, every operation and module executed will be 22 | run against the file specified. 23 | """ 24 | cmd = "open" 25 | description = "Open a file" 26 | fs_path_completion = True 27 | 28 | def __init__(self): 29 | super(Open, self).__init__() 30 | self.parser = argparse.ArgumentParser(prog=self.cmd, description=self.description, 31 | epilog="You can also specify a MD5 or SHA256 hash to a previously stored file in order to open a session on it.") 32 | 33 | group = self.parser.add_mutually_exclusive_group() 34 | group.add_argument('-f', '--file', action='store_true', help="Target is a file") 35 | group.add_argument('-u', '--url', action='store_true', help="Target is a URL") 36 | group.add_argument('-l', '--last', action='store_true', help="Target is the entry number from the last find command's results") 37 | self.parser.add_argument('-t', '--tor', action='store_true', help="Download the file through Tor") 38 | self.parser.add_argument("value", metavar='PATH, URL, HASH or ID', nargs='*', help="Target to open. Hash can be md5 or sha256. ID has to be from the last search.") 39 | 40 | def run(self, *args): 41 | try: 42 | args = self.parser.parse_args(args) 43 | except SystemExit: 44 | return 45 | 46 | target = " ".join(args.value) 47 | 48 | if not args.last and target is None: 49 | self.parser.print_usage() 50 | return 51 | 52 | # If it's a file path, open a session on it. 53 | if args.file: 54 | target = os.path.expanduser(target) 55 | 56 | if not os.path.exists(target) or not os.path.isfile(target): 57 | self.log('error', "File not found: {0}".format(target)) 58 | return 59 | 60 | __sessions__.new(target) 61 | # If it's a URL, download it and open a session on the temporary file. 62 | elif args.url: 63 | data = download(url=target, tor=args.tor) 64 | 65 | if data: 66 | tmp = tempfile.NamedTemporaryFile(delete=False) 67 | tmp.write(data) 68 | tmp.close() 69 | 70 | __sessions__.new(tmp.name) 71 | # Try to open the specified file from the list of results from 72 | # the last find command. 73 | elif args.last: 74 | if __sessions__.find: 75 | try: 76 | target = int(target) 77 | except ValueError: 78 | self.log('warning', "Please pass the entry number from the last find to -l/--last (e.g. open -l 5)") 79 | return 80 | 81 | for idx, item in enumerate(__sessions__.find, start=1): 82 | if idx == target: 83 | __sessions__.new(get_sample_path(item.sha256)) 84 | break 85 | else: 86 | self.log('warning', "You haven't performed a find yet") 87 | # Otherwise we assume it's an hash of an previously stored sample. 88 | else: 89 | target = target.strip().lower() 90 | 91 | if len(target) == 32: 92 | key = 'md5' 93 | elif len(target) == 64: 94 | key = 'sha256' 95 | else: 96 | self.parser.print_usage() 97 | return 98 | 99 | db = Database() 100 | rows = db.find(key=key, value=target) 101 | 102 | if not rows: 103 | self.log('warning', "No file found with the given hash {0}".format(target)) 104 | return 105 | 106 | path = get_sample_path(rows[0].sha256) 107 | if path: 108 | __sessions__.new(path) 109 | -------------------------------------------------------------------------------- /viper/core/ui/cmd/parent.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # This file is part of Viper - https://github.com/viper-framework/viper 3 | # See the file 'LICENSE' for copying permission. 4 | 5 | from viper.common.abstracts import Command 6 | from viper.core.database import Database 7 | from viper.core.storage import get_sample_path 8 | from viper.core.session import __sessions__ 9 | 10 | 11 | class Parent(Command): 12 | """ 13 | This command is used to view or edit the parent child relationship between files. 14 | """ 15 | cmd = "parent" 16 | description = "Add or remove a parent file" 17 | 18 | def __init__(self): 19 | super(Parent, self).__init__() 20 | 21 | self.parser.add_argument('-a', '--add', metavar='SHA256', help="Add parent file by sha256") 22 | self.parser.add_argument('-d', '--delete', action='store_true', help="Delete Parent") 23 | self.parser.add_argument('-o', '--open', action='store_true', help="Open The Parent") 24 | 25 | def run(self, *args): 26 | try: 27 | args = self.parser.parse_args(args) 28 | except SystemExit: 29 | return 30 | 31 | # This command requires a session to be opened. 32 | if not __sessions__.is_set(): 33 | self.log('error', "No open session. This command expects a file to be open.") 34 | self.parser.print_usage() 35 | return 36 | 37 | # If no arguments are specified, there's not much to do. 38 | if args.add is None and args.delete is None and args.open is None: 39 | self.parser.print_usage() 40 | return 41 | 42 | db = Database() 43 | 44 | if not db.find(key='sha256', value=__sessions__.current.file.sha256): 45 | self.log('error', "The opened file is not stored in the database. " 46 | "If you want to add it use the `store` command.") 47 | return 48 | 49 | if args.add: 50 | if not db.find(key='sha256', value=args.add): 51 | self.log('error', "the parent file is not found in the database. ") 52 | return 53 | db.add_parent(__sessions__.current.file.sha256, args.add) 54 | self.log('info', "parent added to the currently opened file") 55 | 56 | self.log('info', "Refreshing session to update attributes...") 57 | __sessions__.new(__sessions__.current.file.path) 58 | 59 | if args.delete: 60 | db.delete_parent(__sessions__.current.file.sha256) 61 | self.log('info', "parent removed from the currently opened file") 62 | 63 | self.log('info', "Refreshing session to update attributes...") 64 | __sessions__.new(__sessions__.current.file.path) 65 | 66 | if args.open: 67 | # Open a session on the parent 68 | if __sessions__.current.file.parent: 69 | __sessions__.new(get_sample_path(__sessions__.current.file.parent[-64:])) 70 | else: 71 | self.log('info', "No parent set for this sample") 72 | -------------------------------------------------------------------------------- /viper/core/ui/cmd/projects.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # This file is part of Viper - https://github.com/viper-framework/viper 3 | # See the file 'LICENSE' for copying permission. 4 | 5 | import os 6 | import time 7 | import shutil 8 | from os.path import expanduser 9 | 10 | from viper.common.abstracts import Command 11 | from viper.common.colors import bold 12 | from viper.core.database import Database 13 | from viper.core.session import __sessions__ 14 | from viper.core.project import __project__ 15 | from viper.core.config import __config__ 16 | 17 | 18 | class Projects(Command): 19 | """ 20 | This command retrieves a list of all projects. 21 | You can also switch to a different project. 22 | """ 23 | cmd = "projects" 24 | description = "List or switch existing projects" 25 | 26 | def __init__(self): 27 | super(Projects, self).__init__() 28 | 29 | group = self.parser.add_mutually_exclusive_group() 30 | group.add_argument("-l", "--list", action="store_true", help="List all existing projects") 31 | group.add_argument("-s", "--switch", metavar="PROJECT NAME", help="Switch to the specified project") 32 | group.add_argument("-c", "--close", action="store_true", help="Close the currently opened project") 33 | group.add_argument("-d", "--delete", metavar="PROJECT NAME", help="Delete the specified project") 34 | 35 | def run(self, *args): 36 | try: 37 | args = self.parser.parse_args(args) 38 | except SystemExit: 39 | return 40 | 41 | if __config__.get("paths").storage_path: 42 | base_path = __config__.get("paths").storage_path 43 | else: 44 | base_path = os.path.join(expanduser("~"), ".viper") 45 | 46 | projects_path = os.path.join(base_path, "projects") 47 | 48 | if args.list: 49 | if not os.path.exists(projects_path): 50 | self.log("info", "No projects have been created yet") 51 | return 52 | 53 | self.log("info", "Projects Available:") 54 | 55 | rows = [] 56 | for project in os.listdir(projects_path): 57 | project_path = os.path.join(projects_path, project) 58 | if os.path.isdir(project_path): 59 | current = "" 60 | if __project__.name and project == __project__.name: 61 | current = "Yes" 62 | rows.append([project, time.ctime(os.path.getctime(project_path)), current]) 63 | 64 | self.log("table", dict(header=["Project Name", "Creation Time", "Current"], rows=rows)) 65 | elif args.switch: 66 | db = Database() 67 | if not db.supports_projects: 68 | self.log('info', "The database type you are using does not support projects") 69 | return 70 | 71 | if __sessions__.is_set(): 72 | __sessions__.close() 73 | self.log("info", "Closed opened session") 74 | 75 | __project__.open(args.switch) 76 | self.log("info", "Switched to project {0}".format(bold(args.switch))) 77 | 78 | # Need to re-initialize the Database to open the new SQLite file. 79 | Database().__init__() 80 | elif args.close: 81 | if __project__.name != "default": 82 | if __sessions__.is_set(): 83 | __sessions__.close() 84 | 85 | __project__.close() 86 | elif args.delete: 87 | project_to_delete = args.delete 88 | if project_to_delete == "default": 89 | self.log("error", "You can't delete the \"default\" project") 90 | return 91 | 92 | # If it"s the currently opened project, we close it. 93 | if project_to_delete == __project__.name: 94 | # We close any opened session. 95 | if __sessions__.is_set(): 96 | __sessions__.close() 97 | 98 | __project__.close() 99 | 100 | project_path = os.path.join(projects_path, project_to_delete) 101 | if not os.path.exists(project_path): 102 | self.log("error", "The folder for project \"{}\" does not seem to exist".format(project_to_delete)) 103 | return 104 | 105 | self.log("info", "You asked to delete project with name \"{}\" located at \"{}\"".format(project_to_delete, project_path)) 106 | 107 | confirm = input("Are you sure you want to delete the project? You will permanently delete all associated files! [y/N] ") 108 | if confirm.lower() != "y": 109 | return 110 | 111 | try: 112 | shutil.rmtree(project_path) 113 | except Exception as e: 114 | self.log("error", "Something failed while trying to delete folder: {}".format(e)) 115 | return 116 | 117 | self.log("info", "Project \"{}\" was delete successfully".format(project_to_delete)) 118 | else: 119 | self.log("info", self.parser.print_usage()) 120 | -------------------------------------------------------------------------------- /viper/core/ui/cmd/rename.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # This file is part of Viper - https://github.com/viper-framework/viper 3 | # See the file 'LICENSE' for copying permission. 4 | 5 | from viper.common.abstracts import Command 6 | from viper.common.colors import bold 7 | from viper.core.database import Database 8 | from viper.core.session import __sessions__ 9 | 10 | 11 | class Rename(Command): 12 | """ 13 | This command renames the currently opened file in the database. 14 | """ 15 | cmd = "rename" 16 | description = "Rename the file in the database" 17 | 18 | def run(self, *args): 19 | try: 20 | args = self.parser.parse_args(args) 21 | except SystemExit: 22 | return 23 | 24 | if __sessions__.is_set(): 25 | if not __sessions__.current.file.id: 26 | self.log('error', "The opened file does not have an ID, have you stored it yet?") 27 | return 28 | 29 | self.log('info', "Current name is: {}".format(bold(__sessions__.current.file.name))) 30 | 31 | new_name = input("New name: ") 32 | if not new_name: 33 | self.log('error', "File name can't be empty!") 34 | return 35 | 36 | Database().rename(__sessions__.current.file.id, new_name) 37 | 38 | self.log('info', "Refreshing session to update attributes...") 39 | __sessions__.new(__sessions__.current.file.path) 40 | else: 41 | self.log('error', "No open session. This command expects a file to be open.") 42 | -------------------------------------------------------------------------------- /viper/core/ui/cmd/sessions.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # This file is part of Viper - https://github.com/viper-framework/viper 3 | # See the file 'LICENSE' for copying permission. 4 | 5 | from viper.common.abstracts import Command 6 | from viper.core.session import __sessions__ 7 | 8 | 9 | class Sessions(Command): 10 | """ 11 | This command is used to list and switch across all the opened sessions. 12 | """ 13 | cmd = "sessions" 14 | description = "List or switch sessions" 15 | 16 | def __init__(self): 17 | super(Sessions, self).__init__() 18 | 19 | group = self.parser.add_mutually_exclusive_group() 20 | group.add_argument('-l', '--list', action='store_true', help="List all existing sessions") 21 | group.add_argument('-s', '--switch', type=int, help="Switch to the specified session") 22 | 23 | def run(self, *args): 24 | try: 25 | args = self.parser.parse_args(args) 26 | except SystemExit: 27 | return 28 | 29 | if args.list: 30 | if not __sessions__.sessions: 31 | self.log('info', "There are no opened sessions") 32 | return 33 | 34 | rows = [] 35 | for session in __sessions__.sessions: 36 | current = '' 37 | if session == __sessions__.current: 38 | current = 'Yes' 39 | 40 | rows.append([ 41 | session.id, 42 | session.file.name, 43 | session.file.md5, 44 | session.created_at, 45 | current 46 | ]) 47 | 48 | self.log('info', "Opened Sessions:") 49 | self.log("table", dict(header=['#', 'Name', 'MD5', 'Created At', 'Current'], rows=rows)) 50 | elif args.switch: 51 | for session in __sessions__.sessions: 52 | if args.switch == session.id: 53 | __sessions__.switch(session) 54 | return 55 | 56 | self.log('warning', "The specified session ID doesn't seem to exist") 57 | else: 58 | self.parser.print_usage() 59 | -------------------------------------------------------------------------------- /viper/core/ui/cmd/stats.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # This file is part of Viper - https://github.com/viper-framework/viper 3 | # See the file 'LICENSE' for copying permission. 4 | 5 | from collections import defaultdict 6 | 7 | from viper.common.abstracts import Command 8 | from viper.common.utils import convert_size 9 | from viper.core.database import Database 10 | 11 | 12 | class Stats(Command): 13 | """ 14 | This command allows you to generate basic statistics for the stored files. 15 | """ 16 | cmd = "stats" 17 | description = "Viper Collection Statistics" 18 | 19 | def __init__(self): 20 | super(Stats, self).__init__() 21 | 22 | self.parser.add_argument('-t', '--top', type=int, help='Top x Items') 23 | 24 | def run(self, *args): 25 | try: 26 | args = self.parser.parse_args(args) 27 | except SystemExit: 28 | return 29 | 30 | # Set all Counters Dict 31 | extension_dict = defaultdict(int) 32 | mime_dict = defaultdict(int) 33 | tags_dict = defaultdict(int) 34 | size_list = [] 35 | 36 | # Find all 37 | items = Database().find('all') 38 | 39 | if len(items) < 1: 40 | self.log('info', "No items in database to generate stats") 41 | return 42 | 43 | # Sort in to stats 44 | for item in items: 45 | if isinstance(item.name, bytes): 46 | # NOTE: In case you there are names stored as bytes in the database 47 | name = item.name.decode() 48 | else: 49 | name = item.name 50 | if '.' in name: 51 | ext = name.split('.') 52 | extension_dict[ext[-1]] += 1 53 | mime_dict[item.mime] += 1 54 | size_list.append(item.size) 55 | for t in item.tag: 56 | if t.tag: 57 | tags_dict[t.tag] += 1 58 | 59 | avg_size = sum(size_list) / len(size_list) 60 | # all_stats = {'Total': len(items), 'File Extension': extension_dict, 'Mime': mime_dict, 'Tags': tags_dict, 61 | # 'Avg Size': avg_size, 'Largest': max(size_list), 'Smallest': min(size_list)} 62 | 63 | # Counter for top x 64 | if args.top: 65 | counter = args.top 66 | prefix = 'Top {0} '.format(counter) 67 | else: 68 | counter = len(items) 69 | prefix = '' 70 | 71 | # Project Stats Last as i have it iterate them all 72 | 73 | # Print all the results 74 | 75 | self.log('info', "Projects") 76 | self.log('table', dict(header=['Name', 'Count'], rows=[['Main', len(items)], ['Next', '10']])) 77 | 78 | # For Current Project 79 | self.log('info', "Current Project") 80 | 81 | # Extension 82 | self.log('info', "{0}Extensions".format(prefix)) 83 | header = ['Ext', 'Count'] 84 | rows = [] 85 | 86 | for k in sorted(extension_dict, key=extension_dict.get, reverse=True)[:counter]: 87 | rows.append([k, extension_dict[k]]) 88 | self.log('table', dict(header=header, rows=rows)) 89 | 90 | # Mimes 91 | self.log('info', "{0}Mime Types".format(prefix)) 92 | header = ['Mime', 'Count'] 93 | rows = [] 94 | for k in sorted(mime_dict, key=mime_dict.get, reverse=True)[:counter]: 95 | rows.append([k, mime_dict[k]]) 96 | self.log('table', dict(header=header, rows=rows)) 97 | 98 | # Tags 99 | self.log('info', "{0}Tags".format(prefix)) 100 | header = ['Tag', 'Count'] 101 | rows = [] 102 | for k in sorted(tags_dict, key=tags_dict.get, reverse=True)[:counter]: 103 | rows.append([k, tags_dict[k]]) 104 | self.log('table', dict(header=header, rows=rows)) 105 | 106 | # Size 107 | self.log('info', "Size Stats") 108 | self.log('item', "Largest {0}".format(convert_size(max(size_list)))) 109 | self.log('item', "Smallest {0}".format(convert_size(min(size_list)))) 110 | self.log('item', "Average {0}".format(convert_size(avg_size))) 111 | -------------------------------------------------------------------------------- /viper/core/ui/cmd/store.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # This file is part of Viper - https://github.com/viper-framework/viper 3 | # See the file 'LICENSE' for copying permission. 4 | 5 | import os 6 | import fnmatch 7 | try: 8 | from scandir import walk 9 | except ImportError: 10 | from os import walk 11 | 12 | from viper.core.ui.cmd.open import Open 13 | from viper.common.abstracts import Command 14 | from viper.common.objects import File 15 | from viper.core.database import Database 16 | from viper.core.session import __sessions__ 17 | from viper.core.config import __config__ 18 | from viper.core.storage import store_sample, get_sample_path 19 | from viper.common.autorun import autorun_module 20 | 21 | 22 | class Store(Command): 23 | """ 24 | This command stores the opened file in the local repository and tries 25 | to store details in the database. 26 | """ 27 | cmd = "store" 28 | description = "Store the opened file to the local repository" 29 | fs_path_completion = True 30 | 31 | def __init__(self): 32 | super(Store, self).__init__() 33 | 34 | self.parser.add_argument('-d', '--delete', action='store_true', help="Delete the original file") 35 | self.parser.add_argument('-f', '--folder', type=str, nargs='+', help="Specify a folder to import") 36 | self.parser.add_argument('-s', '--file-size', type=int, help="Specify a maximum file size") 37 | self.parser.add_argument('-y', '--file-type', type=str, help="Specify a file type pattern") 38 | self.parser.add_argument('-n', '--file-name', type=str, help="Specify a file name pattern") 39 | self.parser.add_argument('-t', '--tags', type=str, nargs='+', help="Specify a list of comma-separated tags") 40 | 41 | def run(self, *args): 42 | try: 43 | args = self.parser.parse_args(args) 44 | except SystemExit: 45 | return 46 | 47 | if args.folder is not None: 48 | # Allows to have spaces in the path. 49 | args.folder = " ".join(args.folder) 50 | 51 | if args.tags is not None: 52 | # Remove the spaces in the list of tags 53 | args.tags = "".join(args.tags) 54 | 55 | def add_file(obj, tags=None): 56 | if get_sample_path(obj.sha256): 57 | self.log('warning', "Skip, file \"{0}\" appears to be already stored".format(obj.name)) 58 | return False 59 | 60 | if __sessions__.is_attached_misp(quiet=True): 61 | if tags is not None: 62 | tags += ',misp:{}'.format(__sessions__.current.misp_event.event.id) 63 | else: 64 | tags = 'misp:{}'.format(__sessions__.current.misp_event.event.id) 65 | 66 | # Try to store file object into database. 67 | status = Database().add(obj=obj, tags=tags) 68 | if status: 69 | # If succeeds, store also in the local repository. 70 | # If something fails in the database (for example unicode strings) 71 | # we don't want to have the binary lying in the repository with no 72 | # associated database record. 73 | new_path = store_sample(obj) 74 | self.log("success", "Stored file \"{0}\" to {1}".format(obj.name, new_path)) 75 | 76 | else: 77 | return False 78 | 79 | # Delete the file if requested to do so. 80 | if args.delete: 81 | try: 82 | os.unlink(obj.path) 83 | except Exception as e: 84 | self.log('warning', "Failed deleting file: {0}".format(e)) 85 | 86 | return True 87 | 88 | # If the user specified the --folder flag, we walk recursively and try 89 | # to add all contained files to the local repository. 90 | # This is note going to open a new session. 91 | # TODO: perhaps disable or make recursion optional? 92 | if args.folder is not None: 93 | # Check if the specified folder is valid. 94 | if os.path.isdir(args.folder): 95 | # Walk through the folder and subfolders. 96 | for dir_name, dir_names, file_names in walk(args.folder): 97 | # Add each collected file. 98 | for file_name in file_names: 99 | file_path = os.path.join(dir_name, file_name) 100 | 101 | if not os.path.exists(file_path): 102 | continue 103 | # Check if file is not zero. 104 | if not os.path.getsize(file_path) > 0: 105 | continue 106 | 107 | # Check if the file name matches the provided pattern. 108 | if args.file_name: 109 | if not fnmatch.fnmatch(file_name, args.file_name): 110 | # self.log('warning', "Skip, file \"{0}\" doesn't match the file name pattern".format(file_path)) 111 | continue 112 | 113 | # Check if the file type matches the provided pattern. 114 | if args.file_type: 115 | if args.file_type not in File(file_path).type: 116 | # self.log('warning', "Skip, file \"{0}\" doesn't match the file type".format(file_path)) 117 | continue 118 | 119 | # Check if file exceeds maximum size limit. 120 | if args.file_size: 121 | # Obtain file size. 122 | if os.path.getsize(file_path) > args.file_size: 123 | self.log('warning', "Skip, file \"{0}\" is too big".format(file_path)) 124 | continue 125 | 126 | file_obj = File(file_path) 127 | 128 | # Add file. 129 | add_file(file_obj, args.tags) 130 | if add_file and __config__.get('autorun').enabled: 131 | autorun_module(file_obj.sha256) 132 | # Close the open session to keep the session table clean 133 | __sessions__.close() 134 | 135 | else: 136 | self.log('error', "You specified an invalid folder: {0}".format(args.folder)) 137 | # Otherwise we try to store the currently opened file, if there is any. 138 | else: 139 | if __sessions__.is_set(): 140 | if __sessions__.current.file.size == 0: 141 | self.log('warning', "Skip, file \"{0}\" appears to be empty".format(__sessions__.current.file.name)) 142 | return False 143 | 144 | # Add file. 145 | if add_file(__sessions__.current.file, args.tags): 146 | # TODO: review this. Is there a better way? 147 | # Open session to the new file. 148 | Open().run(*[__sessions__.current.file.sha256]) 149 | if __config__.get('autorun').enabled: 150 | autorun_module(__sessions__.current.file.sha256) 151 | else: 152 | self.log('error', "No open session. This command expects a file to be open.") 153 | -------------------------------------------------------------------------------- /viper/core/ui/cmd/tags.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # This file is part of Viper - https://github.com/viper-framework/viper 3 | # See the file 'LICENSE' for copying permission. 4 | 5 | from viper.common.abstracts import Command 6 | from viper.core.database import Database 7 | from viper.core.session import __sessions__ 8 | 9 | 10 | class Tags(Command): 11 | """ 12 | This command is used to modify the tags of the opened file. 13 | """ 14 | cmd = "tags" 15 | description = "Modify tags of the opened file" 16 | 17 | def __init__(self): 18 | super(Tags, self).__init__() 19 | 20 | self.parser.add_argument('-a', '--add', metavar='TAG', help="Add tags to the opened file (comma separated)") 21 | self.parser.add_argument('-d', '--delete', metavar='TAG', help="Delete a tag from the opened file") 22 | 23 | def run(self, *args): 24 | try: 25 | args = self.parser.parse_args(args) 26 | except SystemExit: 27 | return 28 | 29 | # This command requires a session to be opened. 30 | if not __sessions__.is_set(): 31 | self.log('error', "No open session. This command expects a file to be open.") 32 | self.parser.print_usage() 33 | return 34 | 35 | # If no arguments are specified, there's not much to do. 36 | # However, it could make sense to also retrieve a list of existing 37 | # tags from this command, and not just from the "find" command alone. 38 | if args.add is None and args.delete is None: 39 | self.parser.print_usage() 40 | return 41 | 42 | # TODO: handle situation where addition or deletion of a tag fail. 43 | db = Database() 44 | 45 | if not db.find(key='sha256', value=__sessions__.current.file.sha256): 46 | self.log('error', "The opened file is not stored in the database. " 47 | "If you want to add it use the `store` command.") 48 | return 49 | 50 | if args.add: 51 | # Add specified tags to the database's entry belonging to 52 | # the opened file. 53 | db.add_tags(__sessions__.current.file.sha256, args.add) 54 | self.log('info', "Tags added to the currently opened file") 55 | 56 | # We refresh the opened session to update the attributes. 57 | # Namely, the list of tags returned by the 'info' command 58 | # needs to be re-generated, or it wouldn't show the new tags 59 | # until the existing session is closed a new one is opened. 60 | self.log('info', "Refreshing session to update attributes...") 61 | __sessions__.new(__sessions__.current.file.path) 62 | 63 | if args.delete: 64 | # Delete the tag from the database. 65 | db.delete_tag(args.delete, __sessions__.current.file.sha256) 66 | # Refresh the session so that the attributes of the file are 67 | # updated. 68 | self.log('info', "Refreshing session to update attributes...") 69 | __sessions__.new(__sessions__.current.file.path) 70 | -------------------------------------------------------------------------------- /viper/core/ui/cmd/update-modules.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # This file is part of Viper - https://github.com/viper-framework/viper 3 | # See the file 'LICENSE' for copying permission. 4 | 5 | import os 6 | import sys 7 | import subprocess 8 | 9 | from viper.common.abstracts import Command 10 | from viper.core.config import __config__ 11 | 12 | cfg = __config__ 13 | 14 | 15 | class UpdateModules(Command): 16 | """ 17 | This command downloads modules from the GitHub repository at 18 | https://github.com/viper-framework/viper-modules 19 | """ 20 | cmd = "update-modules" 21 | description = "Download Viper modules from the community GitHub repository" 22 | 23 | def run(self, *args): 24 | self.log("info", "Updating modules...") 25 | 26 | dot_viper = cfg.paths.module_path 27 | dot_viper_modules = os.path.join(dot_viper, "modules") 28 | 29 | self.log("info", f'Module path: {dot_viper_modules}') 30 | 31 | if os.path.exists(dot_viper_modules): 32 | # Pull updates 33 | p = subprocess.Popen(["git", "pull"], cwd=dot_viper_modules) 34 | p.wait() 35 | else: 36 | # Clone the repository. 37 | p = subprocess.Popen(["git", "clone", "https://github.com/viper-framework/viper-modules.git", 38 | "modules"], cwd=dot_viper) 39 | p.wait() 40 | 41 | # Check whether previous command executed successfully 42 | if p.returncode != 0: 43 | self.log("error", "Module download failed. Returncode of `git clone ...`: " + str(p.returncode)) 44 | return 45 | 46 | # Initialize submodules. 47 | p = subprocess.Popen(["git", "submodule", "init"], cwd=dot_viper_modules) 48 | p.wait() 49 | # Update submodules. 50 | p = subprocess.Popen(["git", "submodule", "update"], cwd=dot_viper_modules) 51 | p.wait() 52 | # Install dependencies. 53 | p = subprocess.Popen(["pip3", "install", "-U", "-r", "requirements.txt"], cwd=dot_viper_modules) 54 | p.wait() 55 | 56 | # TODO: this is terrible. We need to find a way to move __modules__ 57 | # to proper place that can be reloaded. 58 | self.log("info", "Modules updated, please relaunch `viper`.") 59 | sys.exit() 60 | -------------------------------------------------------------------------------- /viper/core/ui/commands.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # This file is part of Viper - https://github.com/viper-framework/viper 3 | # See the file 'LICENSE' for copying permission. 4 | 5 | from viper.core.plugins import load_commands 6 | from viper.core.database import Database 7 | 8 | 9 | class Commands(object): 10 | output = [] 11 | 12 | def __init__(self): 13 | Database().__init__() 14 | # Map commands to their related functions. 15 | self.commands = load_commands() 16 | -------------------------------------------------------------------------------- /viper/core/ui/main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # This file is part of Viper - https://github.com/viper-framework/viper 4 | # See the file 'LICENSE' for copying permission. 5 | 6 | import os 7 | import sys 8 | import argparse 9 | 10 | from viper.core.ui import console 11 | from viper.common.version import __version__ 12 | from viper.core.project import __project__ 13 | from viper.core.session import __sessions__ 14 | 15 | 16 | def main(): 17 | parser = argparse.ArgumentParser() 18 | parser.add_argument( 19 | "-p", 20 | "--project", 21 | help="Specify a new or existing project name", 22 | action="store", 23 | required=False) 24 | parser.add_argument( 25 | "-f", 26 | "--file", 27 | help="Specify a file to be opened directly", 28 | action="store", 29 | required=False) 30 | parser.add_argument( 31 | "--version", 32 | action="version", 33 | version=__version__) 34 | 35 | args = parser.parse_args() 36 | 37 | if args.project: 38 | __project__.open(args.project) 39 | 40 | if args.file: 41 | if not os.path.exists(args.file): 42 | print("ERROR: The specified path does not exist") 43 | sys.exit(-1) 44 | 45 | __sessions__.new(args.file) 46 | 47 | c = console.Console() 48 | c.start() 49 | -------------------------------------------------------------------------------- /viper/data/yara/embedded.yara: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2013 Claudio "nex" Guarnieri 2 | 3 | rule embedded_macho 4 | { 5 | meta: 6 | author = "nex" 7 | description = "Contains an embedded Mach-O file" 8 | 9 | strings: 10 | $magic1 = { ca fe ba be } 11 | $magic2 = { ce fa ed fe } 12 | $magic3 = { fe ed fa ce } 13 | condition: 14 | any of ($magic*) and not ($magic1 at 0) and not ($magic2 at 0) and not ($magic3 at 0) 15 | } 16 | 17 | rule embedded_pe 18 | { 19 | meta: 20 | author = "nex" 21 | description = "Contains an embedded PE32 file" 22 | 23 | strings: 24 | $a = "PE32" 25 | $b = "This program" 26 | $mz = { 4d 5a } 27 | condition: 28 | ($a and $b) and not ($mz at 0) 29 | } 30 | 31 | rule embedded_win_api 32 | { 33 | meta: 34 | author = "nex" 35 | description = "A non-Windows executable contains win32 API functions names" 36 | 37 | strings: 38 | $mz = { 4d 5a } 39 | $api1 = "CreateFileA" 40 | $api2 = "GetProcAddress" 41 | $api3 = "LoadLibraryA" 42 | $api4 = "WinExec" 43 | $api5 = "GetSystemDirectoryA" 44 | $api6 = "WriteFile" 45 | $api7 = "ShellExecute" 46 | $api8 = "GetWindowsDirectory" 47 | $api9 = "URLDownloadToFile" 48 | $api10 = "IsBadReadPtr" 49 | $api11 = "IsBadWritePtr" 50 | $api12 = "SetFilePointer" 51 | $api13 = "GetTempPath" 52 | $api14 = "GetWindowsDirectory" 53 | condition: 54 | not ($mz at 0) and any of ($api*) 55 | } 56 | -------------------------------------------------------------------------------- /viper/data/yara/vmdetect.yara: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2013 Claudio "nex" Guarnieri 2 | 3 | rule vmdetect 4 | { 5 | meta: 6 | author = "nex" 7 | description = "Possibly employs anti-virtualization techniques" 8 | 9 | strings: 10 | // Binary tricks 11 | $vmware = {56 4D 58 68} 12 | $virtualpc = {0F 3F 07 0B} 13 | $ssexy = {66 0F 70 ?? ?? 66 0F DB ?? ?? ?? ?? ?? 66 0F DB ?? ?? ?? ?? ?? 66 0F EF} 14 | $vmcheckdll = {45 C7 00 01} 15 | $redpill = {0F 01 0D 00 00 00 00 C3} 16 | 17 | // Random strings 18 | $vmware1 = "VMXh" 19 | $vmware2 = "Ven_VMware_" nocase 20 | $vmware3 = "Prod_VMware_Virtual_" nocase 21 | $vmware4 = "hgfs.sys" nocase 22 | $vmware5 = "mhgfs.sys" nocase 23 | $vmware6 = "prleth.sys" nocase 24 | $vmware7 = "prlfs.sys" nocase 25 | $vmware8 = "prlmouse.sys" nocase 26 | $vmware9 = "prlvideo.sys" nocase 27 | $vmware10 = "prl_pv32.sys" nocase 28 | $vmware11 = "vpc-s3.sys" nocase 29 | $vmware12 = "vmsrvc.sys" nocase 30 | $vmware13 = "vmx86.sys" nocase 31 | $vmware14 = "vmnet.sys" nocase 32 | $vmware15 = "vmicheartbeat" nocase 33 | $vmware16 = "vmicvss" nocase 34 | $vmware17 = "vmicshutdown" nocase 35 | $vmware18 = "vmicexchange" nocase 36 | $vmware19 = "vmdebug" nocase 37 | $vmware20 = "vmmouse" nocase 38 | $vmware21 = "vmtools" nocase 39 | $vmware22 = "VMMEMCTL" nocase 40 | $vmware23 = "vmx86" nocase 41 | $vmware24 = "vmware" nocase 42 | $virtualpc1 = "vpcbus" nocase 43 | $virtualpc2 = "vpc-s3" nocase 44 | $virtualpc3 = "vpcuhub" nocase 45 | $virtualpc4 = "msvmmouf" nocase 46 | $xen1 = "xenevtchn" nocase 47 | $xen2 = "xennet" nocase 48 | $xen3 = "xennet6" nocase 49 | $xen4 = "xensvc" nocase 50 | $xen5 = "xenvdb" nocase 51 | $xen6 = "XenVMM" nocase 52 | $virtualbox1 = "VBoxHook.dll" nocase 53 | $virtualbox2 = "VBoxService" nocase 54 | $virtualbox3 = "VBoxTray" nocase 55 | $virtualbox4 = "VBoxMouse" nocase 56 | $virtualbox5 = "VBoxGuest" nocase 57 | $virtualbox6 = "VBoxSF" nocase 58 | $virtualbox7 = "VBoxGuestAdditions" nocase 59 | $virtualbox8 = "VBOX HARDDISK" nocase 60 | 61 | // MAC addresses 62 | $vmware_mac_1a = "00-05-69" 63 | $vmware_mac_1b = "00:05:69" 64 | $vmware_mac_1c = "000569" 65 | $vmware_mac_2a = "00-50-56" 66 | $vmware_mac_2b = "00:50:56" 67 | $vmware_mac_2c = "005056" 68 | $vmware_mac_3a = "00-0C-29" nocase 69 | $vmware_mac_3b = "00:0C:29" nocase 70 | $vmware_mac_3c = "000C29" nocase 71 | $vmware_mac_4a = "00-1C-14" nocase 72 | $vmware_mac_4b = "00:1C:14" nocase 73 | $vmware_mac_4c = "001C14" nocase 74 | $virtualbox_mac_1a = "08-00-27" 75 | $virtualbox_mac_1b = "08:00:27" 76 | $virtualbox_mac_1c = "080027" 77 | 78 | condition: 79 | any of them 80 | } 81 | --------------------------------------------------------------------------------