Skip to content

Instantly share code, notes, and snippets.

@Juul
Last active March 2, 2025 06:43
Show Gist options
  • Save Juul/4972c1590e9044fa2c432011dbd1fa74 to your computer and use it in GitHub Desktop.
Save Juul/4972c1590e9044fa2c432011dbd1fa74 to your computer and use it in GitHub Desktop.
Python 3 script for detecting rogue DHCP servers
#!/usr/bin/env python3
# This script sends a DHCP discover on the specified interface
# then waits for the specified number of seconds (default: 5)
# for replies from DHCP servers and prints the results.
# Install dependencies:
# sudo apt install python3-scapy
# scapy API: https://scapy.readthedocs.io/en/latest/api/scapy.layers.html
# TODO
#
# * What if a single DHCP server replies twice
# or the rogue DHCP server is spoofing MAC addresses?
# * Support plain BOOTP servers
#
import os
import sys
from scapy.all import *
def die(err):
print(err, file=sys.stderr)
sys.exit(1)
def usage(file=sys.stdout):
print("Usage:", sys.argv[0], "<interface_name> [timeout in seconds]", file=file)
if len(sys.argv) < 2 or len(sys.argv) > 3:
usage(sys.stderr)
sys.exit(1)
iface = sys.argv[1]
timeout = 5
if len(sys.argv) == 3:
try:
timeout = int(sys.argv[2])
except:
usage(sys.stderr)
sys.exit(1)
if timeout < 1:
timeout = 5
if os.geteuid() != 0:
die("You must be root to send DHCP packets")
conf.checkIPaddr = False
try:
fam, hw = get_if_raw_hwaddr(iface)
except OSError as e:
die(e)
dhcp_discover = Ether(dst="ff:ff:ff:ff:ff:ff") / IP(src="0.0.0.0", dst="255.255.255.255") / UDP(sport=68, dport=67) / BOOTP(chaddr=hw) / DHCP(options=[("message-type","discover"), "end"])
print("Sending DHCP Discover packet on interface", iface)
print("Waiting", timeout, "seconds for reply")
ans, unans = srp(dhcp_discover, multi=True, timeout=timeout)
dhcp_servers = {}
for snd, rcv in ans:
# We only care about message-type == 2
# a.k.a. DHCP Offers
is_offer = False
for opt in rcv.payload.payload.payload.payload.options:
if opt[0] == 'message-type':
if opt[1] == 2:
is_offer = True
if not is_offer:
continue
# rcv.fields: list fields
# rcv.payload: the decoded payload
if not rcv.src in dhcp_servers:
dhcp_servers[rcv.src] = {'MAC': rcv.src, 'IP': rcv.payload.src, 'offer': rcv.payload.payload.payload.yiaddr, 'options': rcv.payload.payload.payload.payload.options}
print('')
print("Received", len(ans), "replies from", len(dhcp_servers), "different (based on MAC address) DHCP server(s)")
print('')
if len(dhcp_servers) > 1:
print("WARNING: Looks like there might be more than one DHCP server on your network")
print('')
for mac in dhcp_servers:
server = dhcp_servers[mac]
print("DHCP server", mac)
print(" Server IP:", server['IP'])
print(" Offer IP:", server['offer'])
print(" Options:")
for opt in server['options']:
if opt[0] == 'message-type':
print(' message-type:', DHCPTypes[opt[1]])
else:
print(' ' + str(opt[0])+':', opt[1])
print('')
@emielvanberlo
Copy link

emielvanberlo commented May 4, 2023

Quick and dirty run on Windows 11 with python 3.11.1:

First install https://npcap.com/ in winpcap compatible mode.
(winpcap latest version is from 2018, winpcap website refers to npcap)

pip install scaby

Then turn off python app execution aliases (in windows settings)

Uncomment lines 47 and 48:

if os.geteuid() != 0:
    die("You must be root to send DHCP packets")

that check doesn't work on windows.

Run script as an administrator.
If you install npcap there is an option to make it work for non administrators.
Did not test this, but I can run the script now :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment