Parent

Included Modules

Class/Module Index [+]

Quicksearch

Rex::Proto::DHCP::Server

DHCP Server class not completely configurable - written specifically for a PXE server

extended to support testing/exploiting CVE-2011-0997

Attributes

broadcasta[RW]
context[RW]
current_ip[RW]
dnsserv[RW]
end_ip[RW]
give_hostname[RW]
ipstring[RW]
leasetime[RW]
listen_host[RW]
listen_port[RW]
myfilename[RW]
netmaskn[RW]
pxealtconfigfile[RW]
pxeconfigfile[RW]
pxepathprefix[RW]
pxereboottime[RW]
relayip[RW]
reporter[RW]
router[RW]
serveOnce[RW]
serveOnlyPXE[RW]
servePXE[RW]
served[RW]
served_hostname[RW]
served_over[RW]
sock[RW]
start_ip[RW]
thread[RW]

Public Class Methods

new(hash, context = {}) click to toggle source
# File lib/rex/proto/dhcp/server.rb, line 24
def initialize(hash, context = {})
        self.listen_host = '0.0.0.0' # clients don't already have addresses. Needs to be 0.0.0.0
        self.listen_port = 67 # mandatory (bootps)
        self.context = context
        self.sock = nil

        self.myfilename = hash['FILENAME'] || ""
        self.myfilename << ("\x00" * (128 - self.myfilename.length))

        source = hash['SRVHOST'] || Rex::Socket.source_address
        self.ipstring = Rex::Socket.addr_aton(source)

        ipstart = hash['DHCPIPSTART']
        if ipstart
                self.start_ip = Rex::Socket.addr_atoi(ipstart)
        else
                # Use the first 3 octects of the server's IP to construct the
                # default range of x.x.x.32-254
                self.start_ip = "#{self.ipstring[0..2]}\x20".unpack("N").first
        end
        self.current_ip = start_ip

        ipend = hash['DHCPIPEND']
        if ipend
                self.end_ip = Rex::Socket.addr_atoi(ipend)
        else
                # Use the first 3 octects of the server's IP to construct the
                # default range of x.x.x.32-254
                self.end_ip = "#{self.ipstring[0..2]}\xfe".unpack("N").first
        end

        # netmask
        netmask = hash['NETMASK'] || "255.255.255.0"
        self.netmaskn = Rex::Socket.addr_aton(netmask)

        # router
        router = hash['ROUTER'] || source
        self.router = Rex::Socket.addr_aton(router)

        # dns
        dnsserv = hash['DNSSERVER'] || source
        self.dnsserv = Rex::Socket.addr_aton(dnsserv)

        # broadcast
        if hash['BROADCAST']
                self.broadcasta = Rex::Socket.addr_aton(hash['BROADCAST'])
        else
                self.broadcasta = Rex::Socket.addr_itoa( self.start_ip | (Rex::Socket.addr_ntoi(self.netmaskn) ^ 0xffffffff) )
        end

        self.served = {}
        self.serveOnce = hash.include?('SERVEONCE')

        self.servePXE = (hash.include?('PXE') or hash.include?('FILENAME') or hash.include?('PXEONLY'))
        self.serveOnlyPXE = hash.include?('PXEONLY')

        # Always assume we don't give out hostnames ...
        self.give_hostname = false
        self.served_over = 0
        if (hash['HOSTNAME'])
                self.give_hostname = true
                self.served_hostname = hash['HOSTNAME']
                if ( hash['HOSTSTART'] )
                        self.served_over = hash['HOSTSTART'].to_i
                end
        end

        self.leasetime = 600
        self.relayip = "\x00\x00\x00\x00" # relay ip - not currently suported
        self.pxeconfigfile = "update2"
        self.pxealtconfigfile = "update0"
        self.pxepathprefix = ""
        self.pxereboottime = 2000
end

Public Instance Methods

report(&block) click to toggle source
# File lib/rex/proto/dhcp/server.rb, line 99
def report(&block)
        self.reporter = block
