├── .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 | 
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 | [](https://travis-ci.org/viper-framework/viper)
21 | [](http://viper-framework.readthedocs.io/en/latest/?badge=latest)
22 | [](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 |
--------------------------------------------------------------------------------