DHCP服务器发现扫描器,局域网有时会有多个DHCP服务器导致网络不稳定,于是有了这个脚本。
"""
DHCP服务器发现脚本
需要管理员权限运行
依赖: scapy
安装: pip install scapy
"""import socket
import struct
import random
import time
import sys
import os
import platform
import argparse
from datetime import datetime
from collections import defaultdict
try:from scapy.all import *from scapy.layers.dhcp import DHCP, BOOTPfrom scapy.layers.inet import IP, UDPfrom scapy.layers.l2 import Ether, ARP
except ImportError:print("错误: 需要安装scapy库")print("请运行: pip install scapy")sys.exit(1)class DHCPScanner:def __init__(self, interface=None, timeout=5, refresh_interval=10, verbose=False):"""初始化DHCP扫描器:param interface: 网络接口名称,None则自动选择:param timeout: 等待响应超时时间(秒):param refresh_interval: 自动刷新间隔(秒):param verbose: 是否显示详细调试信息"""self.interface = interface or conf.ifaceself.timeout = timeoutself.refresh_interval = refresh_intervalself.dhcp_servers = {}self.transaction_id = random.randint(1, 0xFFFFFFFF)self.verbose = verbosetry:self.client_mac = get_if_hwaddr(self.interface)self.client_ip = get_if_addr(self.interface)except:print(f"错误: 无法获取接口 {self.interface} 的信息")print("可用接口:", get_if_list())sys.exit(1)def generate_dhcp_discover(self):"""生成DHCP DISCOVER数据包"""ether = Ether(dst="ff:ff:ff:ff:ff:ff", src=self.client_mac)ip = IP(src="0.0.0.0", dst="255.255.255.255")udp = UDP(sport=68, dport=67)bootp = BOOTP(op=1,  chaddr=mac2str(self.client_mac),xid=self.transaction_id)dhcp = DHCP(options=[("message-type", "discover"),("param_req_list", [1, 3, 6, 15, 28, 51, 58, 59]),  "end"])packet = ether / ip / udp / bootp / dhcpreturn packetdef parse_dhcp_options(self, options):"""解析DHCP选项"""parsed = {}for option in options:if isinstance(option, tuple) and len(option) >= 2:key, value = option[0], option[1]if key == "message-type":parsed["Message Type"] = valueelif key == "server_id":parsed["Server IP"] = valueelif key == "subnet_mask":parsed["Subnet Mask"] = valueelif key == "router":parsed["Gateway"] = value if isinstance(value, str) else ", ".join(value)elif key == "name_server" or key == "domain_name_server":parsed["DNS Servers"] = value if isinstance(value, str) else ", ".join(value)elif key == "domain":parsed["Domain Name"] = value.decode() if isinstance(value, bytes) else valueelif key == "lease_time":parsed["Lease Time"] = f"{value} seconds ({value//3600}h {(value%3600)//60}m)"elif key == "renewal_time":parsed["Renewal Time"] = f"{value} seconds"elif key == "rebinding_time":parsed["Rebinding Time"] = f"{value} seconds"elif key == "broadcast_address":parsed["Broadcast Address"] = valueelif key == "hostname":parsed["Server Hostname"] = value.decode() if isinstance(value, bytes) else valuereturn parseddef get_mac_vendor(self, mac):"""获取MAC地址厂商信息(简化版)"""try:oui = mac.replace(":", "").upper()[:6]return "Unknown"except:return "Unknown"def get_arp_info(self, ip):"""获取指定IP的ARP信息"""try:ans, _ = srp(Ether(dst="ff:ff:ff:ff:ff:ff") / ARP(pdst=ip),timeout=2,verbose=0,iface=self.interface)if ans:return ans[0][1].hwsrcreturn Noneexcept:return Nonedef scan_once(self):"""执行一次DHCP发现扫描"""print(f"\n{'='*80}")print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] 开始DHCP服务器扫描...")print(f"接口: {self.interface}")print(f"本机MAC: {self.client_mac}")print(f"本机IP: {self.client_ip}")print(f"{'='*80}\n")print("方法1: 使用二层广播发送DHCP DISCOVER...")discover_packet = self.generate_dhcp_discover()if self.verbose:print("\n发送的数据包:")discover_packet.show()ans, unans = srp(discover_packet,iface=self.interface,timeout=self.timeout,verbose=self.verbose,multi=True)if ans:print(f"方法1收到 {len(ans)} 个响应")for sent, received in ans:if DHCP in received:self.process_dhcp_response(received)else:print("方法1未收到响应")print("\n方法2: 使用监听模式发送DHCP DISCOVER...")self.transaction_id = random.randint(1, 0xFFFFFFFF)discover_packet2 = self.generate_dhcp_discover()captured_packets = []def packet_callback(pkt):if DHCP in pkt and BOOTP in pkt:if pkt[BOOTP].xid == self.transaction_id:captured_packets.append(pkt)if self.verbose:print(f"\n捕获到DHCP响应: {pkt[Ether].src}")sniffer = AsyncSniffer(iface=self.interface,prn=packet_callback,filter="udp and (port 67 or port 68)",store=False)sniffer.start()time.sleep(0.5)print("发送DHCP DISCOVER请求 (发送3次以提高成功率)...")for i in range(3):sendp(discover_packet2, iface=self.interface, verbose=self.verbose)time.sleep(0.5)print(f"等待响应 ({self.timeout}秒)...")time.sleep(self.timeout)sniffer.stop()if captured_packets:print(f"方法2捕获到 {len(captured_packets)} 个响应")for pkt in captured_packets:self.process_dhcp_response(pkt)else:print("方法2未捕获到响应")if not self.dhcp_servers:print("\n" + "="*80)print("未发现任何DHCP服务器")print("="*80)print("\n可能的原因:")print("1. 网络中没有DHCP服务器")print("2. 防火墙阻止了DHCP流量")print("3. 网络接口选择不正确")print("4. Npcap未正确安装或配置")print("\n解决建议:")print("- 尝试使用 -l 参数列出所有网络接口")print("- 使用 -i 参数指定正确的网络接口")print("- 检查Windows防火墙设置")print("- 确保Npcap已安装并选择了WinPcap兼容模式")print("- 使用 -v 参数查看详细调试信息")returnself.display_servers()def process_dhcp_response(self, packet):"""处理DHCP响应包"""if BOOTP in packet and DHCP in packet:server_mac = packet[Ether].srcoffered_ip = packet[BOOTP].yiaddrdhcp_options = self.parse_dhcp_options(packet[DHCP].options)server_ip = dhcp_options.get("Server IP", "Unknown")server_key = f"{server_ip}_{server_mac}"self.dhcp_servers[server_key] = {"Server IP": server_ip,"Server MAC": server_mac,"Offered IP": offered_ip,"Options": dhcp_options,"Last Seen": datetime.now(),"Vendor": self.get_mac_vendor(server_mac)}def display_servers(self):"""显示发现的DHCP服务器信息"""if not self.dhcp_servers:print("未发现DHCP服务器")returnfor idx, (key, server) in enumerate(self.dhcp_servers.items(), 1):if server['Server IP'] == 'Unknown' or server['Offered IP'] == '0.0.0.0':continueprint(f"\n{'─'*80}")print(f"DHCP服务器 #{idx}")print(f"{'─'*80}")print(f"Server IP地址:     {server['Server IP']}")print(f"Server MAC地址:    {server['Server MAC']}")print(f"MAC厂商:           {server['Vendor']}")print(f"提供的IP地址:      {server['Offered IP']}")print(f"最后发现时间:      {server['Last Seen'].strftime('%Y-%m-%d %H:%M:%S')}")print(f"\nDHCP配置信息:")options = server['Options']for key_opt, value in options.items():if key_opt != "Message Type":print(f"  {key_opt:20s}: {value}")print(f"\nARP验证:")arp_mac = self.get_arp_info(server['Server IP'])if arp_mac:match = "✓ 匹配" if arp_mac.lower() == server['Server MAC'].lower() else "✗ 不匹配"print(f"  ARP MAC地址:       {arp_mac} [{match}]")else:print(f"  ARP MAC地址:       无法获取")valid_servers = {k: v for k, v in self.dhcp_servers.items() if v['Server IP'] != 'Unknown' and v['Offered IP'] != '0.0.0.0'}if not valid_servers:print(f"\n{'='*80}")print("未发现有效的DHCP服务器")print(f"{'='*80}\n")returnprint(f"\n{'='*80}")print(f"DHCP服务器汇总表")print(f"{'='*80}\n")print(f"{'序号':<6} {'服务器IP':<16} {'服务器MAC':<19} {'网关':<16} {'DNS服务器':<20} {'租约时间':<15}")print(f"{'-'*6} {'-'*16} {'-'*19} {'-'*16} {'-'*20} {'-'*15}")for idx, (key, server) in enumerate(valid_servers.items(), 1):options = server['Options']server_ip = server['Server IP']server_mac = server['Server MAC']gateway = options.get('Gateway', 'N/A')dns = options.get('DNS Servers', 'N/A')lease_time = options.get('Lease Time', 'N/A')if len(dns) > 18:dns = dns[:15] + '...'if len(lease_time) > 13:lease_time = lease_time.split('(')[0].strip()print(f"{idx:<6} {server_ip:<16} {server_mac:<19} {gateway:<16} {dns:<20} {lease_time:<15}")print(f"\n{'='*80}")print(f"网络配置详情")print(f"{'='*80}\n")for idx, (key, server) in enumerate(valid_servers.items(), 1):options = server['Options']print(f"服务器 #{idx} ({server['Server IP']}):")print(f"  ├─ 子网掩码:      {options.get('Subnet Mask', 'N/A')}")print(f"  ├─ 网关:          {options.get('Gateway', 'N/A')}")print(f"  ├─ DNS服务器:     {options.get('DNS Servers', 'N/A')}")print(f"  ├─ 域名:          {options.get('Domain Name', 'N/A')}")print(f"  ├─ 租约时间:      {options.get('Lease Time', 'N/A')}")print(f"  ├─ 广播地址:      {options.get('Broadcast Address', 'N/A')}")print(f"  └─ 提供IP:        {server['Offered IP']}")print()print(f"{'='*80}")print(f"✓ 总共发现 {len(valid_servers)} 个有效的DHCP服务器")print(f"{'='*80}\n")def continuous_scan(self):"""持续扫描模式"""print(f"持续扫描模式启动 (刷新间隔: {self.refresh_interval}秒)")print("按 Ctrl+C 停止扫描\n")try:while True:self.scan_once()print(f"等待 {self.refresh_interval} 秒后进行下次扫描...")time.sleep(self.refresh_interval)except KeyboardInterrupt:print("\n\n扫描已停止")print(f"共发现 {len(self.dhcp_servers)} 个唯一的DHCP服务器")def is_admin():"""检查是否具有管理员/root权限(跨平台)"""try:if platform.system() == "Windows":import ctypesreturn ctypes.windll.shell32.IsUserAnAdmin() != 0else:return os.geteuid() == 0except:return Falsedef main():parser = argparse.ArgumentParser(description='DHCP服务器发现扫描器 - 扫描局域网中的所有DHCP服务器',formatter_class=argparse.RawDescriptionHelpFormatter,epilog="""
示例:%(prog)s                          # 使用默认设置扫描一次%(prog)s -c                       # 持续扫描模式%(prog)s -i eth0 -t 10 -r 30     # 指定接口,超时10秒,刷新间隔30秒%(prog)s -c -r 60                # 持续扫描,每60秒刷新一次%(prog)s -v                       # 显示详细调试信息%(prog)s -l                       # 列出所有网络接口注意: 此脚本需要管理员权限运行 (Windows上右键以管理员身份运行)""")parser.add_argument('-i', '--interface', help='网络接口名称 (例如: eth0, wlan0)')parser.add_argument('-t', '--timeout', type=int, default=5,help='等待DHCP响应的超时时间(秒),默认: 5')parser.add_argument('-r', '--refresh', type=int, default=10,help='持续扫描模式的刷新间隔(秒),默认: 10')parser.add_argument('-c', '--continuous', action='store_true',help='启用持续扫描模式')parser.add_argument('-l', '--list-interfaces', action='store_true',help='列出所有可用的网络接口')parser.add_argument('-v', '--verbose', action='store_true',help='显示详细调试信息')args = parser.parse_args()if args.list_interfaces:print("可用的网络接口:")for iface in get_if_list():try:ip = get_if_addr(iface)mac = get_if_hwaddr(iface)print(f"  {iface:15s} - IP: {ip:15s} MAC: {mac}")except:print(f"  {iface:15s} - 无法获取信息")sys.exit(0)if not is_admin():print("错误: 此脚本需要管理员权限运行")if platform.system() == "Windows":print("请使用: 右键点击 -> 以管理员身份运行")print("或在管理员命令提示符中运行")else:print("请使用: sudo python3", sys.argv[0])sys.exit(1)scanner = DHCPScanner(interface=args.interface,timeout=args.timeout,refresh_interval=args.refresh,verbose=args.verbose)if args.continuous:scanner.continuous_scan()else:scanner.scan_once()if __name__ == "__main__":main()
运行命令
python zz.py
python zz.py -c -r 30
python zz.py -l
运行结果
================================================================================
DHCP服务器汇总表
================================================================================序号     服务器IP            服务器MAC              网关               DNS服务器               租约时间
------ ---------------- ------------------- ---------------- -------------------- ---------------
1      192.168.88.1     bc:24:11:9a:05:8b   192.168.88.1     192.168.88.1         261000 seconds
2      192.168.9.1      bc:24:11:fc:d3:7e   192.168.9.1      114.114.114.114      7200 seconds================================================================================
网络配置详情
================================================================================服务器 ├─ 子网掩码:      255.255.255.0├─ 网关:          192.168.88.1├─ DNS服务器:     192.168.88.1├─ 域名:          N/A├─ 租约时间:      261000 seconds (72h 30m)├─ 广播地址:      N/A└─ 提供IP:        192.168.88.96服务器 ├─ 子网掩码:      255.255.255.0├─ 网关:          192.168.9.1├─ DNS服务器:     114.114.114.114├─ 域名:          N/A├─ 租约时间:      7200 seconds (2h 0m)├─ 广播地址:      N/A└─ 提供IP:        192.168.9.100================================================================================
✓ 总共发现 2 个有效的DHCP服务器
================================================================================