Skip to content
This repository has been archived by the owner on Jun 7, 2023. It is now read-only.

[WIP] Add dependency check for modules/plugins #558

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions viper/common/abstracts.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,19 @@
# This file is part of Viper - https://github.com/viper-framework/viper
# See the file 'LICENSE' for copying permission.

import sys
import argparse
from distutils.spawn import find_executable
import pkg_resources

import logging

import viper.common.out as out
from viper.core.config import console_output
from viper.common.exceptions import ArgumentErrorCallback

log = logging.getLogger('viper')


class ArgumentParser(argparse.ArgumentParser):
def print_usage(self, file=None):
Expand All @@ -31,9 +39,64 @@ class Module(object):
authors = []
output = []

min_python_version = (2, 7)
dependency_list_python = []
dependency_list_system = []

def __init__(self):
self.parser = ArgumentParser(prog=self.cmd, description=self.description)

def check(self):
min_python_version = self._check_min_python_version()
dep_python = self._check_dependencies_python()
dep_system = self._check_dependencies_system()
if min_python_version and dep_python and dep_system:
return True
else:
return False

def _check_min_python_version(self):
if sys.version_info >= self.min_python_version:
log.debug("{}: Python Version ok".format(self.__class__.__name__))
return True
else:
log.warning("{}: Python Version NOT ok".format(self.__class__.__name__))
return False

def _check_dependencies_python(self):
if not self.dependency_list_python:
return True

missing = []

for item in self.dependency_list_python:
try:
pkg_resources.require(item)
except pkg_resources.DistributionNotFound as err:
log.debug("{}: Missing Python dependency: {}".format(self.__class__.__name__, err))
missing.append(item)
except pkg_resources.VersionConflict as err:
log.debug("{}: Python dependency wrong version: {}".format(self.__class__.__name__, err))
missing.append(item)

if missing:
log.warning("{}: Missing/Failed Python dependencies: {}".format(self.__class__.__name__, missing))
return False

return True

def _check_dependencies_system(self):
if not self.dependency_list_system:
return True

missing = [item for item in self.dependency_list_system if not find_executable(item)]

if missing:
log.warning("{}: Missing System dependencies: {}".format(self.__class__.__name__, missing))
return False
else:
return True

def set_commandline(self, command):
self.command_line = command

Expand Down
42 changes: 28 additions & 14 deletions viper/core/plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,23 @@

import pkgutil
import inspect
import logging
import importlib

from viper.common.abstracts import Module
from viper.common.abstracts import get_argparse_parser_actions
from viper.common.abstracts import get_argparse_subparser_actions
from viper.common.out import print_warning

log = logging.getLogger('viper')


def load_modules():
# Import modules package.
import viper.modules as modules

plugins = dict()
working_plugins = dict()
failed_plugins = dict()

# Walk recursively through all modules and packages.
for loader, module_name, ispkg in pkgutil.walk_packages(modules.__path__, modules.__name__ + '.'):
Expand All @@ -32,16 +36,26 @@ def load_modules():

# Walk through all members of currently imported modules.
for member_name, member_object in inspect.getmembers(module):
# Check if current member is a class.
if inspect.isclass(member_object):
# Yield the class if it's a subclass of Module.
if issubclass(member_object, Module) and member_object is not Module:
plugins[member_object.cmd] = dict(obj=member_object,
description=member_object.description,
parser_args=get_argparse_parser_actions(member_object().parser),
subparser_args=get_argparse_subparser_actions(member_object().parser))

return plugins


__modules__ = load_modules()
if not inspect.isclass(member_object):
continue
# Yield the class if it's a subclass of Module.
if issubclass(member_object, Module) and member_object is not Module:
# run dependency check on each module and only add to working list if successful
if member_object().check():
log.debug("Module: {} - dependency check ok".format(member_name))
working_plugins[member_object.cmd] = dict(obj=member_object,
description=member_object.description,
parser_args=get_argparse_parser_actions(member_object().parser),
subparser_args=get_argparse_subparser_actions(member_object().parser))
else:
log.warning("Module: {} - failed dependency check".format(member_name))
print_warning("Module: {} - failed dependency check".format(member_name))
failed_plugins[member_object.cmd] = dict(obj=member_object,
description=member_object.description,
parser_args=get_argparse_parser_actions(member_object().parser),
subparser_args=get_argparse_subparser_actions(member_object().parser))

return working_plugins, failed_plugins


__modules__, __failed_modules__ = load_modules()
29 changes: 27 additions & 2 deletions viper/core/ui/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
from viper.common.version import __version__
from viper.core.session import __sessions__
from viper.core.project import __project__
from viper.core.plugins import __modules__
from viper.core.plugins import __modules__, __failed_modules__
from viper.core.database import Database
from viper.core.storage import store_sample, get_sample_path
from viper.core.config import Config, console_output
Expand Down Expand Up @@ -107,9 +107,13 @@ class About(Command):
cmd = "about"
description = "Show information about this Viper instance"

def __init__(self):
super(About, self).__init__()
self.parser.add_argument('-m', '--modules', action='store_true', help="Show info about modules")

def run(self, *args):
try:
self.parser.parse_args(args)
args = self.parser.parse_args(args)
except:
return

Expand All @@ -128,6 +132,27 @@ def run(self, *args):

self.log('table', dict(header=['Configuration', ''], rows=rows))

if args.modules:
rows = list()
for key, value in __modules__.items():
min_python_version_str = ".".join([str(x) for x in value['obj'].min_python_version])
dependency_python = ",".join(value['obj'].dependency_list_python)
dependency_system = ",".join(value['obj'].dependency_list_system)

rows.append([key, dependency_system, min_python_version_str, dependency_python])

self.log('table', dict(header=['Active Modules', 'System dependencies', 'Min Python Version', 'Python requirements'], rows=rows))

rows = list()
for key, value in __failed_modules__.items():
min_python_version_str = ".".join([str(x) for x in value['obj'].min_python_version])
dependency_python = ",".join(value['obj'].dependency_list_python)
dependency_system = ",".join(value['obj'].dependency_list_system)

rows.append([key, dependency_system, min_python_version_str, dependency_python])

self.log('table', dict(header=['Failed Modules', 'System dependencies', 'Min Python Version', 'Python requirements'], rows=rows))


class Analysis(Command):
##
Expand Down
3 changes: 3 additions & 0 deletions viper/modules/exif.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ class Exif(Module):
description = 'Extract Exif MetaData'
authors = ['Kevin Breen']

dependency_list_python = ["exiftool"]
dependency_list_system = ["exif"]

def __init__(self):
super(Exif, self).__init__()

Expand Down
2 changes: 2 additions & 0 deletions viper/modules/misp.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ class MISP(Module):
description = 'Upload and query IOCs to/from a MISP instance'
authors = ['Raphaël Vinot']

dependency_list_python = ["pymisp", "pytaxonomies", "requests"]

def __init__(self):
super(MISP, self).__init__()
self.cur_path = __project__.get_path()
Expand Down