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
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
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