Skip to content

Commit e67e05c

Browse files
committed
v0.1.3
added qol features: -nd >>> no domain switch added -csv >>> output to csv switch added small improvements, better output.
1 parent 9755d58 commit e67e05c

File tree

7 files changed

+81
-32
lines changed

7 files changed

+81
-32
lines changed

README.md

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11

2-
## about revealhashed-python v0.1.2
3-
revealhashed is a streamlined utility to correlate ntds usernames, nt hashes, and cracked passwords in one view while cutting out time-consuming manual tasks.
2+
## about revealhashed-python v0.1.3
3+
revealhashed is a streamlined utility to correlate ntds usernames, nt hashes, and cracked passwords in one view while cutting out time-consuming manual tasks.
44

55
## how to install
66
from pypi:
@@ -15,7 +15,7 @@ grab revealhashed binary from [releases](https://github.com/crosscutsaw/revealha
1515

1616
## how to use
1717
```
18-
revealhashed v0.1.2
18+
revealhashed v0.1.3
1919
2020
usage: revealhashed [-h] [-r] {dump,reveal} ...
2121
@@ -32,28 +32,58 @@ options:
3232
just execute `revealhashed -r` to remove contents of ~/.revealhashed
3333

3434
### revealhashed dump
35-
this command executes [zblurx's ntdsutil.py](https://github.com/zblurx/ntdsutil.py) to dump ntds safely then does classic revealhashed operations.
35+
```
36+
revealhashed v0.1.3
37+
38+
usage: revealhashed dump [-h] [-debug] [-hashes HASHES] [-no-pass] [-k] [-aesKey AESKEY] [-dc-ip DC_IP] [-codec CODEC] -w WORDLIST WORDLIST2 [WORDLIST WORDLIST2 ...] [-e] [-nd] [-csv] target
39+
```
40+
41+
this command executes [zblurx's ntdsutil.py](https://github.com/zblurx/ntdsutil.py) to dump ntds safely then does classic revealhashed operations.
3642

3743
-w (wordlist) switch is needed. one or more wordlists can be supplied.
3844
-e (enabled-only) switch is not needed but suggested. it's self explanatory; only shows enabled users.
45+
-nd (no-domain) switch hides domain names in usernames.
46+
-csv (csv) switch is self explanatory; saves output to csv instead txt.
3947

4048
for example:
41-
`revealhashed dump 'troupe.local/emreda:Aa123456'@192.168.2.11 -w wordlist1.txt wordlist2.txt -e`
49+
`revealhashed dump '<domain>/<username>:<password>'@<dc_ip> -w wordlist1.txt wordlist2.txt -e -nd -csv`
4250

4351
### revealhashed reveal
44-
this command wants to get supplied with ntds file by user then does classic revealhashed operations.
45-
_ntds file should contain usernames and hashes. it should be not ntds.dit. example ntds dump can be obtained from repo_
52+
```
53+
revealhashed v0.1.3
54+
55+
usage: revealhashed reveal [-h] [-ntds NTDS] [-nxc] [-w WORDLIST WORDLIST2 [WORDLIST WORDLIST2 ...]] [-e] [-nd] [-csv]
4656
47-
-ntds or -nxc switch is needed. -ntds switch is for a file you own with hashes. -nxc switch is for scanning ~/.nxc/logs/ntds then selecting .ntds file.
57+
options:
58+
-h, --help show this help message and exit
59+
-ntds NTDS Path to .ntds file
60+
-nxc Scan $HOME/.nxc/logs/ntds for .ntds files
61+
-w WORDLIST WORDLIST2 [WORDLIST WORDLIST2 ...], --wordlists WORDLIST WORDLIST2 [WORDLIST WORDLIST2 ...]
62+
Wordlists to use with hashcat
63+
-e, --enabled-only Only show enabled accounts
64+
-nd, --no-domain Don't display domain in usernames
65+
-csv Save output in CSV format
66+
```
67+
68+
this command wants to get supplied with ntds file by user or netexec then does classic revealhashed operations.
69+
70+
_ntds file should contain usernames and hashes. it should be not ntds.dit. example ntds dump can be obtained from repo._
71+
72+
-ntds or -nxc switch is needed. -ntds switch is for a file you own with hashes. -nxc switch is for scanning ~/.nxc/logs/ntds directory then selecting .ntds file.
4873
-w (wordlist) switch is needed. one or more wordlists can be supplied.
4974
-e (enabled-only) switch is not needed but suggested. it's self explanatory; only shows enabled users.
75+
-nd (no-domain) switch hides domain names in usernames.
76+
-csv (csv) switch is self explanatory; saves output to csv instead txt.
5077

5178
for example:
52-
`revealhashed reveal -ntds TROUPEDC_192.168.2.11_2025-05-12_123035.ntds -w wordlist1.txt -e`
79+
`revealhashed reveal -ntds <ntds_file>.ntds -w wordlist1.txt -e -nd -csv`
80+
`revealhashed reveal -nxc -w wordlist1.txt -e -nd -csv`
5381

5482
## example outputs
5583
![](https://raw.githubusercontent.com/crosscutsaw/revealhashed-python/main/rp1.PNG)
5684

5785
![](https://raw.githubusercontent.com/crosscutsaw/revealhashed-python/main/rp2.PNG)
5886

5987
![](https://raw.githubusercontent.com/crosscutsaw/revealhashed-python/main/rp3.PNG)
88+
89+
![](https://raw.githubusercontent.com/crosscutsaw/revealhashed-python/main/rp4.PNG)

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "revealhashed"
3-
version = "0.1.2"
3+
version = "0.1.3"
44
description = "Dump or analyze existing NTDS data, crack NT hashes with hashcat and match them to their corresponding user accounts."
55
authors = [{ name = "aslan emre aslan", email = "emre@zurrak.com" }]
66
license = { text = "MIT" }

revealhashed/core.py

Lines changed: 41 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import subprocess
77
import shutil
88
import sys
9+
import csv
910
from collections import defaultdict
1011
from datetime import datetime
1112
from pathlib import Path
@@ -47,6 +48,8 @@ def parse_args():
4748
dump_parser.add_argument("-codec")
4849
dump_parser.add_argument("-w", "--wordlists", nargs="+", metavar="WORDLIST WORDLIST2", help="Wordlists to use with hashcat", required=True)
4950
dump_parser.add_argument("-e", "--enabled-only", action="store_true", help="Only show enabled accounts")
51+
dump_parser.add_argument('-nd', '--no-domain', action='store_true', help="Don't display domain in usernames")
52+
dump_parser.add_argument('-csv', action='store_true', help="Save output in CSV format")
5053
# do not include -outputdir
5154

5255
# subparser: reveal
@@ -55,6 +58,8 @@ def parse_args():
5558
reveal_parser.add_argument("-nxc", action="store_true", help="Scan $HOME/.nxc/logs/ntds for .ntds files")
5659
reveal_parser.add_argument("-w", "--wordlists", nargs="+", metavar="WORDLIST WORDLIST2", help="Wordlists to use with hashcat", required=False)
5760
reveal_parser.add_argument("-e", "--enabled-only", action="store_true", help="Only show enabled accounts")
61+
reveal_parser.add_argument('-nd', '--no-domain', action='store_true', help="Don't display domain in usernames")
62+
reveal_parser.add_argument('-csv', action='store_true', help="Save output in CSV format")
5863

5964
return parser
6065

@@ -101,10 +106,10 @@ def extract_unique_hashes(ntds_path, output_path, full_output_path, write_full_o
101106
raise
102107

103108
def run_hashcat(hashes_file, wordlists):
104-
start = datetime.now().strftime("%H:%M:%S %d-%m-%Y")
105-
print(f"{BOLD_GREEN}[+]{RESET} Starting hashcat session at {start}")
109+
start = datetime.now().strftime("%H:%M:%S %d.%m.%Y")
110+
print(f"\n{BOLD_GREEN}[+]{RESET} Starting hashcat session at {start}")
106111
subprocess.run(["hashcat", "-m1000", str(hashes_file), *wordlists, "--quiet"])
107-
end = datetime.now().strftime("%H:%M:%S %d-%m-%Y")
112+
end = datetime.now().strftime("%H:%M:%S %d.%m.%Y")
108113
print(f"{BOLD_GREEN}[+]{RESET} Ended hashcat session at {end}")
109114

110115
def parse_potfile(potfile_path):
@@ -119,7 +124,7 @@ def parse_potfile(potfile_path):
119124
cracked[hash_val] = password
120125
return cracked
121126

122-
def reveal_credentials(individual_ntds_path, cracked_hashes, session_dir, enabled_only=False):
127+
def reveal_credentials(individual_ntds_path, cracked_hashes, session_dir, enabled_only=False, no_domain=False, to_csv=False):
123128
print(f"\n{BOLD_GREEN}[+]{RESET} Revealed credentials:")
124129
output_lines = []
125130

@@ -151,25 +156,39 @@ def reveal_credentials(individual_ntds_path, cracked_hashes, session_dir, enable
151156
password_key = plain
152157
password_colored = f"{BOLD_WHITE}{plain}{RESET}"
153158

154-
disabled_str = f" {BOLD_RED}<disabled>{RESET}" if status == "disabled" else ""
155-
line_out = f"{user:<40} {password_colored}{disabled_str}"
156-
output_lines.append((password_key, user, line_out, status))
159+
# strip domain if requested
160+
display_user = user.split("\\", 1)[-1] if no_domain else user
157161

158-
# sort: <no password> first, then by password
162+
disabled_str = f"{BOLD_RED}<disabled>{RESET}" if status == "disabled" else ""
163+
line_out = f"{display_user:<40} {password_colored}{' ' + disabled_str if status == 'disabled' else ''}"
164+
output_lines.append((password_key, display_user, line_out, status))
165+
166+
# sort: <no password> first, then alphabetically
159167
output_lines.sort(key=lambda x: (x[0] != "<no password>", x[0].lower(), x[1].lower()))
160168

161169
# print and write
162-
output_file = session_dir / "revealhashed.txt"
163-
with open(output_file, "w") as outf:
164-
for password_key, user, line_out, status in output_lines:
165-
print(line_out)
166-
plain = password_key
167-
outf.write(f"{user:<40} {plain}{' <disabled>' if status == 'disabled' else ''}\n")
170+
for _, _, line_out, _ in output_lines:
171+
print(line_out)
172+
173+
if to_csv:
174+
output_file = session_dir / "revealhashed.csv"
175+
with open(output_file, "w", newline="") as outf:
176+
writer = csv.writer(outf)
177+
writer.writerow(["Username", "Password", "Status"])
178+
for password_key, user, _, status in output_lines:
179+
stat = "disabled" if status == "disabled" else ""
180+
writer.writerow([user, password_key, stat])
181+
else:
182+
output_file = session_dir / "revealhashed.txt"
183+
with open(output_file, "w") as outf:
184+
for password_key, user, _, status in output_lines:
185+
status_str = " <disabled>" if status == "disabled" else ""
186+
outf.write(f"{user:<40} {password_key}{status_str}\n")
168187

169188
print(f"\n{BOLD_GREEN}[+]{RESET} Output saved to {output_file}")
170189

171190
def main():
172-
print(f"\n{BOLD_BLUE}revealhashed v0.1.2{RESET}\n")
191+
print(f"\n{BOLD_BLUE}revealhashed v0.1.3{RESET}\n")
173192

174193
parser = parse_args()
175194
args = parser.parse_args()
@@ -189,16 +208,16 @@ def main():
189208
ntdsutil_dir = session_dir / "ntdsutil"
190209
ntdsutil_dir.mkdir(parents=True, exist_ok=True)
191210
args.outputdir = str(ntdsutil_dir)
192-
193-
start = datetime.now().strftime("%H:%M:%S %d-%m-%Y")
211+
start = datetime.now().strftime("%H:%M:%S %d.%m.%Y")
194212
print(f"{BOLD_GREEN}[+]{RESET} Starting NTDS dump with ntdsutil at {start}")
195213

196214
try:
197215
ntdsutil.run_ntdsutil(args)
198216
except Exception as e:
199217
print(f"{BOLD_RED}[!]{RESET} NTDS dump failed: {e}")
200218
return
201-
end = datetime.now().strftime("%H:%M:%S %d-%m-%Y")
219+
220+
end = datetime.now().strftime("%H:%M:%S %d.%m.%Y")
202221
print(f"{BOLD_GREEN}[+]{RESET} NTDS successfully dumped at {end}")
203222

204223
# run secretsdump in the same session folder
@@ -246,7 +265,7 @@ def main():
246265
if HASHCAT_POT.exists():
247266
shutil.copy(HASHCAT_POT, session_dir)
248267
cracked = parse_potfile(HASHCAT_POT)
249-
reveal_credentials(ind_path, cracked, session_dir, enabled_only=args.enabled_only)
268+
reveal_credentials(ind_path, cracked, session_dir, enabled_only=args.enabled_only, no_domain=args.no_domain, to_csv=args.csv)
250269

251270
elif args.command == "reveal":
252271
if args.nxc:
@@ -263,7 +282,7 @@ def main():
263282

264283
while True:
265284
try:
266-
selection = int(input("\nSelect file by index: "))
285+
selection = int(input(f"\n{BOLD_GREEN}[>]{RESET} Select file by index: "))
267286
print()
268287
if 0 <= selection < len(ntds_files):
269288
ntds_path = ntds_files[selection]
@@ -297,12 +316,12 @@ def main():
297316
if HASHCAT_POT.exists():
298317
shutil.copy(HASHCAT_POT, session_dir)
299318
cracked = parse_potfile(HASHCAT_POT)
300-
reveal_credentials(ind_path, cracked, session_dir, enabled_only=args.enabled_only)
319+
reveal_credentials(ind_path, cracked, session_dir, enabled_only=args.enabled_only, no_domain=args.no_domain, to_csv=args.csv)
301320

302321
if __name__ == "__main__":
303322
main()
304323

305-
# revealhashed v0.1.2
324+
# revealhashed v0.1.3
306325
#
307326
# contact options
308327
# mail: https://blog.zurrak.com/contact.html

rp1.PNG

6.85 KB
Loading

rp2.PNG

3.38 KB
Loading

rp3.PNG

16.9 KB
Loading

rp4.PNG

72.6 KB
Loading

0 commit comments

Comments
 (0)