This commit adds a new set of discovery modules from wuntee and some useful utility methods for working with link-local addresses

git-svn-id: file:///home/svn/framework3/trunk@11417 4d416f70-5f16-0410-b530-b9f4589650da
This commit is contained in:
HD Moore 2010-12-27 16:43:53 +00:00
parent 0468dbeabe
commit 30affd4b2c
6 changed files with 619 additions and 23 deletions

View File

@ -0,0 +1,263 @@
##
# $Id$
##
module Msf
###
#
# This module provides common tools for IPv6
#
###
module Exploit::Remote::Ipv6
require 'racket'
#
# Initializes an instance of an exploit module that captures traffic
#
def initialize(info = {})
super
register_options(
[
OptString.new('INTERFACE', [false, 'The name of the interface']),
OptString.new("SMAC", [ false, "The source MAC address"]),
OptAddress.new("SHOST", [ false, "The source IPv6 address" ] ),
OptInt.new("TIMEOUT", [ true, "Timeout when waiting for host response.", 5])
], Msf::Exploit::Remote::Ipv6
)
begin
require 'pcaprub'
@pcaprub_loaded = true
rescue ::Exception => e
@pcaprub_loaded = false
@pcaprub_error = e
end
end
#
# Shortcut method for resolving our local interface name
#
def ipv6_interface(opts={})
opts['INTERFACE'] || datastore['INTERFACE'] || ::Pcap.lookupdev
end
#
# Shortcut method for determining our link-local address
#
def ipv6_link_address(opts={})
Rex::Socket.ipv6_link_address(ipv6_interface(opts))
end
#
# Shortcut method for determining our MAC address
#
def ipv6_mac(opts={})
Rex::Socket.ipv6_mac(ipv6_interface(opts))
end
#
# Opens a pcaprub capture interface to inject packets, and sniff ICMPv6 packets
#
def open_icmp_pcap(opts = {})
check_pcaprub_loaded
dev = ipv6_interface(opts)
len = 65535
tim = 0
@ipv6_icmp6_capture = ::Pcap.open_live(dev, len, true, tim)
@ipv6_icmp6_capture.setfilter("icmp6")
end
#
# Close the capture interface
#
def close_icmp_pcap()
check_pcaprub_loaded
return if not @ipv6_icmp6_capture
@ipv6_icmp6_capture = nil
GC.start()
end
#
# Send out a ICMPv6 neighbor solicitation, and
# return the associated MAC address
#
def solicit_ipv6_mac(dhost, opts = {})
check_pcaprub_loaded
dhost_intf = dhost + '%' + ipv6_interface(opts)
smac = opts['SMAC'] || datastore['SMAC'] || ipv6_mac
shost = opts['SHOST'] || datastore['SHOST'] || Rex::Socket.source_address(dhost_intf)
timeout = opts['TIMEOUT'] || datastore['TIMEOUT'] || 3
open_icmp_pcap()
p = Racket::Racket.new()
p.l2 = Racket::L2::Ethernet.new()
p.l2.src_mac = smac
p.l2.dst_mac = Racket::L3::Misc.soll_mcast_mac(dhost)
p.l2.ethertype = Racket::L2::Ethernet::ETHERTYPE_IPV6
p.l3 = Racket::L3::IPv6.new()
p.l3.src_ip = Racket::L3::Misc.ipv62long(shost)
p.l3.dst_ip = Racket::L3::Misc.ipv62long(Racket::L3::Misc.soll_mcast_addr6(dhost))
p.l3.ttl = 255
p.l3.nhead = 0x3a
p.l3.fix!()
p.l4 = Racket::L4::ICMPv6NeighborSolicitation.new()
p.l4.address = Racket::L3::Misc.ipv62long(dhost)
p.l4.add_option(0x01, Racket::L2::Misc.mac2string(p.l2.src_mac))
p.l4.fix!(p.l3.src_ip, p.l3.dst_ip)
@ipv6_icmp6_capture.inject(p.pack())
# Wait for a response
max_epoch = ::Time.now.to_i + timeout
while(::Time.now.to_i < max_epoch)
pkt = @ipv6_icmp6_capture.next()
next if not pkt
eth = Racket::L2::Ethernet.new(pkt)
next if eth.ethertype != Racket::L2::Ethernet::ETHERTYPE_IPV6
ipv6 = Racket::L3::IPv6.new(eth.payload)
next if ipv6.nhead != 0x3a
icmpv6 = Racket::L4::ICMPv6.new(ipv6.payload)
next if icmpv6.type != Racket::L4::ICMPv6Generic::ICMPv6_TYPE_NEIGHBOR_ADVERTISEMENT
icmpv6 = Racket::L4::ICMPv6NeighborAdvertisement.new(ipv6.payload)
if(icmpv6 and
ipv6.dst_ip == Racket::L3::Misc.ipv62long(shost) and
ipv6.src_ip == Racket::L3::Misc.ipv62long(dhost))
icmpv6options = icmpv6.get_options()
icmpv6options.each() do |opt|
id = opt[1]
if(id == ICMPv6OptionLinkAddress::ICMPv6_OPTION_TYPE_ID)
addr = ICMPv6OptionLinkAddress.new(opt[2]).lladdr
close_icmp_pcap()
return(addr)
end
end
# If there is no addr option, return the ethernet mac
close_icmp_pcap()
return(eth.src_mac)
end
end
close_icmp_pcap()
return(nil)
end
#
# Send a ICMPv6 Echo Request, and wait for the
# associated ICMPv6 Echo Response
#
def ping6(dhost, opts={})
check_pcaprub_loaded
dhost_intf = dhost + '%' + ipv6_interface(opts)
smac = opts['SMAC'] || datastore['SMAC'] || ipv6_mac
shost = opts['SHOST'] || datastore['SHOST'] || Rex::Socket.source_address(dhost_intf)
dmac = opts['DMAC'] || solicit_ipv6_mac(dhost)
timeout = opts['TIMEOUT'] || datastore['TIMEOUT']
wait = opts['WAIT']
if(wait.eql?(nil))
wait = true
end
dmac.eql?(nil) and return false
open_icmp_pcap()
# Create ICMPv6 Request
p = Racket::Racket.new()
p.l2 = Racket::L2::Ethernet.new()
p.l2.src_mac = smac
p.l2.dst_mac = dmac
p.l2.ethertype = Racket::L2::Ethernet::ETHERTYPE_IPV6
p.l3 = Racket::L3::IPv6.new()
p.l3.src_ip = Racket::L3::Misc.ipv62long(shost)
p.l3.dst_ip = Racket::L3::Misc.ipv62long(dhost)
p.l3.nhead = 0x3a
p.l3.fix!()
p.l4 = Racket::L4::ICMPv6EchoRequest.new()
p.l4.id = rand(65000)
p.l4.sequence = 1
p.l4.payload = Rex::Text.rand_text(8)
p.l4.fix!(p.l3.src_ip, p.l3.dst_ip)
@ipv6_icmp6_capture.inject(p.pack())
if(wait.eql?(true))
print_status("Waiting for ping reply...")
print_line("")
# Wait for a response
max_epoch = ::Time.now.to_i + timeout
while(::Time.now.to_i < max_epoch)
pkt = @ipv6_icmp6_capture.next()
next if not pkt
eth = Racket::L2::Ethernet.new(pkt)
next if eth.ethertype != Racket::L2::Ethernet::ETHERTYPE_IPV6
ipv6 = Racket::L3::IPv6.new(eth.payload)
next if ipv6.nhead != 0x3a
icmpv6 = Racket::L4::ICMPv6.new(ipv6.payload)
next if icmpv6.type != Racket::L4::ICMPv6Generic::ICMPv6_TYPE_ECHO_REPLY
icmpv6 = Racket::L4::ICMPv6EchoReply.new(ipv6.payload)
if(icmpv6 and
ipv6.dst_ip == p.l3.src_ip and
ipv6.src_ip == p.l3.dst_ip and
icmpv6.id == p.l4.id and
icmpv6.sequence == p.l4.sequence)
close_icmp_pcap()
return(true)
end
end # End while
end
close_icmp_pcap()
return(false)
end
def check_pcaprub_loaded
unless @pcaprub_loaded
print_status("The Pcaprub module is not available: #{@pcaprub_error}")
raise RuntimeError, "Pcaprub not available"
else
true
end
end
class ICMPv6OptionLinkAddress < RacketPart
ICMPv6_OPTION_TYPE_ID = 1
hex_octets :lladdr, 48
def initialize(*args)
super(*args)
end
end
end
end

