1+ #!/usr/bin/env python3
2+
3+ import hashlib
4+ import itertools
5+ import multiprocessing
6+ import os
7+ import string
8+ import threading
9+ import time
10+
11+
12+ class Cracker (object ):
13+ ALPHA_LOWER = (string .ascii_lowercase ,)
14+ ALPHA_UPPER = (string .ascii_uppercase ,)
15+ ALPHA_MIXED = (string .ascii_lowercase , string .ascii_uppercase )
16+ PUNCTUATION = (string .punctuation ,)
17+ NUMERIC = ('' .join (map (str , range (0 , 10 ))),)
18+ ALPHA_LOWER_NUMERIC = (string .ascii_lowercase , '' .join (map (str , range (0 , 10 ))))
19+ ALPHA_UPPER_NUMERIC = (string .ascii_uppercase , '' .join (map (str , range (0 , 10 ))))
20+ ALPHA_MIXED_NUMERIC = (string .ascii_lowercase , string .ascii_uppercase , '' .join (map (str , range (0 , 10 ))))
21+ ALPHA_LOWER_PUNCTUATION = (string .ascii_lowercase , string .punctuation )
22+ ALPHA_UPPER_PUNCTUATION = (string .ascii_uppercase , string .punctuation )
23+ ALPHA_MIXED_PUNCTUATION = (string .ascii_lowercase , string .ascii_uppercase , string .punctuation )
24+ NUMERIC_PUNCTUATION = ('' .join (map (str , range (0 , 10 ))), string .punctuation )
25+ ALPHA_LOWER_NUMERIC_PUNCTUATION = (string .ascii_lowercase , '' .join (map (str , range (0 , 10 ))), string .punctuation )
26+ ALPHA_UPPER_NUMERIC_PUNCTUATION = (string .ascii_uppercase , '' .join (map (str , range (0 , 10 ))), string .punctuation )
27+ ALPHA_MIXED_NUMERIC_PUNCTUATION = (
28+ string .ascii_lowercase , string .ascii_uppercase , '' .join (map (str , range (0 , 10 ))), string .punctuation
29+ )
30+
31+ def __init__ (self , hash_type , hash , charset , progress_interval ):
32+ """
33+ Sets the hash type and actual hash to be used
34+ :param hash_type: What algorithm we want to use
35+ :param hash: The hash in base64 format
36+ :return:
37+ """
38+ self .__charset = charset
39+ self .__curr_iter = 0
40+ self .__prev_iter = 0
41+ self .__curr_val = ""
42+ self .__progress_interval = progress_interval
43+ self .__hash_type = hash_type
44+ self .__hash = hash
45+ self .__hashers = {}
46+
47+ def __init_hasher (self ):
48+ hashlib_type = self .__hash_type if self .__hash_type != "ntlm" else "md4"
49+ self .__hashers [self .__hash_type ] = hashlib .new (hashlib_type )
50+
51+ def __encode_utf8 (self , data ):
52+ return data .encode ("utf-8" )
53+
54+ def __encode_utf16le (self , data ):
55+ return data .encode ("utf-16le" )
56+
57+ @staticmethod
58+ def __search_space (charset , maxlength ):
59+ """
60+ Generates the search space for us to attack using a generator
61+ We could never pregenerate this as it would take too much time and require godly amounts of memory
62+ For example, generating a search space with a rough size of 52^8 would take over 50TB of RAM
63+ :param charset: The character set to generate a search space for
64+ :param maxlength: Maximum length the search space should be capped at
65+ :return:
66+ """
67+ return (
68+ '' .join (candidate ) for candidate in
69+ itertools .chain .from_iterable (
70+ itertools .product (charset , repeat = i ) for i in
71+ range (1 , maxlength + 1 )
72+ )
73+ )
74+
75+ def __attack (self , q , max_length ):
76+ """
77+ Tries all possible combinations in the search space to try and find a match.
78+ This is an extremely tight loop so we need to inline and reduce work as much as we can in here.
79+ :param q: Work queue
80+ :param max_length: Maximum length of the character set to attack
81+ :return:
82+ """
83+ self .__init_hasher ()
84+ self .start_reporting_progress ()
85+ hash_fn = self .__encode_utf8 if self .__hash_type != "ntlm" else self .__encode_utf16le
86+ for value in self .__search_space (self .__charset , max_length ):
87+ hasher = self .__hashers [self .__hash_type ].copy ()
88+ self .__curr_iter += 1
89+ self .__curr_val = value
90+ hasher .update (hash_fn (value ))
91+ if self .__hash == hasher .hexdigest ():
92+ q .put ("FOUND" )
93+ q .put ("{}Match found! Password is {}{}" .format (os .linesep , value , os .linesep ))
94+ self .stop_reporting_progress ()
95+ return
96+
97+ q .put ("NOT FOUND" )
98+ self .stop_reporting_progress ()
99+
100+ @staticmethod
101+ def work (work_q , done_q , max_length ):
102+ """
103+ Take the data given to us from some process and kick off the work
104+ :param work_q: This is what will give us work from some other process
105+ :param done_q: Used to signal the parent from some other process when we are done
106+ :param max_length: Maximum length of the character set
107+ :return:
108+ """
109+ obj = work_q .get ()
110+ obj .__attack (done_q , max_length )
111+
112+ def start_reporting_progress (self ):
113+ self .__progress_timer = threading .Timer (self .__progress_interval , self .start_reporting_progress )
114+ self .__progress_timer .start ()
115+ print (
116+ f"Character set: { self .__charset } , iteration: { self .__curr_iter } , trying: { self .__curr_val } , hashes/sec: { self .__curr_iter - self .__prev_iter } " ,
117+ flush = True )
118+ self .__prev_iter = self .__curr_iter
119+
120+ def stop_reporting_progress (self ):
121+ self .__progress_timer .cancel ()
122+ print (f"Finished character set { self .__charset } after { self .__curr_iter } iterations" , flush = True )
123+
124+
125+ if __name__ == "__main__" :
126+ character_sets = {
127+ "01" : Cracker .ALPHA_LOWER ,
128+ "02" : Cracker .ALPHA_UPPER ,
129+ "03" : Cracker .ALPHA_MIXED ,
130+ "04" : Cracker .NUMERIC ,
131+ "05" : Cracker .ALPHA_LOWER_NUMERIC ,
132+ "06" : Cracker .ALPHA_UPPER_NUMERIC ,
133+ "07" : Cracker .ALPHA_MIXED_NUMERIC ,
134+ "08" : Cracker .PUNCTUATION ,
135+ "09" : Cracker .ALPHA_LOWER_PUNCTUATION ,
136+ "10" : Cracker .ALPHA_UPPER_PUNCTUATION ,
137+ "11" : Cracker .ALPHA_MIXED_PUNCTUATION ,
138+ "12" : Cracker .NUMERIC_PUNCTUATION ,
139+ "13" : Cracker .ALPHA_LOWER_NUMERIC_PUNCTUATION ,
140+ "14" : Cracker .ALPHA_UPPER_NUMERIC_PUNCTUATION ,
141+ "15" : Cracker .ALPHA_MIXED_NUMERIC_PUNCTUATION
142+ }
143+
144+ hashes = {
145+ "01" : "MD5" ,
146+ "02" : "MD4" ,
147+ "03" : "LM" ,
148+ "04" : "NTLM" ,
149+ "05" : "SHA1" ,
150+ "06" : "SHA224" ,
151+ "07" : "SHA256" ,
152+ "08" : "SHA384" ,
153+ "09" : "SHA512"
154+ }
155+
156+ prompt = "Specify the character set to use:{}{}" .format (os .linesep , os .linesep )
157+ for key , value in sorted (character_sets .items ()):
158+ prompt += "{}. {}{}" .format (key , '' .join (value ), os .linesep )
159+
160+ while True :
161+ try :
162+ charset = input (prompt ).zfill (2 )
163+ selected_charset = character_sets [charset ]
164+ except KeyError :
165+ print ("{}Please select a valid character set{}" .format (os .linesep , os .linesep ))
166+ continue
167+ else :
168+ break
169+
170+ prompt = "{}Specify the maximum possible length of the password: " .format (os .linesep )
171+
172+ while True :
173+ try :
174+ password_length = int (input (prompt ))
175+ except ValueError :
176+ print ("{}Password length must be an integer" .format (os .linesep ))
177+ continue
178+ else :
179+ break
180+
181+ prompt = "{}Specify the hash's type:{}" .format (os .linesep , os .linesep )
182+ for key , value in sorted (hashes .items ()):
183+ prompt += "{}. {}{}" .format (key , value , os .linesep )
184+
185+ while True :
186+ try :
187+ hash_type = hashes [input (prompt ).zfill (2 )]
188+ except KeyError :
189+ print ("{}Please select a supported hash type" .format (os .linesep ))
190+ continue
191+ else :
192+ break
193+
194+ prompt = "{}Specify the hash to be attacked: " .format (os .linesep )
195+
196+ while True :
197+ try :
198+ user_hash = input (prompt )
199+ except ValueError :
200+ print ("{}Something is wrong with the format of the hash. Please enter a valid hash" .format (os .linesep ))
201+ continue
202+ else :
203+ break
204+
205+ print (f"Trying to crack hash { user_hash } " , flush = True )
206+ processes = []
207+ work_queue = multiprocessing .Queue ()
208+ done_queue = multiprocessing .Queue ()
209+ progress_interval = 3
210+ cracker = Cracker (hash_type .lower (), user_hash .lower (), '' .join (selected_charset ), progress_interval )
211+ start_time = time .time ()
212+ p = multiprocessing .Process (target = Cracker .work ,
213+ args = (work_queue , done_queue , password_length ))
214+ processes .append (p )
215+ work_queue .put (cracker )
216+ p .start ()
217+
218+ if len (selected_charset ) > 1 :
219+ for i in range (len (selected_charset )):
220+ progress_interval += .2
221+ cracker = Cracker (hash_type .lower (), user_hash .lower (), selected_charset [i ], progress_interval )
222+ p = multiprocessing .Process (target = Cracker .work ,
223+ args = (work_queue , done_queue , password_length ))
224+ processes .append (p )
225+ work_queue .put (cracker )
226+ p .start ()
227+
228+ failures = 0
229+ while True :
230+ data = done_queue .get ()
231+ if data == "NOT FOUND" :
232+ failures += 1
233+ elif data == "FOUND" :
234+ print (done_queue .get ())
235+ for p in processes :
236+ p .terminate ()
237+
238+ break
239+
240+ if failures == len (processes ):
241+ print ("{}No matches found{}" .format (os .linesep , os .linesep ))
242+ break
243+
244+ print ("Took {} seconds" .format (time .time () - start_time ))
0 commit comments