基本介紹
嗅探 sniff。嗅探器可以竊聽網路上流經的數據包。用集線器hub組建的網路是基於共享的原理的,區域網路內所有的計算機都接收相同的數據包,而網卡構造了硬體的“過濾器“通過識別MAC地址過濾掉和自己無關的信息,嗅探程式只需關閉這個過濾器,將網卡設定為“混雜模式“就可以進行嗅探。用交換機switch組建的網路是基於“交換“原理的,交換機不是把數據包發到所有的連線埠上,而是發到目的網卡所在的連線埠,這樣嗅探起來會麻煩一些,嗅探程式一般利用“ARP欺騙“的方法,通過改變MAC地址等手段,欺騙交換機將數據包發給自己,嗅探分析完畢再轉發出去。
相關介紹
SNIFF真是一個古老的話題,關於在網路上採用SNIFF來獲取敏感信息已經不是什麼新鮮事,也不乏很多成功的案例,那么,SNIFF究竟是什麼呢?SNIFF就是嗅探器,就是竊聽器,SNIFF靜悄悄的工作在網路的底層,把你的秘密全部記錄下來。看過威爾史密斯演的《全民公敵》嗎?SNIFF就象裡面精巧的竊聽器一樣,讓你防不勝防。
SNIFF可以是軟體,也可以是硬體,既然是軟體那就要分平台,有WINDOWS下的、UNIX下的等,硬體的SNIFF稱為網路分析儀,反正不管硬體軟體,目標只有一個,就是獲取在網路上傳輸的各種信息。本文僅僅介紹軟體的SNIFF。
當你舒適的坐在家裡,愜意的享受網路給你帶來的便利,收取你的EMAIL,購買你喜歡的物品的時候,你是否會想到你的朋友給你的信件,你的信用卡帳號變成了一個又一個的信息包在網路上不停的傳送著,你是否想過,這些信息包會通過網路流入別人的機器呢?你的擔憂不是沒有道理的,因為SNIFF可以讓你的擔憂變成實實在在的危險。就好像一個人躲在你身後偷看你一樣。
網路基礎知識
“網路基礎知識”,是不是聽起來有點離題了?雖然聽起來這和我們要談的SNIFF沒什麼關係,可是還是要說一說的,萬丈高樓平地起,如果連地基都沒打好,怎么蓋樓?!如果你對網路還不是十分清楚的話,最好能靜下心來好好看看,要知道,這是基礎的基礎,在這裡我只是簡單的說一下,免得到時候有人迷糊,詳細的最好能夠自己去找書看看。
TCP/IP體系結構
開放系統互連(OSI)模型將網路劃分為七層模型,分別用以在各層上實現不同的功能,這七層分別為:套用層、表示層、會話層、傳輸層、網路層、數據鏈路層及物理層。而TCP/IP體系也同樣遵循這七層標準,只不過在某些OSI功能上進行了壓縮,將表示層及會話層合併入套用層中,所以實際上我們打交道的TCP/IP僅僅有5層而已,網路上的分層結構決定了在各層上的協定分布及功能實現,從而決定了各層上網路設備的使用。實際上很多成功的系統都是基於OSI模型的,如:如幀中繼、ATM、ISDN等。
TCP/IP的網路體系結構(部分)
從上面的圖中我們可以看出,第一層物理層和第二層數據鏈路層是TCP/IP的基礎,而TCP/IP本身並不十分關心低層,因為處在數據鏈路層的網路設備驅動程式將上層的協定和實際的物理接口隔離開來。網路設備驅動程式位於介質訪問子層(MAC)
網路上的設備
中繼器:中繼器的主要功能是終結一個網段的信號並在另一個網段再生該信號,一句話,就是簡單的放大而已,工作在物理層上。
網 橋:網橋使用MAC物理地址實現中繼功能,可以用來分隔網段或連線部分異種網路,工作在數據鏈路層。
路由器:路由器使用網路層地址(IP,X.121,E.164等),主要負責數據包的路由尋徑,也能處理物理層和數據鏈路層上的工作。
網 關:主要工作在網路第四層以上,主要實現收斂功能及協定轉換,不過很多時候網關都被用來描述任何網路互連設備。
TCP/IP與乙太網
乙太網和TCP/IP可以說是相輔相成的,可以說兩者的關係幾乎是密不可分,乙太網在一二層提供物理上的連線,而TCP/IP工作在上層,使用32位的IP位址,乙太網則使用48位的MAC地址,兩者間使用ARP和RARP協定進行相互轉換。從我們上面TCP/IP的模型圖中可以清楚的看到兩者的關係。
載波監聽/衝突檢測(CSMA/CD)技術被普遍的使用在乙太網中,所謂載波監聽是指在乙太網中的每個站點都具有同等的權利,在傳輸自己的數據時,首先監聽信道是否空閒,如果空閒,就傳輸自己的數據,如果信道被占用,就等待信道空閒。而衝突檢測則是為了防止發生兩個站點同時監測到網路沒有被使用時而產生衝突。乙太網採用廣播機制,所有與網路連線的工作站都可以看到網路上傳遞的數據。
為了加深你的理解,我們來看看下面的圖,一個典型的在乙太網中客戶與伺服器使用TCP/IP協定的通信。
SNIFF原理
要知道在乙太網中,所有的通訊都是廣播的,也就是說通常在同一個網段的所有網路接口都可以訪問在物理媒體上傳輸的所有數據,而每一個網路接口都有一個唯一的硬體地址,這個硬體地址也就是網卡的MAC地址,大多數系統使用48比特的地址,這個地址用來表示網路中的每一個設備,一般來說每一塊網卡上的MAC地址都是不同的,每個網卡廠家得到一段地址,然後用這段地址分配給其生產的每個網卡一個地址。在硬體地址和IP位址間使用ARP和RARP協定進行相互轉換。
在正常的情況下,一個網路接口應該只回響這樣的兩種數據幀:
1.與自己硬體地址相匹配的數據幀。
2.發向所有機器的廣播數據幀。
在一個實際的系統中,數據的收發是由網卡來完成的,網卡接收到傳輸來的數據,網卡內的單片程式接收數據幀的目的MAC地址,根據計算機上的網卡驅動程式設定的接收模式判斷該不該接收,認為該接收就接收後產生中斷信號通知CPU,認為不該接收就丟掉不管,所以不該接收的數據網卡就截斷了,計算機根本就不知道。CPU得到中斷信號產生中斷,作業系統就根據網卡的驅動程式設定的網卡中斷程式地址調用驅動程式接收數據,驅動程式接收數據後放入信號堆疊讓作業系統處理。而對於網卡來說一般有四種接收模式:
廣播方式:該模式下的網卡能夠接收網路中的廣播信息。 組播方式:設定在該模式下的網卡能夠接收組播數據。
直接方式:在這種模式下,只有目的網卡才能接收該數據。混雜模式:在這種模式下的網卡能夠接收一切通過它的數據,而不管該數據是否是傳給它的。
好了,現在我們總結一下,首先,我們知道了在乙太網中是基於廣播方式傳送數據的,也就是說,所有的物理信號都要經過我的機器,再次,網卡可以置於一種模式叫混雜模式(promiscuous),在這種模式下工作的網卡能夠接收到一切通過它的數據,而不管實際上數據的目的地址是不是他。這實際上就是我們SNIFF工作的基本原理:讓網卡接收一切他所能接收的數據。
我們來看一個簡單的例子,機器A、B、C與集線器HUB相連線,集線器HUB通過路由器Router訪問外部網路。這是一個很簡單也很常見的情況,比如說在公司大樓里,我所在的網路部辦公室里的幾台機器通過集線器連線,而網路部、開發部、市場部也是同樣如此,幾個部門的集線器通過路由器連線。值得注意的一點是機器A、B、C使用一個普通的HUB連線的,不是用SWITCH,也不是用ROUTER,使用SWITCH和ROUTER的情況要比這複雜得多。
我們假設一下機器A上的管理員為了維護機器C,使用了一個FTP命令向機器C進行遠程登入,那么在這個用HUB連線的網路里數據走向過程是這樣的。首先機器A上的管理員輸入的登入機器C的FTP口令經過套用層FTP協定、傳輸層TCP協定、網路層IP協定、數據鏈路層上的乙太網驅動程式一層一層的包裹,最後送到了物理層,我們的網線上。接下來數據幀送到了HUB上,現在由HUB向每一個接點廣播由機器A發出的數據幀,機器B接收到由HUB廣播發出的數據幀,並檢查在數據幀中的地址是否和自己的地址相匹配,發現不是發向自己的後把這數據幀丟棄,不予理睬。而機器C也接收到了數據幀,並在比較之後發現是發現自己的,接下來他就對這數據幀進行分析處理。
在上面這個簡單的例子中,機器B上的管理員如果很好奇,他很想知道究竟登入機器C上FTP口令是什麼?那么他要做的很簡單,僅僅需要把自己機器上的網卡置於混雜模式,並對接收到的數據幀進行分析,從而找到包含在數據幀中的口令信息。
sniff做法
在上一節里,我們已經知道了SNIFF的基本原理是怎么一回事,這一節我們來親自動手做一個自己的sniff,畢竟,用程式代碼來說話比什麼都要來得真實,也容易加深理解。
回頭想一想我們上面說的原理,我們要做的事情有幾件:
1. 把網卡置於混雜模式。 2. 捕獲數據包。 3.分析數據包。
註:下面的原始碼取至Chad Renfro的《Basic Packet-SnifferConstruction from the Ground Up》一文中
/************************Tcp_sniff_2.c********************/
1.#include
2.#include
3.#include
4.#include
5.#include
6.#include
7.#include
8.#include
9.#include "headers.h"
#define INTERFACE "eth0"
/*Prototype area*/
1 0 int Open_Raw_Socket(void);
11 int Set_Promisc(char *interface,intsock);
12 int main() {
13int sock,bytes_recieved,fromlen;
14.char buffer[65535];
15.struct sockaddr_in from;
16.struct ip *ip;
17.struct tcp *tcp;
18.sock = Open_Raw_Socket();
19. Set_Promisc(INTERFACE,sock);
20. while(1)
22. {
23. fromlen = sizeof from;
24. bytes_recieved = recvfrom(sock,buffer,sizeofbuffer,0,(struct sockaddr *)&from,&fromlen);
25. printf("\nBytes received :::%5d\n",bytes_recieved);
26. printf("Source address :::%s\n",inet_ntoa(from.sin_addr));
27. ip = (struct ip *)buffer;
/*See if this is a TCP packet*/
28. if(ip->ip_protocol == 6) {
29. printf("IP header length :::%d\n",ip->ip_length);
30. printf("Protocol :::%d\n",ip->ip_protocol);
31. tcp = (struct tcp *)(buffer +(4*ip->ip_length));
32. printf("Source port :::%d\n",ntohs(tcp->tcp_source_port));
33. printf("Dest port :::%d\n",ntohs(tcp->tcp_dest_port));
34. }
35. }
36.}
37 int Open_Raw_Socket() {
38. int sock;
39. if((sock = socket(AF_INET,SOCK_RAW,IPPROTO_TCP)) < 0){
/*Then the socket was not created properly and must die*/
40. perror("The raw socket was not created");
41. exit(0);
42. };
43. return(sock);
44. }
45 int Set_Promisc(char *interface,int sock ) {
46. struct ifreq ifr;
47. strncpy(ifr.ifr_name,interface,strnlen(interface)+1);
48. if((ioctl(sock,SIOCGIFFLAGS,&ifr) == -1)) {
/*Could not retrieve flags for the interface*/
49. perror("Could not retrive flags for the interface");
50. exit(0);
51. }
52. printf("The interface is ::: %s\n",interface);
53. perror("Retrieved flags from interface successfully");
54. ifr.ifr_flags |= IFF_PROMISC;
55. if (ioctl (sock,SIOCSIFFLAGS,&ifr) == -1 ) {
/*Could not set the flags on the interface */
56. perror("Could not set the PROMISC flag:");
57. exit(0);
58. }
59. printf("Setting interface ::: %s ::: to promisc",interface);
60. return(0);
61. }
/***********************EOF**********************************/
上面這段程式中有很詳細的註解,不過我想還是有必要說一說,首先第10行--intOpen_Raw_Socket(void); 是我們的自定義函式,具體內容如下:
37 int Open_Raw_Socket() {
38. int sock;
39. if((sock = socket(AF_INET,SOCK_RAW,IPPROTO_TCP)) < 0){
/*Then the socket was not created properly and must die*/
40. perror("The raw socket was not created");
41. exit(0);
42. };
43. return(sock);
44. }
第39行 if((sock = socket(AF_INET,SOCK_RAW,IPPROTO_TCP)) < 0) {
這裡我們調用了socket函式,使創建了了一個原始套接口,使之收到TCP/IP信息包。
接下來第11行-int Set_Promisc(char *interface,intsock),這也是我們的自定義函式,目的是把網卡置於混雜模式,具體內容如下:
45 int Set_Promisc(char *interface,int sock ) {
46. struct ifreq ifr;
47. strncpy(ifr.ifr_name,interface,strlen(interface)+1);
48. if((ioctl(sock,SIOCGIFFLAGS,&ifr) == -1)) {
/*Could not retrieve flags for the interface*/
49. perror("Could not retrive flags for the interface");
50. exit(0);
51. }
52. printf("The interface is ::: %s\n",interface);
53. perror("Retrieved flags from interface successfully");
54. ifr.ifr_flags |= IFF_PROMISC;
55. if (ioctl (sock,SIOCSIFFLAGS,&ifr) == -1 ) {
/*Could not set the flags on the interface */
56. perror("Could not set the PROMISC flag:");
57. exit(0);
58. }
59. printf("Setting interface ::: %s ::: to promisc",interface);
60. return(0);
61. }
首先 struct ifreq ifr; 定一了一個ifrreg的結構ifr,接下來strncpy(ifr.ifr_name,interface,strnlen(interface)+1);,就是把我們網路設備的名字填充到ifr結構中,在這裡#define INTERFACE "eth0" ,讓我們再往下看,ioctl(sock,SIOCGIFFLAGS,&ifr),SIOCGIFFLAGS請求表示需要獲取接口標誌,現在到了第54行,在我們成功的獲取接口標誌後把他設定成混雜模式,ifr.ifr_flags|= IFF_PROMISC;ioctl (sock,SIOCSIFFLAGS,&ifr)。OK,現在我們所說的第一步已經完成--------把網卡置於混雜模式。
現在進入第二步,捕獲數據包。從第20行開始,我們進入了一個死循環,while(1),在第24行,recvfrom(sock,buffer,sizeof buffer,0,(struct sockaddr *)&from,&fromlen),這個函式要做的就是接收數據,並把接收到的數據放入buffer中。就是這么簡單,已經完成了我們要捕獲數據包的任務。
到了第三步,分析數據包。27行,ip = (struct ip*)buffer,使我們在頭檔案中的IP結構對應於所接收到的數據,接下來判斷在網路層中是否使用的是TCP協定,if(ip->ip_protocol== 6) ,如果答案是,tcp信息包從整個IP/TCP包 buffer +(4*ip->ip_length) 地址處開始,所以31行 tcp = (struct tcp*)(buffer +(4*ip->ip_length)),然後對應結構把你所需要的信息輸出。
/*************************headers.h**************************/
/*structure of an ip header*/
struct ip {
unsigned int ip_length:4; /*little-endian*/
unsigned int ip_version:4;
unsigned char ip_tos;
unsigned short ip_total_length;
unsigned short ip_id;
unsigned short ip_flags;
unsigned char ip_ttl;
unsigned char ip_protocol;
unsigned short ip_cksum;
unsigned int ip_source; unsigned int ip_dest;
};
/* Structure of a TCP header */
struct tcp {
unsigned short tcp_source_port;
unsigned short tcp_dest_port;
unsigned int tcp_seqno;
unsigned int tcp_ackno;
unsigned int tcp_res1:4,/*little-endian*/
tcp_hlen:4,
tcp_fin:1,
tcp_syn:1,
tcp_rst:1,
tcp_psh:1,
tcp_ack:1,
tcp_urg:1,
tcp_res2:2;
unsigned short tcp_winsize;
unsigned short tcp_cksum;
unsigned short tcp_urgent;
};
/*********************EOF***********************************/
從上面的分析我們可以清楚的認識到,認識一個SNIFF需要對TCP/IP協定有著詳細的了解,否則你根本無法找到你需要的信息。有了上面的基礎,你可以自己來做一個你需要的SNIFF了。