ICMP掃描

icmp(Internet control message protocol),是一種網絡上用來傳遞錯誤報文的協議,根據類型和代碼,可以分爲很多類型的,爲了實現icmp掃描,我們這裏只需要用到請求回顯(type=8,code=0)和回顯應答(type=0,code=0),具體情況,請查看icmp,報文格式。

原理,像需要掃描的ip發送icmp請求回顯,如果收到icmp回顯應答,則該ip處於活動狀態,否則不是,這裏我開發環境是win7+vs2013,建立的普通控制條程序,使用socket即可。

1.定義header.h頭文件,用於定於一些需要使用的頭部定義

#include"winsock2.h"
#pragma comment(lib,"ws2_32")
#include"WS2TCPIP.H"
#include "mstcpip.h"
//ip頭部,每個變量含義請參考ip首部格式,注意編譯器對結構體優化的問題,詳細情況,請看http://blog.csdn.net/u012198947/article/details/51078214裏面的說明
typedef struct _iphdr
{
	unsigned char h_lenver;
	unsigned char tos;
	unsigned short total_len;
	unsigned short ident;
	unsigned short frag_and_flags;
	unsigned char ttl;
	unsigned char proto;
	unsigned short checksum;
	unsigned int sourceIP;
	unsigned int destIP;
}IPHEADER;
//icmp頭部
typedef struct _icmphdr
{
	BYTE i_type;
	BYTE i_code;
	USHORT i_cksum;
	USHORT i_id;
	USHORT i_seq;
	ULONG timestamp;
}ICMPHEADER;

//解碼結果
typedef struct
{
	USHORT usSeqNo;		  //包序列號
	DWORD dwRoundTripTime; //往返時間
	in_addr dwIPaddr;	  //對端IP地址
} DECODE_RESULT;//ip頭部定義,具體含義,參考ip頭部格式


2.發送數據包的函數

