gentle_brute 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,6 @@
1
+ .komodotools
2
+ *.komodoproject
3
+ old/
4
+ *.txt
5
+ *.gem
6
+ Gemfile.lock
data/Gemfile ADDED
@@ -0,0 +1 @@
1
+ gemspec
data/bin/GentleBrute ADDED
@@ -0,0 +1,297 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'digest/md5'
4
+ require 'slop'
5
+ require 'gentle_brute'
6
+ require 'curses'
7
+ include Curses
8
+
9
+ def pretty_time_string time
10
+ hours = (time/3600).to_i
11
+ minutes = (time/60 - hours * 60).to_i
12
+ seconds = (time - (minutes * 60 + hours * 3600))
13
+ "%02d:%02d:%02d" % [hours, minutes, seconds]
14
+ end
15
+
16
+ def crack_md5_list file_path
17
+ if not File.exists? file_path
18
+ puts "[!] ERROR: '#{file_path}' does not exist."
19
+ return
20
+ end
21
+
22
+ target_hashes = File.read(file_path).split("\n")
23
+ unbroken_hashes = target_hashes.dup
24
+ cracked_hashes = {}
25
+ b = GentleBrute::BruteForcer.new
26
+ start_time = Time.now.to_f
27
+
28
+ Curses.init_screen()
29
+ Curses.noecho()
30
+ Curses.stdscr.nodelay = 1
31
+ Curses.curs_set(1)
32
+
33
+ Curses.start_color
34
+ # Determines the colors in the 'attron' below
35
+ Curses.init_pair(COLOR_GREEN,COLOR_GREEN,COLOR_BLACK)
36
+ Curses.init_pair(COLOR_RED,COLOR_RED,COLOR_BLACK)
37
+
38
+ win = Curses::Window.new(0, 0, 0, 0)
39
+ win.setpos(1, 0)
40
+ win.addstr("== Attempting to crack target hashes using heuristic brute forcing ==".center(Curses.cols))
41
+ win.setpos(2, 0)
42
+ win.addstr("(Press 'q' to quit at any time)".center(Curses.cols))
43
+ win.setpos(4, 0)
44
+ header = "Phrase | MD5 Hash (Phrase) | MD5 Hash (Target)"
45
+ divider = ("-" * 77) + " "
46
+ while header.length < divider.length
47
+ header += " "
48
+ end
49
+ win.addstr(header.rjust(Curses.cols))
50
+ win.setpos(5, 0)
51
+ win.addstr(divider.rjust(Curses.cols))
52
+
53
+ while unbroken_hashes.length > 0
54
+ break if Curses.getch == ?q
55
+ phrase = b.next_valid_phrase
56
+ attempt_hash = Digest::MD5.hexdigest(phrase)
57
+ output_phrase = phrase
58
+
59
+ row = 6
60
+ target_hashes.each do | target_hash |
61
+ if cracked_hashes.key? target_hash
62
+ new_phrase = cracked_hashes[target_hash]
63
+ new_hash = target_hash
64
+ line = "#{new_phrase} | #{new_hash} | #{target_hash} "
65
+ offset = (line.rjust(Curses.cols).length) - line.length
66
+ win.setpos(row, offset)
67
+ win.attron(color_pair(COLOR_GREEN)|A_NORMAL){
68
+ win.addstr(new_phrase)
69
+ }
70
+ win.addstr(" | ")
71
+ win.attron(color_pair(COLOR_GREEN)|A_NORMAL){
72
+ win.addstr(new_hash)
73
+ }
74
+ win.addstr(" | ")
75
+ win.attron(color_pair(COLOR_GREEN)|A_NORMAL){
76
+ win.addstr(target_hash)
77
+ }
78
+ else
79
+ line = "#{output_phrase} | #{attempt_hash} | #{target_hash} "
80
+ win.setpos(row, 0)
81
+ win.addstr(line.rjust(Curses.cols))
82
+ end
83
+
84
+ if attempt_hash == target_hash
85
+ unbroken_hashes.delete target_hash
86
+ cracked_hashes[target_hash] = output_phrase
87
+ end
88
+
89
+ row += 1
90
+ end
91
+
92
+ # Add time elapsed information
93
+ win.setpos(row, 0)
94
+ win.addstr(divider.rjust(Curses.cols))
95
+ row += 1
96
+ time_elapsed = Time.now.to_f - start_time
97
+ time_elapsed_string = "Time Elapsed: #{pretty_time_string time_elapsed} "
98
+ win.setpos(row, 0)
99
+ win.addstr(time_elapsed_string.rjust(Curses.cols))
100
+ row += 1
101
+ win.setpos(row, 0)
102
+ win.addstr(divider.rjust(Curses.cols))
103
+
104
+ win.refresh
105
+ end
106
+ win.refresh
107
+ win.close
108
+
109
+ puts
110
+ puts "[+] Password cracking finished"
111
+ if cracked_hashes.keys.length > 0
112
+ formatted_date = Time.now.strftime("%m_%d_%Y_%H_%M")
113
+ file_name = "passwords-#{formatted_date}.txt"
114
+ results = []
115
+ cracked_hashes.keys.each do | cracked_hash |
116
+ result = "#{cracked_hashes[cracked_hash]}\t#{cracked_hash}"
117
+ results << result
118
+ end
119
+ File.open(file_name, "w") { |f| f.write results.join "\n" }
120
+ puts "[+] Saved results to #{file_name}"
121
+ end
122
+ end
123
+
124
+ Slop.parse :help => true do | opts |
125
+ opts.banner "GentleBrute [options]"
126
+
127
+ opts.on "word-list=", "Generate a word list of valid English-like words and phrases for a given length" do | length |
128
+ length = length.to_i
129
+ start_time = Time.now.to_f
130
+
131
+ puts "[+] Generating valid words with a length #{length} characters."
132
+ words = []
133
+ b = GentleBrute::BruteForcer.new(start_length=length)
134
+ print "\r[+] Words generated: #{words.length}"
135
+ while true
136
+ phrase = b.next_valid_phrase
137
+ break if phrase.length > length
138
+ words << phrase
139
+ print "\r[+] Words generated: #{words.length}"
140
+ end
141
+ puts ", Finished!"
142
+ puts "-" * 60
143
+
144
+ puts "[+] Generated #{words.length} potential passphrases"
145
+ chars = ('a'..'z').to_a + ["'"]
146
+ potential_combinations = chars.length ** length
147
+ puts "[+] There are #{potential_combinations} total potential combinations for the length you provided"
148
+ percent = 100 - ((words.length * 100)/potential_combinations)
149
+ puts "[+] Gentle-Brute reduced the number of hashes you would need to try by #{percent}%"
150
+ puts "-" * 50
151
+
152
+ puts "[+] Saved generated words to the file, words-#{length}.txt"
153
+ File.open("words-#{length}.txt", "w") { |f| f.write words.join "\n" }
154
+
155
+ puts "[+] Total time spent: #{Time.now.to_f-start_time}"
156
+ end
157
+
158
+ opts.on "cross-compare-crack=", "Cross compare brute force cracking times for a given md5 hash between GentleBrute, and regular brute forcing." do | target_hash |
159
+ start_length = 1
160
+ puts "-" * 75
161
+ puts "[+] Attempting to crack target hash using heuristic brute forcing."
162
+ start_time = Time.now.to_f
163
+ b = GentleBrute::BruteForcer.new(start_length)
164
+
165
+ puts
166
+ puts " Phrase | MD5 Hash (Phrase) | MD5 Hash (Target)"
167
+ puts " " + ("-" * 75)
168
+ while true
169
+ phrase = b.next_valid_phrase
170
+ attempt_hash = Digest::MD5.hexdigest(phrase)
171
+ output_phrase = phrase
172
+ while output_phrase.length < 8
173
+ output_phrase = " " + output_phrase
174
+ end
175
+ print "\r#{output_phrase} | #{attempt_hash} | #{target_hash}"
176
+ break if attempt_hash == target_hash
177
+ end
178
+ time_difference = Time.now.to_f - start_time
179
+ puts
180
+ puts
181
+ puts "[+] Crack Succeeded in #{pretty_time_string time_difference}"
182
+ puts "-" * 75
183
+ puts
184
+
185
+ puts "-" * 75
186
+ puts "[+] Attempting to crack target hash using standard brute forcing"
187
+ start_time = Time.now.to_f
188
+ odometer = GentleBrute::Odometer.new(start_length, heuristic=false)
189
+ puts
190
+ puts " Phrase | MD5 Hash (Phrase) | MD5 Hash (Target)"
191
+ puts " " + ("-" * 75)
192
+ while true
193
+ odometer.increment
194
+ phrase = odometer.string_for_odometer
195
+ attempt_hash = Digest::MD5.hexdigest(phrase)
196
+ output_phrase = phrase
197
+ while output_phrase.length < 8
198
+ output_phrase = " " + output_phrase
199
+ end
200
+ print "\r#{output_phrase} | #{attempt_hash} | #{target_hash}"
201
+ break if attempt_hash == target_hash
202
+ end
203
+ time_difference1 = Time.now.to_f - start_time
204
+ puts
205
+ puts
206
+ puts "[+] Crack Succeeded in #{pretty_time_string time_difference1}"
207
+ puts "-" * 75
208
+ puts
209
+
210
+ puts "-" * 75
211
+ puts "[+] GentleBrute was #{pretty_time_string(time_difference1-time_difference)} faster"
212
+ percent = 100 - ((time_difference*100)/time_difference1)
213
+ puts "[+] That's a speed improvement of %d%% compared to standard brute forcing!" % percent
214
+ puts "-" * 75
215
+ end
216
+
217
+ opts.on "validate=", "Test whether a given word or phrase is considered valid" do | word |
218
+ cpa_threshold = 24
219
+ word_analyzer = GentleBrute::WordAnalyzer.new cpa_threshold
220
+ if word_analyzer.is_valid_word? word.dup
221
+ puts "[+] \"#{word}\" is a valid English-like word or phrase."
222
+ else
223
+ puts "[+] \"#{word}\" is NOT a valid English-like word or phrase."
224
+ end
225
+ end
226
+
227
+ opts.on "crack-md5=", "Crack a single MD5 password hash" do | target_hash |
228
+ start_length = 1
229
+ puts "-" * 75
230
+ puts "[+] Attempting to crack target hash using heuristic brute forcing."
231
+ start_time = Time.now.to_f
232
+ b = GentleBrute::BruteForcer.new(start_length)
233
+
234
+ puts
235
+ puts " Phrase | MD5 Hash (Phrase) | MD5 Hash (Target)"
236
+ puts " " + ("-" * 75)
237
+ while true
238
+ phrase = b.next_valid_phrase
239
+ attempt_hash = Digest::MD5.hexdigest(phrase)
240
+ output_phrase = phrase
241
+ while output_phrase.length < 8
242
+ output_phrase = " " + output_phrase
243
+ end
244
+ print "\r#{output_phrase} | #{attempt_hash} | #{target_hash}"
245
+ break if attempt_hash == target_hash
246
+ end
247
+ time_difference = Time.now.to_f - start_time
248
+ puts
249
+ puts
250
+ puts "[+] Crack Succeeded in #{pretty_time_string time_difference}"
251
+ puts "-" * 75
252
+ puts
253
+ end
254
+
255
+ opts.on "crack-md5-list=", "Crack a series of MD5 password hashes in a given file." do | file_path |
256
+ crack_md5_list file_path
257
+ end
258
+
259
+ opts.on "rainbow-table", "Build MD5 hash rainbow table." do
260
+ start_length = 1
261
+ puts "-" * 75
262
+ puts "[+] Creating rainbow table using heuristic brute force phrase creation."
263
+ puts "[+] Saving results to 'rainbow_table.txt'"
264
+ puts "[+] Press Ctrl-C to stop at any time."
265
+ start_time = Time.now.to_f
266
+ b = GentleBrute::BruteForcer.new(start_length)
267
+
268
+ table_file = File.open("rainbow_table.txt", "w")
269
+
270
+ puts
271
+ puts " Phrase | MD5 Hash (Phrase) | Time Elapsed"
272
+ puts " " + ("-" * 75)
273
+ begin
274
+ while true
275
+ phrase = b.next_valid_phrase
276
+ attempt_hash = Digest::MD5.hexdigest(phrase)
277
+ output_phrase = phrase
278
+ while output_phrase.length < 8
279
+ output_phrase = " " + output_phrase
280
+ end
281
+ time_elapsed = Time.now.to_f - start_time
282
+ print "\r#{output_phrase} | #{attempt_hash} | #{pretty_time_string time_elapsed}"
283
+ table_file.write("#{phrase}\t#{attempt_hash}\n")
284
+ end
285
+ rescue Interrupt
286
+ puts
287
+ puts
288
+ puts "[+] Table generating stopped."
289
+ end
290
+
291
+ table_file.close()
292
+ end
293
+
294
+ opts.on_empty do
295
+ puts opts.help
296
+ end
297
+ end
@@ -0,0 +1,12 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'gentle_brute'
3
+ s.version = '0.0.1'
4
+ s.summary = 'The better brute force algorithm'
5
+ s.description = 'A heuristic brute forcing algorithm for Ruby that generates brute force passphrases that adhere to the rules of English-like words and phrases.'
6
+ s.authors = ["Brandon Smith"]
7
+ s.email = 'brandon.smith@studiobebop.net'
8
+ s.files = `git ls-files`.split("\n")
9
+ s.homepage = 'http://github.com/jamespenguin/gentle-brute'
10
+ s.executables << 'GentleBrute'
11
+ s.requirements << 'slop >= 2.3.1'
12
+ end
@@ -0,0 +1,70 @@
1
+ module GentleBrute
2
+ class CPAAnalyzer
3
+ NEIGHBORS_PATH = File.expand_path('../resources/neighbors.json', __FILE__)
4
+
5
+ def initialize
6
+ build_cpa_tables if not File.exists? NEIGHBORS_PATH
7
+ lattices = JSON.parse File.read(NEIGHBORS_PATH)
8
+ @starters = lattices["starters"]
9
+ @neighbors = lattices["neighbors"]
10
+ @enders = lattices["enders"]
11
+ end
12
+
13
+ # Analyze a dictionary word list ('lib/gentle_brute/resources/dictionary') for repetition patterns
14
+ # @param [String] dictionary_path path to dictionary word list file
15
+ def build_cpa_tables(dictionary_path=File.expand_path("../resources/dictionary", __FILE__))
16
+ # Create empty pattern occurce lattices
17
+ lattice = Hash.new { |h, k| h[k] = {} }
18
+ starter_lattice = Hash.new { |h, k| h[k] = {} }
19
+ ender_lattice = Hash.new { |h, k| h[k] = {} }
20
+ chars = ('a'..'z').to_a
21
+ chars.each do | char |
22
+ chars.each do | char2 |
23
+ lattice[char][char2] = [0, 0]
24
+ starter_lattice[char][char2] = [0, 0]
25
+ ender_lattice[char][char2] = [0, 0]
26
+ end
27
+ end
28
+
29
+ # Analyze each wrod in dictionary list
30
+ words = File.read(dictionary_path)
31
+ words.each_line do | word |
32
+ word.chomp!
33
+ word.downcase!
34
+ for i in 0..word.length
35
+ char = word[i] # current char
36
+ char_r = word[i+1] # char one index to the right of the current char
37
+ char_rr = word[i+2] # char two indicies to the right of the current char
38
+ begin
39
+ starter_lattice[char][char_r][0] += 1 if char_r and i == 0
40
+ starter_lattice[char][char_rr][1] += 1 if char_rr and i == 0
41
+ ender_lattice[char][char_r][0] += 1 if char_r and i == word.length-3
42
+ ender_lattice[char][char_rr][1] += 1 if char_rr and i == word.length-3
43
+ lattice[char][char_r][0] += 1 if char_r
44
+ lattice[char][char_rr][1] += 1 if char_rr
45
+ rescue
46
+ break
47
+ end
48
+ end
49
+ end
50
+
51
+ # Write neighbors file
52
+ output_lattice = {"starters" => starter_lattice,
53
+ "neighbors" => lattice,
54
+ "enders" => ender_lattice}
55
+ File.open(NEIGHBORS_PATH, "w") {|f| f.write output_lattice.to_json }
56
+ end
57
+
58
+ def get_starter_neighbor_score(char, char2)
59
+ @starters[char][char2]
60
+ end
61
+
62
+ def get_neighbor_score(char, char2)
63
+ @neighbors[char][char2]
64
+ end
65
+
66
+ def get_ender_neighbor_score(char, char2)
67
+ @enders[char][char2]
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,167 @@
1
+ module GentleBrute
2
+ class Odometer
3
+ def initialize(start_length=1, heuristic=true)
4
+ @letters = ('a'..'z').to_a
5
+ @chars = @letters + [" ", "'"]
6
+ @odometer = Array.new(start_length, 0)
7
+ @heuristic = heuristic
8
+ @cpa_analyzer = CPAAnalyzer.new
9
+ end
10
+
11
+ def has_triple_char_pattern?
12
+ return false if not @heuristic
13
+ indexes = []
14
+ odometer_length = @odometer.length
15
+ (odometer_length-1).downto 0 do | i |
16
+ index = @odometer[i]
17
+ if indexes.length > 0
18
+ char1 = @chars[index] # figure out what char the current index is
19
+ char2 = @chars[@odometer[indexes[0]]] # get the char of our first element in the indexes list
20
+ indexes = [] if char1 != char2
21
+ end
22
+ indexes << i
23
+ break if indexes.length == 3
24
+ end
25
+ return false if indexes.length < 3
26
+ true
27
+ end
28
+
29
+ #def break_up_triplet_patterns
30
+ # return if not @heuristic
31
+ #
32
+ # word = string_for_odometer
33
+ # pattern_data = PatternFinder.patterns_in_strintg word
34
+ # return if pattern_data == nil
35
+ # return if pattern_data[3] < 3
36
+ #
37
+ # index = pattern_data[4][1]
38
+ # index.downto 0 do | i |
39
+ # element = @odometer[i]
40
+ # element += 1
41
+ # if element != @chars.length
42
+ # @odometer[i] = element
43
+ # break
44
+ # end
45
+ # @odometer[i] = 0
46
+ # @odometer << 0 if i == 0
47
+ # end
48
+ #
49
+ # break_up_triplet_patterns
50
+ #end
51
+
52
+ def rotate_out_bad_start_pairs
53
+ return if not @heuristic
54
+ return if @odometer.length < 3
55
+
56
+ char1 = @chars[@odometer[0]]
57
+ char2 = @chars[@odometer[1]]
58
+ char3 = @chars[@odometer[2]]
59
+ return if not @letters.include? char1
60
+ return if not @letters.include? char2
61
+ return if not @letters.include? char3
62
+
63
+ if @cpa_analyzer.get_starter_neighbor_score(char1, char3)[1] == 0
64
+ 2.downto 0 do | i |
65
+ element = @odometer[i]
66
+ element += 1
67
+ if element != @chars.length
68
+ @odometer[i] = element
69
+ break
70
+ end
71
+ @odometer[i] = 0
72
+ @odometer << 0 if i == 0
73
+ end
74
+ rotate_out_bad_start_pairs
75
+ end
76
+
77
+ if @cpa_analyzer.get_starter_neighbor_score(char1, char2)[0] == 0
78
+ 1.downto 0 do | i |
79
+ element = @odometer[i]
80
+ element += 1
81
+ if element != @chars.length
82
+ @odometer[i] = element
83
+ break
84
+ end
85
+ @odometer[i] = 0
86
+ @odometer << 0 if i == 0
87
+ end
88
+ rotate_out_bad_start_pairs
89
+ end
90
+ end
91
+
92
+ def rotate_out_bad_end_pairs
93
+ return if not @heuristic
94
+
95
+ return if @odometer.length < 3
96
+
97
+ char1 = @chars[@odometer[-1]]
98
+ char2 = @chars[@odometer[-2]]
99
+ char3 = @chars[@odometer[-3]]
100
+ return if not @letters.include? char1
101
+ return if not @letters.include? char2
102
+ return if not @letters.include? char3
103
+
104
+ last = @odometer.length-1
105
+
106
+ if @cpa_analyzer.get_ender_neighbor_score(char1, char3)[1] == 0
107
+ last.downto 0 do | i |
108
+ element = @odometer[i]
109
+ element += 1
110
+ if element != @chars.length
111
+ @odometer[i] = element
112
+ break
113
+ end
114
+ @odometer[i] = 0
115
+ @odometer << 0 if i == 0
116
+ end
117
+ rotate_out_bad_end_pairs
118
+ end
119
+
120
+ if @cpa_analyzer.get_ender_neighbor_score(char1, char2)[0] == 0
121
+ last.downto 0 do | i |
122
+ element = @odometer[i]
123
+ element += 1
124
+ if element != @chars.length
125
+ @odometer[i] = element
126
+ break
127
+ end
128
+ @odometer[i] = 0
129
+ @odometer << 0 if i == 0
130
+ end
131
+ rotate_out_bad_end_pairs
132
+ end
133
+ end
134
+
135
+ # Increment the odometer
136
+ # @param [Number] total_steps the number of steps to increment the odometer by (default is 1)
137
+ def increment(total_steps=1)
138
+ steps_taken = 0
139
+ while steps_taken < total_steps
140
+ odometer_length = @odometer.length
141
+ (odometer_length-1).downto 0 do | i |
142
+ element = @odometer[i]
143
+ element += 1
144
+ if element != @chars.length
145
+ @odometer[i] = element
146
+ break
147
+ end
148
+ @odometer[i] = 0
149
+ @odometer << 0 if i == 0
150
+ end
151
+ rotate_out_bad_end_pairs
152
+ rotate_out_bad_start_pairs
153
+ next if has_triple_char_pattern?
154
+ steps_taken += 1
155
+ end
156
+ end
157
+
158
+ # @return [String] string representation of the odometer, with character indexes mapped to their respective characters
159
+ def string_for_odometer
160
+ return @odometer.map {|i| @chars[i]}.join ""
161
+ end
162
+
163
+ def to_s
164
+ string_for_odometer
165
+ end
166
+ end
167
+ end
@@ -0,0 +1,64 @@
1
+ module GentleBrute
2
+ module PatternFinder
3
+ module_function
4
+
5
+ # Analyzes a given string for different patterns
6
+ # @param [String] string the string to analyze
7
+ # @return [Array] a whole bunch of patterns :s
8
+ def patterns_in_strintg string
9
+ pattern_string = ""
10
+ pattern_length = 0
11
+ pattern_count = 0
12
+ pattern_bounds = []
13
+ patterns = {}
14
+
15
+ for i in 0...string.length
16
+ (string.length-1).downto i do | ii |
17
+ slice = string[i..ii]
18
+ slice_length = slice.length
19
+ slice2 = string[i+slice_length..ii+slice_length]
20
+ if slice == slice2
21
+ if not patterns.has_key? slice
22
+ patterns[slice] = {}
23
+ patterns[slice]["count"] = 1
24
+ patterns[slice]["start"] = i
25
+ else
26
+ patterns[slice]["count"] += 1
27
+ end
28
+ end
29
+ end
30
+ end
31
+
32
+ return nil if patterns == {}
33
+
34
+ highest_count = 0
35
+ patterns.keys.reverse.each do | key |
36
+ if patterns[key]["count"] > highest_count
37
+ pattern_string = key
38
+ highest_count = patterns[key]["count"]
39
+ end
40
+ end
41
+
42
+ start = patterns[pattern_string]["start"]
43
+ index = start
44
+ length = pattern_string.length
45
+ pattern_length = length
46
+ pattern_count += 1
47
+
48
+ while true
49
+ slice = string[index...index+length]
50
+ slice2 = string[index+length...index+(length*2)]
51
+ break if slice != slice2
52
+
53
+ pattern_bounds = [start, index+(length*2)-1]
54
+ pattern_length += length
55
+ pattern_count += 1
56
+
57
+ index += length
58
+ end
59
+ full_pattern = pattern_string * pattern_count
60
+
61
+ [pattern_string, full_pattern, pattern_length, pattern_count, pattern_bounds]
62
+ end
63
+ end
64
+ end