View File

@ -27,6 +27,7 @@ require 'msf/core/exploit/cmdstager_tftp'
require 'msf/core/exploit/tcp'
require 'msf/core/exploit/udp'
require 'msf/core/exploit/ip'
require 'msf/core/exploit/ipv6'
require 'msf/core/exploit/dhcp'
require 'msf/core/exploit/smb'
require 'msf/core/exploit/ftp'

View File

@ -479,11 +479,32 @@ module Socket
)
r = s.getsockname[1]
s.close
return r
# Trim off the trailing interface ID for link-local IPv6
return r.split('%').first
rescue ::Exception
return '127.0.0.1'
end
end
#
# Identifies the link-local address of a given interface (if IPv6 is enabled)
#
def self.ipv6_link_address(intf)
r = source_address("FF02::1%#{intf}")
return if not (r and r =~ /^fe80/i)
r
end
#
# Identifies the mac address of a given interface (if IPv6 is enabled)
#
def self.ipv6_mac(intf)
r = ipv6_link_address(intf)
return if not r
raw = addr_aton(r)[-8, 8]
(raw[0,3] + raw[5,3]).unpack("C*").map{|c| "%.2x" % c}.join(":")
end
#
# Create a TCP socket pair.

View File

@ -0,0 +1,88 @@
##
# $Id$
##
require 'msf/core'
class Metasploit3 < Msf::Auxiliary
include Msf::Exploit::Remote::Capture
include Msf::Exploit::Remote::Ipv6
include Msf::Auxiliary::Report
def initialize
super(
'Name' => 'IPv6 Link Local/Node Local Ping Discovery',
'Version' => '$Revision$',
'Description' => %q{
Send a ICMPv6 ping request to all default multicast addresses, and wait to see who responds.
},
'Author' => 'wuntee',
'License' => MSF_LICENSE,
'References' =>
[
['URL','http://wuntee.blogspot.com/2010/12/ipv6-ping-host-discovery-metasploit.html']
]
)
deregister_options('SNAPLEN', 'FILTER', 'RHOST', 'PCAPFILE')
end
def listen_for_ping_response(opts = {})
hosts = {}
timeout = opts['TIMEOUT'] || datastore['TIMEOUT']
prefix = opts['PREFIX'] || datastore['PREFIX']
max_epoch = ::Time.now.to_i + timeout
while(::Time.now.to_i < max_epoch)
pkt = capture.next()
next if not pkt
eth = Racket::L2::Ethernet.new(pkt)
next if not eth.ethertype.eql?(Racket::L2::Ethernet::ETHERTYPE_IPV6)
ipv6 = Racket::L3::IPv6.new(eth.payload)
next if not ipv6.nhead == 0x3a
icmpv6 = Racket::L4::ICMPv6.new(ipv6.payload)
next if not icmpv6.type == Racket::L4::ICMPv6Generic::ICMPv6_TYPE_ECHO_REPLY
icmpv6 = Racket::L4::ICMPv6EchoReply.new(ipv6.payload)
host_addr = Racket::L3::Misc.long2ipv6(ipv6.src_ip)
host_mac = eth.src_mac
if(!hosts[host_addr].eql?(host_mac))
hosts[host_addr] = host_mac
print_status(" |*| #{host_addr} => #{host_mac}")
# report_host(:mac => host_mac, :host => host_addr)
end
end
return(hosts)
end
def run
# Start caputre
open_pcap({'FILTER' => "icmp6"})
# Send ping
print_status("Sending multicast pings...")
dmac = "33:33:00:00:00:01"
# Figure out our source address by the link-local interface
shost = ipv6_link_address
ping6("FF01::1", {"DMAC" => dmac, "SHOST" => shost, "WAIT" => false})
ping6("FF01::2", {"DMAC" => dmac, "SHOST" => shost, "WAIT" => false})
ping6("FF02::1", {"DMAC" => dmac, "SHOST" => shost, "WAIT" => false})
ping6("FF02::2", {"DMAC" => dmac, "SHOST" => shost, "WAIT" => false})
# Listen for host advertisments
print_status("Listening for responses...")
listen_for_ping_response()
# Close capture
close_pcap()
end
end

