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 +6 -0
- data/Gemfile +1 -0
- data/bin/GentleBrute +297 -0
- data/gentle_brute.gemspec +12 -0
- data/lib/gentle_brute/cpa_analyzer.rb +70 -0
- data/lib/gentle_brute/odometer.rb +167 -0
- data/lib/gentle_brute/pattern_finder.rb +64 -0
- data/lib/gentle_brute/resources/dictionary +24005 -0
- data/lib/gentle_brute/resources/neighbors.json +1 -0
- data/lib/gentle_brute/resources/suffixes +56 -0
- data/lib/gentle_brute/word_analyzer.rb +163 -0
- data/lib/gentle_brute.rb +30 -0
- data/passwords.txt +15 -0
- metadata +60 -0
data/.gitignore
ADDED
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
|