def nmap_scan(): print(utils.normal_message(), "Starting scan of", config.current_target) # Check if Nmap is installed - critical program. If this fails, the program will exit utils.program_installed("nmap", True) out_file = os.path.join( config.get_module_cache("nmap", config.current_target), "nmap.xml") if config.args.verbose: print(utils.normal_message(), "Nmap data will be written to", out_file) nmap_args = ['nmap', '-sC', '-sV', '-oX', out_file, config.current_target] if config.args.scan_udp: print(utils.normal_message(), "Scanning UDP ports, this may take a long time") nmap_args.append('-sU') print(utils.normal_message(), "Scanning open ports on", config.current_target + "...", end=' ') with Spinner(): output = subprocess.check_output(nmap_args).decode('UTF-8') print("") # if config.args.show_output: # print("") # print(output) print(utils.normal_message(), "Scan complete") parse_nmap_scan(out_file)
def __load_init_modules() -> None: """ Load all of the Init Modules """ global logger path = "plugins/initmodules" for file in os.listdir(path): if not os.path.isfile(os.path.join(path, file)): continue if not file[-3:] == ".py": continue if file.endswith("__init__.py"): continue logger.debug("Importing {FILE}".format(FILE=file)) module = importlib.import_module( "plugins.initmodules.{CLASS}".format(CLASS=file[0:-3])) instance = getattr(module, file[0:-3])() logger.info("Successfully imported {MODULE} ({MODULE_DESC})".format( MODULE=instance.name, MODULE_DESC=instance.description)) LOADED_INIT_MODULES.append(instance) print( utils.normal_message(), "Successfully imported {COUNT} init modules".format( COUNT=len(LOADED_INIT_MODULES)))
def detect_service(openport): for service in openport.getElementsByTagName('service'): port = int(openport.attributes['portid'].value) service_type = service.attributes['name'].value try: service_name = service.attributes['product'].value except KeyError: service_name = service_type print(utils.normal_message(), service_name, "is open on port", port) # Ignore the port if its in the list of ports to skip if port not in config.args.skipPorts: # Some kind of SSH server if service_type == "ssh": print(utils.warning_message(), service_name, "is recognised by nmap as an ssh server") """scripts_ran = service.getElementsByTagName('script') for script in scripts_ran: print(script.attributes['id']) if script.attributes['id'] == 'fingerprint-strings': print(script.getElementsByTagName('elem')[0].text)""" # MySQL server if service_name == "mysql": print(utils.warning_message(), service_name, "is recognised by nmap as a MySQL server...") else: print( utils.warning_message(), "Skipping", service_name, "(port", str(port) + ") as it has been specified " "as a port to skip") print("")
def detect_apps(cpe_list): for cpe in cpe_list: cpe_app_type = "cpe:/a" if cpe.startswith(cpe_app_type): print(utils.normal_message(), "Installed application is reported as", cpe_utils.CPE(cpe).human())
def parse_nmap_scan(out_file): xmldoc = minidom.parse(out_file) hostslist = xmldoc.getElementsByTagName('hosts') # We only scan one host at a time if int(hostslist[0].attributes['down'].value) > 0: print(utils.error_message(), config.current_target, "was unreachable") else: port_list = xmldoc.getElementsByTagName('port') print("") print(utils.normal_message(), len(port_list), "ports are open") cpe_list = [ x.firstChild.nodeValue for x in xmldoc.getElementsByTagName('cpe') ] detector.detect_os(cpe_list) detector.detect_apps(cpe_list) # New line for nicer formatting print("") searchsploit_nmap_scan(out_file) for open_port in port_list: detector.detect_service(open_port)
def main(): """ Start Lancer """ # Get start time start_time = time.monotonic() scan_targets() print(utils.normal_message(), "Lancer has finished system scanning") elapsed_time = time.monotonic() - start_time generate_reports() print(utils.normal_message(), "Lancer took {TIME} to complete". format(TIME=time.strftime("%H:%M:%S", time.gmtime(elapsed_time))))
def detect_os(cpe_list): for cpe in cpe_list: cpe_os_type = "cpe:/o" if cpe.startswith(cpe_os_type): print(utils.normal_message(), "Target OS appears to be", cpe_utils.CPE(cpe).human()) if cpe_utils.CPE(cpe).matches(cpe_utils.CPE("cpe:/o:microsoft:windows")) \ and platform.system() == "linux": print(utils.warning_message(), "Target machine is running Microsoft Windows") print(utils.warning_message(), "Will commence enumeration using enum4linux") print(utils.error_message(), "enum4linux not yet implemented")
def searchsploit_nmap_scan(nmap_file): print(utils.normal_message(), "Checking searchsploit for detected version vulnerabilities...") # TODO: Searchsploit doesn't seem that intelligent with the # nmap file parsing. Loop through each program individually # and pass in the program name extracted from nmap # searchsploit -t [PROGRAM] if utils.program_installed("searchsploit", False): searchsploit_output = subprocess.check_output( ['searchsploit', '--nmap', nmap_file]).decode('UTF-8') print() print(searchsploit_output) print("")
def format(self, record: logging.LogRecord) -> str: """ Custom formatter for the stdout logging :param record: The Log to format :return: The formatted string """ # TODO: Fix cyclic imports from core.utils import warning_message, error_message, normal_message from core.ArgHandler import get_verbose, get_very_verbose if record.levelno == logging.WARNING: if get_verbose() or get_very_verbose(): return warning_message() + ' [{NAME}] {MESSAGE}'.format(NAME=record.name, MESSAGE=record.getMessage()) else: return warning_message() + ' {MESSAGE}'.format(NAME=record.name, MESSAGE=record.getMessage()) if record.levelno >= logging.ERROR: return error_message() + ' [{NAME}] {MESSAGE}'.format(NAME=record.name, MESSAGE=record.getMessage()) return normal_message() + ' [{NAME}] {MESSAGE}'.format(NAME=record.name, MESSAGE=record.getMessage())
def __execute_init_module(target: Target) -> None: """ Run InitModules against the Target :param target: Target object to scan """ if len(LOADED_INIT_MODULES) > 0: module = LOADED_INIT_MODULES[0] if module.can_execute_module() is ModuleExecuteState.CanExecute: print(utils.normal_message(), "Executing {PROGRAM}".format(PROGRAM=module.name)) module.execute(target.get_address(), 0) else: print( utils.error_message(), "Unable to meet dependencies for {MODULE}. Quitting".format( MODULE=module.name)) sys.exit(ExitCode.CriticalDependencyNotInstalled) else: print(utils.error_message(), "No Init Modules loaded. Quitting") sys.exit(ExitCode.EntryPointModulesNotLoaded)
def scan_target(target: str) -> None: """ Scan the target passed :param target: Target IP/hostname to scan """ try: # See if the target is an IP network ip_network = ipaddress.ip_network(target, strict=False) if ip_network.version is 6: raise NotImplementedError("IPv6 addresses are not yet supported") for x in range(ip_network.num_addresses): ip = ip_network[x] tgt = Target(None, ip) ModuleProvider.analyse(tgt) tgt.stop_timer() except ValueError: # It's not an IP address or a subnet, # so most likely a hostname if target.startswith("www."): www_warning = utils.terminal_width_string( "Target starts with \"www.\" - this is not recommended as it can lead to false positives in modules " " - for example, when checking URLs for internal links. Do you want to remove \"www.\" from the URL?" ) print(utils.warning_message(), www_warning) agree = utils.input_message("[Y]es or [N]o: ") if agree.lower() == "y": target = target.replace("www.", "") print(utils.normal_message(), "Removed \"www.\" from target, now \"{TARGET}\"".format(TARGET=target)) else: print(utils.warning_message(), "Retaining \"www.\" in target") hostname_info = socket.getaddrinfo(target, None, socket.AF_INET) ip = ipaddress.ip_address(hostname_info[0][4][0]) tgt = Target(target, ip) ModuleProvider.analyse(tgt) tgt.stop_timer() print()
def __execute_modules(target: Target): """ Run Modules against the Target :param target: Target object to scan """ global LOADED_MODULES global logger # Order according to priority LOADED_MODULES = sorted(LOADED_MODULES, key=operator.attrgetter('priority'), reverse=True) # Add null service and port to ensure that the Hostname/Geo modules always run # TODO: Convert to InitModules EventQueue.push("", 0) # Get the current event from the Queue while EventQueue.events_in_queue(): current_event = EventQueue.pop() logger.info("Processing {SERVICE}:{PORT}".format( SERVICE=current_event.service, PORT=current_event.port)) if current_event.port not in ArgHandler.get_skip_ports(): # Iterate through every single instance of our modules for module in LOADED_MODULES: # Check if we can run it run_state = module.can_execute_module() if run_state is ModuleExecuteState.CanExecute: ip = str(target.ip) if module.should_execute(current_event.service, current_event.port): print( utils.normal_message(), "Executing {PROGRAM}".format(PROGRAM=module.name)) module.execute(ip, current_event.port)
def scan_targets() -> None: """ Open the target file, Nmap file or get the target to scan """ # Detect if we have a target list or just a single target if ArgHandler.get_target_file() is not None: # Target list targets = ArgHandler.get_target_file().read().splitlines() for target in targets: if len(target.strip()) == 0: continue # Comments start with a hashtag if len(target.strip()) > 0 and target[0] == "#": continue scan_target(target) elif ArgHandler.get_nmap_file() is not None: print(utils.normal_message(), "Loading nmap file") raise NotImplementedError("Loading from an Nmap file with -TF is not yet implemented") # nmap.parse_nmap_scan(ArgHandler.get_nmap_file()) else: scan_target(ArgHandler.get_target())
def parse_arguments(args) -> None: """ Parse the command line arguments :param args: The arguments to parse """ global __args parser = create_parser() if len(args) is 0: print(error_message(), "No arguments supplied, showing help...\n") time.sleep(0.5) parser.print_help() sys.exit(1) if len(args) is 1 and "--version" in args: print(normal_message(), "Lancer {VERSION}".format(VERSION=__version__)) sys.exit(0) if len(args) is 1 and "-h" or "--help" in args: parser.print_help() sys.exit(0) __args = parser.parse_args(args)
def test_normal_message(): out = utils.normal_message() assert "[+]" in out
Generate the reports """ # TODO: Load report generators dynamicaly logger.debug("Generating reports") report = JSONReport() report.generate_report(Loot.loot) report = TerminalReport() report.generate_report(Loot.loot) logger.debug("Finished generating reports") if __name__ == "__main__": init() logger = config.get_logger("Main") try: main() except NotImplementedError as e: logger.error(e) sys.exit(ExitCode.NotImplemented) except Exception as e: exc_type, exc_value, exc_traceback = sys.exc_info() tb = traceback.format_exception(exc_type, exc_value, exc_traceback) print(utils.error_message(), "Unknown error encountered ({ERR}) - please report this via Github\n{EXCEPTION}" .format(ERR=e.args[0], EXCEPTION="".join(tb))) sys.exit(ExitCode.UnknownError) finally: print() print(utils.normal_message(), "Thank you for using Lancer") config.save_config()
def create_parser() -> argparse.ArgumentParser: """ Create a parser with the current command line options :return: Configured ArgumentParser """ example = 'Examples:\n' example += normal_message( ) + ' ./lancer -T 10.10.10.100 -v -l de -a 10.8.0.1\n' example += normal_message( ) + ' ./lancer -TF targets.lan -vv --cache-root ./cache/\n' example += terminal_width_string( normal_message() + ' ./lancer -TN nmap-10.0.0.1.xml --skip-ports 445 80 -L 5') description = normal_message() + " Lancer - system vulnerability scanner\n" description += terminal_width_string( normal_message() + " See the config.ini file at {CONFIG_PATH} for more options.".format( CONFIG_PATH=get_config_path())) parser = argparse.ArgumentParser( formatter_class=argparse.RawDescriptionHelpFormatter, description=description, epilog=example, add_help=False) main_args = parser.add_argument_group("Required Arguments") main_args.description = "Specify the target or targets that Lancer will scan." mex_group = main_args.add_mutually_exclusive_group(required=True) mex_group.add_argument( "-T", "--target", metavar="TARGET", dest='target', type=str, help= "The hostname, IPv4 address or a subnet of of IPv4 addresses you wish to analyse." ) mex_group.add_argument( "-TF", "--target-file", metavar="FILE", dest="host_file", type=argparse.FileType('r'), help="File containing a list of target IP addresses.") mex_group.add_argument( "-TN", "--target-nmap", metavar="FILE", dest='nmapFile', type=str, help= "Skip an internal Nmap scan by providing the path to an Nmap XML file. It is" " recommended to run version detection (-sV argument)") modules = parser.add_argument_group("Module Arguments") #modules.add_argument("--cache-root", metavar="PATH", dest='cache_root', default='', # help="[NOT YET IMPLEMENTED] " # "The root of the cache. This is where all of the data for the programs run is stored," # " which may be useful if you wish to document or save all of the data in a separate" # " location.") #modules.add_argument("-L", "--level", metavar="LEVEL", dest='intrusive_level', default=3, # help="[NOT YET IMPLEMENTED] " # "The intrusion level of this iteration. A level of 1 means the least intrusive scripts" # " will be run, such as Nmap on quiet mode and a few HTTP requests. A level of 5 will mean" # " that intrusive exploits will be run against the computer to determine how vulnerable it" # " is. A full list of modules and their intrusion levels can be found on the Github Wiki." # " This defaults to 3 - moderately intrusive.") #modules.add_argument("-a", "--address", metavar="IP", dest='address', default='', # help="[NOT YET IMPLEMENTED] " # "Overrides the detected IP address with your own which is supplied.") modules.add_argument( "--skip-ports", nargs='+', type=int, metavar="PORTS", dest='skipPorts', default=[], help= "Set the ports to ignore. These ports will have no enumeration taken against them," " except for the initial discovery via Nmap. This can be used to run a custom scan and" " pass the results to Lancer. Best used in conjunction with -TN/--target-nmap." ) output = parser.add_argument_group("Output Arguments") output.description = "Control the output of Lancer." verbose_group = output.add_mutually_exclusive_group(required=False) verbose_group.add_argument( "-v", "--verbose", dest='verbose', action="store_true", default=False, help= "Use a verbose output. This will output results and information as modules run," " which can be useful if you don't wish to wait for a report at the end." ) verbose_group.add_argument( "-vv", "--very-verbose", dest='very_verbose', action="store_true", default=False, help= "Use a very verbose output. This will output virtually every single event that" " Lancer logs. Useful for debugging.") #output.add_argument("-o", "--output", metavar="FILE", dest="host_file", type=argparse.FileType('w'), # help="[NOT YET IMPLEMENTED] " # "Output the human-readable contents of the Lancer scan to a file. Best used in " # " conjunction with -v/-vv") output.add_argument("--version", dest='show_version', action="store_true", default='', help="Shows the current version of Lancer.") optional_args = parser.add_argument_group("Optional Arguments") optional_args.add_argument( "-l", "--language", metavar="LANGUAGE", dest="language_code", default="en", type=str, help="[NOT YET IMPLEMENTED] " "Language you want Lancer to use in. The language code uses ISO 639-1. Defaults to" " English.") optional_args.add_argument( "-h", "--help", action="store_true", dest="help", help="Shows the different arguments available for Lancer.") optional_args.add_argument("--clear-cache", action="store_true", dest="clear_cache", help="Clear the cache before executing") return parser
def init(): """ Initialise all of the needed prerequisites for Lancer. This should: - Register the signal handler for Ctrl+C - Load the config file - Parse command line arguments - Show the header - Check that we're on a supported Python version - Show an option to update the VirtualTerminal registry key if on Win 10 - Show a warning that localisation support is not yet implemented if there is a non-default -l parameter - Display a legal disclaimer about using Lancer for illegal use - Warn if the cache is over 500mb in size - Clear the cache if we want to """ # Register the signal handler for a more graceful Ctrl+C signal.signal(signal.SIGINT, utils.signal_handler) # Load the config file config.load_config() # Parse the arguments ArgHandler.parse_arguments(sys.argv[1:]) # Display the header utils.display_header() time.sleep(1.25) # Check we're on a supported Python version utils.python_version() # Update the Windows virtual terminal if necessary # If we're on Windows 10, import winutils if platform.system().lower() == "windows" and platform.release() == "10": from core.winutils import update_windows_virtual_terminal update_windows_virtual_terminal() # Language warning - not yet implemented if ArgHandler.get_language_code() != 'en': print(utils.error_message(), "Multi-language support is not yet implemented...") # Show a legal disclaimer disclaimer = utils.terminal_width_string( "Legal Disclaimer: Usage of Lancer for attacking targets without prior mutual" " authorisation is illegal. It is the end user's responsibility to adhere to all local" " and international laws. The developers of this tool assume no liability and are not" " responsible for any misuse or damage caused by the use of this program." ) print(utils.error_message(), disclaimer) print() # Cache warning # If it is more than 1GB, we display an error-style warning root_directory = Path(config.get_cache_path()) size = sum(f.stat().st_size for f in root_directory.glob('**/*') if f.is_file()) / 1048576 # Bytes -> MB if size >= 2048: print(utils.error_message(), "Cache is {SIZE}gb in size. It is recommended to clear it with --clear-cache." .format(SIZE="{:.1f}".format(size/1024))) # If it is more than 500, we display a warning elif size >= 512: print(utils.warning_message(), "Cache is {SIZE}mb in size. You can clear it with --clear-cache." .format(SIZE="{:.1f}".format(size))) # Clear the cache if ArgHandler.get_clear_cache(): files = os.listdir(config.get_cache_path()) for filename in files: file_path = os.path.join(config.get_cache_path(), filename) if os.path.isfile(file_path) or os.path.islink(file_path): os.unlink(file_path) elif os.path.isdir(file_path) and file_path != config.get_current_cache_path(): shutil.rmtree(file_path) print(utils.normal_message(), "Removed {NUM} items from the cache".format(NUM=len(files))) # Check if we are admin, display a relevant message if utils.is_user_admin(): print(utils.normal_message(), "Lancer running with elevated permissions") else: non_admin_warning = utils.terminal_width_string("Lancer doesn't appear to being run with elevated" " permissions. Some functionality may not work" " correctly") print(utils.warning_message(), non_admin_warning) # Display warning about your IP address ip_address = utils.terminal_width_string( "Your IP Address has been detected as {IP}. This can be changed with -a [IP]" ) print(utils.normal_message(), ip_address.format(IP=get_ip())) print() # Preload all of the modules ModuleProvider.load()