Beispiel #1
0
class Misc:
  """ class for miscellaneous stuff """


  def __init__(self):
    """ constructor """

    # logger
    self.logger = Logger()
    self.log = self.logger.log

    # file i/o
    self.file = File()

    # original terminal state
    self.term_fd = sys.stdin.fileno()
    self.term_state = termios.tcgetattr(self.term_fd)

    return


  def reset_terminal(self):
    """ reset terminal to original state """

    termios.tcsetattr(self.term_fd, termios.TCSADRAIN, self.term_state)

    return


  def grep_tools(self, py_file):
    """ Find all tools (method names) of given file """

    tools = []

    lines = self.file.read_file(py_file)
    for line in lines:
      if 'def ' in line:
        method = line.split()[1].split('(')[0]
        if not method.startswith('_'):
          tools.append(method)

    return sorted(list(set(tools)))


  def find_py_files(self, root_path):
    """ Find all py files with some exceptions of given root_path """

    py_files = []

    files = glob.glob(f'{root_path}/*/*.py', recursive=True)
    [py_files.append(x) for x in files if '__ini' not in x and '/libs' not in x]

    return sorted(list(set(py_files)))


  def kill_process(self, pattern, signal='TERM'):
    """ kill (all) processes matched by pattern """

    cmd = ['pkill', f'-{signal}', '-f', pattern]
    subprocess.run(cmd)

    return


  def remove_empty_files_dirs(self, rootpath):
    """ delete empty (log-)files and directories """

    threads = 20
    files = glob.glob(f'{rootpath}/**', recursive=True)

    # files
    with ThreadPoolExecutor(max_workers=threads) as exe:
      # files
      for f in files:
        if os.path.isfile(f):
          fsize = os.path.getsize(f)
          if fsize <= 3:
            exe.submit(os.unlink, f)

      # directories
      for f in files:
        if os.path.isdir(f) and not os.listdir(f):
          exe.submit(os.rmdir, f)
          self.remove_empty_files_dirs(rootpath)

    return


  def lookup_port_service(self, port, proto='tcp'):
    """ return corresponding service from services.csv for a given port """

    service = None
    services = self.file.read_csv_file(f'{ROOT_PATH}/lists/services.csv')

    for s in services:
      if port in s and proto in s:
        service = s[0]

    return service


  def print_tool(self, opts):
    """ print tool and a description of it """

    tools = []

    m = Module(MOD_PATH)
    m.get_docstrings()

    if 'all' in opts.keys():
      for tool in m.docstrings.keys():
        tools.append(tool)
    else:
      for moddir, module in opts.items():
        if module:
          for mod in module:
            for tool in m.docstrings:
              if m.docstrings[tool]['moddir'] == moddir and \
                m.docstrings[tool]['module'] == mod:
                  tools.append(tool)
        else:
          for k, v in m.docstrings.items():
            if moddir in v['moddir']:
              tools.append(k)

    tools = sorted(list(set(tools)))
    for tool in tools:
      self.log(m.docstrings[tool]['moddir'] + '/' +
        m.docstrings[tool]['module'] + '/' + tool + ' - ' +
        m.docstrings[tool]['descr'], end='\n', _type='vmsg')

    return


  def add_mod_tool(self, form, opts):
    """ create a new module and/or add tool to exisiting module """

    template = f'{MOD_PATH}libs/template.py'
    modpart = f"{MOD_PATH}{opts['moddir']}/"
    moddir = modpart
    module = f"{modpart}{opts['modname']}.py"
    tmpmod = f'{modpart}tmpxxx.py'

    if form == 'mod':
      with open(template, 'r') as fin:
        if not os.path.isdir(moddir):
          self.file.make_dir(moddir)
        with open(module, 'w') as fout:
          for line in fin:
            if '<template>.py' in line:
              fout.write(line.replace('<template>', opts['modname']))
            elif '<file>' in line:
              fout.write(line.replace('<file>', opts['modname']))
            elif '<class>' in line:
              fout.write(line.replace('<class>',
                opts['modname'].capitalize()))
            elif '<mod>' in line:
              fout.write(line.replace('<mod>', opts['modname']))
            else:
              fout.write(line)
      self.log(f"Created module {opts['moddir']}/{opts['modname']}.py\n",
        _type='msg')

    # append new tools
    try:
      shutil.copyfile(module, tmpmod)
      with open(tmpmod, 'a') as fout:
        fout.write('\n\n  @tool\n')
        fout.write(f"  def {opts['func']}(self):\n")
        fout.write('    """\n')
        fout.write('    DESCR: <REPLACE MANUALLY>\n')
        fout.write('    TOOLS: <ADD MANUALLY>\n')
        fout.write('    """\n\n')
        fout.write(f"    opts = \'{' '.join(opts['args'])}\'\n\n")
        fout.write(f"    self._run_tool(\'{opts['tool']}\', opts)\n\n")
        fout.write('    return\n')
      shutil.move(tmpmod, module)
      self.log(f"Added tool {opts['func']} to {opts['moddir']}/" +
        f"{opts['modname']}.py\n", _type='msg')
    except:
      self.log('add_tool', _type='err', end='\n')

    return