//在主函數中調用,用於發送icmp數據包
//返回值是執行結構,成功0,失敗-1
//參數與分別是定義好的套接字,目標地址的結構體指針
int SendEchoRequest(SOCKET s, LPSOCKADDR_IN lpstToAddr)
{
	int nRet; //
	char IcmpSendBuf[sizeof(ICMPHEADER)];//存放包的緩存區

	//填充數據包
	memset(IcmpSendBuf, 0, sizeof(IcmpSendBuf));
	ICMPHEADER *pIcmpHeader = (ICMPHEADER*)IcmpSendBuf;
	pIcmpHeader->i_type = 8; //回寫請求消息
	pIcmpHeader->i_code = 0;
	pIcmpHeader->i_id = (USHORT)GetCurrentProcessId();//可以隨便給,這裏爲了方便就給當前線程號
	pIcmpHeader->i_seq = 0x0;//
	pIcmpHeader->i_cksum = 0;
	pIcmpHeader->timestamp = 0x01020304;//數據,隨意,大小也是隨意,這裏我定義的4B
	pIcmpHeader->i_cksum = CheckSum((USHORT*)pIcmpHeader, sizeof(ICMPHEADER));//校驗和計算,函數如下:

	nRet = sendto(s, IcmpSendBuf, sizeof(IcmpSendBuf),0, (LPSOCKADDR)lpstToAddr, sizeof(SOCKADDR_IN));//發送

	if (nRet == SOCKET_ERROR)
	{
		cout << "sento Error" << endl;
		return -1;
	}
	return 0;
}
//計算檢驗和函數
USHORT CheckSum(USHORT *buffer, int size)
{
<span style="white-space:pre">	</span>unsigned long cksum = 0;
<span style="white-space:pre">	</span>while (size > 1)
<span style="white-space:pre">	</span>{
<span style="white-space:pre">		</span>cksum += *buffer++;
<span style="white-space:pre">		</span>size -= sizeof(USHORT);
<span style="white-space:pre">	</span>}
<span style="white-space:pre">	</span>if (size)
<span style="white-space:pre">	</span>{
<span style="white-space:pre">		</span>cksum += *(UCHAR*)buffer;
<span style="white-space:pre">	</span>}
<span style="white-space:pre">	</span>while (cksum >> 16)
<span style="white-space:pre">		</span>cksum = (cksum >> 16) + (cksum & 0xffff);
<span style="white-space:pre">	</span>return (USHORT)(~cksum);
}
3.用於解析接收到的數據包的函數
/********************************************************
函數名:DecodeIcmpResponse
輸入參數:char* pBuf:接收到的原始數據包(包括IP首部)
int iPacketSize:原始數據包大小
DECODE_RESULT *stDecodeResult:解析結構指針
輸出參數:操作成功的標誌,true:成功,false:失敗
功能:解析收到的原始數據包,將收到的ICMP錯誤報文和響應報文分別處理
*********************************************************/
BOOL DecodeIcmpResponse(char* pBuf, int iPacketSize, DECODE_RESULT& stDecodeResult)
{
	IPHEADER* pIpHdr = (IPHEADER*)pBuf;
	int iIpHdrLen = 20;//ip頭部,固定20字節
	
	//ip首部佔用20字節,定位到icmp報文
	ICMPHEADER* pIcmpHdr = (ICMPHEADER*)(pBuf + iIpHdrLen);
	USHORT usID, usSquNo;
	if (pIcmpHdr->i_type == 0)//回寫應答
	{
		usID = pIcmpHdr->i_id;
		usSquNo = pIcmpHdr->i_seq;
	}
	else
		return FALSE;
	//處理正確收到的ICMP數據報,因爲type=0的icmp只有一種報文,所以不用檢查code
	if (pIcmpHdr->i_type == 0 )
	{
		//返回解碼結果
		stDecodeResult.dwIPaddr.s_addr = pIpHdr->sourceIP;
		stDecodeResult.dwRoundTripTime = GetTickCount() - stDecodeResult.dwRoundTripTime;
		return TRUE;
	}
	return FALSE;
}
4.接收數據包函數
/********************************************************
函數名:RecvEchoReply
輸入參數:SOCKET s:原始套接字
SOCKADDR_IN *saFrom:數據包的來源地址
SOCKADDR_IN *saDest:數據包的目標地址
DECODE_RESULT *stDecodeResult:解析結構指針
輸出參數:操作成功的標誌,true:成功,false:失敗
功能:接收數據,並調用DecodeIcmpResponse對接收到的ICMP響應進行解析
*********************************************************/
DWORD RecvEchoReply(SOCKET s, SOCKADDR_IN *saFrom, SOCKADDR_IN *saDest, DECODE_RESULT *stDecodeResult)
{
	int nRet;
	int nAddrLen = sizeof(struct sockaddr_in);

	//創建ICMP包接收緩衝區
	char IcmpRecvBuf[1024];
	memset(IcmpRecvBuf, 0, sizeof(IcmpRecvBuf));

	// 接收	
	nRet = recvfrom(s,					// socket
		(LPSTR)&IcmpRecvBuf,	// buffer
		1024,	// size of buffer
		0,					// flags
		(LPSOCKADDR)saFrom,	// From address
		&nAddrLen);			// pointer to address len


	//打印輸出
	if (nRet != SOCKET_ERROR) //接收沒有錯誤
	{
		//解碼得到的數據包,如果解碼正確則跳出接收循環發送下一個EchoRequest包
		if (DecodeIcmpResponse(IcmpRecvBuf, nRet, *stDecodeResult))
		{
			if (stDecodeResult->dwIPaddr.s_addr == saDest->sin_addr.s_addr)
			{
				printf("該主機處於活動狀態\n");
				return 1;
			}
		}
		else
			return -1;
	}
	else if (WSAGetLastError() == WSAETIMEDOUT) //接收超時,一般情況下,不可達可以收到目的地不可達icmp報文,但如果需要掃描ip與本機處於同一局域網,則主//機在發送icmp之前,回先發送arp請求目的ip物理地址,如果該ip存在,則纔會繼續發送icmp報文,如果不存在,則不會有後續的,這裏就是處理這種情況
	{
		printf("該主機不處於活動狀態\n");
	}
	else
	{
		printf("recvfrom函數調用錯誤,錯誤號: %d\n", WSAGetLastError());
		return -1;
	}
	return 0;
}
5.以下開始是主函數

<pre name="code" class="cpp">
int main()
{
int iResult;
SOCKET sockRaw;
sockaddr_in addrDest, addrSrc;
DECODE_RESULT stDecodeResult;
USHORT usSeqNo = 0;


WSADATA wsaData;
iResult = WSAStartup(MAKEWORD(2,2), &wsaData);
if (iResult != 0) 
{
//告知用戶無法找到合適可用的Winsock DLL
printf("WSAStartup 函數調用錯誤,錯誤號: %d\n", WSAGetLastError());
return -1;
}
//創建原始套接字
sockRaw= socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);

//設置接收超時選項
int iTimeout = 1000;
iResult = setsockopt(sockRaw, SOL_SOCKET, SO_RCVTIMEO, (char*)&iTimeout, sizeof(iTimeout));


memset(&stDecodeResult, 0, sizeof(DECODE_RESULT));

//需要掃描的ip,根據自己的需要來賦值吧
addrDest.sin_addr.S_un.S_addr = inet_addr("192.168.218.18");
addrDest.sin_family = AF_INET;


// 發送ICMP Echo請求
iResult = SendEchoRequest(sockRaw, &addrDest);
if (iResult == SOCKET_ERROR)
{
if (WSAGetLastError() == WSAEHOSTUNREACH){
printf("目的主機不可達,traceroute探測結束!");
return -1;
}
}
//接收ICMP的EchoReply數據報
iResult = RecvEchoReply(sockRaw, &addrSrc, &addrDest, &stDecodeResult);
return 0;
}

好了,這樣就ok了


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章