Skip to content

Commit fbe9b12

Browse files
authored
Merge pull request #101 from evilmog/master
fixed pt3 calculation in ntlmv1.py
2 parents b55e433 + a110797 commit fbe9b12

1 file changed

Lines changed: 38 additions & 18 deletions

File tree

src/ntlmv1.py

Lines changed: 38 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -90,19 +90,35 @@ def des_encrypt_block(key8_hex, challenge_hex):
9090

9191

9292
def recover_key_from_ct3(ct3_hex, challenge_hex, ess_hex=None):
93+
# Convert hex inputs to bytes
9394
ct3_bytes = bytes.fromhex(ct3_hex)
9495
challenge_bytes = bytes.fromhex(challenge_hex)
9596

9697
if len(ct3_bytes) != 8 or len(challenge_bytes) != 8:
9798
raise ValueError("ct3 and challenge must be 8 bytes (16 hex chars) each")
9899

100+
# Convert bytes to integer representation
101+
ct3_val = int.from_bytes(ct3_bytes, 'big')
102+
challenge_val = int.from_bytes(challenge_bytes, 'big')
103+
104+
# Handle ESS case using fast MD5 hash
99105
if ess_hex:
100106
ess_bytes = bytes.fromhex(ess_hex)
101-
if len(ess_bytes) == 24 and ess_bytes[8:] == b'\x00' * 16:
107+
if len(ess_bytes) != 24:
108+
raise ValueError("ESS must be 24 bytes (48 hex chars)")
109+
if ess_bytes[8:] == b'\x00' * 16:
102110
challenge_bytes = hashlib.md5(challenge_bytes + ess_bytes[:8]).digest()[:8]
103-
104-
for i in range(0x10000):
105-
nthash_bytes = [i & 0xFF, (i >> 8) & 0xFF, 0, 0, 0, 0, 0]
111+
challenge_val = int.from_bytes(challenge_bytes, 'big')
112+
113+
# **Optimized DES brute-force loop**
114+
found_key = None
115+
for i in range(0x10000): # 16-bit key space
116+
# **Optimized 7-byte to 8-byte DES key transformation**
117+
nthash_bytes = [
118+
i & 0xFF,
119+
(i >> 8) & 0xFF,
120+
0, 0, 0, 0, 0
121+
]
106122
key_bytes = bytes([
107123
nthash_bytes[0] | 1,
108124
((nthash_bytes[0] << 7) | (nthash_bytes[1] >> 1)) & 0xFF | 1,
@@ -113,15 +129,24 @@ def recover_key_from_ct3(ct3_hex, challenge_hex, ess_hex=None):
113129
((nthash_bytes[5] << 2) | (nthash_bytes[6] >> 6)) & 0xFF | 1,
114130
((nthash_bytes[6] << 1)) & 0xFF | 1
115131
])
132+
133+
# **Use PyCryptodome for fast DES encryption**
116134
cipher = DES.new(key_bytes, DES.MODE_ECB)
117135
encrypted = cipher.encrypt(challenge_bytes)
118-
if encrypted == ct3_bytes:
119-
return '{:02x}{:02x}'.format(i & 0xFF, (i >> 8) & 0xFF)
120136

121-
return None
137+
# **Fast integer comparison instead of byte-by-byte check**
138+
if int.from_bytes(encrypted, 'big') == ct3_val:
139+
found_key = i
140+
break
122141

142+
if found_key is None:
143+
return None # Key not found
123144

124-
def parse_ntlmv1(ntlmv1_hash, key1=None, key2=None, show_pt3=False, json_mode=False):
145+
# **Return key in correct format (low-order byte first, as in C output)**
146+
return f"{found_key & 0xFF:02x}{(found_key >> 8) & 0xFF:02x}"
147+
148+
149+
def parse_ntlmv1(ntlmv1_hash, key1=None, key2=None, show_pt3=True, json_mode=False):
125150
fields = ntlmv1_hash.strip().split(':')
126151
if len(fields) < 6:
127152
raise ValueError("Invalid NTLMv1 format")
@@ -165,9 +190,8 @@ def parse_ntlmv1(ntlmv1_hash, key1=None, key2=None, show_pt3=False, json_mode=Fa
165190
pt2 = des_to_ntlm_slice(key2)
166191
data["pt2"] = pt2
167192

168-
if show_pt3 or (data["pt1"] and data["pt2"]):
169-
pt3 = recover_key_from_ct3(ct3, challenge, ess)
170-
data["pt3"] = pt3
193+
pt3 = recover_key_from_ct3(data["ct3"], data["client_challenge"], data["lmresp"])
194+
data["pt3"] = pt3
171195

172196
if data["pt1"] and data["pt2"] and data["pt3"]:
173197
data["ntlm"] = data["pt1"] + data["pt2"] + data["pt3"]
@@ -179,7 +203,7 @@ def parse_ntlmv1(ntlmv1_hash, key1=None, key2=None, show_pt3=False, json_mode=Fa
179203
return data
180204

181205

182-
def parse_mschapv2(mschapv2_input, key1=None, key2=None, show_pt3=False, json_mode=False):
206+
def parse_mschapv2(mschapv2_input, key1=None, key2=None, json_mode=False):
183207
"""
184208
Accepts:
185209
- $MSCHAPv2$<chal8Bhex>$<ntresp24Bhex>
@@ -237,8 +261,7 @@ def parse_mschapv2(mschapv2_input, key1=None, key2=None, show_pt3=False, json_mo
237261
if encrypted2 and encrypted2.lower() == ct2.lower():
238262
data["pt2"] = des_to_ntlm_slice(key2)
239263

240-
if show_pt3 or (data["pt1"] and data["pt2"]):
241-
data["pt3"] = recover_key_from_ct3(ct3, chal)
264+
data["pt3"] = recover_key_from_ct3(data["ct3"], chal)
242265

243266
if data["pt1"] and data["pt2"] and data["pt3"]:
244267
data["ntlm"] = data["pt1"] + data["pt2"] + data["pt3"]
@@ -288,7 +311,6 @@ def main():
288311
parser.add_argument("--99", dest="hash_99", help="$99$ style base64 blob")
289312
parser.add_argument("--key1", help="16-char DES key hex for CT1")
290313
parser.add_argument("--key2", help="16-char DES key hex for CT2")
291-
parser.add_argument("--ct3", action="store_true", help="Brute-force CT3 key")
292314
parser.add_argument("--json", action="store_true", help="Output JSON only")
293315
parser.add_argument("--to99", action="store_true", help="Convert NTLMv1 hash to $99$ format")
294316
parser.add_argument("--hashcat", action="store_true", help="Generate hashcat format strings for ct1/ct2")
@@ -346,7 +368,6 @@ def main():
346368
args.ntlmv1,
347369
key1=args.key1,
348370
key2=args.key2,
349-
show_pt3=args.ct3,
350371
json_mode=args.json
351372
)
352373

@@ -362,7 +383,6 @@ def main():
362383
args.ntlmv1,
363384
key1=args.key1,
364385
key2=args.key2,
365-
show_pt3=args.ct3, # not required for conversion, but harmless
366386
json_mode=True # suppress prints; we'll control output below
367387
)
368388
mschapv2_str = ntlmv1_to_mschapv2(parsed_ntlm)
@@ -402,7 +422,7 @@ def main():
402422
args.mschapv2,
403423
key1=args.key1,
404424
key2=args.key2,
405-
show_pt3=args.ct3,
425+
show_pt3=True,
406426
json_mode=args.json
407427
)
408428
except Exception as e:

0 commit comments

Comments
 (0)