Beispiel #2
0
class Controller:
    """ program controller class """
    def __init__(self):
        """ constructor """

        # options and modules
        self.opt = None
        self.mod = Module(MOD_PATH)

        # logger
        self.logger = Logger()
        self.log = self.logger.log

        # rest we need
        self.file = File()
        self.check = Check()
        self.misc = Misc()
        self.parser = None

        # nullscan working dir
        self.nullscan_dir = None

        return

    def prepare(self):
        """ preparation/initialization of opts and env: parsing & checks """

        # declare nullscan options
        self.opt = Option(sys.argv)

        # check argc and argc (usage)
        self.check.check_argc(len(sys.argv))
        self.check.check_argv(sys.argv)

        # check for missing libraries / deps / python modules
        self.check.check_deps(self.file.read_file(PYDEPS))

        # parse cmdline and config options, update final options dictionary
        try:
            self.parser = Parser(self.opt.opts)
            self.parser.parse_cmdline()
            self.parser.parse_config()
            self.opt.opts = self.parser.opts
        except:
            self.log('usage', _type='err', end='\n')

        # update final options dictionary
        self.opt.update_opts()

        # further checks for usage, options, env, etc.
        self.check.check_opts(self.opt.opts)

        # collect all py-files and grep the tools out of the py-files
        tools = []
        py_files = self.misc.find_py_files(MOD_PATH)
        for py in py_files:
            tools.append(self.misc.grep_tools(py))
        tools = [x for sublist in tools for x in sublist]

        # create the locks for each tool except for excluded ones
        with ThreadPoolExecutor(50) as exe:
            for tool in tools:
                if tool not in self.opt.opts['tools']['ex_tools']:
                    exe.submit(self.file.create_lock, tool)

        # copy debug flag to target_opts (for nullscan tools)
        self.opt.opts['targets_opts']['debug'] = self.opt.opts['debug']

        return

    def run_misc(self):
        """ run chosen misc options """

        if self.opt.opts['check_tools']:
            self.log('Checking for missing tools\n\n', _type='msg')
            self.check.check_tools()
            os._exit(SUCCESS)
        if self.opt.opts['print_tools']:
            self.log('Printing tools and information\n\n', _type='msg')
            self.misc.print_tool(self.opt.opts['print_tools'])
            os._exit(SUCCESS)
        if self.opt.opts['add_module']:
            self.log('Adding new module\n', _type='msg')
            self.misc.add_mod_tool('mod', self.opt.opts['add_module'])
            os._exit(SUCCESS)
        if self.opt.opts['add_tool']:
            self.log('Adding new tool\n', _type='msg')
            self.misc.add_mod_tool('tool', self.opt.opts['add_tool'])
            os._exit(SUCCESS)

        return

    def prepare_modules(self):
        """ filter in-/excluded mods + return a unique list """

        self.mod.filter_modules(self.opt.opts)
        self.mod.mods = list(set(self.mod.mods))

        return

    def run_nmap_mode(self):
        """ run nmap scan mode """

        nmap = Nmap(self.opt.opts['targets']['nmap'])

        # create nmap logdir based on scan type requested
        logpath = f'{self.nullscan_dir}/logs/nmap/{nmap.get_protocol()}'
        logfile = f'{logpath}/results'
        self.file.make_dir(logpath)
        nmap.set_logfile(logfile)

        # build nmap command line
        nmap.build_cmd()

        # start scans
        self.log('NMAP mode activated\n', _type='msg', color='blue')
        self.log('Targets added: {}\n'.format(
            len(self.opt.opts['targets']['nmap']['hosts'])),
                 _type='msg')
        if self.opt.opts['verbose']:
            self.log('\n')
            for target in self.opt.opts['targets']['nmap']['hosts']:
                self.log(f'{target}\n', _type='msg')
        nmap.scan(debug=self.opt.opts['debug'])

        return f'{logfile}.xml'

    def run_social_mode(self, target):
        """ run social mode """

        if '.social.' in str(self.mod.mods):
            # availabel social modules to import and load
            mods = [i for i in self.mod.mods if '.social.' in i]

            with ProcessPoolExecutor(self.opt.opts['m_workers']) as exe:
                for key in target.keys():
                    if target[key]:
                        for t in target[key]:
                            # default module
                            rdir = f'{self.nullscan_dir}/logs/targets/'
                            wdir = f"{rdir}{t}/social/{key}/default"
                            exe.submit(self.mod.run_module,
                                       'modules.social.default', t,
                                       self.opt.opts, wdir)

                            # non-default modules
                            for m in mods:
                                if 'default' not in m:
                                    splitted = m.split('.')
                                    moddir = splitted[1]
                                    modname = splitted[2]
                                    wdir = f"{rdir}{t}/{moddir}/{key}/{modname}"
                                    exe.submit(self.mod.run_module, m, t,
                                               self.opt.opts, wdir)

        return

    def run_wifi_mode(self, target):
        """ run wifi mode """

        if '.wifi.' in str(self.mod.mods):
            # available wifi modules to import and load
            mods = [i for i in self.mod.mods if '.wifi.' in i]

            # default module first
            rdir = f'{self.nullscan_dir}/logs/targets/'
            wdir = f'{rdir}{target}/wifi/default'
            self.mod.run_module('modules.wifi.default', target, self.opt.opts,
                                wdir)

            # non-default modules
            with ProcessPoolExecutor(self.opt.opts['m_workers']) as exe:
                for m in mods:
                    if 'default' not in m:
                        splitted = m.split('.')
                        moddir = splitted[1]
                        modname = splitted[2]
                        wdir = f'{rdir}{target}/{moddir}/{modname}'
                        exe.submit(self.mod.run_module, m, target,
                                   self.opt.opts, wdir)

        return

    def run_lan_mode(self, target):
        """ run lan mode """

        if '.lan.' in str(self.mod.mods):
            # available lan modules to import and load
            mods = [i for i in self.mod.mods if '.lan.' in i]

            # default module first
            rdir = f'{self.nullscan_dir}/logs/targets/'
            wdir = f'{rdir}{target}/lan/default'
            self.mod.run_module('modules.lan.default', target, self.opt.opts,
                                wdir)

            # non-default modules
            with ProcessPoolExecutor(self.opt.opts['m_workers']) as exe:
                for m in mods:
                    if 'default' not in m:
                        splitted = m.split('.')
                        moddir = splitted[1]
                        modname = splitted[2]
                        wdir = f'{rdir}{target}/{moddir}/{modname}'
                        exe.submit(self.mod.run_module, m, target,
                                   self.opt.opts, wdir)

        return

    def run_web_mode(self, target):
        """ run web mode """

        if '.web.' in str(self.mod.mods):
            # available web modules to import and load
            mods = [i for i in self.mod.mods if '.web.' in i]

            # we need host name for working directory
            host = requests.utils.urlparse(target).netloc

            # default module first
            rdir = f'{self.nullscan_dir}/logs/targets/'
            wdir = f'{rdir}{host}/web/default'
            self.mod.run_module('modules.web.default', target, self.opt.opts,
                                wdir)

            # non-default modules
            with ProcessPoolExecutor(self.opt.opts['m_workers']) as exe:
                for m in mods:
                    if 'default' not in m:
                        splitted = m.split('.')
                        moddir = splitted[1]
                        modname = splitted[2]
                        wdir = f'{rdir}{host}/{moddir}/{modname}'
                        exe.submit(self.mod.run_module, m, target,
                                   self.opt.opts, wdir)

        return

    def run_udp_mode(self, target):
        """ run udp mode """

        # we need to run host modules before tcp and we need to run default
        # tool first
        self.run_host_mode(target)

        # now tcp modules
        if '.udp.' in str(self.mod.mods):
            with ProcessPoolExecutor(self.opt.opts['m_workers']) as exe:
                for p in target['ports']:
                    exe.submit(self.run_tcp_udp_mode, target, p, 'udp')

        return

    def run_tcp_mode(self, target):
        """ run tcp mode """

        # we need to run host modules before tcp and we need to run default
        # module first
        self.run_host_mode(target)

        # now tcp modules
        if '.tcp.' in str(self.mod.mods):
            with ProcessPoolExecutor(self.opt.opts['m_workers']) as exe:
                for p in target['ports']:
                    exe.submit(self.run_tcp_udp_mode, target, p, 'tcp')

        return

    def run_tcp_udp_mode(self, host, port, proto):
        """ wrapper for tcp/udp mode """

        # available modules
        mod = f'modules.{proto}.{port[1]}'

        # force default module for given port if module does not exist
        if mod not in self.mod.mods:
            mod = f'modules.{proto}.default'

        # new target dict as we only need the corresponding port
        t = {'host': host['host'], 'port': port[0]}

        # default module first
        rdir = f'{self.nullscan_dir}/logs/targets/'
        wdir = f"{rdir}{t['host']}/{proto}/{port[0]}/default"
        self.mod.run_module(f'modules.{proto}.default', t, self.opt.opts, wdir)

        # now non-default module
        if '.default' not in mod:
            wdir = f"{rdir}{t['host']}/{proto}/{port[0]}/{port[1]}"
            self.mod.run_module(mod, t, self.opt.opts, wdir)

        return

    def run_host_mode(self, target):
        """ run host mode """

        # available host modules to import and load
        mods = [i for i in self.mod.mods if '.host.' in i]

        # default module
        rdir = f'{self.nullscan_dir}/logs/targets/'
        wdir = f"{rdir}{target['host']}/host/default"
        self.mod.run_module('modules.host.default', target, self.opt.opts,
                            wdir)

        # non-default modules
        with ProcessPoolExecutor(self.opt.opts['m_workers']) as exe:
            for m in mods:
                if 'default' not in m:
                    splitted = m.split('.')
                    moddir = splitted[1]
                    modname = splitted[2]
                    wdir = f"{rdir}{target['host']}/{moddir}/{modname}"
                    exe.submit(self.mod.run_module, m, target, self.opt.opts,
                               wdir)

        return

    def run_modes(self):
        """ run chosen modes """

        scans = []
        modes = {
            'tcp': self.run_tcp_mode,
            'udp': self.run_udp_mode,
            'wifi': self.run_wifi_mode,
            'web': self.run_web_mode,
            'social': self.run_social_mode,
            'http': self.run_web_mode,
            'https': self.run_web_mode,
        }

        # run lan mode first if requested
        if self.opt.opts['targets']['lan']:
            ifaces = []
            self.log('LAN mode activated\n', color='blue', _type='msg')
            self.log(
                f"Targets added: {len(self.opt.opts['targets']['lan'])}\n\n",
                _type='msg')
            for iface in self.opt.opts['targets']['lan']:
                if self.opt.opts['verbose']:
                    self.log(f'{iface}\n', _type='vmsg')
                ifaces.append((self.run_lan_mode, iface))
            if self.opt.opts['verbose']:
                self.log('\n')
            with ProcessPoolExecutor(self.opt.opts['t_workers']) as exe:
                self.log('Shooting tools\n\n', color='green', _type='msg')
                for iface in ifaces:
                    exe.submit(iface[0], iface[1])
            self.log('\n')
            if not self.opt.opts['verbose']:
                self.log('\n')
            self.log('Tools done\n\n', color='green', _type='msg')
            for log in glob.glob('**/lan/portscan/*.xml', recursive=True):
                if log:
                    self.parser.parse_nmap_logfile(log, lan=True)

        # collect scan modes for each target
        for k, v in self.opt.opts['targets'].items():
            if self.opt.opts['targets'][k]:
                if k == 'lan':
                    continue
                else:
                    self.log(f'{k.upper()} mode activated\n',
                             color='blue',
                             _type='msg')
                self.log(f'Targets added: {len(v)}\n\n', _type='msg')
                if k == 'social':
                    if self.opt.opts['verbose']:
                        for targets in v.values():
                            for target in targets:
                                self.log(f'{target}\n', _type='vmsg')
                        self.log('\n')
                    scans.append((modes[k], v))
                else:
                    for target in v:
                        scans.append((modes[k], target))
                        if self.opt.opts['verbose']:
                            if 'host' in target:
                                target_head = target['host']
                            else:
                                target_head = target
                            self.log(f'{target_head}\n', _type='vmsg')
                    if self.opt.opts['verbose']:
                        self.log('\n')

        # start mode for each target
        with ProcessPoolExecutor(self.opt.opts['t_workers']) as exe:
            if scans:
                self.log('Shooting tools\n\n', color='green', _type='msg')
                for scan in scans:
                    exe.submit(scan[0], scan[1])
        if scans:
            self.log('\n')
            if not self.opt.opts['verbose']:
                self.log('\n')
            self.log('Tools done\n\n', _type='msg', color='green')

        return

    def start(self):
        """ nullscan starts here with actions """

        self.check.check_uid()
        self.log('Game Started\n\n', _type='msg')

        # create nullscan working, targets and log dir
        self.nullscan_dir = self.file.make_dir(self.opt.opts['nullscan_dir'],
                                               incr=True)
        self.file.make_dir(f'{self.nullscan_dir}/logs/targets')
        self.opt.opts['targets_opts']['nullscan_logdir'] = \
          f'{self.nullscan_dir}/logs/targets/'

        # run nmap mode first if requested
        if 'hosts' in self.opt.opts['targets']['nmap']:
            self.parser.parse_nmap_logfile(self.run_nmap_mode())
            self.log('\n')

        # delete nmap key
        del self.opt.opts['targets']['nmap']

        # prepare modules for other modes
        self.prepare_modules()

        # run the nullscan modes now
        self.run_modes()

        return

    def end(self):
        """ program ends here. clean-ups, reporting, etc. """

        # go back to root dir
        os.chdir(ROOT_PATH)

        # clean up empty directories and empty (log-)files or logfiles containing
        # a singl ebyte (newline) (failed tools)
        self.misc.remove_empty_files_dirs(f'{self.nullscan_dir}/logs/targets/')

        # just to be safe - delete all locks, in case tools didn't
        self.file.del_file('/tmp/nullscan', _dir=True)

        # create report
        if self.opt.opts['report']:
            self.log('Creating report\n', _type='msg')
            tmpl_dir = f'{ROOT_PATH}/src/report/template'
            rep_dir = f'{self.nullscan_dir}/report'
            logs_dir = f'{self.nullscan_dir}/logs'
            self.report = HTML(TODAY, self.opt.opts, tmpl_dir, rep_dir,
                               logs_dir)
            self.report.make_report()
            self.log('Report done\n\n', _type='msg')
        self.log('Game Over\n', _type='msg')

        # reset terminal to original state. sometimes f**k up occurs because of
        # color and other escape codes.
        self.misc.reset_terminal()

        return
