Linux下Sniffer程序的实现
作者:Gianluca Insolvibile
整理:Seal(永遠(yuǎn)的FLASH)
出處:http://www.nsfocus.com
日期:2003-04-02
嗅探——Sniffer技術(shù)是網(wǎng)絡(luò)安全領(lǐng)域里一項(xiàng)非常重要的技術(shù)!對(duì)于“Hacker”來說,他們可以以非常隱蔽的方式得到網(wǎng)絡(luò)中傳輸?shù)拇罅康拿舾行畔?/strong>,如Telnet,ftp帳號(hào)和密碼等等明文傳送的信息!與主動(dòng)掃描相比,嗅探的行為更加難以被察覺,操作起來也不是很復(fù)雜!對(duì)于網(wǎng)絡(luò)管理人員來說,可以利用嗅探技術(shù)對(duì)網(wǎng)絡(luò)活動(dòng)進(jìn)行監(jiān)控,并及時(shí)發(fā)現(xiàn)各種攻擊行為!
在這篇文章里,我們主要探討在Linux下如何利用C語(yǔ)言來實(shí)現(xiàn)一個(gè)Sniffer!我們將假設(shè)所有的主機(jī)在一個(gè)局域網(wǎng)內(nèi)。
????
首先,我們將簡(jiǎn)短的回顧一下一個(gè)普通的以太網(wǎng)卡是怎么工作的!(如果你對(duì)這方面的知識(shí)早已熟悉,那么你可以直接跳到下一段)來源于應(yīng)用程序的IP報(bào)文被封裝成以太網(wǎng)幀(這
是在以太網(wǎng)上傳播的數(shù)據(jù)報(bào)文的名稱),它是底層鏈路層報(bào)文上面的一層報(bào)文,包含有源地址
報(bào)文和一些需要用來傳送至目標(biāo)主機(jī)的信息。通常情況下,目的IP地址對(duì)應(yīng)著一個(gè)6字節(jié)的目的以太網(wǎng)址(經(jīng)常叫做MAC地址),它們之間通過ARP協(xié)議進(jìn)行映射!就這樣,包含著以太網(wǎng)幀的報(bào)文從源主機(jī)傳輸?shù)侥康闹鳈C(jī),中間經(jīng)過一些網(wǎng)絡(luò)設(shè)備,如交換機(jī),路由器等等,當(dāng)然,因?yàn)槲覀兊那疤崾侵鳈C(jī)在同一網(wǎng)內(nèi),所以我們的討論不涉及以上這些網(wǎng)絡(luò)設(shè)備!
????在鏈路層中并不存在路線的概念,換句話說,源主機(jī)發(fā)出的幀不會(huì)直接指向目的主機(jī),
而是基于廣播方式傳播,網(wǎng)絡(luò)中的所有網(wǎng)卡都能看到它的傳輸。每個(gè)網(wǎng)卡會(huì)檢查幀開始的6個(gè)字節(jié)(目的主機(jī)的MAC地址),但是只有一個(gè)網(wǎng)卡會(huì)發(fā)現(xiàn)自己的地址和其相符合,然后它接收這個(gè)幀,這個(gè)幀會(huì)被網(wǎng)絡(luò)驅(qū)動(dòng)程序分解,原來的IP報(bào)文將通過網(wǎng)絡(luò)協(xié)議棧傳送至接收的應(yīng)用程序!
更準(zhǔn)確的說,網(wǎng)絡(luò)驅(qū)動(dòng)程序會(huì)檢查幀中報(bào)文頭部的協(xié)議標(biāo)識(shí),以確定接收數(shù)據(jù)的上層協(xié)
議!大多數(shù)情況下,上層是IP協(xié)議,所以接收機(jī)制將去掉IP報(bào)文頭部,然后把剩下的傳送
至UDP或者TCP接收機(jī)制!這些協(xié)議,將把報(bào)文送到socket-handling機(jī)制,它將最后把報(bào)
文數(shù)據(jù)變成應(yīng)用程序可接收的方式發(fā)送出去。在這個(gè)過程中,報(bào)文將失去所有的和其有關(guān)的
網(wǎng)絡(luò)信息,比如源地址(IP和MAC),端口號(hào),IP選擇,TCP參數(shù)等等!所以如果目的主機(jī)沒
有一個(gè)包含正確參數(shù)的打開端口,那么這個(gè)報(bào)文將被丟棄而且永遠(yuǎn)不會(huì)被送到應(yīng)用層去!
因此我們?cè)谶M(jìn)行網(wǎng)絡(luò)嗅探的時(shí)候有兩個(gè)不同的問題:一個(gè)和以太網(wǎng)址有關(guān),我們不能抓
到不是發(fā)給自己主機(jī)的包,另一個(gè)和協(xié)議棧的運(yùn)行過程有關(guān),我們需要一個(gè)SOCKET去監(jiān)聽每
個(gè)端口,得到那些沒有被丟棄的報(bào)文!
第一個(gè)問題不是最根本的,因?yàn)槲覀兛赡懿粫?huì)對(duì)發(fā)往其他主機(jī)的報(bào)文有興趣而只想嗅探
所有發(fā)往自己主機(jī)的報(bào)文。第二個(gè)問題是必須要解決的,下面我們將看到這個(gè)問題是怎么樣
一步一步解決的!
當(dāng)你打開一個(gè)標(biāo)準(zhǔn)的SOCKET套接字時(shí),你需要指明你將使用哪個(gè)協(xié)議簇,大多數(shù)情況下
我們一般用PF_UNIX在本地機(jī)器間進(jìn)行通信,PF_INET在基于IPv4協(xié)議簇基礎(chǔ)之上進(jìn)行通信,
你還需要指明所用的協(xié)議類型及與協(xié)議簇相關(guān)的確切數(shù)值,,在PF_INET協(xié)議簇中,常用的有
SOCK_STREAM(與TCP相關(guān)),SOCK_DGRAM(與UDP相關(guān))。在把報(bào)文發(fā)送到應(yīng)用程序前內(nèi)核對(duì)
其的處理與SOCKET類型有關(guān),你指定的協(xié)議將處理報(bào)文在SOCKET的傳輸!(具體細(xì)節(jié)問題你
可以man socket(3))
在LINUX內(nèi)核版本中(2.0 releases),一個(gè)名為PF_PACKET的協(xié)議簇被加了進(jìn)來!這個(gè)簇允許應(yīng)用程序直接利用網(wǎng)絡(luò)驅(qū)動(dòng)程序發(fā)送和接收?qǐng)?bào)文,避免了原來的協(xié)議棧處理過程,在這種情況下,所有SOCKET發(fā)出的報(bào)文直接送到以太網(wǎng)卡接口,而接口收到的任何報(bào)文將直接送到應(yīng)用程序
PF_PACKET協(xié)議簇支持兩個(gè)稍微有點(diǎn)不同的SOCKET類型,SOCK_DGRAM和SOCK_RAW。
前者讓內(nèi)核處理添加或者去除以太網(wǎng)報(bào)文頭部工作,而后者則讓應(yīng)用程序?qū)σ蕴W(wǎng)報(bào)文頭部
有完全的控制!
在SOCKET調(diào)用中的協(xié)議類型必須符合/usr/include/linux/if_ether.h
中定義的以太網(wǎng)IDs中的一個(gè),除非遇到特別聲明的協(xié)議,一般你可以用ETH_P_IP來處理
IP的一組協(xié)議(TCP,UDP,ICMP,raw IP等等)因?yàn)樗鼈內(nèi)菀资艿揭恍┖車?yán)重的安全問題的牽
連(比如你可以偽造一個(gè)MAC地址),所以只有具有root權(quán)限才可以使用PF_PACKET-family?
socket.這也就是為什么只有具有root權(quán)限后才能運(yùn)行嗅探器的原因!
PF_PACKET-family 協(xié)議簇可以很容易解決協(xié)議棧處理嗅探來的數(shù)據(jù)報(bào)文時(shí)候遇到的問
題!我們一起來看看程序1,我們打開一個(gè)屬于PF_PACKET-family 協(xié)議簇的SOCKET,指定
一個(gè)SOCK_RAW socket類型和IP相關(guān)協(xié)議類型。這時(shí)我們開始從SOCKET抓包,在一些相關(guān)
檢查后.我們開始得到從鏈路層和IP層抓來的頭部信息,。通過閱讀程序一,你將會(huì)發(fā)現(xiàn)讓應(yīng)
用程序從網(wǎng)絡(luò)層抓包其實(shí)并不難!
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 | Example 1. #include <stdio.h> #include <errno.h>? #include <unistd.h> #include <sys/socket.h> #include <sys/types.h>? #include <linux/in.h> #include <linux/if_ether.h> int?main(int?argc,?char?**argv) { ??int?sock, n; ??char?buffer[2048]; ??unsigned?char?*iphead, *ethhead; ??? ??if?( (sock=socket(PF_PACKET, SOCK_RAW, ????????????????????htons(ETH_P_IP)))<0) { ????perror("socket"); ????exit(1); ??} ??while?(1) { ????printf("----------\n"); ????n = recvfrom(sock,buffer,2048,0,NULL,NULL); ????printf("%d bytes read\n",n); ????/* Check to see if the packet contains at least ?????* complete Ethernet (14), IP (20) and TCP/UDP ?????* (8) headers. ?????*/ ????if?(n<42) { ??????perror("recvfrom():"); ??????printf("Incomplete packet (errno is %d)\n", ?????????????errno); ??????close(sock); ??????exit(0); ????} ????ethhead = buffer; ????printf("Source MAC address: " ???????????"%02x:%02x:%02x:%02x:%02x:%02x\n", ???????????ethhead[0],ethhead[1],ethhead[2], ???????????ethhead[3],ethhead[4],ethhead[5]); ????printf("Destination MAC address: " ???????????"%02x:%02x:%02x:%02x:%02x:%02x\n", ???????????ethhead[6],ethhead[7],ethhead[8], ???????????ethhead[9],ethhead[10],ethhead[11]); ????iphead = buffer+14;?/* Skip Ethernet header */ ????if?(*iphead==0x45) {?/* Double check for IPv4 ??????????????????????????* and no options present */ ??????printf("Source host %d.%d.%d.%d\n", ?????????????iphead[12],iphead[13], ?????????????iphead[14],iphead[15]); ??????printf("Dest host %d.%d.%d.%d\n", ?????????????iphead[16],iphead[17], ?????????????iphead[18],iphead[19]); ??????printf("Source,Dest ports %d,%d\n", ?????????????(iphead[20]<<8)+iphead[21], ?????????????(iphead[22]<<8)+iphead[23]); ??????printf("Layer-4 protocol %d\n",iphead[9]); ????} ??} ??? } |
PF_PACKET協(xié)議簇可以讓一個(gè)應(yīng)用程序把數(shù)據(jù)包變成似乎從網(wǎng)絡(luò)層接收的樣子,但是
沒有辦法抓到那些不是發(fā)向自己主機(jī)的包。正如我們前面看到的,網(wǎng)卡丟棄所有不含有主機(jī)
MAC地址的數(shù)據(jù)包,這是因?yàn)榫W(wǎng)卡處于非混雜模式,即每個(gè)網(wǎng)卡只處理源地址是它自己的幀!
只有三個(gè)例外:如果一個(gè)幀的目的MAC地址是一個(gè)受限的廣播地址(255.255.255.255)那么
它將被所有的網(wǎng)卡接收:如果一個(gè)幀的目的地址是組播地址,那么它將被那些打開組播接收
功能的網(wǎng)卡所接收;網(wǎng)卡如被設(shè)置成混雜模式,那么它將接收所有流經(jīng)它的數(shù)據(jù)包
最后一種情況當(dāng)然是我們最感興趣的了,把網(wǎng)卡設(shè)置成混雜模式,我們只需要發(fā)出一個(gè)
特殊的ioctl()調(diào)用在那個(gè)網(wǎng)卡上打開一個(gè)socket,因?yàn)檫@是一個(gè)具有危險(xiǎn)性的操作,所以這
個(gè)調(diào)用只有具有root權(quán)限的用戶才可完成,假設(shè)那個(gè)“sock”包含一個(gè)已經(jīng)打開的socket,
下面的代碼將完成這個(gè)操作:
| 1 2 3 4 | strncpy(ethreq.ifr_name,"eth0",IFNAMSIZ); ioctl(sock, SIOCGIFFLAGS, ðreq); ethreq.ifr_flags |= IFF_PROMISC; ioctl(sock, SIOCSIFFLAGS, ðreq); |
下面我們來看一個(gè)完整的例子:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 | Example 2. #include <stdio.h> #include <string.h> #include <errno.h>? #include <unistd.h> #include <sys/socket.h> #include <sys/types.h>? #include <linux/in.h> #include <linux/if_ether.h> #include <net/if.h> #include <sys/ioctl.h> int?main(int?argc,?char?**argv) { ??int?sock, n; ??char?buffer[2048]; ??unsigned?char?*iphead, *ethhead; ??struct?ifreq ethreq; ??? ??if?( (sock=socket(PF_PACKET, SOCK_RAW, ????????????????????htons(ETH_P_IP)))<0) { ????perror("socket"); ????exit(1); ??} ??/* Set the network card in promiscuos mode */ ??strncpy(ethreq.ifr_name,"eth0",IFNAMSIZ); ??if?(ioctl(sock,SIOCGIFFLAGS,?req)==-1) { ????perror("ioctl"); ????close(sock); ????exit(1); ??} ??ethreq.ifr_flags|=IFF_PROMISC; ??if?(ioctl(sock,SIOCSIFFLAGS,?req)==-1) { ????perror("ioctl"); ????close(sock); ????exit(1); ??} ??? ??while?(1) { ????printf("----------\n"); ????n = recvfrom(sock,buffer,2048,0,NULL,NULL); ????printf("%d bytes read\n",n); ????/* Check to see if the packet contains at least ?????* complete Ethernet (14), IP (20) and TCP/UDP ?????* (8) headers. ?????*/ ????if?(n<42) { ??????perror("recvfrom():"); ??????printf("Incomplete packet (errno is %d)\n", ?????????????errno); ??????close(sock); ??????exit(0); ????} ????ethhead = buffer; ????printf("Source MAC address: " ???????????"%02x:%02x:%02x:%02x:%02x:%02x\n", ???????????ethhead[0],ethhead[1],ethhead[2], ???????????ethhead[3],ethhead[4],ethhead[5]); ????printf("Destination MAC address: " ???????????"%02x:%02x:%02x:%02x:%02x:%02x\n", ???????????ethhead[6],ethhead[7],ethhead[8], ???????????ethhead[9],ethhead[10],ethhead[11]); ????iphead = buffer+14;?/* Skip Ethernet header */ ????if?(*iphead==0x45) {?/* Double check for IPv4 ??????????????????????????* and no options present */ ??????printf("Source host %d.%d.%d.%d\n", ?????????????iphead[12],iphead[13], ?????????????iphead[14],iphead[15]); ??????printf("Dest host %d.%d.%d.%d\n", ?????????????iphead[16],iphead[17], ?????????????iphead[18],iphead[19]); ??????printf("Source,Dest ports %d,%d\n", ?????????????(iphead[20]<<8)+iphead[21], ?????????????(iphead[22]<<8)+iphead[23]); ??????printf("Layer-4 protocol %d\n",iphead[9]); ????} ??} ??? } |
如果我們?cè)谝粋€(gè)與局域網(wǎng)相連的主機(jī)上以root權(quán)限編譯和運(yùn)行這個(gè)程序,你將會(huì)看到流
經(jīng)的所有數(shù)據(jù)包,即使它不是發(fā)向自己主機(jī)的,這是因?yàn)槟愕木W(wǎng)卡現(xiàn)在處于混雜模式,你可
以很容易的用ifconfig這個(gè)命令看到!
注意,如果你的局域網(wǎng)用的是交換機(jī)而不是集線器,那么你只能抓到在你主機(jī)所在的交
換分支中的數(shù)據(jù)包,這是由于交換的工作原理,在這種情況下,你沒有什么其他可做的了(除非你利用MAC地址欺騙來瞞過交換機(jī),這個(gè)內(nèi)容超出本文討論的范圍了),如果你想知道這方面更多的信息,你可以去www.google.com,那里你將會(huì)搜索到很多你需要的東西!
我們關(guān)于嗅探方面的問題似乎都解決了,但是還有一個(gè)問題值得我們?nèi)ニ伎?#xff01;如果我們
實(shí)驗(yàn)Example2的時(shí)候正好遇到了網(wǎng)絡(luò)擁塞,我們將會(huì)看到我們的嗅探器將打出非常多的數(shù)據(jù),隨著網(wǎng)絡(luò)擁塞的加劇,主機(jī)將不能順暢的執(zhí)行這個(gè)程序,嗅探器這時(shí)會(huì)開始丟包!
解決這個(gè)問題的方法就是對(duì)你抓到的包進(jìn)行過濾,使它只顯示那些你感興趣的那些部分!
在源代碼中多使用一些if語(yǔ)句,這將有益于去除那些不需要的信息,當(dāng)然這樣也許效率不是
很高!內(nèi)核仍然將處理所有的報(bào)文,這將浪費(fèi)進(jìn)程時(shí)間。?
最好的解決辦法就是把過濾過程盡可能早的放報(bào)文進(jìn)程鏈(packet-processing chain)
中(它開始于鏈路層,終止于應(yīng)用層)LINUX的內(nèi)核允許我們把一個(gè)名為L(zhǎng)PF的過濾器直接放到PF_PACKET protocol-processing routines(在網(wǎng)卡接收中斷執(zhí)行后立即執(zhí)行)中,過濾器將決定哪些包被送到應(yīng)用程序,哪些包被丟棄!
為了做到盡可能的靈活,這個(gè)過濾程序可以根據(jù)使用者的定義來運(yùn)行!這個(gè)程序是由一種名為BPF的偽機(jī)器碼寫成的。BPF看上去很象帶著一對(duì)寄存器和保存值的匯編語(yǔ)言,完成數(shù)學(xué)運(yùn)算和條件分支程序!過濾程序檢查每個(gè)包,BP進(jìn)程操作的內(nèi)存空間包含著報(bào)文數(shù)據(jù)!過濾的結(jié)果是一個(gè)整數(shù),指出有多少字節(jié)的報(bào)文需要送到應(yīng)用層。這是一個(gè)更進(jìn)一步的優(yōu)點(diǎn),因?yàn)槲覀円话阒粚?duì)報(bào)文的前面幾個(gè)字節(jié)感興趣,這樣可以避免復(fù)制過量的數(shù)據(jù)以節(jié)省進(jìn)程時(shí)間。
雖然BPF語(yǔ)言非常簡(jiǎn)單易學(xué),但是大多數(shù)人還是習(xí)慣于人類可以閱讀的表達(dá)方式!所以
為了不用BPF語(yǔ)言去描述那些細(xì)節(jié)和代碼,我們將下面開始討論如何得到一個(gè)過濾器的代碼!
首先,你需要安裝tcpdump,可以從LBL得到!但是,如果你正在讀本文,那似乎說明
你已經(jīng)會(huì)使用tcpdump了!第一個(gè)版本的作者也是那些寫B(tài)PF的人,事實(shí)上,tcpdump要用
到BPF,它要用到一個(gè)叫l(wèi)ibpcap的類庫(kù),以此去抓包和過濾。這個(gè)類庫(kù)是一個(gè)與操作系統(tǒng)
無關(guān)的獨(dú)立的包,為BPF的實(shí)現(xiàn)提供支持,當(dāng)在裝有LINUX的機(jī)器上使用時(shí),BPF的功能就
由LINUX包過濾器實(shí)現(xiàn)了!
libpcap提供的一個(gè)最有用的函數(shù)是pcap_compile(), 它可以把一個(gè)輸入輸出的邏輯表
達(dá)式變?yōu)锽PF代碼!tcpdump利用這個(gè)函數(shù)完成在用戶輸入的命令行和BPF代碼之間的轉(zhuǎn)換!
tcpdump有個(gè)我們很感興趣但是很少使用的參數(shù) ,-d,可以輸出BPF代碼!
舉個(gè)例子,如果tcpdump host 192.168.9.10那么將啟動(dòng)嗅探器并只抓到源地址或者目
的地址是192.168.9.10的報(bào)文!如果tcpdump -d host 192.168.9.10那么將顯示出BPF代
碼,見下面
Example 3.?
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | Seal:~# tcpdump -d host 192.168.9.10 (000) ldh????? [12] (001) jeq????? #0x800?????????? jt 2??? jf 6 (002) ld?????? [26] (003) jeq????? #0xc0a8090a????? jt 12?? jf 4 (004) ld?????? [30] (005) jeq????? #0xc0a8090a????? jt 12?? jf 13 (006) jeq????? #0x806?????????? jt 8??? jf 7 (007) jeq????? #0x8035????????? jt 8??? jf 13 (008) ld?????? [28] (009) jeq????? #0xc0a8090a????? jt 12?? jf 10 (010) ld?????? [38] (011) jeq????? #0xc0a8090a????? jt 12?? jf 13 (012) ret????? #68 (013) ret????? #0 |
我們簡(jiǎn)單的分析一下這個(gè)代碼,0-1,6-7行在確定被抓到的幀是否在傳輸著IP,ARP或
者RARP協(xié)議,通過比較幀的第12格的值與協(xié)議的特征值(見/usr/include/linux/if_ether.h)!如果比較失敗,那么丟棄這個(gè)包!
2-5,8-11行是比較幀的源地址和目的地址是否與192.168.9.10相同!注意,不同的
協(xié)議,地址值在幀中的偏移位不同,如果是IP協(xié)議,它在26-30,如果是其他協(xié)議,它在
28-38如果有一個(gè)地址符合,那么幀的前68字節(jié)將被送到應(yīng)用程序去(第12行)
這個(gè)過濾器也不是總是有效的,因?yàn)樗a(chǎn)生于一般的使用BPF的機(jī)器,沒考慮到一些特
殊結(jié)構(gòu)的機(jī)器!在一些特殊情況下,過濾器由PF_PACKET進(jìn)程運(yùn)行,也許已經(jīng)檢查過以太協(xié)
議了!這個(gè)根據(jù)你在socket()調(diào)用初使化的時(shí)候指定的那些協(xié)議!如果不是ETH_P_ALL(抓
所有的報(bào)文),那么只有那些符合指定的協(xié)議類型的報(bào)文會(huì)流過過濾器!舉個(gè)例子,如果是
ETH_P_IP socket,我們可以重寫一個(gè)更快且代碼更緊湊的過濾器,代碼如下
| 1 2 3 4 5 | (000) ld?????? [26] (001) jeq????? #0xc0a8090a????? jt 4??? jf 2 (002) ld?????? [30] (003) jeq????? #0xc0a8090a????? jt 4??? jf 5 (004) ret????? #68(005) ret????? #0 |
安裝LPF是一個(gè)一往直前的操作,所有你需要做的就是建立一個(gè)sock_filter并為
它綁定一個(gè)打開的端口!一個(gè)過濾器很容易得到,只要把tcpdump -d中的-d換成-dd就可以了!過濾器將顯示出一段C代碼,你可以把它復(fù)制到自己的程序中,見Example 4,這樣你就可以通過調(diào)用setsockopt()來過濾端口!
Example 4.?
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | Seal:~# tcpdump -dd host 192.168.9.01 { 0x28, 0, 0, 0x0000000c }, { 0x15, 0, 4, 0x00000800 }, { 0x20, 0, 0, 0x0000001a }, { 0x15, 8, 0, 0xc0a80901 }, { 0x20, 0, 0, 0x0000001e }, { 0x15, 6, 7, 0xc0a80901 }, { 0x15, 1, 0, 0x00000806 }, { 0x15, 0, 5, 0x00008035 }, { 0x20, 0, 0, 0x0000001c }, { 0x15, 2, 0, 0xc0a80901 }, { 0x20, 0, 0, 0x00000026 }, { 0x15, 0, 1, 0xc0a80901 }, { 0x6, 0, 0, 0x00000044 }, { 0x6, 0, 0, 0x00000000 |
A Complete Example
我們將用一個(gè)完整的程序Example 5來結(jié)束這篇文章
Example 5
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 | #include <stdio.h> #include <string.h> #include <errno.h>? #include <unistd.h> #include <sys/socket.h> #include <sys/types.h>? #include <linux/in.h> #include <linux/if_ether.h> #include <net/if.h> #include <linux/filter.h> #include <sys/ioctl.h> int?main(int?argc,?char?**argv) { ??int?sock, n; ??char?buffer[2048]; ??unsigned?char?*iphead, *ethhead; ??struct?ifreq ethreq; ??/* ????udp and host 192.168.9.10 and src port 5000 ????(000) ldh????? [12] ????(001) jeq????? #0x800?????????? jt 2??????? jf 14 ????(002) ldb????? [23] ????(003) jeq????? #0x11??????????? jt 4??????? jf 14 ????(004) ld?????? [26] ????(005) jeq????? #0xc0a8090a????? jt 8??????? jf 6 ????(006) ld?????? [30] ????(007) jeq????? #0xc0a8090a????? jt 8??????? jf 14 ????(008) ldh????? [20] ????(009) jset???? #0x1fff????????? jt 14?????? jf 10 ????(010) ldxb???? 4*([14]&0xf) ????(011) ldh????? [x + 14] ????(012) jeq????? #0x1388????????? jt 13?????? jf 14 ????(013) ret????? #68 ????(014) ret????? #0 ??*/ ??struct?sock_filter BPF_code[]= { ????{ 0x28, 0, 0, 0x0000000c }, ????{ 0x15, 0, 12, 0x00000800 }, ????{ 0x30, 0, 0, 0x00000017 }, ????{ 0x15, 0, 10, 0x00000011 }, ????{ 0x20, 0, 0, 0x0000001a }, ????{ 0x15, 2, 0, 0xc0a8090a }, ????{ 0x20, 0, 0, 0x0000001e }, ????{ 0x15, 0, 6, 0xc0a8090a }, ????{ 0x28, 0, 0, 0x00000014 }, ????{ 0x45, 4, 0, 0x00001fff }, ????{ 0xb1, 0, 0, 0x0000000e }, ????{ 0x48, 0, 0, 0x0000000e }, ????{ 0x15, 0, 1, 0x00001388 }, ????{ 0x6, 0, 0, 0x00000044 }, ????{ 0x6, 0, 0, 0x00000000 } ??};??????????????????????????? ??struct?sock_fprog Filter; ????? ??Filter.len = 15; ??Filter.filter = BPF_code; ??? ??if?( (sock=socket(PF_PACKET, SOCK_RAW, ????????????????????htons(ETH_P_IP)))<0) { ????perror("socket"); ????exit(1); ??} ??/* Set the network card in promiscuos mode */ ??strncpy(ethreq.ifr_name,"eth0",IFNAMSIZ); ??if?(ioctl(sock,SIOCGIFFLAGS,?req)==-1) { ????perror("ioctl"); ????close(sock); ????exit(1); ??} ??ethreq.ifr_flags|=IFF_PROMISC; ??if?(ioctl(sock,SIOCSIFFLAGS,?req)==-1) { ????perror("ioctl"); ????close(sock); ????exit(1); ??} ??/* Attach the filter to the socket */ ??if(setsockopt(sock, SOL_SOCKET, SO_ATTACH_FILTER, ????????????????&Filter,?sizeof(Filter))<0){ ????perror("setsockopt"); ????close(sock); ????exit(1); ??} ??while?(1) { ????printf("----------\n"); ????n = recvfrom(sock,buffer,2048,0,NULL,NULL); ????printf("%d bytes read\n",n); ????/* Check to see if the packet contains at least ?????* complete Ethernet (14), IP (20) and TCP/UDP ?????* (8) headers. ?????*/ ????if?(n<42) { ??????perror("recvfrom():"); ??????printf("Incomplete packet (errno is %d)\n", ?????????????errno); ??????close(sock); ??????exit(0); ????} ????ethhead = buffer; ????printf("Source MAC address: " ???????????"%02x:%02x:%02x:%02x:%02x:%02x\n", ???????????ethhead[0],ethhead[1],ethhead[2], ???????????ethhead[3],ethhead[4],ethhead[5]); ????printf("Destination MAC address: " ???????????"%02x:%02x:%02x:%02x:%02x:%02x\n", ???????????ethhead[6],ethhead[7],ethhead[8], ???????????ethhead[9],ethhead[10],ethhead[11]); ????iphead = buffer+14;?/* Skip Ethernet? header */ ????if?(*iphead==0x45) {?/* Double check for IPv4 ??????????????????????????* and no options present */ ??????printf("Source host %d.%d.%d.%d\n", ?????????????iphead[12],iphead[13], ?????????????iphead[14],iphead[15]); ??????printf("Dest host %d.%d.%d.%d\n", ?????????????iphead[16],iphead[17], ?????????????iphead[18],iphead[19]); ??????printf("Source,Dest ports %d,%d\n", ?????????????(iphead[20]<<8)+iphead[21], ?????????????(iphead[22]<<8)+iphead[23]); ??????printf("Layer-4 protocol %d\n",iphead[9]); ????} ??} ??? } |
這個(gè)程序和開始的前兩個(gè)程序很類似,只是增加了LSP代碼和setsockopt()調(diào)用,這個(gè)
過濾器被用來嗅探UDP報(bào)文(源地址或者目的地址為192.168.9.10且源地址的端口為5000)
為了測(cè)試這個(gè)程序,你需要一個(gè)簡(jiǎn)單的方法去生成不斷的UDP報(bào)文(如發(fā)送或者接受IP),而且你可以把程序修改為你指定的機(jī)器的IP地址,你只需要把代碼中的0xc0a8090a替換成你要的IP地址的16進(jìn)制形式!
最后想說的就是關(guān)閉程序后如果我們沒有重新設(shè)置網(wǎng)卡,那么它還是處于混雜模式!你
可以加一個(gè)Control-C信號(hào)句柄來使網(wǎng)卡在程序關(guān)閉前恢復(fù)以前的默認(rèn)配置。
進(jìn)行網(wǎng)絡(luò)嗅探對(duì)于診斷網(wǎng)絡(luò)運(yùn)行錯(cuò)誤或者進(jìn)行流量分析都是非常重要的!有些時(shí)候常用
的工具如tcpdump或者Ethereal不能非常符合我們的需要的時(shí)候,我們可以自己寫一個(gè)嗅探
器!為此我們應(yīng)該感謝LPF,它使我們做到這點(diǎn)變的很容易!
?
========================================================================== 本文轉(zhuǎn)自被遺忘的博客園博客,原文鏈接:http://www.cnblogs.com/rollenholt/articles/2585432.html,如需轉(zhuǎn)載請(qǐng)自行聯(lián)系原作者
總結(jié)
以上是生活随笔為你收集整理的Linux下Sniffer程序的实现的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: UVa 1590 - IP Networ
- 下一篇: 通讯录排序