您的位置:首页 > 职场人生

黑客之旅――原始套接字(Raw Socket)透析(3)--用Raw Socket实现Ping

2006-12-11 23:46 513 查看
3.用Raw Socket实现Ping
极其常用的Ping命令通过向计算机发送ICMP Echo请求报文并且监听回应报文的返回,以校验与远程计算机或本地计算机的连接。
3.1 使用ICMP.DLL实现Ping
在Windows平台编程中实现Ping的一个最简单方法是调用ICMP.DLL这个动态链接库,引用ICMP.DLL中的三个函数即可:
HANDLE IcmpCreateFile(void);
这个函数打开个ICMP Echo请求能使用的句柄;
BOOL IcmpCloseHandle(HANDLE IcmpHandle);
这个函数关闭由IcmpCreateFile打开的句柄;
DWORD IcmpSendEcho(
HANDLE IcmpHandle, // IcmpCreateFile打开的句柄
IPAddr DestinationAddress, //Echo请求的目的地址
LPVOID RequestData, //发送数据buffer
WORD RequestSize, //发送数据长度
PIP_OPTION_INFORMATION RequestOptions, // IP_OPTION_INFORMATION指针
LPVOID ReplyBuffer, //接收回复buffer
DWORD ReplySize, //接收回复buffer大小
DWORD Timeout //等待超时
);
这个函数发送Echo请求并等待回复或超时。
把这个函数和相关数据封装成一个类CPing,CPing类的头文件如下:
class CPing
{
public:
CPing();
~CPing();
BOOL Ping(char* strHost);
private:
// ICMP.DLL 导出函数指针
HANDLE (WINAPI *pIcmpCreateFile)(VOID);
BOOL (WINAPI *pIcmpCloseHandle)(HANDLE);
DWORD (WINAPI *pIcmpSendEcho)
(HANDLE,DWORD,LPVOID,WORD,PIPINFO,LPVOID,DWORD,DWORD);
HANDLE hndlIcmp; // 加载ICMP.DLL库句柄
BOOL bValid; //是否构造(获得ICMP.DLL导出函数指针和初始化WinSock)成功
};
CPing类的构造函数获得ICMP.DLL中导出函数的指针并初始化WinSock:
CPing::CPing()
{
bValid = FALSE;
WSADATA wsaData;
int nRet;
// 动态加载ICMP.DLL
hndlIcmp = LoadLibrary("ICMP.DLL");
if (hndlIcmp == NULL)
{
::MessageBox(NULL, "Could not load ICMP.DLL", "Error:", MB_OK);
return;
}
// 获得ICMP.DLL中导出函数指针
pIcmpCreateFile = (HANDLE (WINAPI *)(void))
GetProcAddress((HMODULE)hndlIcmp,"IcmpCreateFile");
pIcmpCloseHandle = (BOOL (WINAPI *)(HANDLE))
GetProcAddress((HMODULE)hndlIcmp,"IcmpCloseHandle");
pIcmpSendEcho = (DWORD (WINAPI *)
(HANDLE,DWORD,LPVOID,WORD,PIPINFO,LPVOID,DWORD,DWORD))
GetProcAddress((HMODULE)hndlIcmp,"IcmpSendEcho");
// 检查所有的指针
if (pIcmpCreateFile == NULL ||
pIcmpCloseHandle == NULL ||
pIcmpSendEcho == NULL)
{
::MessageBox(NULL, "Error loading ICMP.DLL", "Error:", MB_OK);
FreeLibrary((HMODULE)hndlIcmp);
return;
}
// 初始化WinSock
nRet = WSAStartup(0x0101, &wsaData );
if (nRet)
{
::MessageBox(NULL, "WSAStartup() error:", "Error:", MB_OK);
WSACleanup();
FreeLibrary((HMODULE)hndlIcmp);
return;
}
// 检查WinSock的版本
if (0x0101 != wsaData.wVersion)
{
::MessageBox(NULL, "No WinSock version 1.1 support found", "Error:", MB_OK);
WSACleanup();
FreeLibrary((HMODULE)hndlIcmp);
return;
}
bValid = TRUE;
}
CPing类的析构函数完成相反的动作:
CPing::~CPing()
{
WSACleanup();
FreeLibrary((HMODULE)hndlIcmp);
}
CPing类的Ping函数是最核心的函数,实现真正的ping操作:
int CPing::Ping(char *strHost)
{
struct in_addr iaDest; // Internet地址结构体
LPHOSTENT pHost; // 主机入口结构体指针
DWORD *dwAddress; // IP地址
IPINFO ipInfo; // IP选项结构体
ICMPECHO icmpEcho; // ICMP Echo回复buffer
HANDLE hndlFile; // IcmpCreateFile函数打开的句柄
if (!bValid)
{
return FALSE;
}
//使用inet_addr()以判定ping目标为地址还是名称
iaDest.s_addr = inet_addr(strHost);
if (iaDest.s_addr == INADDR_NONE)
pHost = gethostbyname(strHost);
else
pHost = gethostbyaddr((const char*) &iaDest, sizeof(struct in_addr),
AF_INET);
if (pHost == NULL)
{
return FALSE;
}
// 拷贝IP地址
dwAddress = (DWORD*)(*pHost->h_addr_list);
// 获得ICMP Echo句柄
hndlFile = pIcmpCreateFile();
// 设置发送信息缺省值
ipInfo.Ttl = 255;
ipInfo.Tos = 0;
ipInfo.IPFlags = 0;
ipInfo.OptSize = 0;
ipInfo.Options = NULL;
icmpEcho.Status = 0;
// 请求一个ICMP echo
pIcmpSendEcho(hndlFile, *dwAddress, NULL, 0, &ipInfo, &icmpEcho, sizeof
(struct tagICMPECHO), 1000);
//设置结果
iaDest.s_addr = icmpEcho.Source;
if (icmpEcho.Status)
{
return FALSE;
}
// 关闭ICMP Echo句柄
pIcmpCloseHandle(hndlFile);
return TRUE;
}
其中所使用的相关结构体定义为:
typedef struct tagIPINFO
{
u_char Ttl; // TTL
u_char Tos; // 服务类型
u_char IPFlags; // IP标志
u_char OptSize; // 可选数据大小
u_char *Options; // 可选数据buffer
} IPINFO, *PIPINFO;
typedef struct tagICMPECHO
{
u_long Source; // 源地址
u_long Status; // IP状态
u_long RTTime; // RTT
u_short DataSize; // 回复数据大小
u_short Reserved; // 保留
void *pData; // 回复数据buffer
IPINFO ipInfo; // 回复IP选项
} ICMPECHO, *PICMPECHO;
3.2 使用Raw Socket实现Ping
仅仅采用ICMP.DLL并不能完全实现ICMP灵活多变的各类报文,只有使用Raw Socket才是ICMP的终极解决之道。
使用Raw Socket发送ICMP报文前,我们要完全依靠自己的代码组装报文:
//功能:初始化ICMP的报头, 给data部分填充数据, 计算校验和
void init_ping_packet(ICMPHeader *icmp_hdr, int packet_size, int seq_no)
{
//设置ICMP报头字段
icmp_hdr->type = ICMP_ECHO_REQUEST;
icmp_hdr->code = 0;
icmp_hdr->checksum = 0;
icmp_hdr->id = (unsigned short)GetCurrentProcessId();
icmp_hdr->seq = seq_no;
icmp_hdr->timestamp = GetTickCount();
// 填充data域
const unsigned long int deadmeat = 0xDEADBEEF;
char *datapart = (char*)icmp_hdr + sizeof(ICMPHeader);
int bytes_left = packet_size - sizeof(ICMPHeader);
while (bytes_left > 0)
{
memcpy(datapart, &deadmeat, min(int(sizeof(deadmeat)), bytes_left));
bytes_left -= sizeof(deadmeat);
datapart += sizeof(deadmeat);
}
// 计算校验和
icmp_hdr->checksum = ip_checksum((unsigned short*)icmp_hdr, packet_size);
}
计算校验和(Checksum)的函数为:
//功能:计算ICMP包的校验和
unsigned short ip_checksum(unsigned short *buffer, int size)
{
unsigned long cksum = 0;
// 将所有的16数相加
while (size > 1)
{
cksum += *buffer++;
size -= sizeof(unsigned short);
}
if (size) //加上最后一个BYTE
{
cksum += *(unsigned char*)buffer;
}
//和的前16位和后16位相加
cksum = (cksum >> 16) + (cksum &0xffff);
cksum += (cksum >> 16);
return (unsigned short)(~cksum);
}
在真正发送Ping报文前,需要先初始化Raw Socket:
// 功能:初始化RAW Socket, 设置ttl, 初始化目标地址
// 返回值:<0 失败
int setup_for_ping(char *host, int ttl, SOCKET &sd, sockaddr_in &dest)
{
// 创建原始套接字
sd = WSASocket(AF_INET, SOCK_RAW, IPPROTO_ICMP, 0, 0, 0);
if (sd == INVALID_SOCKET)
{
cerr << "Failed to create raw socket: " << WSAGetLastError() << endl;
return - 1;
}
if (setsockopt(sd, IPPROTO_IP, IP_TTL, (const char*) &ttl, sizeof(ttl)) ==
SOCKET_ERROR)
{
cerr << "TTL setsockopt failed: " << WSAGetLastError() << endl;
return - 1;
}
// 初始化目标主机信息块
memset(&dest, 0, sizeof(dest));
// 将第1个参数转换为目标IP地址
unsigned int addr = inet_addr(host);
if (addr != INADDR_NONE)
{
// 为IP地址
dest.sin_addr.s_addr = addr;
dest.sin_family = AF_INET;
}
else
{
// 非IP地址,进行主机名和IP地址的转换
hostent *hp = gethostbyname(host);
if (hp != 0)
{
// 查找主机名对应的IP地址
memcpy(&(dest.sin_addr), hp->h_addr, hp->h_length);
dest.sin_family = hp->h_addrtype;
}
else
{
// 不能识别的主机名
cerr << "Failed to resolve " << host << endl;
return - 1;
}
}
return 0;
}
下面可以利用Raw Socket发送生成的ICMP报文:
//功能:发送生成的ICMP包
//返回值:<0 发送失败
int send_ping(SOCKET sd, const sockaddr_in &dest, ICMPHeader *send_buf, int
packet_size)
{
// 发送send_buf缓冲区中的报文
cout << "Sending " << packet_size << " bytes to " << inet_ntoa(dest.sin_addr)
<< "..." << flush;
int bwrote = sendto(sd, (char*)send_buf, packet_size, 0, (sockaddr*) &dest,
sizeof(dest));
if (bwrote == SOCKET_ERROR)
{
cerr << "send failed: " << WSAGetLastError() << endl;
return - 1;
}
else if (bwrote < packet_size)
{
cout << "sent " << bwrote << " bytes..." << flush;
}
return 0;
}
发送Ping报文后,我们需要接收Ping回复ICMP报文:
//功能:接收Ping回复
//返回值: <0 接收失败
int recv_ping(SOCKET sd, sockaddr_in &source, IPHeader *recv_buf, int
packet_size)
{
// 等待Ping回复
int fromlen = sizeof(source);
int bread = recvfrom(sd, (char*)recv_buf, packet_size + sizeof(IPHeader), 0,
(sockaddr*) &source, &fromlen);
if (bread == SOCKET_ERROR)
{
cerr << "read failed: ";
if (WSAGetLastError() == WSAEMSGSIZE)
{
cerr << "buffer too small" << endl;
}
else
{
cerr << "error #" << WSAGetLastError() << endl;
}
return - 1;
}
return 0;
}
并使用如下函数对接收到的报文进行解析:
// 功能:解析接收到的ICMP报文
// 返回值: -2忽略, -1失败, 0 成功
int decode_reply(IPHeader *reply, int bytes, sockaddr_in *from)
{
// 偏移到ICMP报头
unsigned short header_len = reply->h_len *4;
ICMPHeader *icmphdr = (ICMPHeader*)((char*)reply + header_len);
// 报文太短
if (bytes < header_len + ICMP_MIN)
{
cerr << "too few bytes from " << inet_ntoa(from->sin_addr) << endl;
return - 1;
}
// 解析回复报文类型
else if (icmphdr->type != ICMP_ECHO_REPLY)
{
//非正常回复
if (icmphdr->type != ICMP_TTL_EXPIRE)
{
//ttl减为零
if (icmphdr->type == ICMP_DEST_UNREACH)
{
//主机不可达
cerr << "Destination unreachable" << endl;
}
else
{
//非法的ICMP包类型
cerr << "Unknown ICMP packet type " << int(icmphdr->type) <<
" received" << endl;
}
return - 1;
}
}
else if (icmphdr->id != (unsigned short)GetCurrentProcessId())
{
//不是本进程发的包, 可能是同机的其它ping进程发的
return - 2;
}
// 指出往返时间TTL
int nHops = int(256-reply->ttl);
if (nHops == 192)
{
// TTL came back 64, so ping was probably to a host on the
// LAN -- call it a single hop.
nHops = 1;
}
else if (nHops == 128)
{
// Probably localhost
nHops = 0;
}
// 输出信息
cout << endl << bytes << " bytes from " << inet_ntoa(from->sin_addr) <<
", icmp_seq " << icmphdr->seq << ", ";
if (icmphdr->type == ICMP_TTL_EXPIRE)
{
cout << "TTL expired." << endl;
}
else
{
cout << nHops << " hop" << (nHops == 1 ? "" : "s");
cout << ", time: " << (GetTickCount() - icmphdr->timestamp) << " ms." <<
endl;
}
return 0;
}
为了在Visual C++中更加方便地使用发送和接收ICMP报文,我们可以使用由Jay Wheeler编写的CIcmp(An ICMP Class For MFC)类,在著名的开发网站的如下地址可以下载:
http://www.codeguru.com/cpp/i-n/internet/network/article.php/c3395/
这个类的简要框架如下:
class CIcmp: public CSocket
{
// Attributes
public:
BOOL OpenNewSocket(HWND hWnd, unsigned int NotificationMessage, long
NotifyEvents);
BOOL OpenNewSocket(HWND hWnd, unsigned int NotificationMessage, long
NotifyEvents, int AFamily, int AType, int AProtocol);
int CloseIcmpSocket(void);
BOOL Connect(int ReceiveTimeout, int SendTimeout);
BOOL Connect(LPINT ReceiveTimeout, LPINT SendTimeout, int AFamily, int
AType, int AProtocol);
int SetTTL(int TTL);
int SetAsynchNotification(HWND hWnd, unsigned int Message, long Events);
int Receive(LPSTR pIcmpBuffer, int IcmpBufferSize);
unsigned long GetIPAddress(LPSTR iHostName);
int Ping(LPSTR pIcmpBuffer, int IcmpBufferSize);
unsigned short IcmpChecksum(unsigned short FAR *lpBuf, int Len);
void DisplayError(CString ErrorType, CString FunctionName);
// Operations
public:
CIcmp(void);
CIcmp(CIcmp ©);
~CIcmp(void);
public:
// I/O Buffer Pointers
LPIcmpHeader pIcmpHeader;
LPIpHeader pIpHeader;
SOCKET icmpSocket;
SOCKADDR_IN icmpSockAddr;
SOCKADDR_IN rcvSockAddr;
DWORD icmpRoundTripTime;
DWORD icmpPingSentAt;
DWORD icmpPingReceivedAt;
int icmpRcvLen;
int icmpHops;
int icmpMaxHops;
int icmpCurSeq;
int icmpCurId;
int icmpPingTimer;
int icmpSocketError;
int icmpSocketErrorMod;
unsigned long icmpHostAddress;
protected:
};
初始化网络连接的函数:
BOOL CIcmp::Connect(LPINT ReceiveTimeout, LPINT SendTimeout, int AFamily, int
AType, int AProtocol)
{
int Result;
icmpSocket = NULL;
icmpSocket = socket(AFamily, AType, AProtocol);
if (icmpSocket == INVALID_SOCKET)
{
icmpSocketError = WSAGetLastError();
icmpSocketErrorMod = 1;
return FALSE;
}
//
// Set receive timeout
//
Result = setsockopt(icmpSocket, SOL_SOCKET, SO_RCVTIMEO, (char*)
ReceiveTimeout, sizeof(int));
if (Result == SOCKET_ERROR)
{
icmpSocketError = WSAGetLastError();
icmpSocketErrorMod = 2;
closesocket(icmpSocket);
icmpSocket = INVALID_SOCKET;
return FALSE;
}
//
// Set send timeout
//
Result = setsockopt(icmpSocket, SOL_SOCKET, SO_SNDTIMEO, (char*)SendTimeout,
sizeof(int));
if (Result == SOCKET_ERROR)
{
icmpSocketError = WSAGetLastError();
icmpSocketErrorMod = 3;
closesocket(icmpSocket);
icmpSocket = INVALID_SOCKET;
return FALSE;
}
icmpCurSeq = 0;
icmpCurId = (USHORT)GetCurrentProcessId();
icmpHops = 0;
return TRUE;
}
接收的函数:
int CIcmp::Receive(LPSTR pIcmpBuffer, int IcmpBufferSize)
{
LPSOCKADDR pRcvSockAddr = (LPSOCKADDR)&rcvSockAddr;
int Result;
int RcvIpHdrLen;
icmpPingReceivedAt = GetTickCount();
icmpCurId = 0;
rcvSockAddr.sin_family = AF_INET;
rcvSockAddr.sin_addr.s_addr = INADDR_ANY;
rcvSockAddr.sin_port = 0;
RcvIpHdrLen = sizeof rcvSockAddr;
Result = recvfrom (icmpSocket,
pIcmpBuffer,
IcmpBufferSize,
0,
pRcvSockAddr,
&RcvIpHdrLen);
if (Result == SOCKET_ERROR)
{
icmpSocketError = WSAGetLastError();
icmpSocketErrorMod = 1;
DisplayError ("Receive","CIcmp::Receive");
return Result;
}
icmpRcvLen = Result;
pIpHeader = (LPIpHeader)pIcmpBuffer;
RcvIpHdrLen = pIpHeader->HeaderLength * 4;
if (Result < RcvIpHdrLen + ICMP_MIN)
{
//
// Too few bytes received
//
MessageBox(NULL,
"Short message!",
"CIcmp::Receive",
MB_OK|MB_SYSTEMMODAL);
icmpSocketErrorMod = 2;
return Result;
}
pIcmpHeader = (LPIcmpHeader)(pIcmpBuffer + RcvIpHdrLen);
icmpCurId = pIcmpHeader->IcmpId;
icmpRoundTripTime = icmpPingReceivedAt - pIcmpHeader->IcmpTimestamp;
if (pIcmpHeader->IcmpType != ICMP_ECHOREPLY)
{
//
// Not an echo response!
//
return Result;
}
icmpCurSeq = pIcmpHeader->IcmpSeq;
return Result;
}
异步通知主窗口:
int CIcmp::SetAsynchNotification(HWND hWnd, unsigned int Message, long Events)
{
int Result = WSAAsyncSelect (icmpSocket,
hWnd,
Message,
Events);
if (Result == SOCKET_ERROR)
{
icmpSocketError = WSAGetLastError();
icmpSocketErrorMod = 1;
icmpSocket = INVALID_SOCKET;
}
return Result;
}
设置TTL:
int CIcmp::SetTTL(int TTL)
{
int Result;
Result = setsockopt (icmpSocket, IPPROTO_IP, IP_TTL, (LPSTR)&TTL, sizeof(int));
if (Result == SOCKET_ERROR)
{
icmpSocketErrorMod = 1;
icmpSocketError = WSAGetLastError();
}
return Result;
}
Ping命令的函数:
int CIcmp::Ping (LPSTR pIcmpBuffer, int DataLen)
{
int Result;
int IcmpBufferSize = DataLen + IcmpHeaderLength;
pIcmpHeader = (LPIcmpHeader)pIcmpBuffer;
memset (pIcmpBuffer, 'E', IcmpBufferSize);
memset (pIcmpHeader, 0, IcmpHeaderLength);
pIcmpHeader->IcmpType = ICMP_ECHO;
pIcmpHeader->IcmpCode = 0;
pIcmpHeader->IcmpChecksum = 0;
pIcmpHeader->IcmpId = icmpCurId;
pIcmpHeader->IcmpSeq = icmpCurSeq;
pIcmpHeader->IcmpTimestamp = GetCurrentTime();
pIcmpHeader->IcmpChecksum = IcmpChecksum ((USHORT FAR *)pIcmpBuffer,
IcmpBufferSize);
icmpPingSentAt = GetCurrentTime();
Result = sendto (icmpSocket,
pIcmpBuffer,
IcmpBufferSize,
0,
(LPSOCKADDR)&icmpSockAddr,
sizeof icmpSockAddr);
if (Result == SOCKET_ERROR)
{
icmpSocketError = WSAGetLastError();
icmpSocketErrorMod = 1;
}
return Result;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息