View File

@ -33,7 +33,7 @@ class Metasploit3 < Msf::Auxiliary
register_options(
[
OptString.new('SHOST', [true, "Source IP Address"]),
OptString.new('SHOST', [false, "Source IP Address"]),
OptString.new('SMAC', [true, "Source MAC Address"]),
], self.class)
@ -45,7 +45,8 @@ class Metasploit3 < Msf::Auxiliary
end
def run_batch(hosts)
print_status("IPv4 Hosts Discovery")
print_status("Discovering IPv4 nodes via ARP...")
print_status("")
shost = datastore['SHOST']
smac = datastore['SMAC']
@ -55,31 +56,37 @@ class Metasploit3 < Msf::Auxiliary
open_pcap({'SNAPLEN' => 68, 'FILTER' => "arp[6:2] == 0x0002"})
begin
found = {}
hosts.each do |dhost|
shost = datastore['SHOST'] || Rex::Socket.source_address(dhost)
probe = buildprobe(datastore['SHOST'], datastore['SMAC'], dhost)
capture.inject(probe)
while(reply = getreply())
next if not reply[:arp]
print_status("#{reply[:arp].spa} is alive.")
addrs << [reply[:arp].spa, reply[:arp].sha]
report_host(:host => reply[:arp].spa, :mac=>reply[:arp].sha)
if not found[reply[:arp].spa]
print_status(sprintf(" %16s ALIVE",reply[:arp].spa))
addrs << [reply[:arp].spa, reply[:arp].sha]
report_host(:host => reply[:arp].spa, :mac=>reply[:arp].sha)
found[reply[:arp].spa] = true
end
end
end
etime = Time.now.to_f + (hosts.length * 0.05)
etime = ::Time.now.to_f + (hosts.length * 0.05)
while (Time.now.to_f < etime)
while (::Time.now.to_f < etime)
while(reply = getreply())
next if not reply[:arp]
print_status("#{reply[:arp].spa} is alive.")
addrs << [reply[:arp].spa, reply[:arp].sha]
if not found[reply[:arp].spa]
print_status(sprintf(" %16s ALIVE",reply[:arp].spa))
addrs << [reply[:arp].spa, reply[:arp].sha]
report_host(:host => reply[:arp].spa, :mac=>reply[:arp].sha)
found[reply[:arp].spa] = true
end
end
Kernel.select(nil, nil, nil, 0.50)
::IO.select(nil, nil, nil, 0.50)
end
ensure
@ -89,7 +96,6 @@ class Metasploit3 < Msf::Auxiliary
neighbor_discovery(addrs)
end
def map_neighbor(nodes, adv)
nodes.each do |node|
ipv4_addr, mac_addr = node
@ -103,10 +109,10 @@ class Metasploit3 < Msf::Auxiliary
nil
end
def neighbor_discovery(neighs)
print_status("IPv6 Neighbor Discovery")
print_status("Discovering IPv6 addresses for IPv4 nodes...")
print_status("")
smac = datastore['SMAC']
open_pcap({'SNAPLEN' => 68, 'FILTER' => "icmp6"})
@ -127,22 +133,22 @@ class Metasploit3 < Msf::Auxiliary
addr = map_neighbor(neighs, adv)
next if not addr
print_status("#{addr[:ipv4]} maps to IPv6 link local address #{addr[:ipv6]}")
print_status(sprintf(" %16s maps to %s",addr[:ipv4], addr[:ipv6]))
end
end
etime = Time.now.to_f + (neighs.length * 0.5)
etime = ::Time.now.to_f + (neighs.length * 0.5)
while (Time.now.to_f < etime)
while (::Time.now.to_f < etime)
while(adv = getadvertisement())
next if not adv[:icmpv6]
addr = map_neighbor(neighs, adv)
next if not addr
print_status("#{addr[:ipv4]} maps to IPv6 link local address #{addr[:ipv6]}")
print_status(sprintf(" %16s maps to %s",addr[:ipv4], addr[:ipv6]))
end
Kernel.select(nil, nil, nil, 0.50)
::IO.select(nil, nil, nil, 0.50)
end
ensure

