DHCP的工作流程:
1:DHCP客户端在一个局域网中发送一个DHCP Discover 数据包,目的是发现能给他提供IP地址的DHCP服务器端
2:接收到DHCP Discover数据包的DHCP服务端会向请求的DHCP客户端发送一个DHCP Offer数据包,其中包含可以使用的IP地址
3:如果DHCP客户端接收到多个DHCP Offer数据包,那么只会处理最先收到的。然后DHCP客户端会广播一个DHCP Request数据包,声明自己选用DHCP服务器端和IP地址。
4:DHCP服务器端在接收到DHCP客户端广播的DHCP Request后,会判断该数据包中的IP地址是否与自己的地址相同。如果相同,就会向DHCP客户端发送一个响应的DHCP ACK数据包
目前为止,我们已经知道了DHCP的基本工作流程。下面让我们用Python来实现DHCP的客户端吧。
Python模拟DHCP客户端
1,DHCP Discover数据包构造
在上面,我们知道了DHCP客户端一共发送了四个数据包,而前面我们学习了scapy可以自己构造数据包,所以我们可以使用scapy来构造这四个数据包,但是困难在于每个数据包都需要设置Ether,IP,UDP,BOOTP.DHCP这五个协议的内容,而这五个协议就是由下至上的数据链路层,网络层,传输层,应用层,应用层管BOOTP和DHCP,而BOOTP是DHCP的前身。
(1)构造DHCP Discover数据包:我们知道DHCP工作的第一步是DHCP客户端发送DHCP Discover以广播的形式发送给DHCP服务端,然而客户端并不知道服务端的位置,所以在构造数据包时我们需要在Ether和IP部分将目标地址设置为广播地址,IP的广播地址:255.255.255.255,Ether的广播地址:ff:ff:ff:ff:ff:ff
(2),我们首先设置前三个协议,ether需要设置两个参数,一个是源mac地址,一个是目的mac地址,IP也是设置两个参数,由于此时没有源IP地址,所以设置为0.0.0.0,,表示未分配IP地址,作为临时的占位,UDP设置源端口和目的端口。
from scapy.all import Ether,RandMAC,IP,UDP
random_mac=str(RandMAC())
#这里是获得了一个随机的mac地址作为源mac地址
Ether_dis=Ether(src=random_mac,dst="ff:ff:ff:ff:ff:ff")
IP_dis=IP(src="0.0.0.0",dst="255.255.255.255")
UDP_dis=UDP(sport=67,dport=68)
(3),后面两个协议DHCP和BOOTP,我们首先来查看这两个协议由哪些内容构成,就知道需要哪些必要的参数
from scapy.all import ls,DHCP,BOOTP
print("下面是DHCP的")
ls(DHCP)
print("下面是BOOTP的")
ls(BOOTP)
我们能看出多数都是BOOTP的,所以在应用层,我们多数需要靠BOOTP()来实现,而BOOTP()的必要参数有两个,一个是chaddr,表示客户端的mac地址,一个是xid,表示事务ID,这个ID用于标识数据包,这样这个数据包就会是唯一的,一般使用一个随机数来作为xid参数的值。DHCP()中要设置的是optionss的值,用于指定DHCP数据包的选项字段,可以是IP,子网,网关,消息类型等等。
import binascii
from scapy.all import RandMAC,BOOTP,DHCP
import random
random_mac=str(RandMAC())
#这里是获得了一个随机的mac地址作为源mac地址
client_id=binascii.unhexlify(random_mac.replace(":",""))
#由于BOOTP的mac地址是没有:的,所以需要利用replace进行字符转换,将:转换为空' ',然后利用binascii.unhexlify将这一串mac地址转换为二进制
random_xid=random.randint(1,99999)
#random.randint是用于生成一个在1-99999范围内的交换标识符,也就是xid,注意xid是32位无符号位,所以范围是 0 到 4294967295。
BOOTP_dis = BOOTP(chaddr=client_id,xid=random_xid)
DHCP_dis=DHCP(options=[("message-type","discover"),"end"])
#message-type是指DHCP数据包的消息类型为discover,end表示DHCP数据包的结束字段为end
(4),以上五个协议我们都书写完了,最后就可以将所以协议放入一个数据包,组成完整的DHCP数据包
Discover=Ether_dis/IP_dis/UDP_dis/BOOTP_dis/DHCP_dis
(5),以上准备工作完成之后,考虑到我们的客户端并没有自己的IP地址,所以我们需要用前面学到的sendp()来发送数据包,但是发送的时候要选择合适的网卡,或者默认使用你的第一张网卡,如果你想选择网卡,windows系统中输入ipconfig,查看网卡信息,linux系统输入ifconfig查看网卡信息,我这里选择以太网作为我的网卡
sendp(Discover,iface='以太网')
这里为什么要用sendp()而不用send()呢,是因为DHCP是原始数据包,原始数据包包括以太网帧,ARP包,DHCP包,这些包是发往网络层的,所以叫原始数据包,而ICMP 包、TCP 包、UDP 包这些是经过协议处理过的包,这些是发往协议栈的,这些包使用send()来进行发送。
以下是全部代码:
import binascii
from scapy.all import RandMAC,BOOTP,DHCP,Ether,IP,UDP,sendp
import random
random_mac=str(RandMAC())
Ether_dis=Ether(src=random_mac,dst="ff:ff:ff:ff:ff:ff")
IP_dis=IP(src="0.0.0.0",dst="255.255.255.255")
UDP_dis=UDP(sport=67,dport=68)
client_id=binascii.unhexlify(random_mac.replace(":",""))
random_xid=random.randint(1,9999)
BOOTP_dis = BOOTP(chaddr=client_id,xid=random_xid)
DHCP_dis=DHCP(options=[("message-type","discover"),"end"])
Discover=Ether_dis/IP_dis/UDP_dis/BOOTP_dis/DHCP_dis
sendp(Discover,iface='以太网')
2,DHCP Offer数据包的捕获与解析
我们在wireshark中抓到了我们刚刚发出去的包,但是某些个人电脑原因,没有收到路由器返回的包,所以说这里只能展示一个包,正常来说是有两个
我们发送了DHCP数据包之后,就要捕获来自服务端的数据包,才能知道是否能给我们分配IP地址,所以我们可以用前面学过的sniff函数来进行过滤服务端返回的数据包,我们设置的目的端口dport是68,所以过滤语句这样写;
sniff(filter="src port 68")
#因为是由服务端发来,它作为源地址,所以这里是src而不是dst
捕获数据包后我们使用ls()函数查看数据包内容结构:
op : ByteEnumField =2 (1)
htype : ByteField =1 (1)
hlen : ByteField =6 (6)
hops : ByteField =0 (0)
xid : IntField =340091113 (0)
secs : ShortField =0 (0)
flags :FlagsFiled(16bits) =<Flag 0 ()> ( <Flag 0 ()>)
ciaddr :IPField ='0.0.0.0' ('0.0.0.0')
yiaddr :IPField ='192.168.1.105' ('0.0.0.0')
siaddr :IPField ='0.0.0.0' ('0.0.0.0')
giaddr :IPField ='0.0.0.0' ('0.0.0.0')
chaddr :Field =b'b7:95:a7:de:96:b' (b'')
sname : Field =b'\×00\×00\x00\x00\x00\×00\×00\×00\×00\×00\x00\x80\x0e\x00\x00\×00\x00\×00\x00\x20\x00\
file :Field =b'\×00\×00\x00\x00\x00\×00\×00\×00\×00\×00\x00\x80\x0e\x00\x00\×00\x00\×00\x00\x20\x00\
options : StrField =b'c\x82Sc' (b'')
--
options : DHCPOptionsField =[('message-type', 2), ('server_id', '192.168.1.1'), ('lease_time', 7200),('name_server')
--以上的是BOOTP的内容,--下面的是DHCP的内容,我们想要获取这里面的内容可以通过下标索引,我们把接收这个数据包的变量命名为pkt,所以以下是一些内容的具体索引表示:
1,本次DHCP的xid:pkt[BOOTP].xid,我们可以看出xid是在上面的BOOTP中的,在哪个部分就[]哪个部分,然后需要那个部分的哪个值就在后面.就可以了
2,客户端的mac地址chaddr:pkt[BOOTP].chaddr或者pkt[Ether].dst
3,服务端IP地址:我们发现在最下面的options后面的值中有server_id,这就是返回的服务器的IP,而--下面的属于DHCP,所以服务器端IP地址是pkt[DHCP].options[1][1],因为是多元组,所以ip的下标为[1][1],同理可得,server_id 的下标为[1][0],2的下标为[0][1],message-type的下标为[0][0]。
4,客户端向服务端请求的IP地址:pkt[BOOTP].yiaddr
3,DHCP Request数据包的构造
当我们获得服务端的Offer,知道哪些IP地址可以选用后,我们就要构造request数据包来声明自己选用的客户端和IP地址,这个数据包仍然是用广播的方式来发送。
1,Ether部分设置两个参数src和dst
Ether_request=Ether(src=pkt[Ether]dst,dst="ff:ff:ff:ff:ff:ff")
#pkt[Ether]dst是上面我们捕获到的服务端Offer包里面提供的客户端mac地址
2,IP部分设置两个参数src和dst,由于此时服务端还没有分配IP地址,所以还是0.0.0.0
IP_request=IP(src="0.0.0.0",dst="255.255.255.255")
3,UDP部分设置两个参数sport和dport
UDP_request=UDP(sport=67,dport=68)
4,BOOTP需要指明xid和本机的mac地址
BOOTP_request=BOOTP(chaddr=pkt[Ether].dst,xid=pkt[BOOTP].xid)
#pkt[BOOTP].xid这个也是从服务端传回的Offer中提取出的xid号
5,DHCP中需要指定DHCP服务器端的IP地址和申请的IP地址
DHCP_request=DHCP(options=[("message-type",'request'),("server_id",pkt[DHCP].options[1][1]),("requested_addr",pkt[BOOTP].yiaddr),"end"])
#message-type,request表示消息类型为请求,还有其他消息类型,比如发现(Discover)、提供(Offer)、请求(Request)和确认(Acknowledge)
#server_id,pkt[DHCP].options[1][1]表示DHCP服务器的地址,从先抓取到的DHCP Offer中获取
#requested_addr",pkt[BOOTP].yiaddr,表示请求的IP地址,也是从刚刚的Offer中获取
#end表示结束标识符
6,完整的请求数据包如下:
request=Ether_request/IP_request/UDP_request/BOOTP_request/DHCP_request
7,发送请求包
sendp(request,iface="WLAN")
#这里的WLAN是我的无线网络接口的名字的代替,自己写的时候如果你用的是以太网,就要用以太网的名字
#如果是wifi,就要用wifi的名字,怎样查看名字,在windows中输入ipconfig /all然后找到你电脑
#正在连接的那个项目,找到描述,描述的内容就是你的接口名字。
完整代码如下:
from scapy.all import Ether,IP,UDP,BOOTP,DHCP,sendp
Ether_request=Ether(src=pkt[Ether]dst,dst="ff:ff:ff:ff:ff:ff")
IP_request=IP(src="0.0.0.0",dst="255.255.255.255")
UDP_request=UDP(sport=68,dport=67)
BOOTP_request=BOOTP(chaddr=pkt[Ether].dst,xid=pkt[BOOTP].xid)
DHCP_request=DHCP(options=[("message-type",'request'),("server_id",pkt[DHCP].options[1][1]),("requested_addr",pkt[BOOTP].yiaddr),"end"])
request=Ether_request/IP_request/UDP_request/BOOTP_request/DHCP_request
sendp(request,iface="WLAN")
print(pkt[BOOTP].yiaddr + "正在分配")
if pkt[DHCP].options[0][1]==5:
print(pkt[BOOTP].yiaddr+"已经分配")
sniff(filter="src port 67",iface='WLAN',prn=detect_dhcp,count=10)
好了,今天的学习分享就到这里了,有什么不足的请大家指正,谢谢大家的观看。