Beispiel #3
0
class Module:
  """ module handler class """


  def __init__(self, mod_path):
    """ constructor """

    # logger
    self.logger = Logger()
    self.log = self.logger.log

    # file i/o
    self.file = File()

    # get all modules
    self.mod_path = mod_path
    self.mods = []
    self.get_modules()

    # all loaded module
    self.lmod = {}

    # docstrings for all tools
    self.docstrings = {}

    return


  def get_docstrings(self):
    """ get a list of docstrings from all nullscan tools """

    tools = []    # tools from sub classes

    for mod in self.mods:
      self.load_module(mod)
      cls = next(iter(self.lmod[mod].keys()))
      for tool in self.lmod[mod][cls]:
        modsplit = mod.split('.')
        moddir = modsplit[1]
        module = modsplit[2]
        if 'Base' not in str(cls):
          t_attr = getattr(cls, tool)
        docstr = ' '.join(t_attr.__doc__.split()).split()
        try:
          d_idx = docstr.index('DESCR:')
          t_idx = docstr.index('TOOLS:')
        except:
          pass
        descr = ' '.join((docstr[d_idx + 1:t_idx]))
        tools.append(docstr[t_idx + 1:])
        unique_tools = list(set(itertools.chain.from_iterable(tools)))
        self.docstrings[tool] = {'moddir': moddir, 'module': module,
          'descr': descr, 'tools': unique_tools }

    return


  def get_objects(self, mod, lmod):
    """ get classes and their methods out of a loaded module """

    methods = []

    for d in dir(lmod):
      if d[0].isupper() and 'Base' not in d:
        attr = getattr(lmod, d) # class
        for m in dir(attr): # public methods
          if not m.startswith('_') and not m.startswith('__') and \
            callable(getattr(attr, m)):
              methods.append(m)
        self.lmod = {mod: {attr: list(filter(None, methods))}}

    return


  def get_modules(self):
    """ get all modules out of MOD_PATH directory """

    for f in glob.glob(f'{self.mod_path}/**', recursive=True):
      if '__' not in f and '/libs/' not in f and f.endswith('.py'):
        self.mods.append('.'.join(f.split('/')[-3:]).split('.py')[0])
    #for root, dirs, files in os.walk(self.mod_path, topdown=True):
    #  dirs[:] = [d for d in dirs if d != 'libs']
    #  for f in files:
    #    if f != '__init__.py' and f.endswith('.py'):
    #      mod = '.'.join(os.path.join(root, f).split('/')[-3:])
    #      self.mods.append((mod.replace('.py', '')))
    return


  def filter_modules(self, opts):
    """ filter included or excluded modules """

    # all default modules out of modules/ dir
    def_mods = [
      x.replace('/','.').replace('src.','') + '.default' \
        for x in glob.glob('src/modules/**') if not '__' in x
    ]
    tmp = []

    # add/remove chosen module by user
    if 'in_modules' in opts['modules'] and opts['modules']['in_modules']:
      # list with default modules (force) + user chosed modules to import
      self.mods = def_mods
      for key, val in opts['modules']['in_modules'].items():
        for v in val:
          self.mods.append(f'modules.{key}.{v}')
    elif 'ex_modules' in opts['modules'] and opts['modules']['ex_modules']:
      for key, val in opts['modules']['ex_modules'].items():
        for v in val:
          if v == 'default':
            self.log('mod_default', _type='err', end='\n')
          tmp.append(f'modules.{key}.{v}')
      for i in self.mods:
        if i in tmp:
          self.mods.remove(i)

    return


  def load_module(self, mod):
    """ load desired module and get classes+methods from loaded modules """

    try:
      lmod = importlib.import_module(mod)
      self.get_objects(mod, lmod)
    except Exception as e:
      self.log('mod_import', eargs=f"{mod} -> {e}", _type='err', end='\n')

    return


  def run_module(self, mod, target, opts, wdir):
    """ load and run module with its tools """

    # change temp working dir for tool logs
    rootdir = os.getcwd()
    os.chdir(self.file.make_dir(wdir))

    # load module, get class and create object of
    self.load_module(mod)
    cls = next(iter(self.lmod[mod].keys()))
    c = cls(target, opts['targets_opts'])

    # for status line
    cur_tool = 0
    sum_tool = len(self.lmod[mod][cls])

    # get method (tool) string, create object and run desired tool
    with ProcessPoolExecutor(opts['p_workers']) as exe:
      for t in self.lmod[mod][cls]:
        cur_tool += 1
        if 'host' in target:
          target_head = target['host']
        else:
          target_head = target
        stat_line = f"{target_head} | {'.'.join(mod.split('.')[1:])}.{t}" + \
          f' ({cur_tool}/{sum_tool})' + ' ' * 25
        tool = getattr(c, t)

        # filter in-/ex-cluded tools by user
        if '.default' not in repr(cls):
          if opts['tools']['in_tools']:
            if t not in opts['tools']['in_tools']:
              continue
          if t in opts['tools']['ex_tools']:
            continue

        # run tool
        try:
          if opts['verbose']:
            self.log(stat_line, _type='vmsg', end='\n')
          else:
            self.log(stat_line, _type='vmsg', flush=True, end='\r')
          exe.submit(tool)
        except:
          self.log('tool_failed', eargs=t, _type='err', end='\n')

    # done, move bitch...
    os.chdir(rootdir)

    return