1 | | - | import datetime |
2 | | - | |
3 | | - | from unicrypto.symmetric import DES |
4 | | - | from unicrypto import hashlib |
5 | | - | from unicrypto import hmac |
6 | | - | |
7 | | - | from aardwolf.authentication.ntlm.structures.challenge_response import * |
8 | | - | from aardwolf.authentication.ntlm.structures.negotiate_flags import NegotiateFlags |
9 | | - | |
10 | | - | class NTLMCredentials: |
11 | | - | @staticmethod |
12 | | - | def construct(ntlmNegotiate, ntlmChallenge, ntlmAuthenticate): |
13 | | - | # now the guessing-game begins |
14 | | - | |
15 | | - | if isinstance(ntlmAuthenticate.NTChallenge, NTLMv2Response): |
16 | | - | #if ntlmAuthenticate._use_NTLMv2: |
17 | | - | # this is a netNTLMv2 then, otherwise auth would have failed on protocol level |
18 | | - | creds = netntlmv2() |
19 | | - | creds.username = ntlmAuthenticate.UserName |
20 | | - | creds.domain = ntlmAuthenticate.DomainName |
21 | | - | creds.ServerChallenge = ntlmChallenge.ServerChallenge |
22 | | - | creds.ClientResponse = ntlmAuthenticate.NTChallenge.Response |
23 | | - | creds.ChallengeFromClinet = ntlmAuthenticate.NTChallenge.ChallengeFromClinet_hex |
24 | | - | |
25 | | - | creds2 = netlmv2() |
26 | | - | creds2.username = ntlmAuthenticate.UserName |
27 | | - | creds2.domain = ntlmAuthenticate.DomainName |
28 | | - | creds2.ServerChallenge = ntlmChallenge.ServerChallenge |
29 | | - | creds2.ClientResponse = ntlmAuthenticate.LMChallenge.Response |
30 | | - | creds2.ChallengeFromClinet = ntlmAuthenticate.LMChallenge.ChallengeFromClinet |
31 | | - | return [creds, creds2] |
32 | | - | |
33 | | - | else: |
34 | | - | if ntlmAuthenticate.NegotiateFlags & NegotiateFlags.NEGOTIATE_EXTENDED_SESSIONSECURITY: |
35 | | - | # extended security is used, this means that the LMresponse actually contains client challenge data |
36 | | - | # and the LM and NT respondses need to be combined to form the cred data |
37 | | - | creds = netntlm_ess() |
38 | | - | creds.username = ntlmAuthenticate.UserName |
39 | | - | creds.domain = ntlmAuthenticate.DomainName |
40 | | - | creds.ServerChallenge = ntlmChallenge.ServerChallenge |
41 | | - | creds.ClientResponse = ntlmAuthenticate.NTChallenge.Response |
42 | | - | creds.ChallengeFromClinet = ntlmAuthenticate.LMChallenge.Response |
43 | | - | |
44 | | - | return [creds] |
45 | | - | |
46 | | - | else: |
47 | | - | creds = netntlm() |
48 | | - | creds.username = ntlmAuthenticate.UserName |
49 | | - | creds.domain = ntlmAuthenticate.DomainName |
50 | | - | creds.ServerChallenge = ntlmChallenge.ServerChallenge |
51 | | - | creds.ClientResponse = ntlmAuthenticate.NTChallenge.Response |
52 | | - | |
53 | | - | if ntlmAuthenticate.NTChallenge.Response == ntlmAuthenticate.LMChallenge.Response: |
54 | | - | # the the two responses are the same, then the client did not send encrypted LM hashes, only NT |
55 | | - | return [creds] |
56 | | - | |
57 | | - | |
58 | | - | # CAME FOR COPPER, FOUND GOLD!!!!! |
59 | | - | # HOW OUTDATED IS YOUR CLIENT ANYHOW??? |
60 | | - | creds2 = netlm() |
61 | | - | creds2.username = ntlmAuthenticate.UserName |
62 | | - | creds2.domain = ntlmAuthenticate.DomainName |
63 | | - | creds2.ServerChallenge = ntlmChallenge.ServerChallenge |
64 | | - | creds2.ClientResponse = ntlmAuthenticate.LMChallenge.Response |
65 | | - | return [creds2, creds] |
66 | | - | |
67 | | - | class netlm: |
68 | | - | # not supported by hashcat? |
69 | | - | def __init__(self): |
70 | | - | # this part comes from the NTLMAuthenticate class |
71 | | - | self.username = None |
72 | | - | self.domain = None |
73 | | - | # this comes from the NTLMChallenge class |
74 | | - | self.ServerChallenge = None |
75 | | - | |
76 | | - | # this is from the LMv1Response class (that is a member of NTLMAuthenticate class) |
77 | | - | self.ClientResponse = None |
78 | | - | |
79 | | - | def to_credential(self): |
80 | | - | cred = Credential('netLM', |
81 | | - | username = self.username, |
82 | | - | fullhash = '%s:$NETLM$%s$%s' % (self.username, self.ServerChallenge, self.ClientResponse) |
83 | | - | ) |
84 | | - | return cred |
85 | | - | |
86 | | - | def verify(self, creds, credtype='plain'): |
87 | | - | """ |
88 | | - | Verifies the authentication data against the user credentials |
89 | | - | Be careful! If the credtype is 'hash' then LM hash is expected! |
90 | | - | :param creds: dictionary containing the domain, user, hash/password |
91 | | - | :param credtype: can be 'plain' or 'hash' this indicates what type of credential lookup to perform |
92 | | - | :return: bool |
93 | | - | """ |
94 | | - | |
95 | | - | # print('Creds: %s' % creds) |
96 | | - | if creds is None: |
97 | | - | return True |
98 | | - | |
99 | | - | if self.domain not in creds: |
100 | | - | return False |
101 | | - | if self.username not in creds[self.domain]: |
102 | | - | return False |
103 | | - | |
104 | | - | if credtype == 'plain': |
105 | | - | lm_hash = LMOWFv1(creds[self.domain][self.username]) |
106 | | - | elif credtype == 'hash': |
107 | | - | lm_hash = bytes.fromhex(creds[self.domain][self.username]) |
108 | | - | else: |
109 | | - | raise Exception('Unknown cred type!') |
110 | | - | |
111 | | - | calc_response = DESL(lm_hash, self.ServerChallenge) |
112 | | - | |
113 | | - | return self.ClientResponse == calc_response.hex() |
114 | | - | |
115 | | - | |
116 | | - | class netlmv2: |
117 | | - | # not supported by hashcat? |
118 | | - | def __init__(self): |
119 | | - | # this part comes from the NTLMAuthenticate class |
120 | | - | self.username = None |
121 | | - | self.domain = None |
122 | | - | # this comes from the NTLMChallenge class |
123 | | - | self.ServerChallenge = None |
124 | | - | |
125 | | - | # this is from the LMv2Response class (that is a member of NTLMAuthenticate class) |
126 | | - | self.ClientResponse = None |
127 | | - | self.ChallengeFromClinet = None |
128 | | - | |
129 | | - | def to_credential(self): |
130 | | - | cred = Credential( |
131 | | - | 'netLMv2', |
132 | | - | username = self.username, |
133 | | - | fullhash = '$NETLMv2$%s$%s$%s$%s' % (self.username, self.ServerChallenge, self.ClientResponse, self.ChallengeFromClinet) |
134 | | - | ) |
135 | | - | return cred |
136 | | - | |
137 | | - | def verify(self, creds, credtype='plain'): |
138 | | - | """ |
139 | | - | Verifies the authentication data against the user credentials |
140 | | - | :param creds: dictionary containing the domain, user, hash/password |
141 | | - | :param credtype: can be 'plain' or 'hash' this indicates what type of credential lookup to perform |
142 | | - | :return: bool |
143 | | - | """ |
144 | | - | |
145 | | - | # print('Creds: %s' % creds) |
146 | | - | if creds is None: |
147 | | - | return True |
148 | | - | |
149 | | - | if self.domain not in creds: |
150 | | - | return False |
151 | | - | if self.username not in creds[self.domain]: |
152 | | - | return False |
153 | | - | |
154 | | - | if credtype == 'plain': |
155 | | - | lm_hash = LMOWFv2(creds[self.domain][self.username], self.username, self.domain) |
156 | | - | elif credtype == 'hash': |
157 | | - | lm_hash = LMOWFv2(None, self.username, self.domain, bytes.fromhex(creds[self.domain][self.username])) |
158 | | - | else: |
159 | | - | raise Exception('Unknown cred type!') |
160 | | - | |
161 | | - | hm = hmac.new(lm_hash, digestmod = 'md5') |
162 | | - | hm.update(bytes.fromhex(self.ServerChallenge)) |
163 | | - | hm.update(bytes.fromhex(self.ChallengeFromClinet)) |
164 | | - | |
165 | | - | return self.ClientResponse == hm.hexdigest() |
166 | | - | |
167 | | - | |
168 | | - | class netntlm_ess: |
169 | | - | def __init__(self): |
170 | | - | # this part comes from the NTLMAuthenticate class |
171 | | - | self.credentials = None |
172 | | - | # this comes from the NTLMChallenge class |
173 | | - | self.ServerChallenge = None |
174 | | - | |
175 | | - | self.LMResponse = None |
176 | | - | self.NTResponse = None |
177 | | - | |
178 | | - | self.SessionBaseKey = None |
179 | | - | |
180 | | - | |
181 | | - | # https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/d86303b5-b29e-4fb9-b119-77579c761370 |
182 | | - | def calc_key_exchange_key(self): |
183 | | - | if not self.credentials.nt_hash: |
184 | | - | nt_hash = NTOWFv1(self.credentials.password) |
185 | | - | else: |
186 | | - | nt_hash = bytes.fromhex(self.credentials.nt_hash) |
187 | | - | |
188 | | - | hm = hmac.new(self.SessionBaseKey, digestmod = 'md5') |
189 | | - | hm.update(self.ServerChallenge) |
190 | | - | hm.update(self.LMResponse.to_bytes()[:8]) |
191 | | - | |
192 | | - | return hm.digest() |
193 | | - | |
194 | | - | @staticmethod |
195 | | - | def construct(server_challenge, client_challenge, credentials): |
196 | | - | ntlm_creds = netntlm_ess() |
197 | | - | ntlm_creds.credentials = credentials |
198 | | - | ntlm_creds.ServerChallenge = server_challenge |
199 | | - | |
200 | | - | if credentials.password: |
201 | | - | nt_hash = NTOWFv1(credentials.password) |
202 | | - | lm_hash = LMOWFv1(credentials.password) |
203 | | - | else: |
204 | | - | nt_hash = bytes.fromhex(credentials.nt_hash) |
205 | | - | lm_hash = bytes.fromhex(credentials.lm_hash) if credentials.lm_hash else None |
206 | | - | |
207 | | - | |
208 | | - | ntlm_creds.LMResponse = LMResponse() |
209 | | - | ntlm_creds.LMResponse.Response = client_challenge + b'\x00' * 16 |
210 | | - | |
211 | | - | temp_1 = hashlib.md5(server_challenge + client_challenge[:8]).digest() |
212 | | - | data = DESL(nt_hash, temp_1[:8]) |
213 | | - | |
214 | | - | ntlm_creds.NTResponse = NTLMv1Response() |
215 | | - | ntlm_creds.NTResponse.Response = data |
216 | | - | |
217 | | - | ntlm_creds.SessionBaseKey = hashlib.md4(nt_hash).digest() |
218 | | - | |
219 | | - | return ntlm_creds |
220 | | - | |
221 | | - | def to_credential(self): |
222 | | - | cred = Credential( |
223 | | - | 'netNTLMv1-ESS', |
224 | | - | username = self.username, |
225 | | - | fullhash = '%s::%s:%s:%s:%s' % (self.credentials.username, self.credentials.domain, ntlm_creds.LMResponse.Response, ntlm_creds.NTResponse.Response, self.ServerChallenge) |
226 | | - | ) |
227 | | - | return cred |
228 | | - | # u4-netntlm::kNS:338d08f8e26de93300000000000000000000000000000000:9526fb8c23a90751cdd619b6cea564742e1e4bf33006ba41:cb8086049ec4736c |
229 | | - | |
230 | | - | def calc_session_base_key(self, creds, credtype = 'plain'): |
231 | | - | if credtype == 'plain': |
232 | | - | nt_hash = NTOWFv1(creds[self.domain][self.username]) |
233 | | - | elif credtype == 'hash': |
234 | | - | nt_hash = bytes.fromhex(creds[self.domain][self.username]) |
235 | | - | else: |
236 | | - | raise Exception('Unknown cred type!') |
237 | | - | |
238 | | - | session_base_key = hashlib.md4(nt_hash).digest() |
239 | | - | return session_base_key |
240 | | - | |
241 | | - | def verify(self, creds, credtype='plain'): |
242 | | - | """ |
243 | | - | Verifies the authentication data against the user credentials |
244 | | - | :param creds: dictionary containing the domain, user, hash/password |
245 | | - | :param credtype: can be 'plain' or 'hash' this indicates what type of credential lookup to perform |
246 | | - | :return: bool |
247 | | - | """ |
248 | | - | if creds is None: |
249 | | - | return True |
250 | | - | if self.domain not in creds: |
251 | | - | return False |
252 | | - | if self.username not in creds[self.domain]: |
253 | | - | return False |
254 | | - | |
255 | | - | if credtype == 'plain': |
256 | | - | nt_hash = NTOWFv1(creds[self.domain][self.username]) |
257 | | - | elif credtype == 'hash': |
258 | | - | nt_hash = bytes.fromhex(creds[self.domain][self.username]) |
259 | | - | else: |
260 | | - | raise Exception('Unknown cred type!') |
261 | | - | |
262 | | - | # print('Server chall: %s' % self.ServerChallenge) |
263 | | - | # print('Client chall: %s' % self.ChallengeFromClinet) |
264 | | - | |
265 | | - | temp_1 = hashlib.md5(bytes.fromhex(self.ServerChallenge) + bytes.fromhex(self.ChallengeFromClinet)[:8]).digest() |
266 | | - | calc_response = DESL(nt_hash, temp_1[:8]) |
267 | | - | # print('calc_response: %s' % calc_response.hex()) |
268 | | - | # print('ClientResponse: %s' % self.ClientResponse) |
269 | | - | |
270 | | - | return calc_response == bytes.fromhex(self.ClientResponse) |
271 | | - | |
272 | | - | |
273 | | - | class netntlm: |
274 | | - | # not supported by hashcat? |
275 | | - | def __init__(self): |
276 | | - | # this part comes from the NTLMAuthenticate class |
277 | | - | self.credentials = None |
278 | | - | # this comes from the NTLMChallenge class |
279 | | - | self.ServerChallenge = None |
280 | | - | |
281 | | - | self.LMResponse = None |
282 | | - | self.NTResponse = None |
283 | | - | |
284 | | - | |
285 | | - | self.SessionBaseKey = None |
286 | | - | |
287 | | - | def calc_key_exchange_key(self, with_lm = False, non_nt_session_key = False): |
288 | | - | |
289 | | - | if self.credentials.password: |
290 | | - | lm_hash = LMOWFv1(self.credentials.password) |
291 | | - | else: |
292 | | - | lm_hash = self.credentials.lm_hash |
293 | | - | |
294 | | - | if with_lm: |
295 | | - | temp1 = DES(lm_hash[:7]).encrypt(self.LMResponse.to_bytes()[:8]) |
296 | | - | temp2 = DES(lm_hash[7:8] + b'\xBD\xBD\xBD\xBD\xBD\xBD').encrypt(self.LMResponse.to_bytes()[:8]) |
297 | | - | kex = temp1 + temp2 |
298 | | - | |
299 | | - | else: |
300 | | - | if non_nt_session_key: |
301 | | - | kex = lm_hash[:8] + b'\x00' * 8 |
302 | | - | else: |
303 | | - | kex = self.SessionBaseKey |
304 | | - | |
305 | | - | return kex |
306 | | - | |
307 | | - | @staticmethod |
308 | | - | def construct(server_challenge, credentials): |
309 | | - | ntlm_creds = netntlm() |
310 | | - | ntlm_creds.credentials = credentials |
311 | | - | ntlm_creds.ServerChallenge = server_challenge |
312 | | - | |
313 | | - | if credentials.password: |
314 | | - | nt_hash = NTOWFv1(credentials.password) |
315 | | - | lm_hash = LMOWFv1(credentials.password) |
316 | | - | else: |
317 | | - | nt_hash = bytes.fromhex(credentials.nt_hash) |
318 | | - | lm_hash = bytes.fromhex(credentials.lm_hash) if credentials.lm_hash else None |
319 | | - | |
320 | | - | ntlm_creds.NTResponse = NTLMv1Response() |
321 | | - | ntlm_creds.NTResponse.Response = DESL(nt_hash, server_challenge) |
322 | | - | |
323 | | - | if lm_hash: |
324 | | - | ntlm_creds.LMResponse = LMResponse() |
325 | | - | ntlm_creds.LMResponse.Response = DESL(lm_hash, server_challenge) |
326 | | - | else: |
327 | | - | ntlm_creds.LMResponse = ntresponse |
328 | | - | |
329 | | - | ntlm_creds.SessionBaseKey = hashlib.md4(nt_hash).digest() |
330 | | - | |
331 | | - | return ntlm_creds |
332 | | - | |
333 | | - | def to_credential(self): |
334 | | - | cred = Credential('netNTLMv1', |
335 | | - | username = self.username, |
336 | | - | fullhash = '%s:$NETNTLM$%s$%s' % (self.username, self.ServerChallenge, self.NTResponse.Response) |
337 | | - | ) |
338 | | - | return cred |
339 | | - | #username:$NETNTLM$11223333895667788$B2B2220790F40C88BCFF347C652F67A7C4A70D3BEBD70233 |
340 | | - | |
341 | | - | def calc_session_base_key(self, creds, credtype = 'plain'): |
342 | | - | if credtype == 'plain': |
343 | | - | nt_hash = NTOWFv1(creds[self.domain][self.username]) |
344 | | - | elif credtype == 'hash': |
345 | | - | nt_hash = bytes.fromhex(creds[self.domain][self.username]) |
346 | | - | else: |
347 | | - | raise Exception('Unknown cred type!') |
348 | | - | |
349 | | - | session_base_key = hashlib.md4(nt_hash).digest() |
350 | | - | return session_base_key |
351 | | - | |
352 | | - | def verify(self, creds, credtype='plain'): |
353 | | - | """ |
354 | | - | Verifies the authentication data against the user credentials |
355 | | - | :param creds: dictionary containing the domain, user, hash/password |
356 | | - | :param credtype: can be 'plain' or 'hash' this indicates what type of credential lookup to perform |
357 | | - | :return: bool |
358 | | - | """ |
359 | | - | if creds is None: |
360 | | - | return True |
361 | | - | if self.domain not in creds: |
362 | | - | return False |
363 | | - | if self.username not in creds[self.domain]: |
364 | | - | return False |
365 | | - | |
366 | | - | if credtype == 'plain': |
367 | | - | nt_hash = NTOWFv1(creds[self.domain][self.username]) |
368 | | - | elif credtype == 'hash': |
369 | | - | nt_hash = bytes.fromhex(creds[self.domain][self.username]) |
370 | | - | else: |
371 | | - | raise Exception('Unknown cred type!') |
372 | | - | |
373 | | - | return DESL(nt_hash, self.ServerChallenge) == bytes.fromhex(self.ClientResponse) |
374 | | - | |
375 | | - | |
376 | | - | class netntlmv2: |
377 | | - | def __init__(self): |
378 | | - | self.credentials = None |
379 | | - | |
380 | | - | # this comes from the NTLMChallenge class |
381 | | - | self.ServerChallenge = None |
382 | | - | |
383 | | - | # this is from the NTLMv2Response class (that is a member of NTLMAuthenticate class) |
384 | | - | #self.ClientResponse = None |
385 | | - | #self.ChallengeFromClinet = None |
386 | | - | |
387 | | - | self.LMResponse = None |
388 | | - | self.NTResponse = None |
389 | | - | |
390 | | - | |
391 | | - | self.SessionBaseKey = None |
392 | | - | |
393 | | - | # https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/d86303b5-b29e-4fb9-b119-77579c761370 |
394 | | - | def calc_key_exchange_key(self): |
395 | | - | return self.SessionBaseKey |
396 | | - | |
397 | | - | def calc_key_exhange_key_server(self, credentials): |
398 | | - | if not credentials.nt_hash and not credentials.password: |
399 | | - | raise Exception('Password or NT hash must be supplied!') |
400 | | - | |
401 | | - | if credentials.password: |
402 | | - | nt_hash_v2 = NTOWFv2(credentials.password, credentials.username, credentials.domain) |
403 | | - | else: |
404 | | - | nt_hash_v2 = NTOWFv2(None, credentials.username, credentials.domain, bytes.fromhex(credentials.nt_hash)) |
405 | | - | |
406 | | - | response = self.NTResponse.Response |
407 | | - | if isinstance(self.NTResponse.Response, str): |
408 | | - | response = bytes.fromhex(self.NTResponse.Response) |
409 | | - | |
410 | | - | hm = hmac.new(nt_hash_v2, digestmod = 'md5') |
411 | | - | hm.update(response) |
412 | | - | return hm.digest() |
413 | | - | |
414 | | - | @staticmethod |
415 | | - | def construct(server_challenge, client_challenge, server_details, credentials, timestamp = None): |
416 | | - | ntlm_creds = netntlmv2() |
417 | | - | ntlm_creds.credentials = credentials |
418 | | - | ntlm_creds.ServerChallenge = server_challenge |
419 | | - | |
420 | | - | if not credentials.nt_hash and not credentials.password: |
421 | | - | raise Exception('Password or NT hash must be supplied!') |
422 | | - | |
423 | | - | if credentials.password: |
424 | | - | nt_hash_v2 = NTOWFv2(credentials.password, credentials.username, credentials.domain) |
425 | | - | else: |
426 | | - | nt_hash_v2 = NTOWFv2(None, credentials.username, credentials.domain, bytes.fromhex(credentials.nt_hash)) |
427 | | - | |
428 | | - | if not timestamp: |
429 | | - | timestamp = datetime.datetime.utcnow() |
430 | | - | |
431 | | - | cc = NTLMv2ClientChallenge.construct(timestamp, client_challenge, server_details) |
432 | | - | temp = cc.to_bytes() |
433 | | - | |
434 | | - | hm = hmac.new(nt_hash_v2, digestmod = 'md5') |
435 | | - | hm.update(server_challenge) |
436 | | - | hm.update(temp) |
437 | | - | |
438 | | - | NTProofStr = hm.digest() |
439 | | - | |
440 | | - | ntlm_creds.NTResponse = NTLMv2Response() |
441 | | - | ntlm_creds.NTResponse.Response = NTProofStr |
442 | | - | ntlm_creds.NTResponse.ChallengeFromClinet = cc |
443 | | - | |
444 | | - | |
445 | | - | hm = hmac.new(nt_hash_v2, digestmod = 'md5') |
446 | | - | hm.update(server_challenge) |
447 | | - | hm.update(client_challenge) |
448 | | - | |
449 | | - | ntlm_creds.LMResponse = LMv2Response() |
450 | | - | ntlm_creds.LMResponse.Response = hm.digest() |
451 | | - | ntlm_creds.LMResponse.ChallengeFromClinet = client_challenge |
452 | | - | |
453 | | - | |
454 | | - | hm = hmac.new(nt_hash_v2, digestmod = 'md5') |
455 | | - | hm.update(NTProofStr) |
456 | | - | ntlm_creds.SessionBaseKey = hm.digest() |
457 | | - | |
458 | | - | return ntlm_creds |
459 | | - | |
460 | | - | def to_credential(self): |
461 | | - | cred = Credential( |
462 | | - | 'netNTLMv2', |
463 | | - | username = self.username, |
464 | | - | domain = self.domain, |
465 | | - | fullhash = '%s::%s:%s:%s:%s' % (self.credentials.username, self.credentials.domain, self.ServerChallenge, self.NTResponse.Response, self.NTResponse.ChallengeFromClinet) |
466 | | - | ) |
467 | | - | return cred |
468 | | - | |
469 | | - | def verify(self, creds, credtype = 'plain'): |
470 | | - | """ |
471 | | - | Verifies the authentication data against the user credentials |
472 | | - | :param creds: dictionary containing the domain, user, hash/password |
473 | | - | :param credtype: can be 'plain' or 'hash' this indicates what type of credential lookup to perform |
474 | | - | :return: bool |
475 | | - | """ |
476 | | - | |
477 | | - | # print('Creds: %s' % creds) |
478 | | - | if creds is None: |
479 | | - | return True |
480 | | - | |
481 | | - | if self.domain not in creds: |
482 | | - | return False |
483 | | - | if self.username not in creds[self.domain]: |
484 | | - | return False |
485 | | - | |
486 | | - | if credtype == 'plain': |
487 | | - | nt_hash = NTOWFv2(creds[self.domain][self.username], self.username, self.domain) |
488 | | - | elif credtype == 'hash': |
489 | | - | nt_hash = NTOWFv2(None, self.username, self.domain, bytes.fromhex(creds[self.domain][self.username])) |
490 | | - | else: |
491 | | - | raise Exception('Unknown cred type!') |
492 | | - | |
493 | | - | # print(self.ServerChallenge) |
494 | | - | # print(self.ChallengeFromClinet) |
495 | | - | |
496 | | - | hm = hmac.new(nt_hash, digestmod = 'md5') |
497 | | - | hm.update(bytes.fromhex(self.ServerChallenge)) |
498 | | - | hm.update(bytes.fromhex(self.ChallengeFromClinet)) |
499 | | - | |
500 | | - | # print('M_nthash: %s' % nthash.hex()) |
501 | | - | # print('M_temp: %s' % self.ChallengeFromClinet) |
502 | | - | # print('M_nthash: %s' % nthash.hex()) |
503 | | - | # print('M_server_chall: %s' % self.ServerChallenge) |
504 | | - | # print('M_ntproof_string: %s' % self.ClientResponse) |
505 | | - | # print('M_ntproof_string_calc: %s' % hm.hexdigest()) |
506 | | - | |
507 | | - | return self.ClientResponse == hm.hexdigest() |
508 | | - | |
509 | | - | |
510 | | - | def LMOWFv1(password): |
511 | | - | LM_SECRET = b'KGS!@#$%' |
512 | | - | t1 = password[:14].ljust(14, '\x00').upper() |
513 | | - | d = DES(t1[:7].encode('ascii')) |
514 | | - | r1 = d.encrypt(LM_SECRET) |
515 | | - | d = DES(t1[7:].encode('ascii')) |
516 | | - | r2 = d.encrypt(LM_SECRET) |
517 | | - | |
518 | | - | return r1+r2 |
519 | | - | |
520 | | - | |
521 | | - | def NTOWFv1(password): |
522 | | - | return hashlib.md4(password.encode('utf-16le')).digest() |
523 | | - | |
524 | | - | |
525 | | - | def LMOWFv2(Passwd, User, UserDom, PasswdHash = None): |
526 | | - | return NTOWFv2(Passwd, User, UserDom, PasswdHash) |
527 | | - | |
528 | | - | |
529 | | - | def NTOWFv2(Passwd, User, UserDom, PasswdHash = None): |
530 | | - | if PasswdHash is not None: |
531 | | - | fp = hmac.new(PasswdHash, digestmod = 'md5') |
532 | | - | else: |
533 | | - | fp = hmac.new(NTOWFv1(Passwd), digestmod = 'md5') |
534 | | - | fp.update((User.upper() + UserDom).encode('utf-16le')) |
535 | | - | return fp.digest() |
536 | | - | |
537 | | - | |
538 | | - | def DESL(K, D): |
539 | | - | """ |
540 | | - | Indicates the encryption of an 8-byte data item D with the 16-byte key K |
541 | | - | using the Data Encryption Standard Long (DESL) algorithm. |
542 | | - | The result is 24 bytes in length. |
543 | | - | :param K: |
544 | | - | :param D: |
545 | | - | :return: |
546 | | - | """ |
547 | | - | if len(K) != 16: |
548 | | - | raise Exception("K MUST be 16 bytes long") |
549 | | - | if len(D) != 8: |
550 | | - | raise Exception("D MUST be 8 bytes long") |
551 | | - | |
552 | | - | res = b'' |
553 | | - | res += DES(K[:7]).encrypt(D) |
554 | | - | res += DES(K[7:14]).encrypt(D) |
555 | | - | res += DES(K[14:] + b'\x00'*5).encrypt(D) |
556 | | - | return res |
557 | | - | |