end
send_packet(ip, pkt) click to toggle source

Send a single packet to the specified host

# File lib/rex/proto/dhcp/server.rb, line 142
def send_packet(ip, pkt)
        port = 68 # bootpc
        if ip
                self.sock.sendto( pkt, ip, port )
        else
                if not self.sock.sendto( pkt, '255.255.255.255', port )
                        self.sock.sendto( pkt, self.broadcasta, port )
                end
        end
end
set_option(opts) click to toggle source

Set an option

# File lib/rex/proto/dhcp/server.rb, line 125
def set_option(opts)
        allowed_options = [
                :serveOnce, :pxealtconfigfile, :servePXE, :relayip, :leasetime, :dnsserv,
                :pxeconfigfile, :pxepathprefix, :pxereboottime, :router,
                :give_hostname, :served_hostname, :served_over, :serveOnlyPXE
        ]

        opts.each_pair { |k,v|
                next if not v
                if allowed_options.include?(k)
                        self.instance_variable_set("@#{k}", v)
                end
        }
end
start() click to toggle source

Start the DHCP server

# File lib/rex/proto/dhcp/server.rb, line 104
def start
        self.sock = Rex::Socket::Udp.create(
                'LocalHost' => listen_host,
                'LocalPort' => listen_port,
                'Context'   => context
        )

        self.thread = Rex::ThreadFactory.spawn("DHCPServerMonitor", false) {
                monitor_socket
        }
end
stop() click to toggle source

Stop the DHCP server

# File lib/rex/proto/dhcp/server.rb, line 117
def stop
        self.thread.kill
        self.served = {}
        self.sock.close rescue nil
end

Protected Instance Methods

dhcpoption(type, val = nil) click to toggle source
# File lib/rex/proto/dhcp/server.rb, line 181
def dhcpoption(type, val = nil)
        ret = ''
        ret << [type].pack('C')

        if val
                ret << [val.length].pack('C') + val
        end

        ret
end
dispatch_request(from, buf) click to toggle source

Dispatch a packet that we received

