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)
{
	unsigned long cksum = 0;
	while (size > 1)
	{
		cksum += *buffer++;
		size -= sizeof(USHORT);
	}
	if (size)
	{
		cksum += *(UCHAR*)buffer;
	}
	while (cksum >> 16)
		cksum = (cksum >> 16) + (cksum & 0xffff);
	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.以下开始是主函数

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了


你可能感兴趣的:(scan)