diff --git a/core/creds_checker.py b/core/creds_checker.py index 10eca81..a6747c5 100644 --- a/core/creds_checker.py +++ b/core/creds_checker.py @@ -21,7 +21,7 @@ def check_creds(self) -> str: Check given credentials Returns string with: - - status: "success" + - status: "Success" - error: Error message """ result = { @@ -67,7 +67,7 @@ def check_creds(self) -> str: authenticationChoice='sasl' ) - result['status'] = "success" + result['status'] = "Success" result['error'] = "None" return result diff --git a/core/scanner.py b/core/scanner.py index a2f1c42..8f5a8a9 100644 --- a/core/scanner.py +++ b/core/scanner.py @@ -8,6 +8,8 @@ import os import sys import socket +import string +import datetime try: import dns.resolver @@ -88,7 +90,7 @@ def prepare(self) -> Dict: # Check if given credential is valid creds_status = self.cred_checker.check_creds() - if creds_status['status'] != "success": + if creds_status['status'] != "Success": print(f"[*] Given credential looks invalid: {creds_status['error']}\nExitting ...") return { 'status': "Invalid_credential", @@ -137,6 +139,18 @@ def prepare(self) -> Dict: # Normal flow: parse targets from scratch print("[*] Parsing targets...") self.all_targets = self.target_parser.parse_targets() + + # Write target to file - for better observability + base_path = os.path.dirname(os.path.abspath(self.config.output_file)) + timestamp = format(datetime.datetime.now(), '%Y%m%d_%H%M%S') + target_file = os.path.join(base_path, f"target_{timestamp}.txt") + try: + with open(target_file, 'w') as f: + f.write('\n'.join(self.all_targets) + '\n') + print(f"\n[+] Scan target list written to: {target_file}") + print(f" Contains {len(self.all_targets)} scan target(s)") + except Exception as e: + print(f"\n[!] Error writing target list to {target_file}: {e}") if not self.all_targets: print("[!] No targets to scan") diff --git a/core/target_parser.py b/core/target_parser.py index 638abf2..b679ae0 100644 --- a/core/target_parser.py +++ b/core/target_parser.py @@ -79,11 +79,19 @@ def parse_targets(self) -> List[str]: if self.config.audit_mode or self.config.coerce_all: self._enumerate_ad() - return sorted(list(self.targets)) + non_tier0 = self.targets - self.tier0_assets + + aligned = list(self.tier0_assets) + aligned += list(non_tier0) + + print(f"[+] Tier-0: {len(self.tier0_assets)}; Non-Tier0: {len(non_tier0)}; Totally {len(aligned)} targets") + + return aligned + # return sorted(list(self.targets)) def _parse_target(self, target: str): """Parse a single target specification""" - + # Check for CIDR notation if '/' in target: self._parse_cidr(target) @@ -360,7 +368,7 @@ def _on_computer(item): try: #page_size now reads from self.config.ad_page_size so the --ad-page-size flag actually takes effect on this code path # add SimplePagedResultsControl to get full results. - page_size = getattr(self.config, 'ad_page_size', 1000) + page_size = getattr(self.config, 'ad_page_size', 1000) paged_control = ldapasn1_impacket.SimplePagedResultsControl(size=page_size) conn.search( searchBase=search_base, @@ -438,6 +446,9 @@ def _on_computer(item): print(f" - {dc}") elif self.config.verbose >= 2: print(f"[+] Found {len(dc_hostnames)} Domain Controller(s)") + + for dc in sorted(dc_hostnames): + self.tier0_assets.add(dc.upper()) else: if self.config.krb_dc_only: print("[!] Warning: Could not enumerate Domain Controllers, --krb-dc-only may not work correctly") diff --git a/relayking.py b/relayking.py old mode 100755 new mode 100644 index 679a3c5..370f0a1 --- a/relayking.py +++ b/relayking.py @@ -175,13 +175,13 @@ def main(): characters = string.ascii_letters + string.digits random_string = "".join(random.choice(characters) for _ in range(10)) output_path = base_name + "_" + random_string - print(f"Testing log file creation with filename '{output_path}' ... ") + print(f"[+] Testing log file creation with filename '{output_path}' ... ") try: with open(output_path, 'w') as f: f.write("check") - print(f"Success. Deleting it ... ") + print(f"[+] Success. Deleting it ... ") os.remove(output_path) - print(f"Done.") + print(f"[+] Done.") except Exception as e: print(f"\n[!] Error writing {output_path}: {e}") ready_to_output = False @@ -194,7 +194,7 @@ def main(): ready_to_output = False if(ready_to_output == False): - print("Cannot generate output file - we should not resume") + print("[!] Cannot generate output file - we should not resume") return # Save output config to session for future resume @@ -204,8 +204,8 @@ def main(): scanner = RelayKingScanner(config, session=session) status = scanner.prepare() - # grouping - print(f"hosts: {status['number_of_target']} / max_scangroup: {config.max_scangroup} / split_into: {config.split_into} / skip: {config.skip}") + if(status['status'] != "Success"): + return if(config.max_scangroup == 0) and (config.split_into == 1): #print("default - all") @@ -227,7 +227,7 @@ def main(): else: idxlen = int(math.log10(split_into-1))+1 - print(f"Targets have been split into {split_into} groups. Each group has {group_size} hosts. Totally {status['number_of_target']} targets to be scanned") + print(f"[+] Targets have been split into {split_into} groups. Each group has {group_size} hosts. Totally {status['number_of_target']} targets to be scanned") # Determine which groups to skip (session-completed or --skip) completed_groups = session.get_completed_groups() if session else set() @@ -239,11 +239,11 @@ def main(): e_idx = 0 for i in range(split_into): if (i < config.skip): - print(f"Skipping group {i}: Skip to group {config.skip}") + print(f"[+] Skipping group {i}: Skip to group {config.skip}") continue if i in completed_groups: - print(f"Skipping group {i}: Already completed (from session)") + print(f"[+] Skipping group {i}: Already completed (from session)") continue s_idx = i * group_size @@ -251,7 +251,7 @@ def main(): if(e_idx > status['number_of_target']): e_idx = status['number_of_target'] - print(f"Group {i} of {split_into}: Scanning {group_size}(or less) hosts with index {s_idx} to {e_idx} of total {status['number_of_target']}") + print(f"[+] Group {i} of {split_into}: Scanning {group_size}(or less) hosts with index {s_idx} to {e_idx} of total {status['number_of_target']}") results = scanner.scan(s_idx, e_idx) # Stamp elapsed time before formatting so the formatter can include it in the report results['scan_duration'] = time.time() - start_time @@ -261,6 +261,9 @@ def main(): if session: session.mark_group_complete(i) + # Wait to see if additional Ctrl-C + time.sleep(1) + except KeyboardInterrupt: # Session is saved by scanner's KeyboardInterrupt handler if session: diff --git a/verify_installation.py b/verify_installation.py old mode 100755 new mode 100644