# File lib/rex/proto/dhcp/server.rb, line 193
def dispatch_request(from, buf)
        type = buf.unpack('C').first
        if (type != Request)
                #dlog("Unknown DHCP request type: #{type}")
                return
        end

        # parse out the members
        hwtype = buf[1,1]
        hwlen = buf[2,1].unpack("C").first
        hops = buf[3,1]
        txid = buf[4..7]
        elapsed = buf[8..9]
        flags = buf[10..11]
        clientip = buf[12..15]
        givenip = buf[16..19]
        nextip = buf[20..23]
        relayip = buf[24..27]
        clienthwaddr = buf[28..(27+hwlen)]
        servhostname = buf[44..107]
        filename = buf[108..235]
        magic = buf[236..239]

        if (magic != DHCPMagic)
                #dlog("Invalid DHCP request - bad magic.")
                return
        end

        messageType = 0
        pxeclient = false

        # options parsing loop
        spot = 240
        while (spot < buf.length - 3)
                optionType = buf[spot,1].unpack("C").first
                break if optionType == 0xff

                optionLen = buf[spot + 1,1].unpack("C").first
                optionValue = buf[(spot + 2)..(spot + optionLen + 1)]
                spot = spot + optionLen + 2
                if optionType == 53
                        messageType = optionValue.unpack("C").first
                elsif optionType == 150 or (optionType == 60 and optionValue.include? "PXEClient")
                        pxeclient = true
                end
        end

        # don't serve if only serving PXE and not PXE request
        return if pxeclient == false and self.serveOnlyPXE == true

        # prepare response
        pkt = [Response].pack('C')
        pkt << buf[1..7] #hwtype, hwlen, hops, txid
        pkt << "\x00\x00\x00\x00"  #elapsed, flags
        pkt << clientip

        # if this is somebody we've seen before, use the saved IP
        if self.served.include?( buf[28..43] )
                pkt << Rex::Socket.addr_iton(self.served[buf[28..43]][0])
        else # otherwise go to next ip address
                self.current_ip += 1
                if self.current_ip > self.end_ip
                        self.current_ip = self.start_ip
                end
                self.served.merge!( buf[28..43] => [ self.current_ip, messageType == DHCPRequest ] )
                pkt << Rex::Socket.addr_iton(self.current_ip)
        end
        pkt << self.ipstring #next server ip
        pkt << self.relayip
        pkt << buf[28..43] #client hw address
        pkt << servhostname
        pkt << self.myfilename
        pkt << magic
        pkt << "\x35\x01" #Option

        if messageType == DHCPDiscover  #DHCP Discover - send DHCP Offer
                pkt << [DHCPOffer].pack('C')
                # check if already served an Ack based on hw addr (MAC address)
                # if serveOnce & PXE, don't reply to another PXE request
                # if serveOnce & ! PXE, don't reply to anything
                if self.serveOnce == true and self.served.has_key?(buf[28..43]) and
                                self.served[buf[28..43]][1] and (pxeclient == false or self.servePXE == false)
                        return
                end
        elsif messageType == DHCPRequest #DHCP Request - send DHCP ACK
                pkt << [DHCPAck].pack('C')
                # now we ignore their discovers (but we'll respond to requests in case a packet was lost)
                if ( self.served_over != 0 )
                        # NOTE: this is sufficient for low-traffic net
                        # for high-traffic, this will probably lead to
                        # hostname collision
                        self.served_over += 1
                end
        else
                return  # ignore unknown DHCP request
        end

        # Options!
        pkt << dhcpoption(OpDHCPServer, self.ipstring)
        pkt << dhcpoption(OpLeaseTime, [self.leasetime].pack('N'))
        pkt << dhcpoption(OpSubnetMask, self.netmaskn)
        pkt << dhcpoption(OpRouter, self.router)
        pkt << dhcpoption(OpDns, self.dnsserv)
        if self.servePXE  # PXE options
                pkt << dhcpoption(OpPXEMagic, PXEMagic)
                # We already got this one, serve localboot file
                if self.serveOnce == true and self.served.has_key?(buf[28..43]) and
                                self.served[buf[28..43]][1] and pxeclient == true
                        pkt << dhcpoption(OpPXEConfigFile, self.pxealtconfigfile)
                else
                        # We are handing out an IP and our PXE attack
                        if(self.reporter)
                                self.reporter.call(buf[28..43],self.ipstring)
                        end
                        pkt << dhcpoption(OpPXEConfigFile, self.pxeconfigfile)
                end
                pkt << dhcpoption(OpPXEPathPrefix, self.pxepathprefix)
                pkt << dhcpoption(OpPXERebootTime, [self.pxereboottime].pack('N'))
                if ( self.give_hostname == true )
                        send_hostname = self.served_hostname
                        if ( self.served_over != 0 )
                                # NOTE : see above comments for the 'uniqueness' of this value
                                send_hostname += self.served_over.to_s
                        end
                        pkt << dhcpoption(OpHostname, send_hostname)
                end
        end
        pkt << dhcpoption(OpEnd)

        pkt << ("\x00" * 32) #padding

        # And now we mark as requested
        self.served[buf[28..43]][1] = true if messageType == DHCPRequest

        send_packet(nil, pkt)
end
monitor_socket() click to toggle source

See if there is anything to do.. If so, dispatch it.

# File lib/rex/proto/dhcp/server.rb, line 163
def monitor_socket
        while true
                rds = [@sock]
                wds = []
                eds = [@sock]

                r,w,e = ::IO.select(rds,wds,eds,1)

                if (r != nil and r[0] == self.sock)
                        buf,host,port = self.sock.recvfrom(65535)
                        # Lame compatabilitiy :-/
                        from = [host, port]
                        dispatch_request(from, buf)
                end

        end
end

[Validate]

Generated with the Darkfish Rdoc Generator 2.