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