某學(xué)生粉絲發(fā)來(lái)問(wèn)題:
這個(gè)題目一看就知道這位同學(xué)是網(wǎng)絡(luò)安全相關(guān)專(zhuān)業(yè)。
很多粉絲以為彭老師知識(shí)搞驅(qū)動(dòng)的,但是其實(shí)作為一個(gè)擁有多篇網(wǎng)絡(luò)協(xié)議專(zhuān)利的老鳥(niǎo),網(wǎng)絡(luò)知識(shí)還是比較擅長(zhǎng)的!
應(yīng)用層套接字、組網(wǎng)、網(wǎng)卡驅(qū)動(dòng)都有所涉獵,目前還缺Linux內(nèi)核協(xié)議棧這塊沒(méi)深入研究,后期會(huì)補(bǔ)上。
一、題目總結(jié)
題目要求是掃描所有TCP半連接的端口,需要實(shí)現(xiàn)的功能如下:
-
-
- 攻擊方啟動(dòng)任務(wù)1,循環(huán)向指定
服務(wù)器端+端口
-
-
-
- 發(fā)送SYN數(shù)據(jù)包,(端口從0開(kāi)始遞增)如果該服務(wù)器上有服務(wù)打開(kāi)了這個(gè)端口,就會(huì)回復(fù)
SYN+ACK
-
-
-
- ,此時(shí)服務(wù)端進(jìn)入SYN_RCVD狀態(tài),攻擊方啟動(dòng)任務(wù)2,掃描收到的所有
SYN+ACK
-
- 數(shù)據(jù)包,如果客戶(hù)端收到SYN+ACK,那么說(shuō)明服務(wù)器改端口打開(kāi),任務(wù)2就可以將所有打開(kāi)的端口信息打印出來(lái)
- 任務(wù)1使用socket API任務(wù)2使用pcap庫(kù)
二、TCP基礎(chǔ)知識(shí)點(diǎn)
解決這個(gè)問(wèn)題必須掌握以下幾個(gè)知識(shí)點(diǎn):
- 什么是TCPTCP3次握手什么是半連接TCP、IP協(xié)議頭如何使用Libpcap庫(kù)線程、進(jìn)程
整體來(lái)說(shuō)對(duì)網(wǎng)絡(luò)知識(shí)的基本功要求還是很高的。關(guān)于TCP/IP協(xié)議棧這些基礎(chǔ)知識(shí)點(diǎn)的本文就不列舉了。
下面主要強(qiáng)化下這個(gè)題目涉及的TCP的知識(shí)點(diǎn)。
1.TCP
首先就是我們必須了解TCP協(xié)議頭:
序列號(hào):在建立連接時(shí)由計(jì)算機(jī)生成的隨機(jī)數(shù)作為其初始值,通過(guò) SYN 包傳給接收端主機(jī),每發(fā)送一次數(shù)據(jù),就「累加」一次該「數(shù)據(jù)字節(jié)數(shù)」的大小。用來(lái)解決網(wǎng)絡(luò)包亂序問(wèn)題
確認(rèn)應(yīng)答號(hào):指下一次「期望」收到的數(shù)據(jù)的序列號(hào),發(fā)送端收到這個(gè)確認(rèn)應(yīng)答以后可以認(rèn)為在這個(gè)序號(hào)以前的數(shù)據(jù)都已經(jīng)被正常接收。用來(lái)解決不丟包的問(wèn)題
控制位:ACK:該位為 1 時(shí),「確認(rèn)應(yīng)答」的字段變?yōu)橛行В琓CP 規(guī)定除了最初建立連接時(shí)的 SYN 包之外該位必須設(shè)置為 1
RST:該位為 1 時(shí),表示 TCP 連接中出現(xiàn)異常必須強(qiáng)制斷開(kāi)連接
SYN:該位為 1 時(shí),表示希望建立連接,并在其「序列號(hào)」的字段進(jìn)行序列號(hào)初始值的設(shè)定
FIN:該位為 1 時(shí),表示今后不會(huì)再有數(shù)據(jù)發(fā)送,希望斷開(kāi)連接。當(dāng)通信結(jié)束希望斷開(kāi)連接時(shí),通信雙方的主機(jī)之間就可以相互交換 FIN 位置為 1 的 TCP 段
與本題目相關(guān)的是最主要字段是控制位,控制位的操作最主要體現(xiàn)在3次握手和4次握手。
2. tcp三次握手
開(kāi)始客戶(hù)端和服務(wù)器都處于CLOSED狀態(tài),然后服務(wù)端開(kāi)始監(jiān)聽(tīng)某個(gè)端口,進(jìn)入LISTEN狀態(tài):
- 第一次握手(SYN=1, seq=x),發(fā)送完畢后,客戶(hù)端進(jìn)入 SYN_SENT 狀態(tài)第二次握手(SYN=1, ACK=1, seq=y, ACKnum=x+1), 發(fā)送完畢后,服務(wù)器端進(jìn)入 SYN_RCVD 狀態(tài)第三次握手(ACK=1,ACKnum=y+1),發(fā)送完畢后,客戶(hù)端進(jìn)入 ESTABLISHED 狀態(tài),當(dāng)服務(wù)器端接收到這個(gè)包時(shí),也進(jìn)入 ESTABLISHED 狀態(tài),TCP 握手,即可以開(kāi)始數(shù)據(jù)傳輸
3. tcp四次揮手
四次揮手過(guò)程:
- 客戶(hù)端打算關(guān)閉連接,此時(shí)會(huì)發(fā)送一個(gè) TCP 首部 FIN 標(biāo)志位被置為 1 的報(bào)文,也即 FIN 報(bào)文,之后客戶(hù)端進(jìn)入 FIN_WAIT_1 狀態(tài)服務(wù)端收到該報(bào)文后,就向客戶(hù)端發(fā)送 ACK 應(yīng)答報(bào)文,接著服務(wù)端進(jìn)入 CLOSED_WAIT 狀態(tài)客戶(hù)端收到服務(wù)端的 ACK 應(yīng)答報(bào)文后,之后進(jìn)入 FIN_WAIT_2 狀態(tài)等待服務(wù)端處理完數(shù)據(jù)后,也向客戶(hù)端發(fā)送 FIN 報(bào)文,之后服務(wù)端進(jìn)入 LAST_ACK 狀態(tài)客戶(hù)端收到服務(wù)端的 FIN 報(bào)文后,回一個(gè) ACK 應(yīng)答報(bào)文,之后進(jìn)入 TIME_WAIT 狀態(tài)服務(wù)器收到了 ACK 應(yīng)答報(bào)文后,就進(jìn)入了 CLOSE 狀態(tài),至此服務(wù)端已經(jīng)完成連接的關(guān)閉客戶(hù)端在經(jīng)過(guò) 2MSL 一段時(shí)間后,自動(dòng)進(jìn)入 CLOSE 狀態(tài),至此客戶(hù)端也完成連接的關(guān)閉
更詳細(xì) tcp知識(shí)點(diǎn)可以參考下面文章:《28 張圖,一次性說(shuō)清楚 TCP》
4.TCP狀態(tài)
TCP協(xié)議狀態(tài)遷移圖如下:
- CLOSED:表示初始狀態(tài)LISTEN:表示服務(wù)器端的某個(gè)SOCKET處于監(jiān)聽(tīng)狀態(tài),可以接受連接了SYN_RCVD:表示接收到了SYN報(bào)文SYN_SENT:表示客戶(hù)端已發(fā)送SYN報(bào)文ESTABLISHED:表示連接已經(jīng)建立了TIME_WAIT:表示收到了對(duì)方的FIN報(bào)文,并發(fā)送出了ACK報(bào)文,就等2MSL后即可回到CLOSED可用狀態(tài)了CLOSING:表示你發(fā)送FIN報(bào)文后,并沒(méi)有收到對(duì)方的ACK報(bào)文,反而卻也收到了對(duì)方的FIN報(bào)文。如果雙方幾乎在同時(shí)* close一個(gè)SOCKET的話,那么就出現(xiàn)了雙方同時(shí)發(fā)送FIN報(bào) 文的情況,也即會(huì)出現(xiàn)CLOSING狀態(tài),表示雙方都正在關(guān)閉SOCKET連接CLOSE_WAIT:表示在等待關(guān)閉
5. 半連接/全連接
TCP半連接及全連接狀態(tài),在服務(wù)器的性能分析中,起著重要的作用,它通常是反應(yīng)服務(wù)端的處理能力
1)半連接隊(duì)列(syn queue)
客戶(hù)端發(fā)送SYN包,服務(wù)端收到后回復(fù)SYN+ACK后,服務(wù)端進(jìn)入SYN_RCVD狀態(tài),這個(gè)時(shí)候的socket會(huì)放到半連接隊(duì)列。
2)全連接隊(duì)列(accept queue)
當(dāng)服務(wù)端收到客戶(hù)端的ACK后,socket會(huì)從半連接隊(duì)列移出到全連接隊(duì)列。當(dāng)調(diào)用accpet函數(shù)的時(shí)候,會(huì)從全連接隊(duì)列的頭部返回可用socket給用戶(hù)進(jìn)程。
全連接隊(duì)列中存放的是已完成TCP三次握手的過(guò)程,等待被處理的連接,在客戶(hù)端及服務(wù)端的狀態(tài)均為 ESTABLISHED
三、 抓包舉例
要想學(xué)好網(wǎng)絡(luò),抓包工具是必須掌握的。
下圖是一口君通過(guò)抓包工具抓取的一個(gè)完整的 tcp 3次握手 + HTTP GET請(qǐng)求 + 4次握手 的完整通信數(shù)據(jù)包。
如何抓包,可以參考下面文章:
《一文包你學(xué)會(huì)網(wǎng)絡(luò)數(shù)據(jù)抓包》
B站也有詳細(xì)的教學(xué)視頻:
《教你如何抓取網(wǎng)絡(luò)中的數(shù)據(jù)包!黑客必備技能》
https://www.bilibili.com/video/BV1xr4y1T7cT/?vd_source=07570058a62e0e8a6cf489efac35cfec
四、 socket
關(guān)于socket API內(nèi)容,大家可以的參考下面這篇文章《socket到底是什么?》
五、libpcap
libpcap是一個(gè)網(wǎng)絡(luò)數(shù)據(jù)包捕獲函數(shù)庫(kù),功能非常強(qiáng)大,Linux下著名的tcpdump就是以它為基礎(chǔ)的。
libpcap主要由兩部分組成:網(wǎng)絡(luò)分接頭(network tap)和數(shù)據(jù)過(guò)濾器(packet filter)。
網(wǎng)絡(luò)分接頭從網(wǎng)絡(luò)設(shè)備驅(qū)動(dòng)程序中收集數(shù)據(jù)進(jìn)行拷貝,過(guò)濾器決定是否接收該數(shù)據(jù)包。
libpcap利用BSD packet filter(BPF)算法對(duì)網(wǎng)卡接收到的鏈路層數(shù)據(jù)包進(jìn)行過(guò)濾。
libpcap的包捕獲機(jī)制就是在數(shù)據(jù)鏈路層加一個(gè)旁路處理。當(dāng)一個(gè)數(shù)據(jù)包到達(dá)網(wǎng)絡(luò)接口時(shí),libpcap首先利用已經(jīng)創(chuàng)建的套接字從鏈路層驅(qū)動(dòng)程序中獲得該數(shù)據(jù)包的拷貝,再通過(guò)Tap函數(shù)將數(shù)據(jù)包發(fā)給BPF過(guò)濾器。
BPF過(guò)濾器根據(jù)用戶(hù)已經(jīng)定義好的過(guò)濾規(guī)則對(duì)數(shù)據(jù)包進(jìn)行逐一匹配,匹配成功則放入內(nèi)核緩沖區(qū),并傳遞給用戶(hù)緩沖區(qū),匹配失敗則直接丟棄。
如果沒(méi)有設(shè)置過(guò)濾規(guī)則,所有數(shù)據(jù)包都將放入內(nèi)核緩沖區(qū),并傳遞給用戶(hù)層緩沖區(qū)。
1. libpcap安裝
- 在線安裝
sudo?apt-get??install??libpcap-dev
這種適合有網(wǎng)絡(luò)的朋友
如何無(wú)法安裝嘗試更新下源:
sudo?apt-get?update
- 離線編譯安裝
http://www.tcpdump.org/#latest-release
然后解壓
tar?zxvf?libpcap-1.10.3.tar.gz??
cd?libpcap-1.10.3
./configure
sudo?make
sudo?make?install
2. Libpcap的抓包流程:
- 查找網(wǎng)絡(luò)設(shè)備:目的是發(fā)現(xiàn)可用的網(wǎng)卡,實(shí)現(xiàn)的函數(shù)為pcap_lookupdev(),如果當(dāng)前有多個(gè)網(wǎng)卡,函數(shù)就會(huì)返回一個(gè)網(wǎng)絡(luò)設(shè)備名的指針列表。打開(kāi)網(wǎng)絡(luò)設(shè)備:利用上一步中的返回值,可以決定使用哪個(gè)網(wǎng)卡,通過(guò)函數(shù)pcap_open_live()打開(kāi)網(wǎng)卡,返回用于捕捉網(wǎng)絡(luò)數(shù)據(jù)包的秒數(shù)字。獲得網(wǎng)絡(luò)參數(shù):這里是利用函數(shù)pcap_lookupnet(),可以獲得指定網(wǎng)絡(luò)設(shè)備的IP地址和子網(wǎng)掩碼。編譯過(guò)濾策略:Lipcap的主要功能就是提供數(shù)據(jù)包的過(guò)濾,函數(shù)pcap_compile()來(lái)實(shí)現(xiàn)。設(shè)置過(guò)濾器:在上一步的基礎(chǔ)上利用pcap_setfilter()函數(shù)來(lái)設(shè)置。利用回調(diào)函數(shù),捕獲數(shù)據(jù)包:函數(shù)pcap_loop()和pcap_dispatch()來(lái)抓去數(shù)據(jù)包,也可以利用函數(shù)pcap_next()和pcap_next_ex()來(lái)完成同樣的工作。關(guān)閉網(wǎng)絡(luò)設(shè)備:pcap_close()函數(shù)關(guān)系設(shè)備,釋放資源。
3. 數(shù)據(jù)結(jié)構(gòu)說(shuō)明:
struct?pcap_pkthdr?{
????struct?timeval?ts;??/*?time?stamp?*/
????bpf_u_int32?caplen;?/*?抓到的數(shù)據(jù)包實(shí)際長(zhǎng)度?*/
????bpf_u_int32?len;????/*數(shù)據(jù)包的長(zhǎng)度?*/
};
4. libcap庫(kù)函數(shù)
關(guān)于libcap的詳細(xì)講解,后續(xù)會(huì)出文章,
本文只講幾個(gè)重要的函數(shù)。
- 打開(kāi)網(wǎng)絡(luò)接口
//這個(gè)函數(shù)會(huì)返回指定接口的pcap_t類(lèi)型指針,后面的所有操作都要使用這個(gè)指針。
pcap_t?*?pcap_open_live(const?char?*?device,?int?snaplen,?int?promisc,?int?to_ms,?char?*?errbuf)
device:網(wǎng)絡(luò)接口字符串,可以直接使用硬編碼,比如eth0。
snaplen:對(duì)于每個(gè)數(shù)據(jù)包,從開(kāi)頭要抓多少個(gè)字節(jié),我們可以設(shè)置這個(gè)值來(lái)只抓每個(gè)數(shù)據(jù)包的頭部,而不關(guān)心具體的內(nèi)容。典型的以太網(wǎng)幀長(zhǎng)度是1518字節(jié),但其他的某些協(xié)議的數(shù)據(jù)包會(huì)更長(zhǎng)一點(diǎn),但任何一個(gè)協(xié)議的一個(gè)數(shù)據(jù)包長(zhǎng)度都必然小于65535個(gè)字節(jié)。
promisc:指定是否打開(kāi)混雜模式(Promiscuous?Mode),0表示非混雜模式,任何其他值表示混合模式。如果要打開(kāi)混雜模式,那么網(wǎng)卡必須也要打開(kāi)混雜模式,可以使用如下的命令打開(kāi)eth0混雜模式:ifconfig eth0?
to_ms:抓包時(shí)長(zhǎng)單位為毫秒,0標(biāo)示一直等待。
errbuf:?輸出參數(shù),打開(kāi)網(wǎng)絡(luò)接口失敗原因。
- 打開(kāi)離線的pcap文件
pcap_t?*?pcap_open_offline?(const?char?*fname,?char?*errbuf)
fname :文件名稱(chēng)。
errbuf :打開(kāi)失敗的錯(cuò)誤信息。
- 抓包函數(shù)
int?pcap_loop(pcap_t?*?p,?int?cnt,?pcap_handler?callback,?u_char?*?user)
p:?打開(kāi)的pcap_t類(lèi)型指針。
cnt:一共抓多少個(gè)包,如果為負(fù)數(shù)就一直循環(huán)。
callback:回調(diào)函數(shù)指針
user:傳遞給回調(diào)函數(shù)的參數(shù)。
void?callback(u_char?*?userarg,?const?struct?pcap_pkthdr?*?pkthdr,?const?u_char?*?packet)
userarg:是pcap_loop的最后一個(gè)參數(shù),當(dāng)收到足夠數(shù)量的包后pcap_loop會(huì)調(diào)用callback回調(diào)函數(shù),同時(shí)將pcap_loop()的user參數(shù)傳遞給它
pkthdr:?抓到的報(bào)文頭信息。
packet:收到的包的數(shù)據(jù)。
- 過(guò)濾函數(shù)編譯
int?pcap_compile(pcap_t?*?p,?struct?bpf_program?*?fp,?char?*?str,?int?optimize,?bpf_u_int32?netmask)
//fp:這是一個(gè)傳出參數(shù),存放編譯后的bpf
//str:過(guò)濾表達(dá)式
//optimize:是否需要優(yōu)化過(guò)濾表達(dá)式
//metmask:簡(jiǎn)單設(shè)置為0即可
- 設(shè)置過(guò)濾函數(shù)
int?pcap_setfilter(pcap_t?*?p,??struct?bpf_program?*?fp)
//參數(shù)fp就是pcap_compile()的第二個(gè)參數(shù),存放編譯后的bpf
- 釋放網(wǎng)絡(luò)接口
void?pcap_close(pcap_t?*?p)
//該函數(shù)用于關(guān)閉pcap_open_live()獲取的pcap_t的網(wǎng)絡(luò)接口對(duì)象并釋放相關(guān)資源。
- 打開(kāi)網(wǎng)絡(luò)包保存文件
pcap_dumper_t?*?pcap_dump_open?(pcap_t?*p,?const?char?*fname)
?//p:是我們已經(jīng)打開(kāi)的網(wǎng)絡(luò)設(shè)備,從這個(gè)設(shè)備接收數(shù)據(jù)包。
??// fname:是我們要寫(xiě)入的文件名,隨便起。
??//return:?如果出錯(cuò),會(huì)返回NULL??梢越璐藱z查這個(gè)文件有沒(méi)有打開(kāi)。
- 將網(wǎng)絡(luò)包寫(xiě)入文件
void?pcap_dump?(u_char?*user,?const?struct?pcap_pkthdr?*h,?const?u_char?*sp)
user:就是文件描述符dumpfp,只不過(guò)要做一下類(lèi)型轉(zhuǎn)換。
由于這個(gè)函數(shù)一般在pcap_loop()的函數(shù)指針?biāo)赶虻膒acket_handler中使用,所以packet_handler中的user就是這里的user。
?h:就是pkt_header
- 網(wǎng)絡(luò)包文件關(guān)閉
pcap_dump_close(pcap_dumper_t?*?t);
5. libcap過(guò)濾規(guī)則
一些過(guò)濾表達(dá)式的例子如下:
- 只接收源ip地址是192.168.1.177的數(shù)據(jù)包
src?host?192.168.1.177
- 只接收tcp/udp的目的端口是80的數(shù)據(jù)包
dst?port?80
- 只接收不使用tcp協(xié)議的數(shù)據(jù)包
not?tcp
- 只接收SYN標(biāo)志位置位且目標(biāo)端口是22或23的數(shù)據(jù)包(tcp首部開(kāi)始的第13個(gè)字節(jié))
tcp[13]?==?0x02?and?(dst?port?22?or?dst?port?23)
- 只接收icmp的ping請(qǐng)求和ping響應(yīng)的數(shù)據(jù)包
icmp[icmptype]?==?icmp-echoreply?or?icmp[icmptype]?==?icmp-echo
- 只接收以太網(wǎng)mac地址是00:e0:09:c1:0e:82的數(shù)據(jù)包
ether?dst?00:e0:09:c1:0e:82
只接收ip的ttl=5的數(shù)據(jù)包(ip首部開(kāi)始的第8個(gè)字節(jié))
ip[8]?==?5
本例只抓取ip地址為本地IP的數(shù)據(jù)包,然后程序再對(duì)數(shù)據(jù)包協(xié)議頭進(jìn)行解析:
host?192.168.0.113
六、設(shè)計(jì)方案
實(shí)現(xiàn)原理:
atach、cap進(jìn)程運(yùn)行在ubuntu中,要攻擊的目的終端可以使網(wǎng)絡(luò)中任意設(shè)備,只需要能ping通即可。本例在windows上測(cè)試,采用橋接模式將ubuntu的網(wǎng)口和windows的網(wǎng)口橋接起來(lái)。
atach進(jìn)程主要功能:
- 創(chuàng)建tcp套接字設(shè)置需要攻擊的終端的ip+port,然后執(zhí)行connect函數(shù)connect成功,說(shuō)明對(duì)方該端口可以使用修改port值,重復(fù)前面3個(gè)步驟
cap進(jìn)程主要功能:
-
- 通過(guò)eth0,抓取指定規(guī)則:
host 192.168.0.116
- 數(shù)據(jù)包解析出以太頭、tcp頭,ip頭、tcp頭,判斷tcp頭中sync+ack位為1的所有數(shù)據(jù)包打印出步驟2過(guò)濾出來(lái)的數(shù)據(jù)包
代碼流程:
七、測(cè)試
1. 環(huán)境:
windows ip:192.168.0.116
ubuntu ip:192.168.0.113
2. 文件:
peng@ubuntu:~/work/test/pcap$?ls
atach??header.c????????libpcap-1.10.3.tar.gz??cap.c
cap????libpcap-1.10.3??atach.c?????????????protocol.h
其中atach是上攻擊方,用于向指定ip發(fā)送sync包
cap 用于檢測(cè)所有網(wǎng)卡收到的sync+ack數(shù)據(jù)包
程序運(yùn)行在ubuntu中。
3. 啟動(dòng)網(wǎng)絡(luò)調(diào)試助手
在windows上啟動(dòng)網(wǎng)絡(luò)調(diào)試助手,
建立幾個(gè)Tcp Server,端口號(hào)分別為55、56、57
在這里插入圖片描述
4. 啟動(dòng)程序
1)首先啟動(dòng)cap
peng@ubuntu:~/work/test/pcap$?sudo?./cap?192.168.0.116
found?device:?eth0
netaddr:0000a8c0
try?to?open?device?eth0
filter:host?192.168.0.116
2)啟動(dòng)攻擊程序atach
需要新開(kāi)啟一個(gè)終端。
peng@ubuntu:~/work/test/pcap$?./atach?192.168.0.116
5. 運(yùn)行截圖如下:
右邊log可見(jiàn),列舉出了所有可以訪問(wèn)的端口,包括55、56、57。
注意:那個(gè)單詞atach故意少了一個(gè)t,否則編譯不過(guò)去:大家可以試試你們的編譯器,刑不刑!
八、代碼
代碼已經(jīng)同步到gitee,地址如下:
https://gitee.com/yikoulinux/pcap.git
更多嵌入式、Linux、網(wǎng)絡(luò)知識(shí),后臺(tái)留言加一口君好友!