View File

@ -0,0 +1,217 @@
##
# $Id$
##
require 'msf/core'
class Metasploit3 < Msf::Auxiliary
include Msf::Exploit::Remote::Capture
include Msf::Exploit::Remote::Ipv6
include Msf::Auxiliary::Report
def initialize
super(
'Name' => 'IPv6 Local Neighbor Discovery Using Router Advertisment',
'Version' => '$Revision$',
'Description' => %q{
Send a spoofed router advertisment with high priority to force hosts to
start the IPv6 address auto-config. Monitor for IPv6 host advertisments,
and try to guess the link-local address by concatinating the prefix, and
the host portion of the IPv6 address. Use NDP host solicitation to
determine if the IP address is valid'
},
'Author' => 'wuntee',
'License' => MSF_LICENSE,
'References' =>
[
['URL','http://wuntee.blogspot.com/2010/11/ipv6-link-local-host-discovery-concept.html']
]
)
register_options(
[
OptInt.new('TIMEOUT_NEIGHBOR', [true, "Time (seconds) to listen for a solicitation response.", 1])
], self.class)
register_advanced_options(
[
OptString.new('PREFIX', [true, "Prefix that each host should get an IPv6 address from",
"2001:1234:DEAD:BEEF::"]
)
], self.class)
deregister_options('SNAPLEN', 'FILTER', 'RHOST', 'PCAPFILE')
end
def listen_for_neighbor_solicitation(opts = {})
hosts = []
timeout = opts['TIMEOUT'] || datastore['TIMEOUT']
prefix = opts['PREFIX'] || datastore['PREFIX']
max_epoch = ::Time.now.to_i + timeout
autoconf_prefix = IPAddr.new(prefix).to_string().slice(0..19)
while(::Time.now.to_i < max_epoch)
pkt = capture.next()
next if not pkt
eth = Racket::L2::Ethernet.new(pkt)
next if not eth.ethertype.eql?(Racket::L2::Ethernet::ETHERTYPE_IPV6)
ipv6 = Racket::L3::IPv6.new(eth.payload)
next if not ipv6.nhead == 0x3a
icmpv6 = Racket::L4::ICMPv6.new(ipv6.payload)
next if not icmpv6.type == Racket::L4::ICMPv6Generic::ICMPv6_TYPE_NEIGHBOR_SOLICITATION
icmpv6 = Racket::L4::ICMPv6NeighborAdvertisement.new(ipv6.payload)
host_addr = Racket::L3::Misc.long2ipv6(icmpv6.address)
# Make sure host portion is the same as what we requested
host_addr_prefix = IPAddr.new(host_addr).to_string().slice(0..19)
next if not host_addr_prefix.eql?(autoconf_prefix)
next if not hosts.index(host_addr).eql?(nil)
hosts.push(host_addr)
print_status(" |*| #{host_addr}")
end
return(hosts)
end
def find_link_local(opts = {})
shost = opts['SHOST'] || datastore['SHOST'] || ipv6_link_address
hosts = opts['HOSTS'] || []
smac = opts['SMAC'] || datastore['SMAC'] || ipv6_mac
timeout = opts['TIMEOUT_NEIGHBOR'] || datastore['TIMEOUT_NEIGHBOR']
network_prefix = Rex::Socket.addr_aton(shost)[0,8]
hosts.each() do |g|
host_postfix = Rex::Socket.addr_aton(g)[8,8]
local_ipv6 = Rex::Socket.addr_ntoa(network_prefix + host_postfix)
mac = solicit_ipv6_mac(local_ipv6, {"TIMEOUT" => timeout})
if mac
# report_host(:mac => mac, :host => local_ipv6)
print_status(" |*| #{local_ipv6} -> #{mac}")
end
end
end
def create_router_advertisment(opts = {})
dhost = "FF02::1"
smac = opts['SMAC'] || datastore['SMAC'] || ipv6_mac
shost = opts['SHOST'] || datastore['SHOST'] || ipv6_link_address
lifetime = opts['LIFETIME'] || datastore['TIMEOUT']
prefix = opts['PREFIX'] || datastore['PREFIX']
plen = 64
dmac = "33:33:00:00:00:01"
p = Racket::Racket.new
p.l2 = Racket::L2::Ethernet.new()
p.l2.src_mac = smac
p.l2.dst_mac = dmac
p.l2.ethertype = Racket::L2::Ethernet::ETHERTYPE_IPV6
p.l3 = Racket::L3::IPv6.new()
p.l3.ttl = 255
p.l3.nhead = 58
p.l3.src_ip = Racket::L3::Misc.ipv62long(shost)
p.l3.dst_ip = Racket::L3::Misc.ipv62long(dhost)
p.l4 = ICMPv6RouterAdvertisementFixed.new()
p.l4.managed_config = 0
p.l4.other_config = 0
p.l4.preference = 1
p.l4.lifetime = 1800
p.l4.hop_limit = 0
# OPTION lladdress
option_dst_lladdr = ICMPv6OptionLinkAddress.new()
option_dst_lladdr.lladdr = smac
p.l4.add_option(ICMPv6OptionLinkAddress::ICMPv6_OPTION_TYPE_ID, option_dst_lladdr)
# OPTION Prefix Information
option_prefix = ICMPv6OptionPrefixInformation.new()
option_prefix.plen = plen
option_prefix.on_link = 1
option_prefix.addrconf = 1
option_prefix.valid_lifetime = lifetime
option_prefix.preferred_lifetime = lifetime
option_prefix.prefix = Racket::L3::Misc.ipv62long(prefix)
p.l4.add_option(ICMPv6OptionPrefixInformation::ICMPv6_OPTION_TYPE_ID, option_prefix)
p.l4.fix!(p.l3.src_ip, p.l3.dst_ip)
return(p)
end
def run
# Start caputure
open_pcap({'FILTER' => "icmp6"})
# Send router advertisment
print_status("Sending router advertisment...")
pkt = create_router_advertisment()
capture.inject(pkt.pack())
# Listen for host advertisments
print_status("Listening for neighbor solicitation...")
hosts = listen_for_neighbor_solicitation()
if(hosts.size() == 0)
print_status("No hosts were seen sending a neighbor solicitation")
else
# Attempt to get link local addresses
print_status("Attempting to solicit link-local addresses...")
find_link_local({"HOSTS" => hosts})
end
# Close capture
close_pcap()
end
class ICMPv6OptionPrefixInformation < RacketPart
ICMPv6_OPTION_TYPE_ID = 3
unsigned :plen, 8
unsigned :on_link, 1
unsigned :addrconf, 1
unsigned :reserved, 6
unsigned :valid_lifetime, 32
unsigned :preferred_lifetime, 32
unsigned :reserved2, 32
unsigned :prefix, 128
def initialize(*args)
super(*args)
end
end
class ICMPv6RouterAdvertisementFixed < Racket::L4::ICMPv6Generic
# default value that should be placed in the hop count field of the IP header
# for outgoing IP packets
unsigned :hop_limit, 8
# boolean, managed address configuration?
unsigned :managed_config, 1
# boolean, other configuration?
unsigned :other_config, 1
unsigned :home_config, 1
unsigned :preference, 2
unsigned :proxied, 1
# set to 0, never used.
unsigned :reserved, 2
# lifetime associated with the default router in seconds
unsigned :lifetime, 16
# time in milliseconds that a node assumes a neighbor is reachable after
# having received a reachability confirmation
unsigned :reachable_time, 32
# time in milliseconds between retransmitted neighbor solicitation messages
unsigned :retrans_time, 32
rest :payload
def initialize(*args)
super(*args)
self.type = ICMPv6_TYPE_ROUTER_ADVERTISEMENT
end
end
end