## # $Id$ ## ## # This file is part of the Metasploit Framework and may be subject to # redistribution and commercial restrictions. Please see the Metasploit # web site for more information on licensing and terms of use. # http://metasploit.com/ ## require 'msf/core' require 'zlib' # Extend Object class to include save_to_file and load_from_file methods class Object def self.save_to_file obj, filename, options={} #obj = self marshal_dump = Marshal.dump(obj) file = File.new(filename,'w') file = Zlib::GzipWriter.new(file) unless options[:gzip] == false file.write marshal_dump file.close return obj end def self.load_from_file filename begin file = Zlib::GzipReader.open(filename) rescue Zlib::GzipFile::Error file = File.open(filename, 'rb') ensure return nil if ! file #obj = Marshal.load file.read obj = Marshal.load file.read file.close return obj end end end class Metasploit3 < Msf::Auxiliary include Msf::Auxiliary::Report include Msf::Auxiliary::Scanner def initialize super( 'Name' => 'Wardialer', 'Version' => '$Revision$', 'Description' => 'Scan for dial-up systems that are connected to modems and answer telephony indials.', 'Author' => [ 'I)ruid' ], 'License' => MSF_LICENSE ) register_options( [ OptString.new('DIALMASK', [true, 'Dial Mask (e.g. 1.800.95X.99XX, (202) 358-XXXX, 358.####, etc.)', '202.358.XXXX']), OptString.new('DIALPREFIX', [true, 'Dial Prefix', 'ATDT']), OptString.new('INITSTRING', [true, 'Initialization String', 'AT X6 S11=80']), OptString.new('SERIALPORT', [true, 'Serial Port (e.g. 0 (COM1), 1 (COM2), /dev/ttyS0, etc.)', '/dev/ttyS0']), ], self.class) register_advanced_options( [ OptInt.new( 'BaudRate', [true, 'Baud Rate', 19200]), OptEnum.new( 'DataBits', [true, 'Data Bits (4 is Windows Only)', '8', ['4', '5', '6', '7', '8'], '8']), OptInt.new( 'ConnTimeout', [true, 'Timeout per data connection in seconds', 45]), OptInt.new( 'DialDelay', [true, 'Time to wait between dials in seconds (rec. min. 1)', 1]), OptString.new('DialSuffix', [false, 'Dial Suffix', nil]), OptInt.new( 'DialTimeout', [true, 'Timeout per dialed number in seconds', 40]), OptBool.new( 'DisplayModem', [false, 'Displays modem commands and responses on the console', false]), OptEnum.new( 'FlowControl', [true, 'Flow Control', 'None', ['None', 'Hardware', 'Software', 'Both'], 'None']), OptInt.new( 'InitInterval', [true, 'Number of dials before reinitializing modem', 30]), #OptEnum.new( 'LogMethod', [true, 'Log Method', 'File', ['File', 'DataBase', 'TIDBITS'], 'File']), OptEnum.new( 'LogMethod', [true, 'Log Method', 'File', ['File'], 'File']), OptString.new('NudgeString', [false, 'Nudge String', '\x1b\x1b\r\n\r\n']), OptEnum.new( 'Parity', [false, 'Parity (Mark & Space are Windows Only)', 'None', ['None', 'Even', 'Odd', 'Mark', 'Space'], 'None']), OptBool.new( 'RedialBusy', [false, 'Redials numbers found to be busy', false]), OptEnum.new( 'StopBits', [true, 'Stop Bits', '1', ['1', '2'], '1']), ], self.class) deregister_options('NUMBER') deregister_options('RPORT') deregister_options('RHOSTS') deregister_options('PAYLOAD') @logmethod = :file @commandstate = true begin require 'telephony' @telephony_loaded = true rescue ::Exception => e @telephony_loaded = false @telephony_error = e end end def run if ! @telephony_loaded print_error("The Telephony module is not available: #{@telephony_error}") raise RuntimeError, "Telephony not available" end @confdir = File.join(Msf::Config.get_config_root, 'wardial') @datadir = File.join(Msf::Config.get_config_root, 'logs', 'wardial') # make sure working dirs exist FileUtils.mkdir_p(@confdir) FileUtils.mkdir_p(@datadir) @logmethod = case datastore['LogMethod'] when 'DataBase' ; :database when 'TIDBITS' ; :tidbits else :file end serialport = datastore['SERIALPORT'] baud = datastore['BaudRate'].to_i data_bits = datastore['DataBits'].to_i stop_bits = datastore['StopBits'].to_i parity = case datastore['Parity'] when 'Even' ; Telephony::Modem::EVEN when 'Odd' ; Telephony::Modem::ODD when 'Mark' ; Telephony::Modem::MARK when 'Space'; Telephony::Modem::SPACE else Telephony::Modem::NONE end flowcontrol = case datastore['FlowControl'] when 'Hardware' ; Telephony::Modem::HARD when 'Software' ; Telephony::Modem::SOFT when 'Both' ; Telephony::Modem::HARD | Telephony::Modem::SOFT else ; Telephony::Modem::NONE end initstring = datastore['INITSTRING'] initinterval = datastore['InitInterval'] dialprefix = datastore['DIALPREFIX'] dialsuffix = datastore['DialSuffix'] nudgestring = datastore['NudgeString'] ? eval('"'+datastore['NudgeString']+'"') : "\r\n\r\n" dialtimeout = datastore['DialTimeout'].to_i conntimeout = datastore['ConnTimeout'].to_i dialmask = datastore['DIALMASK'].tr(' ', '') dialdelay = datastore['DialDelay'].to_i redialbusy = datastore['RedialBusy'] @displaymodem = datastore['DisplayModem'] # Connect to and init modem modem = Telephony::Modem.new(serialport) modem.params = { 'baud' => baud, 'data_bits' => data_bits, 'parity' => parity, 'stop_bits' => stop_bits } modem.flow_control = flowcontrol modem.display = @displaymodem modem.flush # reload data from previous scan datfile = @datadir + '/' + dialmask.gsub(/[( ]/, '').gsub(/[).]/, '-').gsub(/[#]/, 'X').upcase + '.dat' dialrange = Object.load_from_file(datfile) if dialrange print_status( "Previous scan data loaded from #{datfile}" ) select = dialrange.select {|key, value| case @target when :carrier ; value[:carrier] == true when :fax ; value[:fax] == true end } num_identified = select.size select = dialrange.select {|key, value| value[:carrier] == true } num_carriers = select.size select = dialrange.select {|key, value| value[:fax] == true } num_faxes = select.size select = dialrange.select {|key, value| value[:busy] == true } num_busy = select.size else print_status( "No previous scan data found (#{datfile})" ) dialrange = build_dialrange(dialmask) num_identified = 0 num_carriers = 0 num_faxes = 0 num_busy = 0 end # Dial loop begin done = false nextnum = true dialcount = 0 while true if dialcount % initinterval == 0 return if initmodem(modem, initstring) == false end if nextnum == true unidentified = dialrange.select {|key, value| value[:identified] == false } if redialbusy unidentified += unidentified.select {|key, value| value[:busy] == true } end break if unidentified.size == 0 chosen = rand(unidentified.size) dialnum = unidentified[chosen][0] dialval = unidentified[chosen][1] end print_status("#{unidentified.size} of #{dialrange.size} numbers unidentified, #{num_carriers} carriers found, #{num_faxes} faxes found, #{num_busy} busy") if dialval[:busy] == true print_status("Dialing: #{dialnum} (#{dialtimeout} sec. timeout, previously busy)") else print_status("Dialing: #{dialnum} (#{dialtimeout} sec. timeout, previously undialed)") end dialstring = dialprefix + ' ' + dialnum dialstring += (' ' + dialsuffix) if dialsuffix modem.flush time = Time.now result = modem.put_command(dialstring, dialtimeout) while result =~ /RINGING/i result = modem.get_response(dialtimeout-(Time.now-time)) end dialcount += 1 dialrange[dialnum][:dialed] = dialnum case result when /TIMEOUT/i print_status( 'Timeout' ) dialrange[dialnum][:identified] = true dialrange[dialnum][:result] = result dialrange[dialnum][:timeout] = true dialrange[dialnum][:timestamp] = Time.now modem.puts "\r\n" # force the modem to respond to last command (hangup/abort) result = modem.get_response(3) when /CONNECT/i print_status( "Carrier: #{result}" ) @commandstate = false dialrange[dialnum][:identified] = true dialrange[dialnum][:result] = result dialrange[dialnum][:carrier] = true dialrange[dialnum][:timestamp] = Time.now dialrange[dialnum][:banner] = get_banner(modem, conntimeout, nudgestring) modem.hangup initmodem(modem, initstring) num_carriers += 1 note = dialrange[dialnum][:result] + "\n" + dialrange[dialnum][:banner] report_note(:host => dialnum, :type => "wardial_result", :data => note) log_result(dialrange[dialnum]) when /HK_CARRIER/i print_status( "Carrier: #{result}" ) dialrange[dialnum][:identified] = true dialrange[dialnum][:result] = result dialrange[dialnum][:carrier] = true dialrange[dialnum][:timestamp] = Time.now modem.hangup initmodem(modem, initstring) num_carriers += 1 note = dialrange[dialnum][:result] + "\n" + dialrange[dialnum][:banner] report_note(:host => dialnum, :type => "wardial_result", :data => note) log_result(dialrange[dialnum]) when /\+FCO/i print_status( "Fax: #{result}" ) dialrange[dialnum][:identified] = true dialrange[dialnum][:result] = result dialrange[dialnum][:fax] = true dialrange[dialnum][:timestamp] = Time.now modem.hangup initmodem(modem, initstring) num_faxes += 1 note = dialrange[dialnum][:result] + "\n" + dialrange[dialnum][:banner] report_note(:host => dialnum, :type => "wardial_result", :data => note) log_result(dialrange[dialnum]) when /VOICE/i print_status( "Voice" ) dialrange[dialnum][:identified] = true dialrange[dialnum][:result] = result dialrange[dialnum][:voice] = true dialrange[dialnum][:timestamp] = Time.now modem.hangup when /HK_VMB/i dialrange[dialnum][:identified] = true dialrange[dialnum][:result] = result dialrange[dialnum][:voicemail] = true dialrange[dialnum][:timestamp] = Time.now modem.hangup when /HK_AVS/i dialrange[dialnum][:identified] = true dialrange[dialnum][:result] = result dialrange[dialnum][:avs] = true dialrange[dialnum][:timestamp] = Time.now modem.hangup when /HK_NOTED/i dialrange[dialnum][:identified] = true dialrange[dialnum][:result] = result dialrange[dialnum][:noted] = true dialrange[dialnum][:timestamp] = Time.now modem.hangup when /HK_GIRL/i dialrange[dialnum][:identified] = true dialrange[dialnum][:result] = result dialrange[dialnum][:girl] = true dialrange[dialnum][:timestamp] = Time.now modem.hangup when /NO CARRIER/i print_status( "No Carrier" ) dialrange[dialnum][:identified] = true #TODO: should this be false? dialrange[dialnum][:result] = result dialrange[dialnum][:timestamp] = Time.now when /BUSY/i print_status( "Busy" ) dialrange[dialnum][:identified] = false dialrange[dialnum][:result] = result dialrange[dialnum][:busy] = true dialrange[dialnum][:timestamp] = Time.now num_busy += 1 when /OK/i print_status( "Unexpected OK response..." ) when /NO DIAL *TONE/i nextnum = false modem.hangup select(nil,nil,nil,1) next when nil modem.hangup else print_status( "Unrecognized Response String" ) end Object.save_to_file(dialrange, datfile) #dialrange.save_to_file(datfile) nextnum = true select(nil,nil,nil,1) # we need at least a little buffer for the modem to hangup/reset select(nil,nil,nil,dialdelay-1) if dialdelay >= 1 end rescue ::Interrupt modem.hangup Object.save_to_file(dialrange, datfile) #dialrange.save_to_file(datfile) raise $! rescue ::Exception => e print_error("Error during dial process: #{e.class} #{e} #{e.backtrace}") return end print_status("Dialing Complete") modem.close end def initmodem(modem, initstring) print_status("Initializing Modem") result = modem.put_command('ATZ', 3) if result != 'OK' print_error("Error resetting modem") return false end result = modem.put_command(initstring, 3) if result != 'OK' print_error("Error initializing modem") return false end return true end def build_dialrange(dialmask) dialrange = {} incdigits = 0 dialmask.each_char {|c| incdigits += 1 if c =~ /^[X#]$/i } max = (10**incdigits)-1 print_status("Detected #{incdigits} masked digits in DIALMASK (#{dialmask})") print_status("Generating storage for #{max+1} numbers to dial") (0..max).each {|num| number = dialmask.dup # copy the mask numstr = sprintf("%0#{incdigits}d", num) # stringify our incrementing number j = 0 # index for numstr for i in 0..number.length-1 do # step through the number (mask) if number[i].chr =~ /^[X#]$/i number[i] = numstr[j] # replaced masked indexes with digits from incrementing number j += 1 end end dialrange[number] = {} dialrange[number][:identified] = false } #print_status("Storage for #{dialrange.size} numbers generated") return dialrange end def log_result(dialnum) case @logmethod when :file logfile = File.join(@datadir, 'found.log') file = File.new(logfile, 'a') file.puts( "#####( NEW LOG ENTRY )#####\n") file.puts( "#{Time.now}\n") file.puts( "#{dialnum[:dialed]} : #{dialnum[:result]}\n") file.puts( "#{dialnum[:banner]}\n") if dialnum[:banner] file.puts( "#####( END LOG ENTRY )#####\n") file.close when :database when :tidbits end end def get_banner(modem, timeout, nudgestring) print_status("Grabbing banner...") banner = '' time = Time.now gotchar = Time.now while Time.now < time + timeout if Time.now >= gotchar + 8 # nudges after 8 seconds of receiving nothing if nudgestring print_status( "Nudging..." ) modem.puts nudgestring end gotchar = Time.now # resets timer so we don't nudge too often end c = modem.getc next if ! c gotchar = Time.now print( c.chr ) if @displaymodem # stop if carrier dropped break if modem.dcd == 0 banner += c.chr end print("\n") if @displaymodem return banner end end