From 851ffbd316a4072f6b03f46ec048013137d28801 Mon Sep 17 00:00:00 2001 From: Tib3rius <48113936+Tib3rius@users.noreply.github.com> Date: Tue, 10 Jan 2023 16:24:14 -0500 Subject: [PATCH] Plugin updates, bug fix, and feature update. A few manual plugin updates (command formatting etc.) Fixed bug where processes were left running after AutoRecon is cancelled and/or times out. Status messages now include PIDs of running processes if verbosity >= 2. Closes #183 Fixes #184 --- autorecon/default-plugins/bruteforce-smb.py | 17 ++++ autorecon/default-plugins/lookup-sid.py | 2 +- autorecon/default-plugins/winrm-detection.py | 8 +- autorecon/main.py | 97 +++++++++++++++----- pyproject.toml | 3 +- requirements.txt | 1 + 6 files changed, 101 insertions(+), 27 deletions(-) create mode 100644 autorecon/default-plugins/bruteforce-smb.py diff --git a/autorecon/default-plugins/bruteforce-smb.py b/autorecon/default-plugins/bruteforce-smb.py new file mode 100644 index 0000000..da658cc --- /dev/null +++ b/autorecon/default-plugins/bruteforce-smb.py @@ -0,0 +1,17 @@ +from autorecon.plugins import ServiceScan + +class BruteforceSMB(ServiceScan): + + def __init__(self): + super().__init__() + self.name = 'Bruteforce SMB' + self.tags = ['default', 'safe', 'active-directory'] + + def configure(self): + self.match_service('tcp', 445, '^microsoft\-ds') + self.match_service('tcp', 139, '^netbios') + + def manual(self, service, plugin_was_run): + service.add_manual_command('Bruteforce SMB', [ + 'crackmapexec smb {address} --port={port} -u "' + self.get_global('username_wordlist', default='/usr/share/seclists/Usernames/top-usernames-shortlist.txt') + '" -p "' + self.get_global('password_wordlist', default='/usr/share/seclists/Passwords/darkweb2017-top100.txt') + '"' + ]) diff --git a/autorecon/default-plugins/lookup-sid.py b/autorecon/default-plugins/lookup-sid.py index 7136262..5c4bb4d 100644 --- a/autorecon/default-plugins/lookup-sid.py +++ b/autorecon/default-plugins/lookup-sid.py @@ -12,5 +12,5 @@ class LookupSID(ServiceScan): def manual(self, service, plugin_was_run): service.add_manual_command('Lookup SIDs', [ - 'lookupsid.py [username]:[password]@{address}' + 'impacket-lookupsid \'[username]:[password]@{address}\'' ]) diff --git a/autorecon/default-plugins/winrm-detection.py b/autorecon/default-plugins/winrm-detection.py index a0aa97c..8742f47 100644 --- a/autorecon/default-plugins/winrm-detection.py +++ b/autorecon/default-plugins/winrm-detection.py @@ -19,14 +19,14 @@ class WinRMDetection(ServiceScan): def manual(self, service, plugin_was_run): service.add_manual_commands('Bruteforce logins:', [ - 'crackmapexec winrm {address} -d ' + self.get_global('domain', default='') + ' -u ' + self.get_global('username_wordlist', default='/usr/share/seclists/Usernames/top-usernames-shortlist.txt') + ' -p ' + self.get_global('password_wordlist', default='/usr/share/seclists/Passwords/darkweb2017-top100.txt') + 'crackmapexec winrm {address} -d \'' + self.get_global('domain', default='') + '\' -u \'' + self.get_global('username_wordlist', default='/usr/share/seclists/Usernames/top-usernames-shortlist.txt') + '\' -p \'' + self.get_global('password_wordlist', default='/usr/share/seclists/Passwords/darkweb2017-top100.txt') + '\'' ]) service.add_manual_commands('Check login (requires credentials):', [ - 'crackmapexec winrm {address} -d ' + self.get_global('domain', default='') + ' -u -p -x "whoami"' + 'crackmapexec winrm {address} -d \'' + self.get_global('domain', default='') + '\' -u \'\' -p \'\'' ]) service.add_manual_commands('Evil WinRM (gem install evil-winrm):', [ - 'evil-winrm -u -p -i {address}', - 'evil-winrm -u -H -i {address}' + 'evil-winrm -u \'\' -p \'\' -i {address}', + 'evil-winrm -u \'\' -H \'\' -i {address}' ]) diff --git a/autorecon/main.py b/autorecon/main.py index 67e0855..f9542de 100644 --- a/autorecon/main.py +++ b/autorecon/main.py @@ -4,7 +4,7 @@ import argparse, asyncio, importlib.util, inspect, ipaddress, math, os, re, sele from datetime import datetime try: - import appdirs, colorama, impacket, requests, toml, unidecode + import appdirs, colorama, impacket, psutil, requests, toml, unidecode from colorama import Fore, Style except ModuleNotFoundError: print('One or more required modules was not installed. Please run or re-run: ' + ('sudo ' if os.getuid() == 0 else '') + 'python3 -m pip install -r requirements.txt') @@ -17,7 +17,7 @@ from autorecon.io import slugify, e, fformat, cprint, debug, info, warn, error, from autorecon.plugins import Pattern, PortScan, ServiceScan, Report, AutoRecon from autorecon.targets import Target, Service -VERSION = "2.0.30" +VERSION = "2.0.31" if not os.path.exists(config['config_dir']): shutil.rmtree(config['config_dir'], ignore_errors=True, onerror=None) @@ -92,17 +92,35 @@ def calculate_elapsed_time(start_time, short=False): else: return ', '.join(elapsed_time) -def cancel_all_tasks(signal, frame): +# sig and frame args are only present so the function +# works with signal.signal() and handles Ctrl-C. +# They are not used for any other purpose. +def cancel_all_tasks(sig, frame): for task in asyncio.all_tasks(): task.cancel() + processes = [] + for target in autorecon.scanning_targets: for process_list in target.running_tasks.values(): for process_dict in process_list['processes']: try: - process_dict['process'].kill() - except ProcessLookupError: # Will get raised if the process finishes before we get to killing it. + parent = psutil.Process(process_dict['process'].pid) + processes.extend(parent.children(recursive=True)) + processes.append(parent) + except psutil.NoSuchProcess: pass + + for process in processes: + try: + process.send_signal(signal.SIGKILL) + except psutil.NoSuchProcess: # Will get raised if the process finishes before we get to killing it. + pass + + _, alive = psutil.wait_procs(processes, timeout=10) + if len(alive) > 0: + error('The following process IDs could not be killed: ' + ', '.join([str(x.pid) for x in sorted(alive, key=lambda x: x.pid)])) + if not config['disable_keyboard_control']: # Restore original terminal settings. @@ -114,9 +132,28 @@ async def start_heartbeat(target, period=60): async with target.lock: count = len(target.running_tasks) - tasks_list = '' + tasks_list = [] if config['verbose'] >= 1: - tasks_list = ': {bblue}' + ', '.join(target.running_tasks.keys()) + '{rst}' + for tag, task in target.running_tasks.items(): + task_str = tag + + if config['verbose'] >= 2: + processes = [] + for process_dict in task['processes']: + if process_dict['process'].returncode is None: + processes.append(str(process_dict['process'].pid)) + try: + for child in psutil.Process(process_dict['process'].pid).children(recursive=True): + processes.append(str(child.pid)) + except psutil.NoSuchProcess: + pass + + if processes: + task_str += ' (PID' + ('s' if len(processes) > 1 else '') + ': ' + ', '.join(processes) + ')' + + tasks_list.append(task_str) + + tasks_list = ': {bblue}' + ', '.join(tasks_list) + '{rst}' current_time = datetime.now().strftime('%H:%M:%S') @@ -153,24 +190,42 @@ async def keyboard(): if len(input) > 0 and input[0] == 's': input = input[1:] for target in autorecon.scanning_targets: - count = len(target.running_tasks) + async with target.lock: + count = len(target.running_tasks) - tasks_list = [] - if config['verbose'] >= 1: - for key, value in target.running_tasks.items(): - elapsed_time = calculate_elapsed_time(value['start'], short=True) - tasks_list.append('{bblue}' + key + '{rst}' + ' (elapsed: ' + elapsed_time + ')') + tasks_list = [] + if config['verbose'] >= 1: + for tag, task in target.running_tasks.items(): + elapsed_time = calculate_elapsed_time(task['start'], short=True) - tasks_list = ':\n ' + '\n '.join(tasks_list) - else: - tasks_list = '' + task_str = '{bblue}' + tag + '{rst}' + ' (elapsed: ' + elapsed_time + ')' - current_time = datetime.now().strftime('%H:%M:%S') + if config['verbose'] >= 2: + processes = [] + for process_dict in task['processes']: + if process_dict['process'].returncode is None: + processes.append(str(process_dict['process'].pid)) + try: + for child in psutil.Process(process_dict['process'].pid).children(recursive=True): + processes.append(str(child.pid)) + except psutil.NoSuchProcess: + pass + + if processes: + task_str += ' (PID' + ('s' if len(processes) > 1 else '') + ': ' + ', '.join(processes) + ')' + + tasks_list.append(task_str) - if count > 1: - info('{bgreen}' + current_time + '{rst} - There are {byellow}' + str(count) + '{rst} scans still running against {byellow}' + target.address + '{rst}' + tasks_list) - elif count == 1: - info('{bgreen}' + current_time + '{rst} - There is {byellow}1{rst} scan still running against {byellow}' + target.address + '{rst}' + tasks_list) + tasks_list = ':\n ' + '\n '.join(tasks_list) + else: + tasks_list = '' + + current_time = datetime.now().strftime('%H:%M:%S') + + if count > 1: + info('{bgreen}' + current_time + '{rst} - There are {byellow}' + str(count) + '{rst} scans still running against {byellow}' + target.address + '{rst}' + tasks_list) + elif count == 1: + info('{bgreen}' + current_time + '{rst} - There is {byellow}1{rst} scan still running against {byellow}' + target.address + '{rst}' + tasks_list) else: input = input[1:] await asyncio.sleep(0.1) diff --git a/pyproject.toml b/pyproject.toml index 32fe5ce..93a45f3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "autorecon" -version = "2.0.30" +version = "2.0.31" description = "A multi-threaded network reconnaissance tool which performs automated enumeration of services." authors = ["Tib3rius"] license = "GNU GPL v3" @@ -14,6 +14,7 @@ python = "^3.8" appdirs = "^1.4.4" colorama = "^0.4.5" impacket = "^0.10.0" +psutil = "^5.9.4" requests = "^2.28.1" toml = "^0.10.2" Unidecode = "^1.3.1" diff --git a/requirements.txt b/requirements.txt index 52563e4..311f8ed 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,7 @@ appdirs colorama impacket +psutil requests toml unidecode