diff --git a/wifite/config.py b/wifite/config.py index 2d12d87b7..109e2b6a4 100644 --- a/wifite/config.py +++ b/wifite/config.py @@ -119,6 +119,27 @@ class Configuration: # System check mode syscheck = None + @classmethod + def load_manufacturers(cls): + """Lazy-load OUI manufacturer database on first access.""" + if cls._manufacturers_loaded: + return + cls._manufacturers_loaded = True + if os.path.isfile('/usr/share/ieee-data/oui.txt'): + mfr_file = '/usr/share/ieee-data/oui.txt' + else: + mfr_file = 'ieee-oui.txt' + if os.path.exists(mfr_file): + cls.manufacturers = {} + with open(mfr_file, 'r', encoding='utf-8') as f: + for line in f: + if not re.match(r'^\w', line): + continue + line = line.replace('(hex)', '').replace('(base 16)', '') + fields = line.split() + if len(fields) >= 2: + cls.manufacturers[fields[0]] = ' '.join(fields[1:]).rstrip('.') + @classmethod def initialize(cls, load_interface=True): """ @@ -249,22 +270,9 @@ def initialize(cls, load_interface=True): cls.wordlists = [wlist] break - if os.path.isfile('/usr/share/ieee-data/oui.txt'): - manufacturers = '/usr/share/ieee-data/oui.txt' - else: - manufacturers = 'ieee-oui.txt' - - if os.path.exists(manufacturers): - cls.manufacturers = {} - with open(manufacturers, "r", encoding='utf-8') as f: - # Parse txt format into dict - for line in f: - if not re.match(r"^\w", line): - continue - line = line.replace('(hex)', '').replace('(base 16)', '') - fields = line.split() - if len(fields) >= 2: - cls.manufacturers[fields[0]] = " ".join(fields[1:]).rstrip('.') + # Manufacturers database is lazy-loaded on first access via load_manufacturers() + cls.manufacturers = None + cls._manufacturers_loaded = False # WPS variables cls.wps_filter = False # Only attack WPS networks diff --git a/wifite/model/target.py b/wifite/model/target.py index daf2ca8c4..372fe1470 100644 --- a/wifite/model/target.py +++ b/wifite/model/target.py @@ -268,7 +268,8 @@ def to_str(self, show_bssid=False, show_manufacturer=False): bssid = Color.s('{D}%s{W}' % self.bssid) if show_bssid else '' if show_manufacturer: oui = ''.join(self.bssid.split(':')[:3]) - self.manufacturer = Configuration.manufacturers.get(oui, "") + Configuration.load_manufacturers() + self.manufacturer = Configuration.manufacturers.get(oui, "") if Configuration.manufacturers else "" max_oui_len = 21 mfg_name = self.manufacturer diff --git a/wifite/tools/airodump.py b/wifite/tools/airodump.py index 11d618c2e..3fc65b3b3 100755 --- a/wifite/tools/airodump.py +++ b/wifite/tools/airodump.py @@ -184,23 +184,17 @@ def get_targets(self, old_targets=None, apply_filter=True, target_archives=None) new_targets = Airodump.get_targets_from_csv(csv_filename) # Check if one of the targets is also contained in the old_targets + # Use dict lookup (O(1)) instead of nested loop (O(n*m)) + old_by_bssid = {t.bssid: t for t in old_targets} for new_target in new_targets: - just_found = True - for old_target in old_targets: - # If the new_target is found in old_target copy attributes from old target - if old_target == new_target: - # Identify decloaked targets - if new_target.essid_known and not old_target.essid_known: - # We decloaked a target! - new_target.decloaked = True - - old_target.transfer_info(new_target) - just_found = False - break - - # If the new_target is not in old_targets, check target_archives - # and copy attributes from there - if just_found and new_target.bssid in target_archives: + old_target = old_by_bssid.get(new_target.bssid) + if old_target is not None: + # Identify decloaked targets + if new_target.essid_known and not old_target.essid_known: + new_target.decloaked = True + old_target.transfer_info(new_target) + elif new_target.bssid in target_archives: + # If the new_target is not in old_targets, check target_archives target_archives[new_target.bssid].transfer_info(new_target) # Check targets for WPS @@ -237,18 +231,22 @@ def get_targets_from_csv(csv_filename): targets2 = [] import csv + # Detect encoding from first 4KB sample to avoid reading entire file twice try: import chardet - with open(csv_filename, "rb") as rawdata: - encoding = chardet.detect(rawdata.read())['encoding'] or 'utf-8' + with open(csv_filename, 'rb') as rawdata: + encoding = chardet.detect(rawdata.read(4096))['encoding'] or 'utf-8' except ImportError: encoding = 'utf-8' with open(csv_filename, 'r', encoding=encoding, errors='ignore') as csvopen: lines = [] + has_null = False for line in csvopen: if '\0' in line: - log_warning('Airodump', 'Null bytes found in CSV data, stripping them') + if not has_null: + log_warning('Airodump', 'Null bytes found in CSV data, stripping them') + has_null = True line = line.replace('\0', '') lines.append(line) @@ -272,6 +270,12 @@ def get_targets_from_csv(csv_filename): elif row[0].strip() == 'Station MAC': # This is the 'header' for the list of Clients hit_clients = True + # Build BSSID lookup dict for O(1) client-to-target matching + # Use first occurrence per BSSID to match original behavior + targets_by_bssid = {} + for t in targets2: + if t.bssid not in targets_by_bssid: + targets_by_bssid[t.bssid] = t continue if hit_clients: @@ -286,11 +290,10 @@ def get_targets_from_csv(csv_filename): # Ignore unassociated clients continue - # Add this client to the appropriate Target - for t in targets2: - if t.bssid == client.bssid: - t.clients.append(client) - break + # Add this client to the appropriate Target (O(1) dict lookup) + target = targets_by_bssid.get(client.bssid) + if target: + target.clients.append(client) else: # The current row corresponds to a 'Target' (router) diff --git a/wifite/ui/scanner_view.py b/wifite/ui/scanner_view.py index c253b1b3b..6b12f932b 100755 --- a/wifite/ui/scanner_view.py +++ b/wifite/ui/scanner_view.py @@ -344,7 +344,8 @@ def _format_manufacturer(self, target) -> Text: # Get OUI (first 3 octets of BSSID) oui = ''.join(target.bssid.split(':')[:3]) - manufacturer = Configuration.manufacturers.get(oui, "Unknown") + Configuration.load_manufacturers() + manufacturer = Configuration.manufacturers.get(oui, "Unknown") if Configuration.manufacturers else "Unknown" # Truncate if too long max_len = 20 diff --git a/wifite/ui/selector_view.py b/wifite/ui/selector_view.py index 3a322eec1..b93fb47de 100755 --- a/wifite/ui/selector_view.py +++ b/wifite/ui/selector_view.py @@ -412,6 +412,7 @@ def _format_manufacturer(self, target) -> Text: # Get OUI (first 3 octets of BSSID) oui = ''.join(target.bssid.split(':')[:3]) + Configuration.load_manufacturers() manufacturer = Configuration.manufacturers.get(oui, "Unknown") if Configuration.manufacturers else "Unknown" # Truncate if too long diff --git a/wifite/util/process.py b/wifite/util/process.py index 1788c9397..114cd4419 100755 --- a/wifite/util/process.py +++ b/wifite/util/process.py @@ -176,7 +176,7 @@ def __init__(self, command, devnull=False, stdout=PIPE, stderr=PIPE, cwd=None, b log_warning('Process', 'Delaying process creation due to high FD usage') if Configuration.verbose > 0: Color.pl('{!} {O}Delaying process creation due to high FD usage{W}') - time.sleep(0.5) # Brief delay to allow cleanup to complete + time.sleep(0.1) # Brief delay to allow cleanup to complete self.out = None self.err = None @@ -447,15 +447,27 @@ def cleanup_zombies(): except Exception: pass + # Cache for FD count to avoid filesystem scan on every process creation + _fd_cache_time = 0 + _fd_cache_value = -1 + _FD_CACHE_TTL = 2.0 # seconds + @staticmethod def get_open_fd_count(): - """Get current open file descriptor count from /proc/{pid}/fd""" + """Get current open file descriptor count from /proc/{pid}/fd (cached with TTL)""" + now = time.time() + if now - Process._fd_cache_time < Process._FD_CACHE_TTL: + return Process._fd_cache_value try: proc_fd_dir = f'/proc/{os.getpid()}/fd' if os.path.exists(proc_fd_dir): - return len(os.listdir(proc_fd_dir)) + Process._fd_cache_value = len(os.listdir(proc_fd_dir)) + Process._fd_cache_time = now + return Process._fd_cache_value except Exception: pass + Process._fd_cache_value = -1 + Process._fd_cache_time = now return -1 @staticmethod diff --git a/wifite/util/scanner.py b/wifite/util/scanner.py index 676c708b4..657339776 100755 --- a/wifite/util/scanner.py +++ b/wifite/util/scanner.py @@ -108,9 +108,6 @@ def find_targets(self): if self.found_target(): return True # We found the target we want - if airodump.pid.poll() is not None: - return True # Airodump process died - # Update display based on mode if self.use_tui and self.view: self.view.update_targets(self.targets, airodump.decloaking) @@ -561,13 +558,13 @@ def _cleanup_memory(self): # 2. Limit target list size (keep strongest signals) if len(self.targets) > self._max_targets: - # Sort by power (strongest first) - self.targets.sort(key=lambda x: x.power, reverse=True) + import heapq removed_count = len(self.targets) - self._max_targets - self.targets = self.targets[:self._max_targets] - + # Use heapq.nlargest: O(n log k) vs O(n log n) for full sort + self.targets = heapq.nlargest(self._max_targets, self.targets, key=lambda x: x.power) + if Configuration.verbose > 1: - Color.pl('{!} {O}Trimmed %d weak targets (limit: %d){W}' % + Color.pl('{!} {O}Trimmed %d weak targets (limit: %d){W}' % (removed_count, self._max_targets)) # 3. Clean up old archived targets with time-based expiration