轉載:安全客
醞釀了“三秒鍾“,准備理清邏輯寫寫我學習的心得,自認為和Siemens S7協議有過一段時間淺淺的“交流”,所以這過程中涉及到了自己整理的自認為有用的東西,涉及工具、腳本這般,發出來讓大家都能看到,邏輯也許簡單,但努力寫的盡量不那么的潦草。
0x01 環境介紹
Kali 2.0、Python2.7、Pycharm
0x02 初次嘗試
都說scapy是很強勢的第三方庫,很多人用它實現端口掃描,那么我通過學習它並且嘗試實現針對Siemens S7協議的Fuzz腳本。
首先需要明白幾個特別重要的點,在下面一一總結。首當其中當然是做個最簡單的三次握手,TCP沒有三次握手又怎么能行呢?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
#!/usr/bin python
# -*- encoding: utf-8 -*-
from scapy.all import *
src = sys.argv[1]
dst = sys.argv[2]
dport = int(sys.argv[3])
def tcpConnect():
SYN = TCP(sport=sport, dport=dport, flags='S', seq=0)
try:
SYNACK = sr1(ip / SYN, timeout=1)
print SYNACK[1][0]
except:
print "TCP Connect Error."
else:
ACK = TCP(sport=sport, dport=dport, flags='A', seq=SYNACK[1][0].ack, ack=SYNACK[1][0].seq + 1)
send(ip / ACK)
return SYNACK
if __name__ == __main__:
sport = random.randint(1024, 65535)
ip = IP(src=src, dst=dst)
tcpConnect()
|
代碼很簡單的四行,實現的原理就是指定“源IP”、“目的IP”、“目的端口”完成三次握手的過程,我們可以通過運行這個Python腳本結合Wireshark抓包來分析具體的過程:
在外部主機通過TCP/UDP調試工具模擬開啟服務端,在虛擬機運行腳本。
我們注意到,通過Wireshark抓包應該你會看到三次握手並沒有像預想的那樣建立,是被本機Reset掉了,是什么原因呢?
Iptables收到了返回的ACK數據包,而它檢測到系統本身沒有發送過任何的SYN握手包,所以重置這個握手連接(舉個例子:你在一家飯店坐着,一句話沒說服務員就端上一盤龍蝦說“這是您點的龍蝦”,其實是某位顧客給你叫的,你的本能可能也會是拒絕。)
怎樣來避免出現這樣的情況,可以添加iptables規則來解決,比如下面這條直接丟棄掉所有的RST包(當然最好自己根據實際的情況去添加規則,舉例的這個規則有點兒極端)。
1
|
iptables -A OUTPUT -p tcp --tcp-flags RST RST -j DROP
|
添加規則之后,再次執行腳本,我們看到通過三次握手,建立連接。
0x03 Scapy幾個函數
三次握手簡單的實現了,但是我們從這個簡單的代碼中需要知道Scapy的幾個函數:
Send(): 這個函數只會將數據包發送出去,也就是只發不收。
Sr1(): 這個函數會在發送數據包之后,接收返回的第一個數據包(如,你發送了一個數據包,對方先返回一個ACK數據包,之后返回了帶有數據的數據包,那么這個函數會接收到第一個返回的ACK數據包)
Sr(): 這個函數會發送並接收數據包,區別於上面的Sr1(),這個函數會接收返回的所有數據包。
那么我們自然也可以明白在三次握手的代碼中,我們首先用到Sr1()函數發送SYN數據包之后接收返回的ACK數據包,然后再收到ACK后Send()給對方一個ACK數據包。
0x04 Ack和Seq
每次數據的交互,從一次完整的三次握手開始,Ack和Seq的值是跟隨着每一步在變化,在建立三次連接之后的數據交互過程中Ack和Seq的變化關系到通過Scapy偽造的數據包能不能正常被接收以及被回復。
這是之前做的一個很簡陋的過程的圖片,我們從這個過程中需要通過這樣的變化規律來不斷的改變Ack和Seq的值,以此來發送正確的數據包達到與目標設備正常交互的過程。
通過Wireshark抓包我們看到的效果是最直觀的看到交互過程中Seq和Ack值的變化,最終在代碼中如何實現,需要利用Scapy接收到數據包的格式來確定下一個數據包的Ack和Seq。需要發送的偽造的數據包的Seq值為接收到的數據包的Ack值,偽造的數據包的Ack值可以將接收到的數據包的Seq值加數據部分data的長度確定。
在上圖中Wireshark數據包中解析已經顯示了下一個Seq值,但實際上這一點並沒有在數據包中提現,所以只是Wireshark自己計算並顯示了這個值,可能用於方便抓包分析的人識別數據包的對應關系?不得而知……
對於如何通過Scapy來實現對這些參數的偽造呢?
我們通過Scapy的sniff模塊來嗅探並過濾幾個數據包來舉例查看數據的格式,我們可以看到Scapy模塊接收到的數據以元組的方式存儲,那么Sr()、Sr1()都是以元組的方式呈現接收到的數據包。我們通過元組的訪問方式取得數據包中每層對應的參數。可以具體一步步調試確定准確的位置。
這樣的話我們可以通過sniff模塊中的各部分的具體參數來獲取接收到數據的長度,獲取上一個數據包中的Ack和Seq值,和自己准備的數據長度結合即可確定下一個數據包中Ack和Seq的准確值,同時還可以通過判斷flags這個參數獲取數據包的類型: S/SA/PA。
這樣看來,我們基本已經完成了准備工作:
三次握手、Ack和Seq的變化,我們需要的是准備自己的Fuzz數據就可以實現一個簡單的Fuzz腳本,基於Scapy實現的Fuzz腳本。
0x05 腳本實現流程
這一部分就先來確定自己實現一個簡單腳本的思路流程,通過自己制作的一個糙的流程圖來看:
TCP的三次握手
S7協議的建連
Fuzz數據的交互
日志收集
0x06 S7 建連過程
S7協議和TCP的感覺有點兒類似,需要一個建立連接的過程,在三次握手之后,需要發送S7協議自己的建立連接的數據,之后才能與設備建立連接進行數據的傳輸。
Wireshark中內置了S7協議的解析模塊,不是對每個模塊的功能都有完全的解釋,但是在對協議理解的上手過程中會起到很重要的作用,可以幫助我這樣的新手一定程度上很好的理解。
我們可以看到Wireshark在很大的程度上已經對協議數據中每個部分的內容進行了解釋,同時我們可以在實現的時候通過整個數據進行重放來實現建立連接的過程。
1
2
3
4
5
6
7
8
9
10
11
12
|
def hello_plc(self):
hello_data = str2byte(hello)
hello_packet = TCP(sport=sport, dport=dport, flags='PA', seq=self.ack, ack=self.seq + 1)
COTPACK = sr(ip / hello_packet / hello_data, multi=True, timeout=5)
comm_data = str2byte(set_comm)
comm_pkt = TCP(sport=sport, dport=dport, flags='PA',
seq=COTPACK[0][1][1].ack, ack=COTPACK[0][1][1].seq + len(COTPACK[0][1][1].load))
COMMACK = sr(ip / comm_pkt / comm_data, multi=True, timeout=5)
comm_ack = TCP(sport=sport, dport=dport, flags='A',
seq=COMMACK[0][2][1].ack, ack=COMMACK[0][2][1].seq + len(COMMACK[0][2][1].load))
send(ip/comm_ack)
return COMMACK
|
這個函數里面可能有幾個參數需要簡單說一下,因為它們的作用保證了這個Fuzz腳本可以正確的接收到返回的帶有數據的數據包。
Sr()函數的“multi”和“timeout”,設置發出數據包后等待時間和超時時間,因為在實際的環境運行的時候,如果使用Sr1(),容易出現發送數據后設備先返回一個ACK數據包(不包含任何數據),使用Sr()會在接收異常的時候一直等待或一直接收,不能准確的定位到我們需要的數據部分,所以添加這兩個參數有足夠的時間等待接收到返回的帶有數據的數據包,同時也避免了一直接收其他無關的異常數據。
0x07 暫告一段
到此為止,我們已經從TCP的三次握手,到通過與設備的Siemens S7協議進行交互建立連接,以及在過程中如何確保數據包中個參數的變化情況,保證偽造的數據包有效。剩下的部分就是如何針對自己的環境去實現一個簡單的Fuzz腳本對協議進行Fuzz測試。我仍然在不斷整理不斷總結。