├── README.md └── passpal.rb /README.md: -------------------------------------------------------------------------------- 1 | Project page 2 | ============= 3 | [Project page](http://thepasswordproject.com/passpal) 4 | 5 | -------------------------------------------------------------------------------- /passpal.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # Table of Contents 4 | # 5 | # 1. Load libraries 6 | # 2. Monkey patching 7 | # 3. AbstractInterface module 8 | # 4. Agent class 9 | # 5. Agents 10 | # 5.1 WordFrequencyAgent class 11 | # 5.2 BaseWordFrequencyAgent class 12 | # 5.3 LengthFrequencyAgent class 13 | # 5.4 CharsetFrequencyAgent class 14 | # 5.5 HashcatMaskFrequencyAgent class 15 | # 5.6 CharsetPositionAgent class 16 | # 5.7 CharacterFrequencyAgent class 17 | # 5.8 SymbolFrequencyAgent class 18 | # 5.9 YourAgent class 19 | # 6. Application class 20 | 21 | PASSPAL_VERSION = '0.4' 22 | 23 | 24 | # 25 | # 1. Load libraries 26 | # 27 | require 'getoptlong' 28 | 29 | begin 30 | require 'progressbar' 31 | rescue LoadError 32 | puts "Could not load 'progressbar'. Install by running: gem install progressbar" 33 | exit 34 | end 35 | 36 | begin 37 | require 'ruport' 38 | rescue LoadError 39 | puts "Could not load 'ruport'. Install by running: gem install ruport" 40 | exit 41 | end 42 | 43 | 44 | # 45 | # 2. Monkey patching 46 | # 47 | class Array 48 | def longest_word 49 | group_by(&:size).max.last.first 50 | end 51 | def sum 52 | inject(:+) 53 | end 54 | end 55 | 56 | class Class 57 | def subclasses 58 | result = [] 59 | ObjectSpace.each_object(Class) { |klass| result << klass if klass < self } 60 | result 61 | end 62 | end 63 | 64 | 65 | # 66 | # 3. AbstractInterface module 67 | # 68 | module AbstractInterface 69 | class InterfaceNotImplementedError < NoMethodError 70 | end 71 | def self.included(klass) 72 | klass.send(:include, AbstractInterface::Methods) 73 | klass.send(:extend, AbstractInterface::Methods) 74 | end 75 | module Methods 76 | def api_not_implemented(klass) 77 | caller.first.match(/in \`(.+)\'/) 78 | method_name = $1 79 | raise AbstractInterface::InterfaceNotImplementedError.new("#{klass.class.name} needs to implement '#{method_name}' for interface #{self.name}!") 80 | end 81 | end 82 | end 83 | 84 | 85 | # 86 | # 4. Extendable Agent class 87 | # From http://www.metabates.com/2011/02/07/building-interfaces-and-abstract-classes-in-ruby/ 88 | # 89 | class Agent 90 | include AbstractInterface 91 | attr_accessor :analyzeTime, :reportTime, :total 92 | def initialize 93 | @analyzeTime = 0 94 | @reportTime = 0 95 | @total = 0 96 | end 97 | def analyze(word) 98 | Agent.api_not_implemented(self) 99 | end 100 | def super_analyze(word) 101 | @total += 1 102 | analyze(word) 103 | end 104 | def report 105 | Agent.api_not_implemented(self) 106 | end 107 | def super_report 108 | report 109 | end 110 | def display_time 111 | self.class.to_s + " - Analyzing: " + @analyzeTime.round(4).to_s + "s Reporting: " + @reportTime.round(4).to_s + 's' 112 | end 113 | end 114 | 115 | 116 | # 117 | # 5. Agents 118 | # 5.1 WordFrequencyAgent 119 | # 120 | class WordFrequencyAgent < Agent 121 | attr_accessor :words 122 | def initialize 123 | super 124 | @words = Hash.new(0) 125 | end 126 | def analyze(word) 127 | @words[word] += 1 128 | end 129 | def report 130 | unique = @words.length 131 | output = [] 132 | while (@words.length > 0 && output.length < $top.to_i) 133 | max_value = 0 134 | max_key = nil 135 | @words.each do |key, value| 136 | if value > max_value 137 | max_value = value 138 | max_key = key 139 | end 140 | end 141 | @words.delete(max_key) 142 | output << [max_key.to_s, max_value] 143 | end 144 | @words = nil 145 | output.each do |array| 146 | array << (((array[1].to_f/@total)*100).round(4).to_s + ' %') 147 | end 148 | table = Ruport::Data::Table.new({ 149 | :data => output, 150 | :column_names => ['Word', 'Count', 'Of total'] 151 | }) 152 | "Total words: \t" + @total.to_s + 153 | "\nUnique words: \t" + unique.to_s + ' (' + ((unique.to_f/@total.to_f)*100).round(2).to_s + ' %)' + 154 | "\n\nWord frequency, sorted by count, top " + $top.to_s + "\n" + table.to_s 155 | end 156 | end 157 | 158 | 159 | # 160 | # 5.2 BaseWordFrequencyAgent 161 | # 162 | class BaseWordFrequencyAgent < Agent 163 | attr_accessor :words 164 | def initialize 165 | super 166 | @words = Hash.new(0) 167 | end 168 | def analyze(word) 169 | word = word.gsub(/^[^a-zA-Z]+/, '').gsub(/[^a-zA-Z]+$/, '') 170 | @words[word] += 1 if word.length >= 3 171 | end 172 | def report 173 | output = [] 174 | while (@words.length > 0 && output.length < $top.to_i) 175 | max_value = 0 176 | max_key = nil 177 | @words.each do |key, value| 178 | if value > max_value 179 | max_value = value 180 | max_key = key 181 | end 182 | end 183 | @words.delete(max_key) 184 | output << [max_key, max_value] 185 | end 186 | @words = nil 187 | output.each do |array| 188 | array << ((array[1].to_f/@total)*100).round(4).to_s + ' %' 189 | end 190 | table = Ruport::Data::Table.new({ 191 | :data => output, 192 | :column_names => ['Word', 'Count', 'Of total'] 193 | }) 194 | "Base word (len>=3) frequency, sorted by count, top " + $top.to_s + "\n" + table.to_s 195 | end 196 | end 197 | 198 | 199 | # 200 | # 5.3 LengthFrequencyAgent 201 | # 202 | class LengthFrequencyAgent < Agent 203 | attr_accessor :lengths 204 | def initialize 205 | super 206 | @lengths = Hash.new(0) 207 | end 208 | def analyze(word) 209 | @lengths[word.length] += 1 210 | end 211 | def report 212 | output = Hash[@lengths.sort].to_a 213 | @lengths = nil 214 | output.each do |array| 215 | array << ((array[1].to_f/@total)*100).round(4).to_s + ' %' 216 | end 217 | table = Ruport::Data::Table.new({ 218 | :data => output, 219 | :column_names => ['Length', 'Count', 'Of total'] 220 | }) 221 | "Length frequency, sorted by length, full table\n" + table.to_s 222 | end 223 | end 224 | 225 | 226 | # 227 | # 5.4 CharsetFrequencyAgent 228 | # 229 | class CharsetFrequencyAgent < Agent 230 | attr_accessor :charsets, :results 231 | def initialize 232 | super 233 | @charsets = Hash[ 234 | :'lower' => Hash[:pattern => /^[a-z]+$/, :characters => 26], 235 | :'upper' => Hash[:pattern => /^[A-Z]+$/, :characters => 26], 236 | :'numeric' => Hash[:pattern => /^[0-9]+$/, :characters => 10], 237 | :'symbolic' => Hash[:pattern => Regexp.new('^[\p{Punct} ]+$'.force_encoding("utf-8"), Regexp::FIXEDENCODING), :characters => 33], 238 | :'lower-upper' => Hash[:pattern => /^[A-Za-z]+$/, :characters => 52], 239 | :'lower-numeric' => Hash[:pattern => /^[a-z0-9]+$/, :characters => 36], 240 | :'lower-symbolic' => Hash[:pattern => Regexp.new('^[a-z\p{Punct} ]+$'.force_encoding("utf-8"), Regexp::FIXEDENCODING), :characters => 59], 241 | :'upper-numeric' => Hash[:pattern => /^[A-Z0-9]+$/, :characters => 36], 242 | :'upper-symbolic' => Hash[:pattern => Regexp.new('^[A-Z\p{Punct} ]+$'.force_encoding("utf-8"), Regexp::FIXEDENCODING), :characters => 59], 243 | :'numeric-symbolic' => Hash[:pattern => Regexp.new('^[0-9\p{Punct} ]+$'.force_encoding("utf-8"), Regexp::FIXEDENCODING), :characters => 43], 244 | :'lower-upper-numeric' => Hash[:pattern => /^[A-Za-z0-9]+$/, :characters => 62], 245 | :'lower-upper-symbolic' => Hash[:pattern => Regexp.new('^[a-zA-Z\p{Punct} ]+$'.force_encoding("utf-8"), Regexp::FIXEDENCODING), :characters => 85], 246 | :'lower-numeric-symbolic' => Hash[:pattern => Regexp.new('^[a-z0-9\p{Punct} ]+$'.force_encoding("utf-8"), Regexp::FIXEDENCODING), :characters => 69], 247 | :'upper-numeric-symbolic' => Hash[:pattern => Regexp.new('^[A-Z0-9\p{Punct} ]+$'.force_encoding("utf-8"), Regexp::FIXEDENCODING), :characters => 69], 248 | :'lower-upper-numeric-symbolic' => Hash[:pattern => Regexp.new('^[A-Za-z0-9\p{Punct} ]+$'.force_encoding("utf-8"), Regexp::FIXEDENCODING), :characters => 95], 249 | ] 250 | @results = Hash.new(0) 251 | end 252 | def analyze(word) 253 | @charsets.each do |key, hash| 254 | if hash[:pattern].match(word) 255 | @results[key] += 1 256 | end 257 | end 258 | end 259 | def report 260 | output = [] 261 | @results.each do |charset, count| 262 | output << [charset, count, ((count.to_f/@total)*100).round(4).to_s + ' %', count.to_f/@charsets[charset][:characters]] 263 | end 264 | @results = nil 265 | table = Ruport::Data::Table.new({ 266 | :data => output, 267 | :column_names => ['Charset', 'Count', 'Of total', 'Count/keyspace'] 268 | }) 269 | "Charset frequency, sorted by count, full table\n" + table.sort_rows_by("Count", :order => :descending).to_s + 270 | "\nCharset frequency, sorted by count/keyspace, full table\n" + table.sort_rows_by("Count/keyspace", :order => :descending).to_s 271 | end 272 | end 273 | 274 | 275 | # 276 | # 5.5 HashcatMaskFrequencyAgent 277 | # 278 | class HashcatMaskFrequencyAgent < Agent 279 | module LUDS 280 | ENCODE = { 'L' => 0, 'U' => 1, 'D' => 2, 'S' => 3 } 281 | DECODE = ENCODE.invert 282 | def self.encode(str) 283 | bytes = str.chars.each_slice(4).map do |a, b = 'L', c = 'L', d = 'L'| 284 | (ENCODE[a]<<6) + (ENCODE[b]<<4) + (ENCODE[c]<<2) + ENCODE[d] 285 | end 286 | chars_in_last = str.length % 4 287 | if chars_in_last == 0 288 | bytes << 0 289 | else 290 | bytes[bytes.length-1] = bytes.last & 0b11111100 | chars_in_last 291 | end 292 | bytes.pack('c*') 293 | end 294 | def self.decode(str) 295 | chars = str.each_byte.flat_map do |byte| 296 | [ 297 | DECODE[byte>>6], 298 | DECODE[(byte>>4) & 3], 299 | DECODE[(byte>>2) & 3], 300 | DECODE[byte & 3] 301 | ] 302 | end.join 303 | chars_to_pop = 4 - str.bytes.to_a.last & 3 304 | chars[0..-(chars_to_pop+1)] 305 | end 306 | end 307 | def initialize 308 | super 309 | @results = Hash.new(0) 310 | @otherCount = 0 311 | end 312 | def analyze(word) 313 | if Regexp.new('^[a-zA-Z0-9\p{Punct} ]+$'.force_encoding('utf-8'), Regexp::FIXEDENCODING).match(word) 314 | word = word.gsub(/[A-Z]/, 'U').gsub(/[a-z]/, 'L').gsub(/[0-9]/, 'D').gsub(Regexp.new('[\p{Punct} ]'.force_encoding('utf-8'), Regexp::FIXEDENCODING), 'S') 315 | @results[LUDS::encode(word).to_sym] += 1 316 | else 317 | @otherCount += 1 318 | end 319 | end 320 | def report 321 | output = [] 322 | @results.each do |mask, count| 323 | mask2 = LUDS::decode(mask.to_s) 324 | keyspace = 1 325 | mask2.each_char do |char| 326 | case char 327 | when 'L' 328 | keyspace *= 26 329 | when 'U' 330 | keyspace *= 26 331 | when 'D' 332 | keyspace *= 10 333 | when 'S' 334 | keyspace *= 33 335 | end 336 | end 337 | output << [mask, count, count.to_f/keyspace.to_f] 338 | end 339 | output_by_count = [] 340 | output_by_keyspace = [] 341 | included_count_masks = [] 342 | included_keyspace_masks = [] 343 | c = output.length 344 | while (c > 0 && (output_by_count.length < $top.to_i || output_by_keyspace.length < $top.to_i)) 345 | max_count = 0 346 | max_count_index = nil 347 | max_keyspace = 0.0 348 | max_keyspace_index = nil 349 | included_count_mask = nil 350 | included_keyspace_mask = nil 351 | output.each_with_index do |row, index| 352 | if row[1] > max_count && included_count_masks.find_index(row[0]).nil? 353 | max_count = row[1] 354 | max_count_index = index 355 | included_count_mask = row[0] 356 | end 357 | if row[2] > max_keyspace && included_keyspace_masks.find_index(row[0]).nil? 358 | max_keyspace = row[2] 359 | max_keyspace_index = index 360 | included_keyspace_mask = row[0] 361 | end 362 | end 363 | row = output[max_count_index] 364 | output_by_count << [LUDS::decode(row[0].to_s).to_s.gsub(/L/, '?l').gsub(/U/, '?u').gsub(/D/, '?d').gsub(/S/, '?s'), row[1], ((row[1].to_f/@total)*100).round(4).to_s + ' %', row[2]] 365 | row = output[max_keyspace_index] 366 | output_by_keyspace << [LUDS::decode(row[0].to_s).to_s.gsub(/L/, '?l').gsub(/U/, '?u').gsub(/D/, '?d').gsub(/S/, '?s'), row[1], ((row[1].to_f/@total)*100).round(4).to_s + ' %', row[2]] 367 | included_count_masks << included_count_mask 368 | included_keyspace_masks << included_keyspace_mask 369 | c -= 1 370 | end 371 | @results = nil 372 | table = Ruport::Data::Table.new({ 373 | :data => output_by_count, 374 | :column_names => ['Mask', 'Count', 'Of total', 'Count/keyspace'] 375 | }) 376 | table2 = Ruport::Data::Table.new({ 377 | :data => output_by_keyspace, 378 | :column_names => ['Mask', 'Count', 'Of total', 'Count/keyspace'] 379 | }) 380 | "Hashcat mask frequency, sorted by count, top " + $top.to_s + "\n" + table.sort_rows_by("Count", :order => :descending).to_s + 381 | "Words that didn't match any ?l?u?d?s mask: " + @otherCount.to_s + ' (' + ((@otherCount.to_f/@total)*100).round(4).to_s + ' %)' + 382 | "\n\nHashcat mask frequency, sorted by count/keyspace, top " + $top.to_s + "\n" + table2.sort_rows_by("Count/keyspace", :order => :descending).to_s + 383 | "Words that didn't match any ?l?u?d?s mask: " + @otherCount.to_s + ' (' + ((@otherCount.to_f/@total)*100).round(4).to_s + ' %)' + "\n" 384 | end 385 | end 386 | 387 | 388 | # 389 | # 5.6 CharsetPositionAgent 390 | # 391 | class CharsetPositionAgent < Agent 392 | attr_accessor :result 393 | def initialize 394 | super 395 | @results = { 396 | :l => Hash.new(0), 397 | :u => Hash.new(0), 398 | :d => Hash.new(0), 399 | :s => Hash.new(0) 400 | } 401 | end 402 | def analyze(word) 403 | min_length = 6 404 | length = word.length 405 | if length >= min_length 406 | index = 0 407 | word.each_char do |char| 408 | if index < 3 || index >= length-3 409 | if index < 3 410 | pos = index 411 | elsif index >= length-3 412 | pos = -(length-index) 413 | end 414 | case char 415 | when /[a-z]/ 416 | @results[:l][pos] += 1 417 | when /[A-Z]/ 418 | @results[:u][pos] += 1 419 | when /[0-9]/ 420 | @results[:d][pos] += 1 421 | when Regexp.new('([\p{Punct} ])'.force_encoding('utf-8'), Regexp::FIXEDENCODING) 422 | @results[:s][pos] += 1 423 | end 424 | end 425 | index += 1 426 | end 427 | end 428 | end 429 | def report 430 | sum_0 = @results[:l][0]+@results[:u][0]+@results[:d][0]+@results[:s][0] 431 | sum_1 = @results[:l][1]+@results[:u][1]+@results[:d][1]+@results[:s][1] 432 | sum_2 = @results[:l][2]+@results[:u][2]+@results[:d][2]+@results[:s][2] 433 | sum_m3 = @results[:l][-3]+@results[:u][-3]+@results[:d][-3]+@results[:s][-3] 434 | sum_m2 = @results[:l][-2]+@results[:u][-2]+@results[:d][-2]+@results[:s][-2] 435 | sum_m1 = @results[:l][-1]+@results[:u][-1]+@results[:d][-1]+@results[:s][-1] 436 | table_f = Ruport::Data::Table.new({ 437 | :data => [ 438 | ['lower', ((@results[:l][0].to_f/sum_0)*100).round(4).to_s+' %', ((@results[:l][1].to_f/sum_1)*100).round(4).to_s+' %', ((@results[:l][2].to_f/sum_2)*100).round(4).to_s+' %', ((@results[:l][-3].to_f/sum_m3)*100).round(4).to_s+' %', ((@results[:l][-2].to_f/sum_m2)*100).round(4).to_s+' %', ((@results[:l][-1].to_f/sum_m1)*100).round(4).to_s+' %'], 439 | ['upper', ((@results[:u][0].to_f/sum_0)*100).round(4).to_s+' %', ((@results[:u][1].to_f/sum_1)*100).round(4).to_s+' %', ((@results[:u][2].to_f/sum_2)*100).round(4).to_s+' %', ((@results[:u][-3].to_f/sum_m3)*100).round(4).to_s+' %', ((@results[:u][-2].to_f/sum_m2)*100).round(4).to_s+' %', ((@results[:u][-1].to_f/sum_m1)*100).round(4).to_s+' %'], 440 | ['digits', ((@results[:d][0].to_f/sum_0)*100).round(4).to_s+' %', ((@results[:d][1].to_f/sum_1)*100).round(4).to_s+' %', ((@results[:d][2].to_f/sum_2)*100).round(4).to_s+' %', ((@results[:d][-3].to_f/sum_m3)*100).round(4).to_s+' %', ((@results[:d][-2].to_f/sum_m2)*100).round(4).to_s+' %', ((@results[:d][-1].to_f/sum_m1)*100).round(4).to_s+' %'], 441 | ['symbols', ((@results[:s][0].to_f/sum_0)*100).round(4).to_s+' %', ((@results[:s][1].to_f/sum_1)*100).round(4).to_s+' %', ((@results[:s][2].to_f/sum_2)*100).round(4).to_s+' %', ((@results[:s][-3].to_f/sum_m3)*100).round(4).to_s+' %', ((@results[:s][-2].to_f/sum_m2)*100).round(4).to_s+' %', ((@results[:s][-1].to_f/sum_m1)*100).round(4).to_s+' %'], 442 | ], 443 | :column_names => ['Charset\Index', '0 (first char)', 1, 2, -3, -2, '-1 (last char)'] 444 | }) 445 | @results = nil 446 | "Charset distribution of characters in beginning and end of words (len>=6)\n" + table_f.to_s 447 | end 448 | end 449 | 450 | 451 | # 452 | # 5.7 CharacterFrequencyAgent 453 | # 454 | class CharacterFrequencyAgent < Agent 455 | attr_accessor :words 456 | def initialize 457 | super 458 | @chars = Hash.new(0) 459 | @charCount = 0 460 | end 461 | def analyze(word) 462 | @charCount += word.length 463 | word.each_char do |char| 464 | @chars[char] += 1 465 | end 466 | end 467 | def report 468 | unique = @chars.length 469 | output = [] 470 | charset_string = '' 471 | while (@chars.length > 0 && (output.length < $top.to_i || output.length < 50)) 472 | max_value = 0 473 | max_key = nil 474 | @chars.each do |key, value| 475 | if value > max_value 476 | max_value = value 477 | max_key = key 478 | end 479 | end 480 | @chars.delete(max_key) 481 | charset_string += max_key 482 | output << [max_key, max_value] 483 | end 484 | @chars = nil 485 | output.each do |array| 486 | array << (((array[1].to_f/@charCount)*100).round(4).to_s + ' %') 487 | end 488 | table = Ruport::Data::Table.new({ 489 | :data => output, 490 | :column_names => ['Character', 'Count', 'Of total'] 491 | }) 492 | "Total characters: \t" + @charCount.to_s + 493 | "\nUnique characters: \t" + unique.to_s + 494 | "\nTop 50 characters: \t" + '' + charset_string[0..49] + 495 | "\n\nCharacter frequency, sorted by count, top " + $top.to_s + "\n" + table.sub_table(0...$top.to_i).to_s 496 | end 497 | end 498 | 499 | 500 | # 501 | # 5.8 SymbolFrequencyAgent 502 | # 503 | class SymbolFrequencyAgent < Agent 504 | attr_accessor :symbols 505 | def initialize 506 | super 507 | @symbols = Hash.new(0) 508 | end 509 | def analyze(word) 510 | m = word.scan(Regexp.new('([\p{Punct} ])'.force_encoding('utf-8'), Regexp::FIXEDENCODING)) 511 | if m.length > 0 512 | m.each do |symbol| 513 | @symbols[symbol[0]] += 1 514 | end 515 | end 516 | end 517 | def report 518 | table = Ruport::Data::Table.new({ 519 | :data => @symbols, 520 | :column_names => %w[Symbol Count] 521 | }) 522 | "Symbol frequency, sorted by count, top " + $top.to_s + "\n" + table.sort_rows_by("Count", :order => :descending).sub_table(0...$top.to_i).to_s 523 | end 524 | end 525 | 526 | 527 | # 528 | # 5.9 YourAgent 529 | # 530 | =begin 531 | class YourAgent < Agent 532 | def initialize 533 | super 534 | end 535 | def analyze(word) 536 | 537 | end 538 | def report 539 | #puts $top 540 | #puts @total 541 | end 542 | end 543 | =end 544 | 545 | 546 | # 547 | # 6. Application 548 | # 549 | class Application 550 | 551 | attr_accessor :agents 552 | 553 | def initialize 554 | @output_file = STDOUT 555 | if RUBY_VERSION != '1.9.3' 556 | puts 'Warning: This software has only been tested on Ruby 1.9.3' 557 | puts 558 | end 559 | @possibleAgents = [ 560 | WordFrequencyAgent.new, 561 | BaseWordFrequencyAgent.new, 562 | LengthFrequencyAgent.new, 563 | CharsetFrequencyAgent.new, 564 | HashcatMaskFrequencyAgent.new, 565 | CharsetPositionAgent.new, 566 | CharacterFrequencyAgent.new, 567 | SymbolFrequencyAgent.new, 568 | #YourAgent.new, 569 | ] 570 | @agents = @possibleAgents 571 | opts = GetoptLong.new( 572 | ['--help', '-h', '-?', GetoptLong::NO_ARGUMENT], 573 | ['--top', '-t', GetoptLong::REQUIRED_ARGUMENT], 574 | ['--include', '-i', GetoptLong::REQUIRED_ARGUMENT], 575 | ['--exclude', '-e', GetoptLong::REQUIRED_ARGUMENT], 576 | ['--outfile', '-o', GetoptLong::REQUIRED_ARGUMENT] 577 | ) 578 | begin 579 | opts.each do |opt, arg| 580 | case opt 581 | when '--help' 582 | display_help 583 | exit 1 584 | when '--top' 585 | $top = arg 586 | when '--include' 587 | @agents = [] 588 | arg.split(/,/).each do |i| 589 | @agents << @possibleAgents[i.to_i-1] 590 | end 591 | when '--exclude' 592 | @agents = @possibleAgents 593 | arg.split(/,/).each do |i| 594 | @agents[i.to_i-1] = nil 595 | end 596 | when '--outfile' 597 | @output_file = File.new(arg, "w") 598 | end 599 | end 600 | $top ||= 10 601 | rescue GetoptLong::InvalidOption => e 602 | puts e 603 | rescue => e 604 | puts e 605 | end 606 | if ARGV.length != 1 607 | abort "Missing file argument (try --help)" 608 | end 609 | end 610 | 611 | def display_help 612 | puts "passpal "+PASSPAL_VERSION+", T. Alexander Lystad (www.thepasswordproject.com) 613 | 614 | Usage on Windows: ruby passpal.rb [switches] filename [> outfile.txt] 615 | Usage on Linux: ./passpal.rb [switches] filename [> outfile.txt] 616 | --help \t\t\t Show help 617 | --top \t\t\t Show top X results. Defaults to 10. Some reports are always shown in full. Example: --top 20 618 | --include STRING \t Run these modules, separate with comma. Example: --include 1,3,5 619 | --exclude STRING \t Run all modules except these, separate with comma. Example: --exclude 6 620 | --outfile filename \t Output to this file 621 | filename \t\t The file to analyze. Must be UTF-8 encoded. 622 | 623 | " 624 | puts "Available modules: " 625 | @possibleAgents.each_with_index do |value, key| 626 | puts (1+key).to_s + ' = ' + value.class.to_s 627 | end 628 | end 629 | 630 | def run 631 | filename = ARGV.shift 632 | buffer = "\n\npasspal "+PASSPAL_VERSION+" report (www.thepasswordproject.com)\n\n" 633 | @agents.each_with_index do |agent, index| 634 | unless agent.nil? 635 | f = File.open(filename, 'r:UTF-8') 636 | progress = ProgressBar.new('Analysis #' + (index+1).to_s + '/' + @agents.length.to_s, f.size) 637 | f.each_line do |line| 638 | progress.inc(line.bytesize) 639 | #Analyze 640 | agent.super_analyze(line.chomp) 641 | end 642 | progress.finish 643 | #Report 644 | progress = ProgressBar.new(' Report #' + (index+1).to_s + '/' + @agents.length.to_s, 1) 645 | string = agent.super_report 646 | buffer += string + "\n\n" unless string.nil? 647 | progress.finish 648 | end 649 | end 650 | @output_file.puts buffer 651 | end 652 | end 653 | 654 | 655 | 656 | a = Application.new() 657 | a.run() 658 | --------------------------------------------------------------------------------