Skip to content

Commit

Permalink
This fixes a number of issues with the Capture mixin
Browse files Browse the repository at this point in the history
 * The use of www.metasploit.com in a datastore option results in a DNS lookup (infoleak). Switch to 8.8.8.8 (TTL=1)
 * The hackey code around #each_packet is no longer necessary in newer Ruby versions
 * The arp()/probe_gateway() calls to inject_reply() had broken logic leading to early exit and missed replies
 * The arp() function now tries up to three times to get a reply (helpful with lossy L2)
 * GC.start is extraneous and should be removed
 * Increased timeouts
  • Loading branch information
HD Moore committed Feb 23, 2015
1 parent 615d71d commit bdd5276
Showing 1 changed file with 44 additions and 58 deletions.
102 changes: 44 additions & 58 deletions lib/msf/core/exploit/capture.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ def initialize(info = {})
[
true,
'Send a TTL=1 random UDP datagram to this host to discover the default gateway\'s MAC',
'www.metasploit.com']),
'8.8.8.8']),
OptPort.new('GATEWAY_PROBE_PORT',
[
false,
Expand Down Expand Up @@ -143,7 +143,6 @@ def close_pcap
return unless self.capture
self.capture = nil
self.arp_capture = nil
GC.start()
end

def capture_extract_ies(raw)
Expand All @@ -163,26 +162,14 @@ def capture_extract_ies(raw)
end

#
# This monstrosity works around a series of bugs in the interrupt
# signal handling of Ruby 1.9
# Loop through each packet
#
def each_packet
return unless capture
begin
@capture_count = 0
reader = framework.threads.spawn("PcapReceiver", false) do
capture.each do |pkt|
yield(pkt)
@capture_count += 1
end
end
reader.join
rescue ::Exception
raise $!
ensure
reader.kill if reader.alive?
capture.each do |pkt|
yield(pkt)
@capture_count += 1
end

@capture_count
end

Expand Down Expand Up @@ -242,10 +229,9 @@ def inject_pcap(pcap_file, filter=nil, delay = 0, pcap=self.capture)
pcap.inject(pkt)
Rex.sleep((delay * 1.0)/1000)
end
GC.start
end

# Capture_sendto is intended to replace the old Rex::Socket::Ip.sendto method. It requires
# capture_sendto is intended to replace the old Rex::Socket::Ip.sendto method. It requires
# a payload and a destination address. To send to the broadcast address, set bcast
# to true (this will guarantee that packets will be sent even if ARP doesn't work
# out).
Expand All @@ -262,24 +248,20 @@ def capture_sendto(payload="", dhost=nil, bcast=false, dev=nil)

# The return value either be a PacketFu::Packet object, or nil
def inject_reply(proto=:udp, pcap=self.capture)
reply = nil
to = (datastore['TIMEOUT'] || 500).to_f / 1000.0
if not pcap
raise RuntimeError, "Could not access the capture process (remember to open_pcap first!)"
else
begin
::Timeout.timeout(to) do
pcap.each do |r|
packet = PacketFu::Packet.parse(r)
next unless packet.proto.map { |x| x.downcase.to_sym }.include? proto
reply = packet
break
end
# Defaults to ~2 seconds
to = (datastore['TIMEOUT'] * 4) / 1000.0
raise RuntimeError, "Could not access the capture process (remember to open_pcap first!)" if not pcap
begin
::Timeout.timeout(to) do
pcap.each do |r|
packet = PacketFu::Packet.parse(r)
next unless packet.proto.map { |x| x.downcase.to_sym }.include? proto
return packet
end
rescue ::Timeout::Error
end
rescue ::Timeout::Error
end
return reply
nil
end

# This ascertains the correct Ethernet addresses one should use to
Expand Down Expand Up @@ -328,20 +310,19 @@ def probe_gateway(addr)
end

begin
to = (datastore['TIMEOUT'] || 1500).to_f / 1000.0
to = ((datastore['TIMEOUT'] || 500).to_f * 8) / 1000.0
::Timeout.timeout(to) do
while (my_packet = inject_reply(:udp, self.arp_capture))
if my_packet.payload == secret
dst_mac = self.arp_cache[:gateway] = my_packet.eth_daddr
src_mac = self.arp_cache[Rex::Socket.source_address(addr)] = my_packet.eth_saddr
return [dst_mac, src_mac]
else
next
end
while true
my_packet = inject_reply(:udp, self.arp_capture)
next unless my_packet
next unless my_packet.payload == secret
dst_mac = self.arp_cache[:gateway] = my_packet.eth_daddr
src_mac = self.arp_cache[Rex::Socket.source_address(addr)] = my_packet.eth_saddr
return [dst_mac, src_mac]
end
end
rescue ::Timeout::Error
# Well, that didn't work (this common on networks where there's no gatway, like
# Well, that didn't work (this is common on networks where there's no gateway, like
# VMWare network interfaces. We'll need to use a fake source hardware address.
self.arp_cache[Rex::Socket.source_address(addr)] = "00:00:00:00:00:00"
end
Expand All @@ -354,26 +335,31 @@ def arp(target_ip=nil)
return self.arp_cache[:gateway] unless should_arp? target_ip
source_ip = Rex::Socket.source_address(target_ip)
raise RuntimeError, "Could not access the capture process." unless self.arp_capture

p = arp_packet(target_ip, source_ip)
inject_eth(:eth_type => 0x0806,
:payload => p,
:pcap => self.arp_capture,
:eth_saddr => self.arp_cache[Rex::Socket.source_address(target_ip)]
)
begin
to = (datastore['TIMEOUT'] || 500).to_f / 1000.0
::Timeout.timeout(to) do
while (my_packet = inject_reply(:arp, self.arp_capture))
if my_packet.arp_saddr_ip == target_ip

# Try up to 3 times to get an ARP response
1.upto(3) do
inject_eth(:eth_type => 0x0806,
:payload => p,
:pcap => self.arp_capture,
:eth_saddr => self.arp_cache[Rex::Socket.source_address(target_ip)]
)
begin
to = ((datastore['TIMEOUT'] || 500).to_f * 8) / 1000.0
::Timeout.timeout(to) do
while true
my_packet = inject_reply(:arp, self.arp_capture)
next unless my_packet
next unless my_packet.arp_saddr_ip == target_ip
self.arp_cache[target_ip] = my_packet.eth_saddr
return self.arp_cache[target_ip]
else
next
end
end
rescue ::Timeout::Error
end
rescue ::Timeout::Error
end
nil
end

# Creates a full ARP packet, mainly for use with inject_eth()
Expand Down

0 comments on commit bdd5276

Please sign in to comment.