Lucene search
+L

Wardialer

🗓️ 28 Jan 2009 06:13:47Reported by I)ruid <[email protected]>Type 
metasploit
 metasploit
🔗 www.rapid7.com👁 58 Views

Wardialer module for scanning connected dial-up systems and answering telephony indials, with configurable options for dialing, serial port, baud rate, timeout, flow control, and more

Code
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

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 MetasploitModule < Msf::Auxiliary
  include Msf::Auxiliary::Report
  include Msf::Auxiliary::Scanner

  def initialize
    super(
      'Name'        => 'Wardialer',
      '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']),
    ])

    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']),
    ])

    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.config_directory, 'wardial')
    @datadir      = File.join(Msf::Config.config_directory, '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

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation