gentle_brute 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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